diff --git a/ondeck/src/lib/shape-import/run-import.ts b/ondeck/src/lib/shape-import/run-import.ts index f8c693f..5f18f63 100644 --- a/ondeck/src/lib/shape-import/run-import.ts +++ b/ondeck/src/lib/shape-import/run-import.ts @@ -50,6 +50,7 @@ interface DbTemplate { name: string daysOffset: number designationId: string | null + level: string } interface DbClient { @@ -511,6 +512,7 @@ async function loadDbState(log: (l: string) => Promise): Promise const allTemplates = await prisma.taskTemplate.findMany({ where: { designationId: { in: [shapeDes.id, shape2Des.id] }, isActive: true }, + select: { id: true, name: true, daysOffset: true, designationId: true, level: true }, }) const shapeTemplates = allTemplates.filter((t) => t.designationId === shapeDes.id) const shape2Templates = allTemplates.filter((t) => t.designationId === shape2Des.id) @@ -532,8 +534,8 @@ async function loadDbState(log: (l: string) => Promise): Promise return { clientsByNormalizedName, allClients: clients.map((c) => ({ id: c.id, name: c.name, claimsAdvocateId: c.claimsAdvocateId })), - shapeTemplates: shapeTemplates.map((t) => ({ id: t.id, name: t.name, daysOffset: t.daysOffset, designationId: t.designationId })), - shape2Templates: shape2Templates.map((t) => ({ id: t.id, name: t.name, daysOffset: t.daysOffset, designationId: t.designationId })), + shapeTemplates: shapeTemplates.map((t) => ({ id: t.id, name: t.name, daysOffset: t.daysOffset, designationId: t.designationId, level: t.level })), + shape2Templates: shape2Templates.map((t) => ({ id: t.id, name: t.name, daysOffset: t.daysOffset, designationId: t.designationId, level: t.level })), shapeDesignationId: shapeDes.id, shape2DesignationId: shape2Des.id, } @@ -594,7 +596,45 @@ async function ensureTaskAssignment(taskId: string, userId: string, stats: Impor } catch { /* already exists */ } } -async function processTaskRow(clientId: string, row: ParsedRow, template: DbTemplate, effectiveDate: Date, advocateUserId: string, stats: ImportStats, dryRun: boolean): Promise { +async function resolvePolicyLink(clientId: string, effectiveDate: Date, templateLevel: string): Promise<{ policyId?: string; policyGroupId?: string }> { + if (templateLevel === 'CLIENT') return {} + const windowMs = 40 * 86400 * 1000 + const from = new Date(effectiveDate.getTime() - windowMs) + const to = new Date(effectiveDate.getTime() + windowMs) + + if (templateLevel === 'RENEWAL_GROUP' || templateLevel === 'BOTH') { + const group = await prisma.policyGroup.findFirst({ + where: { clientId, renewalDate: { gte: from, lte: to } }, + orderBy: { renewalDate: 'asc' }, + select: { id: true, renewalDate: true }, + }) + if (group) return { policyGroupId: group.id } + } + + if (templateLevel === 'POLICY' || templateLevel === 'BOTH') { + const policies = await prisma.policy.findMany({ + where: { + clientId, + expirationDate: { gte: new Date(from.getTime() - 86400 * 1000), lte: new Date(to.getTime() - 86400 * 1000) }, + status: { notIn: ['Cancelled', 'Non-Renewed', 'Rewritten', 'Not taken'] as any[] }, + }, + select: { id: true, expirationDate: true, policyGroupId: true }, + orderBy: { expirationDate: 'asc' }, + }) + if (policies.length > 0) { + const best = policies.reduce((a, b) => { + const aRenewal = new Date(a.expirationDate).getTime() + 86400 * 1000 + const bRenewal = new Date(b.expirationDate).getTime() + 86400 * 1000 + return Math.abs(aRenewal - effectiveDate.getTime()) <= Math.abs(bRenewal - effectiveDate.getTime()) ? a : b + }) + if (best.policyGroupId) return { policyGroupId: best.policyGroupId } + return { policyId: best.id } + } + } + return {} +} + +async function processTaskRow(clientId: string, row: ParsedRow, template: DbTemplate & { level?: string }, effectiveDate: Date, advocateUserId: string, stats: ImportStats, dryRun: boolean): Promise { const dueDate = new Date(effectiveDate) dueDate.setDate(dueDate.getDate() + Math.round(row.daysAfterRenewal)) const { status, completedAt, naReason } = interpretDateCompleted(row.dateCompleted) @@ -617,15 +657,16 @@ async function processTaskRow(clientId: string, row: ParsedRow, template: DbTemp } } } else { + const link = dryRun ? {} : await resolvePolicyLink(clientId, effectiveDate, template.level ?? 'CLIENT') if (status === 'COMPLETED' || status === 'NA') { if (!dryRun) { - const newTask = await prisma.task.create({ data: { title: template.name, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: template.daysOffset, dueDate, status, priority: 'MEDIUM', clientId, templateId: template.id, completedAt: completedAt ?? undefined, completedBy: status === 'COMPLETED' ? advocateUserId : undefined, naReason: naReason ?? undefined, notes: row.notes || undefined, isAdHoc: false, createdBy: advocateUserId } }) + const newTask = await prisma.task.create({ data: { title: template.name, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: template.daysOffset, dueDate, status, priority: 'MEDIUM', clientId, templateId: template.id, ...link, completedAt: completedAt ?? undefined, completedBy: status === 'COMPLETED' ? advocateUserId : undefined, naReason: naReason ?? undefined, notes: row.notes || undefined, isAdHoc: false, createdBy: advocateUserId } }) await ensureTaskAssignment(newTask.id, advocateUserId, stats, dryRun) } else { stats.tasksAssigned++ } stats.tasksCreated++ } else { if (!dryRun) { - const newTask = await prisma.task.create({ data: { title: template.name, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: template.daysOffset, dueDate, status: 'NOT_STARTED', priority: 'MEDIUM', clientId, templateId: template.id, notes: row.notes || undefined, isAdHoc: false, createdBy: advocateUserId } }) + const newTask = await prisma.task.create({ data: { title: template.name, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: template.daysOffset, dueDate, status: 'NOT_STARTED', priority: 'MEDIUM', clientId, templateId: template.id, ...link, notes: row.notes || undefined, isAdHoc: false, createdBy: advocateUserId } }) await ensureTaskAssignment(newTask.id, advocateUserId, stats, dryRun) } else { stats.tasksAssigned++ } stats.tasksCreated++