diff --git a/ondeck/src/app/api/tasks/generate-and-assign/route.ts b/ondeck/src/app/api/tasks/generate-and-assign/route.ts index 2e8594c..aa840e3 100644 --- a/ondeck/src/app/api/tasks/generate-and-assign/route.ts +++ b/ondeck/src/app/api/tasks/generate-and-assign/route.ts @@ -1,14 +1,19 @@ import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' -import { prisma } from '@/lib/db' -import { isRelevantDueDate } from '@/lib/sync/task-due-date' +import { + parseGenerateAndAssignTarget, + runGenerateAndAssign, + GenerateAndAssignError, +} from '@/lib/tasks/generate-and-assign-core' /** * POST /api/tasks/generate-and-assign - * Find clients matching the given advocate + designation, generate tasks from - * active templates, and assign them to the advocate. - * Body: { advocateId: string, designationId: string } + * Generate tasks from active templates for either (a) every client matching + * an advocate + designation, or (b) one specific client, and assign them to + * 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. */ export async function POST(request: NextRequest) { @@ -24,260 +29,18 @@ export async function POST(request: NextRequest) { } const body = await request.json() - const { advocateId, designationId } = body + const { target, dryRun } = parseGenerateAndAssignTarget(body) - if (!advocateId || !designationId) { - return NextResponse.json( - { error: 'advocateId and designationId are required' }, - { 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 }, + const result = await runGenerateAndAssign(target, { + dryRun, + actorUserId: (session.user as any).id, }) - if (clients.length === 0) { - return NextResponse.json({ - tasksCreated: 0, - tasksAssigned: 0, - clientsFound: 0, - groupsProcessed: 0, - advocateName: advocate.displayName, - }) + return NextResponse.json(result) + } catch (error: any) { + if (error instanceof GenerateAndAssignError) { + return NextResponse.json({ error: error.message }, { status: error.status }) } - - 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) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) }