refactor(tasks): route generate-and-assign through the shared core module
This commit is contained in:
parent
a1206f9515
commit
5daab12d98
1 changed files with 18 additions and 255 deletions
|
|
@ -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 result = await runGenerateAndAssign(target, {
|
||||
dryRun,
|
||||
actorUserId: (session.user as any).id,
|
||||
})
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error: any) {
|
||||
if (error instanceof GenerateAndAssignError) {
|
||||
return NextResponse.json({ error: error.message }, { status: error.status })
|
||||
}
|
||||
|
||||
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({
|
||||
tasksCreated: 0,
|
||||
tasksAssigned: 0,
|
||||
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)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue