From ad8b03e7ba298d8b367a24c9feb700d7ad21e6b8 Mon Sep 17 00:00:00 2001 From: lorentz Date: Sun, 12 Apr 2026 20:55:54 +0000 Subject: [PATCH] Task cards: show group renewalDate; historical reset script; CLIENT task level; default window 30d --- ondeck/scripts/historical-reset.mjs | 386 +++++++++++++++++ .../src/app/(dashboard)/tasks/page-client.tsx | 2 +- .../app/api/admin/historical-reset/route.ts | 399 ++++++++++++++++++ 3 files changed, 786 insertions(+), 1 deletion(-) create mode 100644 ondeck/scripts/historical-reset.mjs create mode 100644 ondeck/src/app/api/admin/historical-reset/route.ts diff --git a/ondeck/scripts/historical-reset.mjs b/ondeck/scripts/historical-reset.mjs new file mode 100644 index 0000000..a91f599 --- /dev/null +++ b/ondeck/scripts/historical-reset.mjs @@ -0,0 +1,386 @@ +/** + * historical-reset.mjs + * Run with: node --env-file=.env scripts/historical-reset.mjs + * + * 1. Deletes all tasks + assignments + * 2. Deletes all policy groups (unlinks policies) + * 3. Runs recommendation engine per Shape client → creates groups + * 4. Generates tasks from templates (POLICY, RENEWAL_GROUP/BOTH, CLIENT) + * 5. Assigns tasks to existing claimsAdvocateId + */ + +import { PrismaClient } from '@prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' +import pg from 'pg' +import { readFileSync } from 'fs' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' + +// Load .env manually since --env-file may not work with all node versions +const __dirname = dirname(fileURLToPath(import.meta.url)) +const envPath = resolve(__dirname, '../.env') +try { + const envFile = readFileSync(envPath, 'utf8') + for (const line of envFile.split('\n')) { + const match = line.match(/^([^#=]+)=(.*)$/) + if (match) process.env[match[1].trim()] = match[2].trim().replace(/^["']|["']$/g, '') + } +} catch {} + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +const WINDOW_DAYS = 30 +const RULE = 'nearest-to-year-start' + +// ── Recommendation engine (inline, mirrors renewal-group-recommendations.ts) ─ + +function addDays(date, days) { + const d = new Date(date) + d.setDate(d.getDate() + days) + return d +} + +function dayOfYear(date) { + const start = new Date(date.getFullYear(), 0, 0) + return Math.floor((date - start) / 86400000) +} + +function computeRenewalDate(policies, rule) { + const dates = policies.map((p) => p.expirationDate) + let picked + if (rule === 'earliest') { + picked = dates.reduce((a, b) => (a < b ? a : b)) + } else if (rule === 'latest') { + picked = dates.reduce((a, b) => (a > b ? a : b)) + } else { + picked = dates.reduce((a, b) => (dayOfYear(a) <= dayOfYear(b) ? a : b)) + } + return addDays(picked, 1) +} + +function recommendGroups(policies, { windowDays, rule }) { + const groupable = policies + .filter((p) => p.expirationDate) + .sort((a, b) => a.expirationDate - b.expirationDate) + + const ungroupable = policies.filter((p) => !p.expirationDate) + + if (groupable.length === 0) return { groups: [], ungroupable } + + const clusters = [] + let current = [groupable[0]] + let windowStart = groupable[0].expirationDate + + for (let i = 1; i < groupable.length; i++) { + const diffDays = (groupable[i].expirationDate - windowStart) / 86400000 + if (diffDays <= windowDays) { + current.push(groupable[i]) + } else { + clusters.push(current) + current = [groupable[i]] + windowStart = groupable[i].expirationDate + } + } + clusters.push(current) + + const groups = clusters.map((cluster) => ({ + policies: cluster, + proposedRenewalDate: computeRenewalDate(cluster, rule), + })) + + return { groups, ungroupable } +} + +// ── Main ───────────────────────────────────────────────────────────────────── + +async function main() { + console.log('=== Historical Reset ===\n') + + // 1. Delete tasks + assignments + const delAssign = await prisma.taskAssignment.deleteMany({}) + const delTasks = await prisma.task.deleteMany({}) + console.log(`✓ Deleted ${delAssign.count} assignments, ${delTasks.count} tasks`) + + // 2. Unlink policies from groups, then delete groups + await prisma.policy.updateMany({ + where: { policyGroupId: { not: null } }, + data: { policyGroupId: null }, + }) + const delGroups = await prisma.policyGroup.deleteMany({}) + console.log(`✓ Deleted ${delGroups.count} policy groups`) + + // 3. Reset setupCompletedAt on all clients + await prisma.client.updateMany({ data: { setupCompletedAt: null, renewalDate: null } }) + console.log('✓ Reset setupCompletedAt on all clients\n') + + // 4. Fetch Shape clients with active future policies + const today = new Date() + today.setHours(0, 0, 0, 0) + + const clients = await prisma.client.findMany({ + where: { + designation: { name: { in: ['Shape', 'Shape 2'] } }, + }, + select: { + id: true, + name: true, + claimsAdvocateId: true, + designationId: true, + designation2Id: true, + policies: { + where: { + expirationDate: { gte: today }, + status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, + }, + select: { id: true, expirationDate: true }, + }, + }, + }) + + console.log(`Processing ${clients.length} Shape clients...\n`) + + let groupsCreated = 0 + let clientsSetup = 0 + + // 5. Re-run recommendations and save groups + for (const client of clients) { + if (client.policies.length === 0) continue + + const inputs = client.policies.map((p) => ({ + policyId: p.id, + expirationDate: new Date(p.expirationDate), + })) + + const { groups } = recommendGroups(inputs, { windowDays: WINDOW_DAYS, rule: RULE }) + if (groups.length === 0) continue + + await prisma.$transaction(async (tx) => { + for (let i = 0; i < groups.length; i++) { + const g = groups[i] + const created = await tx.policyGroup.create({ + data: { + clientId: client.id, + name: `Group ${i + 1}`, + renewalDate: g.proposedRenewalDate, + }, + }) + groupsCreated++ + + if (g.policies.length > 0) { + await tx.policy.updateMany({ + where: { id: { in: g.policies.map((p) => p.policyId) }, clientId: client.id }, + data: { policyGroupId: created.id }, + }) + } + } + + await tx.client.update({ + where: { id: client.id }, + data: { + renewalDate: groups[0].proposedRenewalDate, + setupCompletedAt: new Date(), + }, + }) + }) + + clientsSetup++ + } + + console.log(`✓ Created ${groupsCreated} groups across ${clientsSetup} clients\n`) + + // 6. Generate tasks ───────────────────────────────────────────────────────── + + let groupTasksCreated = 0 + let policyTasksCreated = 0 + let clientTasksCreated = 0 + let errors = 0 + + // 6a. Group-level tasks + const allGroups = await prisma.policyGroup.findMany({ + where: { policies: { some: {} } }, + include: { + client: { select: { designationId: true, designation2Id: true, claimsAdvocateId: true } }, + }, + }) + + for (const group of allGroups) { + try { + const desIds = [group.client.designationId, group.client.designation2Id].filter(Boolean) + + const templates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, + level: { in: ['BOTH', 'RENEWAL_GROUP'] }, + OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], + }, + }) + if (templates.length === 0) continue + + const renewalDate = new Date(group.renewalDate) + const tasksToCreate = templates.map((t) => { + const dueDate = new Date(renewalDate) + dueDate.setDate(dueDate.getDate() + t.daysOffset) + return { + title: t.name, description: t.description, department: t.department, + timing: t.timing, daysOffset: t.daysOffset, dueDate, + status: 'NOT_STARTED', priority: t.defaultPriority, + clientId: group.clientId, policyGroupId: group.id, templateId: t.id, + } + }) + + const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) + groupTasksCreated += created.count + + if (group.client.claimsAdvocateId && created.count > 0) { + const newTasks = await prisma.task.findMany({ + where: { policyGroupId: group.id, templateId: { in: templates.map((t) => t.id) } }, + select: { id: true }, + }) + await prisma.taskAssignment.createMany({ + data: newTasks.map((t) => ({ taskId: t.id, userId: group.client.claimsAdvocateId })), + skipDuplicates: true, + }) + } + } catch (e) { + console.error(`Group ${group.id}: ${e.message}`) + errors++ + } + } + + // 6b. Policy-level tasks (ungrouped policies only) + const ungroupedPolicies = await prisma.policy.findMany({ + where: { + policyGroupId: null, + expirationDate: { gte: today }, + status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, + client: { designation: { name: { in: ['Shape', 'Shape 2'] } } }, + }, + select: { + id: true, expirationDate: true, clientId: true, + client: { select: { designationId: true, designation2Id: true, claimsAdvocateId: true } }, + }, + }) + + for (const policy of ungroupedPolicies) { + try { + const desIds = [policy.client.designationId, policy.client.designation2Id].filter(Boolean) + + const templates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, + level: { in: ['BOTH', 'POLICY'] }, + OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], + }, + }) + if (templates.length === 0) continue + + const anchorDate = new Date(policy.expirationDate) + anchorDate.setDate(anchorDate.getDate() + 1) + + const tasksToCreate = templates.map((t) => { + const dueDate = new Date(anchorDate) + dueDate.setDate(dueDate.getDate() + t.daysOffset) + return { + title: t.name, description: t.description, department: t.department, + timing: t.timing, daysOffset: t.daysOffset, dueDate, + status: 'NOT_STARTED', priority: t.defaultPriority, + clientId: policy.clientId, policyId: policy.id, templateId: t.id, + } + }) + + const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) + policyTasksCreated += created.count + + if (policy.client.claimsAdvocateId && created.count > 0) { + const newTasks = await prisma.task.findMany({ + where: { policyId: policy.id, templateId: { in: templates.map((t) => t.id) } }, + select: { id: true }, + }) + await prisma.taskAssignment.createMany({ + data: newTasks.map((t) => ({ taskId: t.id, userId: policy.client.claimsAdvocateId })), + skipDuplicates: true, + }) + } + } catch (e) { + console.error(`Policy ${policy.id}: ${e.message}`) + errors++ + } + } + + // 6c. CLIENT-level tasks (once per client) + const allClientIds = [...new Set(allGroups.map((g) => g.clientId))] + + for (const clientId of allClientIds) { + try { + const clientRecord = await prisma.client.findUnique({ + where: { id: clientId }, + select: { + designationId: true, designation2Id: true, claimsAdvocateId: true, + policyGroups: { select: { renewalDate: true } }, + policies: { where: { policyGroupId: null }, select: { expirationDate: true } }, + }, + }) + if (!clientRecord) continue + + const desIds = [clientRecord.designationId, clientRecord.designation2Id].filter(Boolean) + + const clientTemplates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, level: 'CLIENT', + OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], + }, + }) + if (clientTemplates.length === 0) 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 dueDate = new Date(anchorDate) + dueDate.setDate(dueDate.getDate() + template.daysOffset) + + 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, + }, + select: { id: true }, + }) + clientTasksCreated++ + + if (clientRecord.claimsAdvocateId) { + await prisma.taskAssignment.create({ + data: { taskId: created.id, userId: clientRecord.claimsAdvocateId }, + }) + } + } + } catch (e) { + console.error(`Client ${clientId} (CLIENT tasks): ${e.message}`) + errors++ + } + } + + console.log(`✓ Tasks created:`) + console.log(` Group-level: ${groupTasksCreated}`) + console.log(` Policy-level: ${policyTasksCreated}`) + console.log(` Client-level: ${clientTasksCreated}`) + console.log(` Total: ${groupTasksCreated + policyTasksCreated + clientTasksCreated}`) + if (errors) console.log(` Errors: ${errors}`) + console.log('\n=== Done ===') +} + +main() + .catch((e) => { console.error(e); process.exit(1) }) + .finally(() => prisma.$disconnect()) diff --git a/ondeck/src/app/(dashboard)/tasks/page-client.tsx b/ondeck/src/app/(dashboard)/tasks/page-client.tsx index d3dc12e..44efe7a 100644 --- a/ondeck/src/app/(dashboard)/tasks/page-client.tsx +++ b/ondeck/src/app/(dashboard)/tasks/page-client.tsx @@ -262,7 +262,7 @@ function TaskCard({ task: initial }: { task: Task }) {
Policy # {task.policy?.policyNumber || '—'} Type {task.policy?.policyType || '—'} - Renewal {task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'} + Renewal {task.policyGroup?.renewalDate ? formatDate(task.policyGroup.renewalDate) : task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'}
{task.assignments.length > 0 && (

diff --git a/ondeck/src/app/api/admin/historical-reset/route.ts b/ondeck/src/app/api/admin/historical-reset/route.ts new file mode 100644 index 0000000..dd81f80 --- /dev/null +++ b/ondeck/src/app/api/admin/historical-reset/route.ts @@ -0,0 +1,399 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getServerSession } from 'next-auth' +import { authOptions } from '@/lib/auth' +import { prisma } from '@/lib/db' +import { recommendGroups } from '@/lib/renewal-group-recommendations' + +/** + * POST /api/admin/historical-reset + * Admin-only one-shot endpoint: + * 1. Deletes all tasks and task assignments + * 2. Deletes all policy groups (clears policyGroupId on policies) + * 3. Re-runs recommendation engine per client (30-day window, nearest-to-year-start) + * 4. Saves groups and runs task generation using the cron logic + * 5. Assigns tasks to existing claimsAdvocateId + */ +export async function POST(request: NextRequest) { + try { + const session = await getServerSession(authOptions) + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userRoles = (session.user as any).roles || [] + if (!userRoles.includes('Admin')) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + const log: string[] = [] + + // ─── 1. Delete all tasks and assignments ──────────────────────────────── + const deletedAssignments = await prisma.taskAssignment.deleteMany({}) + const deletedTasks = await prisma.task.deleteMany({}) + log.push(`Deleted ${deletedAssignments.count} assignments, ${deletedTasks.count} tasks`) + + // ─── 2. Delete all policy groups (cascade unlinks policies) ───────────── + // First unlink all policies from their groups + await prisma.policy.updateMany({ + where: { policyGroupId: { not: null } }, + data: { policyGroupId: null }, + }) + const deletedGroups = await prisma.policyGroup.deleteMany({}) + log.push(`Deleted ${deletedGroups.count} policy groups`) + + // ─── 3. Reset setupCompletedAt on all clients ──────────────────────────── + await prisma.client.updateMany({ + data: { setupCompletedAt: null, renewalDate: null }, + }) + log.push('Reset setupCompletedAt and renewalDate on all clients') + + // ─── 4. Fetch all clients with their policies and designations ─────────── + const clients = await prisma.client.findMany({ + select: { + id: true, + claimsAdvocateId: true, + designationId: true, + designation2Id: true, + notes: true, + policies: { + where: { + expirationDate: { gte: new Date() }, + status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, + }, + select: { id: true, expirationDate: true }, + }, + }, + }) + + log.push(`Processing ${clients.length} clients`) + + const windowDays = 30 + const rule = 'nearest-to-year-start' as const + let groupsCreated = 0 + let clientsWithGroups = 0 + let clientErrors = 0 + + // ─── 5. Re-run recommendations and save groups per client ──────────────── + for (const client of clients) { + try { + if (client.policies.length === 0) continue + + const inputs = client.policies.map((p) => ({ + policyId: p.id, + expirationDate: p.expirationDate ? new Date(p.expirationDate) : null, + })) + + const result = recommendGroups(inputs, { windowDays, rule }) + if (result.groups.length === 0) continue + + const defaultGroup = result.groups[0] + const defaultRenewalDate = defaultGroup.proposedRenewalDate + + await prisma.$transaction(async (tx) => { + for (let i = 0; i < result.groups.length; i++) { + const g = result.groups[i] + const isDefault = i === 0 + + const created = await tx.policyGroup.create({ + data: { + clientId: client.id, + name: `Group ${i + 1}`, + renewalDate: g.proposedRenewalDate, + createdBy: (session.user as any).id, + }, + }) + groupsCreated++ + + if (g.policies.length > 0) { + await tx.policy.updateMany({ + where: { id: { in: g.policies.map((p) => p.policyId) }, clientId: client.id }, + data: { policyGroupId: created.id }, + }) + } + } + + await tx.client.update({ + where: { id: client.id }, + data: { + renewalDate: defaultRenewalDate, + setupCompletedAt: new Date(), + }, + }) + }) + + clientsWithGroups++ + } catch (err: any) { + log.push(`Error on client ${client.id}: ${err.message}`) + clientErrors++ + } + } + + log.push(`Created ${groupsCreated} groups across ${clientsWithGroups} clients (${clientErrors} errors)`) + + // ─── 6. Run task generation (same logic as auto-generate cron)─────────── + let groupTasksCreated = 0 + let policyTasksCreated = 0 + let clientTasksCreated = 0 + const taskErrors: string[] = [] + + const groups = await prisma.policyGroup.findMany({ + where: { + policies: { some: {} }, + }, + include: { + client: { + select: { + designationId: true, + designation2Id: true, + claimsAdvocateId: true, + }, + }, + }, + }) + + for (const group of groups) { + try { + const designationIds = [ + group.client.designationId, + group.client.designation2Id, + ].filter(Boolean) as string[] + + const templates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, + level: { in: ['BOTH', 'RENEWAL_GROUP'] }, + OR: [ + { designationId: null }, + ...(designationIds.length > 0 ? [{ designationId: { in: designationIds } }] : []), + ], + }, + orderBy: [{ department: 'asc' }, { daysOffset: 'asc' }], + }) + + if (templates.length === 0) continue + + const renewalDate = new Date(group.renewalDate) + + const tasksToCreate = templates.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, + } + }) + + const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) + groupTasksCreated += created.count + + if (group.client.claimsAdvocateId && created.count > 0) { + const newTasks = await prisma.task.findMany({ + where: { policyGroupId: group.id, templateId: { in: templates.map((t) => t.id) } }, + select: { id: true }, + }) + await prisma.taskAssignment.createMany({ + data: newTasks.map((t) => ({ taskId: t.id, userId: group.client.claimsAdvocateId! })), + skipDuplicates: true, + }) + } + } catch (err: any) { + taskErrors.push(`Group ${group.id}: ${err.message}`) + } + } + + // Policy-level tasks (ungrouped policies) + const ungroupedPolicies = await prisma.policy.findMany({ + where: { + policyGroupId: null, + expirationDate: { gte: new Date() }, + status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, + }, + select: { + id: true, + expirationDate: true, + clientId: true, + client: { + select: { + designationId: true, + designation2Id: true, + claimsAdvocateId: true, + }, + }, + }, + }) + + for (const policy of ungroupedPolicies) { + try { + const designationIds = [ + policy.client.designationId, + policy.client.designation2Id, + ].filter(Boolean) as string[] + + const templates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, + level: { in: ['BOTH', 'POLICY'] }, + OR: [ + { designationId: null }, + ...(designationIds.length > 0 ? [{ designationId: { in: designationIds } }] : []), + ], + }, + orderBy: [{ department: 'asc' }, { daysOffset: 'asc' }], + }) + + if (templates.length === 0) continue + + const anchorDate = new Date(policy.expirationDate) + anchorDate.setDate(anchorDate.getDate() + 1) + + const tasksToCreate = templates.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, + } + }) + + const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) + policyTasksCreated += created.count + + if (policy.client.claimsAdvocateId && created.count > 0) { + const newTasks = await prisma.task.findMany({ + where: { policyId: policy.id, templateId: { in: templates.map((t) => t.id) } }, + select: { id: true }, + }) + await prisma.taskAssignment.createMany({ + data: newTasks.map((t) => ({ taskId: t.id, userId: policy.client.claimsAdvocateId! })), + skipDuplicates: true, + }) + } + } catch (err: any) { + taskErrors.push(`Policy ${policy.id}: ${err.message}`) + } + } + + // CLIENT-level tasks (once per client) + const allClientIds = [...new Set([ + ...groups.map((g) => g.clientId), + ...ungroupedPolicies.map((p) => p.clientId), + ])] + + for (const clientId of allClientIds) { + try { + const clientRecord = await prisma.client.findUnique({ + where: { id: clientId }, + select: { + designationId: true, + designation2Id: true, + claimsAdvocateId: true, + policyGroups: { select: { renewalDate: true } }, + policies: { + where: { policyGroupId: null }, + select: { expirationDate: true }, + }, + }, + }) + if (!clientRecord) continue + + const designationIds = [ + clientRecord.designationId, + clientRecord.designation2Id, + ].filter(Boolean) as string[] + + const clientTemplates = await prisma.taskTemplate.findMany({ + where: { + isActive: true, + level: 'CLIENT', + OR: [ + { designationId: null }, + ...(designationIds.length > 0 ? [{ designationId: { in: designationIds } }] : []), + ], + }, + orderBy: [{ department: 'asc' }, { daysOffset: 'asc' }], + }) + + if (clientTemplates.length === 0) 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 dueDate = new Date(anchorDate) + dueDate.setDate(dueDate.getDate() + template.daysOffset) + + 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, + }, + select: { id: true }, + }) + clientTasksCreated++ + + if (clientRecord.claimsAdvocateId) { + await prisma.taskAssignment.create({ + data: { taskId: created.id, userId: clientRecord.claimsAdvocateId }, + }) + } + } + } catch (err: any) { + taskErrors.push(`Client ${clientId} (CLIENT tasks): ${err.message}`) + } + } + + log.push(`Tasks created: ${groupTasksCreated} group, ${policyTasksCreated} policy, ${clientTasksCreated} client-level`) + if (taskErrors.length > 0) log.push(`Task errors: ${taskErrors.join('; ')}`) + + return NextResponse.json({ + success: true, + summary: { + deletedTasks: deletedTasks.count, + deletedGroups: deletedGroups.count, + clientsProcessed: clientsWithGroups, + groupsCreated, + groupTasksCreated, + policyTasksCreated, + clientTasksCreated, + totalTasksCreated: groupTasksCreated + policyTasksCreated + clientTasksCreated, + }, + log, + errors: taskErrors.length > 0 ? taskErrors : undefined, + }) + } catch (error: any) { + console.error('Historical reset error:', error) + return NextResponse.json({ error: 'Internal server error', detail: error.message }, { status: 500 }) + } +}