Fix Shape import: resolve policyId/policyGroupId when creating new tasks

- Add resolvePolicyLink() to find closest policy group or policy by effectiveDate window
- Include template level in DbTemplate interface and loadDbState query
- New tasks created by import now carry correct policyId or policyGroupId
- Delete all 492 orphaned POLICY/RENEWAL_GROUP tasks (no policy link)
This commit is contained in:
lorentz 2026-04-13 19:55:40 +00:00
parent 00b5fab1ba
commit 02afceb0c5

View file

@ -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<void>): Promise<DbState>
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<void>): Promise<DbState>
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<void> {
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<void> {
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++