refactor(tasks): route generate-and-assign through the shared core module

This commit is contained in:
lorentz 2026-07-18 12:54:44 +00:00
parent a1206f9515
commit 5daab12d98

View file

@ -1,14 +1,19 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth' import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth' import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/db' import {
import { isRelevantDueDate } from '@/lib/sync/task-due-date' parseGenerateAndAssignTarget,
runGenerateAndAssign,
GenerateAndAssignError,
} from '@/lib/tasks/generate-and-assign-core'
/** /**
* POST /api/tasks/generate-and-assign * POST /api/tasks/generate-and-assign
* Find clients matching the given advocate + designation, generate tasks from * Generate tasks from active templates for either (a) every client matching
* active templates, and assign them to the advocate. * an advocate + designation, or (b) one specific client, and assign them to
* Body: { advocateId: string, designationId: string } * the relevant claims advocate. Pass `dryRun: true` to compute the summary
* without creating anything.
* Body: { advocateId, designationId, dryRun? } | { clientId, dryRun? }
* Requires Admin or Manager role. * Requires Admin or Manager role.
*/ */
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
@ -24,260 +29,18 @@ export async function POST(request: NextRequest) {
} }
const body = await request.json() const body = await request.json()
const { advocateId, designationId } = body const { target, dryRun } = parseGenerateAndAssignTarget(body)
if (!advocateId || !designationId) { const result = await runGenerateAndAssign(target, {
return NextResponse.json( dryRun,
{ error: 'advocateId and designationId are required' }, actorUserId: (session.user as any).id,
{ status: 400 }
)
}
const advocate = await prisma.user.findUnique({
where: { id: advocateId },
select: { id: true, displayName: true, isActive: true },
})
if (!advocate || !advocate.isActive) {
return NextResponse.json({ error: 'Advocate not found or inactive' }, { status: 404 })
}
// Clients assigned to this advocate with this designation
const clients = await prisma.client.findMany({
where: {
claimsAdvocateId: advocateId,
OR: [{ designationId }, { designation2Id: designationId }],
},
select: { id: true },
}) })
if (clients.length === 0) { return NextResponse.json(result)
return NextResponse.json({ } catch (error: any) {
tasksCreated: 0, if (error instanceof GenerateAndAssignError) {
tasksAssigned: 0, return NextResponse.json({ error: error.message }, { status: error.status })
clientsFound: 0,
groupsProcessed: 0,
advocateName: advocate.displayName,
})
} }
const clientIds = clients.map((c) => c.id)
const allTemplates = await prisma.taskTemplate.findMany({
where: {
isActive: true,
OR: [{ designationId: null }, { designationId }],
},
orderBy: [{ department: 'asc' }, { daysOffset: 'asc' }],
})
if (allTemplates.length === 0) {
return NextResponse.json({
tasksCreated: 0,
tasksAssigned: 0,
clientsFound: clients.length,
groupsProcessed: 0,
advocateName: advocate.displayName,
message: 'No active templates found for this designation',
})
}
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
// ── Policy groups with no template-generated tasks yet ──────────────────
const groups = await prisma.policyGroup.findMany({
where: {
clientId: { in: clientIds },
tasks: { none: { templateId: { not: null } } },
},
})
for (const group of groups) {
const renewalDate = new Date(group.renewalDate)
const tasksToCreate = groupTemplates
.map((template) => {
const dueDate = new Date(renewalDate)
dueDate.setDate(dueDate.getDate() + template.daysOffset)
return {
title: template.name,
description: template.description,
department: template.department,
timing: template.timing,
daysOffset: template.daysOffset,
dueDate,
status: 'NOT_STARTED' as const,
priority: template.defaultPriority,
clientId: group.clientId,
policyGroupId: group.id,
templateId: template.id,
createdBy: (session.user as any).id,
}
})
.filter((task) => isRelevantDueDate(task.dueDate))
if (tasksToCreate.length === 0) continue
const created = await prisma.task.createMany({ data: tasksToCreate })
tasksCreated += created.count
if (created.count > 0) {
const newTasks = await prisma.task.findMany({
where: { policyGroupId: group.id, templateId: { in: groupTemplates.map((t) => t.id) } },
select: { id: true },
})
if (newTasks.length > 0) {
const assigned = await prisma.taskAssignment.createMany({
data: newTasks.map((t) => ({ taskId: t.id, userId: advocateId })),
skipDuplicates: true,
})
tasksAssigned += assigned.count
}
groupsProcessed++
}
}
// ── Ungrouped policies with no template-generated tasks yet ─────────────
const policies = await prisma.policy.findMany({
where: {
clientId: { in: clientIds },
policyGroupId: null,
tasks: { none: { templateId: { not: null } } },
},
})
for (const policy of policies) {
const anchorDate = new Date(policy.expirationDate)
anchorDate.setDate(anchorDate.getDate() + 1) // renewal date = expiration + 1
const tasksToCreate = policyTemplates
.map((template) => {
const dueDate = new Date(anchorDate)
dueDate.setDate(dueDate.getDate() + template.daysOffset)
return {
title: template.name,
description: template.description,
department: template.department,
timing: template.timing,
daysOffset: template.daysOffset,
dueDate,
status: 'NOT_STARTED' as const,
priority: template.defaultPriority,
clientId: policy.clientId,
policyId: policy.id,
templateId: template.id,
createdBy: (session.user as any).id,
}
})
.filter((task) => isRelevantDueDate(task.dueDate))
if (tasksToCreate.length === 0) continue
const created = await prisma.task.createMany({ data: tasksToCreate })
tasksCreated += created.count
if (created.count > 0) {
const newTasks = await prisma.task.findMany({
where: { policyId: policy.id, templateId: { in: policyTemplates.map((t) => t.id) } },
select: { id: true },
})
if (newTasks.length > 0) {
const assigned = await prisma.taskAssignment.createMany({
data: newTasks.map((t) => ({ taskId: t.id, userId: advocateId })),
skipDuplicates: true,
})
tasksAssigned += assigned.count
}
}
}
// ── CLIENT-level tasks (once per client) ────────────────────────────────
const clientTemplates = allTemplates.filter((t: any) => t.level === 'CLIENT')
if (clientTemplates.length > 0) {
for (const clientId of clientIds) {
const clientRecord = await prisma.client.findUnique({
where: { id: clientId },
select: {
policyGroups: { select: { renewalDate: true } },
policies: {
where: { policyGroupId: null },
select: { expirationDate: true },
},
},
})
if (!clientRecord) continue
const groupDates = clientRecord.policyGroups.map((g) => new Date(g.renewalDate).getTime())
const policyDates = clientRecord.policies.map((p) => {
const d = new Date(p.expirationDate)
d.setDate(d.getDate() + 1)
return d.getTime()
})
const allDates = [...groupDates, ...policyDates]
if (allDates.length === 0) continue
const anchorDate = new Date(Math.min(...allDates))
for (const template of clientTemplates) {
const existing = await prisma.task.findFirst({
where: { clientId, templateId: template.id, policyId: null, policyGroupId: null },
select: { id: true },
})
if (existing) continue
const dueDate = new Date(anchorDate)
dueDate.setDate(dueDate.getDate() + template.daysOffset)
if (!isRelevantDueDate(dueDate)) continue
const created = await prisma.task.create({
data: {
title: template.name,
description: template.description,
department: template.department,
timing: template.timing,
daysOffset: template.daysOffset,
dueDate,
status: 'NOT_STARTED',
priority: template.defaultPriority,
clientId,
templateId: template.id,
createdBy: (session.user as any).id,
},
select: { id: true },
})
tasksCreated++
const assigned = await prisma.taskAssignment.create({
data: { taskId: created.id, userId: advocateId },
})
tasksAssigned++
}
}
}
await prisma.auditLog.create({
data: {
userId: (session.user as any).id,
action: 'GENERATE_AND_ASSIGN_TASKS',
entityType: 'Task',
newValues: {
advocateId,
advocateName: advocate.displayName,
designationId,
clientsFound: clients.length,
tasksCreated,
tasksAssigned,
},
},
})
return NextResponse.json({
tasksCreated,
tasksAssigned,
clientsFound: clients.length,
groupsProcessed,
advocateName: advocate.displayName,
})
} catch (error) {
console.error('Generate and assign error:', error) console.error('Generate and assign error:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
} }