Task templates: add level field (Policy/Renewal Group/Both) with schema migration, UI selector, and generation logic

This commit is contained in:
lorentz 2026-04-10 18:31:38 +00:00
parent cfefc255b1
commit c1a836d5d8
5 changed files with 54 additions and 7 deletions

View file

@ -271,6 +271,12 @@ enum TaskTiming {
POST_RENEWAL
}
enum TaskLevel {
POLICY
RENEWAL_GROUP
BOTH
}
enum DepartmentType {
PERSONAL_LINES
COMMERCIAL_LINES
@ -290,6 +296,7 @@ model TaskTemplate {
taskGroup String? @map("task_group")
isActive Boolean @default(true) @map("is_active")
displayOrder Int? @map("display_order")
level TaskLevel @default(BOTH)
designationId String? @map("designation_id")
createdBy String? @map("created_by")
createdAt DateTime @default(now()) @map("created_at")

View file

@ -66,6 +66,7 @@ export async function POST(request: NextRequest) {
const templates = await prisma.taskTemplate.findMany({
where: {
isActive: true,
level: { in: ['BOTH', 'RENEWAL_GROUP'] },
OR: [
{ designationId: null },
...(designationIds.length > 0 ? [{ designationId: { in: designationIds } }] : []),
@ -159,6 +160,7 @@ export async function POST(request: NextRequest) {
const templates = await prisma.taskTemplate.findMany({
where: {
isActive: true,
level: { in: ['BOTH', 'POLICY'] },
OR: [
{ designationId: null },
...(designationIds.length > 0 ? [{ designationId: { in: designationIds } }] : []),

View file

@ -57,6 +57,7 @@ export async function POST(
where: {
isActive: true,
id: { notIn: alreadyGeneratedTemplateIds },
level: { in: ['BOTH', 'RENEWAL_GROUP'] },
OR: [
{ designationId: null },
...(designationIds.length > 0

View file

@ -61,7 +61,7 @@ export async function POST(request: NextRequest) {
const clientIds = clients.map((c) => c.id)
const templates = await prisma.taskTemplate.findMany({
const allTemplates = await prisma.taskTemplate.findMany({
where: {
isActive: true,
OR: [{ designationId: null }, { designationId }],
@ -69,7 +69,7 @@ export async function POST(request: NextRequest) {
orderBy: [{ department: 'asc' }, { daysOffset: 'asc' }],
})
if (templates.length === 0) {
if (allTemplates.length === 0) {
return NextResponse.json({
tasksCreated: 0,
tasksAssigned: 0,
@ -80,7 +80,8 @@ export async function POST(request: NextRequest) {
})
}
const templateIds = templates.map((t) => t.id)
const groupTemplates = allTemplates.filter((t: any) => t.level === 'BOTH' || t.level === 'RENEWAL_GROUP')
const policyTemplates = allTemplates.filter((t: any) => t.level === 'BOTH' || t.level === 'POLICY')
let tasksCreated = 0
let tasksAssigned = 0
let groupsProcessed = 0
@ -95,7 +96,7 @@ export async function POST(request: NextRequest) {
for (const group of groups) {
const renewalDate = new Date(group.renewalDate)
const tasksToCreate = templates.map((template) => {
const tasksToCreate = groupTemplates.map((template) => {
const dueDate = new Date(renewalDate)
dueDate.setDate(dueDate.getDate() + template.daysOffset)
return {
@ -119,7 +120,7 @@ export async function POST(request: NextRequest) {
if (created.count > 0) {
const newTasks = await prisma.task.findMany({
where: { policyGroupId: group.id, templateId: { in: templateIds } },
where: { policyGroupId: group.id, templateId: { in: groupTemplates.map((t) => t.id) } },
select: { id: true },
})
if (newTasks.length > 0) {
@ -144,7 +145,7 @@ export async function POST(request: NextRequest) {
for (const policy of policies) {
const anchorDate = new Date(policy.expirationDate)
const tasksToCreate = templates.map((template) => {
const tasksToCreate = policyTemplates.map((template) => {
const dueDate = new Date(anchorDate)
dueDate.setDate(dueDate.getDate() + template.daysOffset)
return {
@ -168,7 +169,7 @@ export async function POST(request: NextRequest) {
if (created.count > 0) {
const newTasks = await prisma.task.findMany({
where: { policyId: policy.id, templateId: { in: templateIds } },
where: { policyId: policy.id, templateId: { in: policyTemplates.map((t) => t.id) } },
select: { id: true },
})
if (newTasks.length > 0) {

View file

@ -45,6 +45,7 @@ import { Label } from '@/components/ui/label'
type DepartmentType = 'PERSONAL_LINES' | 'COMMERCIAL_LINES' | 'CLAIMS' | 'BENEFITS' | 'OTHER'
type TaskTiming = 'PRE_RENEWAL' | 'POST_RENEWAL'
type TaskPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'
type TaskLevel = 'POLICY' | 'RENEWAL_GROUP' | 'BOTH'
interface Designation {
id: string
@ -63,6 +64,7 @@ interface TaskTemplate {
taskGroup: string | null
isActive: boolean
displayOrder: number | null
level: TaskLevel
designationId: string | null
designation: Designation | null
_count: { tasks: number }
@ -93,6 +95,12 @@ const PRIORITIES: { value: TaskPriority; label: string; color: string }[] = [
{ value: 'URGENT', label: 'Urgent', color: 'bg-red-100 text-red-700' },
]
const LEVELS: { value: TaskLevel; label: string }[] = [
{ value: 'BOTH', label: 'Both (Policy & Group)' },
{ value: 'RENEWAL_GROUP', label: 'Renewal Group only' },
{ value: 'POLICY', label: 'Policy only' },
]
const emptyTemplate = {
name: '',
description: '',
@ -103,6 +111,7 @@ const emptyTemplate = {
taskGroup: '',
isActive: true,
displayOrder: null as number | null,
level: 'BOTH' as TaskLevel,
designationId: null as string | null,
}
@ -154,6 +163,7 @@ export function TaskTemplateManager({
taskGroup: template.taskGroup || '',
isActive: template.isActive,
displayOrder: template.displayOrder,
level: template.level,
designationId: template.designationId,
})
setIsDialogOpen(true)
@ -446,6 +456,26 @@ export function TaskTemplateManager({
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label>Level</Label>
<Select
value={formData.level}
onValueChange={(value: TaskLevel) =>
setFormData((prev) => ({ ...prev, level: value }))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{LEVELS.map((lvl) => (
<SelectItem key={lvl.value} value={lvl.value}>
{lvl.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label>Designation</Label>
<Select
@ -546,6 +576,7 @@ export function TaskTemplateManager({
<TableHead>Name</TableHead>
<TableHead>Department</TableHead>
<TableHead>Timing</TableHead>
<TableHead>Level</TableHead>
<TableHead>Designation</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Status</TableHead>
@ -583,6 +614,11 @@ export function TaskTemplateManager({
</span>
</div>
</TableCell>
<TableCell>
<span className="text-sm">
{LEVELS.find((l) => l.value === template.level)?.label || template.level}
</span>
</TableCell>
<TableCell>
{template.designation ? (
<Badge variant="outline" className="flex items-center gap-1 w-fit">