import { CatalogEntityKind, LabTaskStatus, Prisma } from '@prisma/client'; import { normalizeCatalogLocale } from '../catalog/catalog-label.service'; type TransactionClient = Prisma.TransactionClient; export async function generateLabCaseTasks( tx: TransactionClient, labCaseId: string, localeInput?: string | null, ): Promise { const existingCount = await tx.labCaseTask.count({ where: { labCaseId } }); if (existingCount > 0) { return 0; } const locale = normalizeCatalogLocale(localeInput); const toothProsthesisRows = await tx.labCaseToothProsthesis.findMany({ where: { labCaseId }, include: { detail: { select: { id: true, treatmentType: true, toothSelectionGroups: true } }, }, }); if (toothProsthesisRows.length === 0) { return 0; } const prosthesisCodes = [...new Set(toothProsthesisRows.map((r) => r.prosthesisTypeCode))]; const prosthesisTypes = await tx.prosthesisType.findMany({ where: { code: { in: prosthesisCodes }, isActive: true }, include: { steps: { orderBy: { stepOrder: 'asc' }, include: { labWorkflowStep: { select: { code: true } } }, }, }, }); const stepsByProsthesisCode = new Map( prosthesisTypes.map((type) => [ type.code, type.steps.map((s) => ({ stepOrder: s.stepOrder, workflowStepCode: s.labWorkflowStep.code, })), ]), ); const allStepCodes = [ ...new Set( prosthesisTypes.flatMap((type) => type.steps.map((s) => s.labWorkflowStep.code), ), ), ]; const stepLabels = await resolveStepLabels(tx, allStepCodes, locale); // Merge all teeth that share the same prosthesis type on a detail into one // task set (pre–selection-group behavior). selectionGroupId is stored for // UI/history only — not part of the grouping key. const groups = new Map< string, { treatmentDetailId: string; treatmentType: string; prosthesisTypeCode: string; selectionGroupId: string; teeth: string[]; } >(); for (const row of toothProsthesisRows) { const key = `${row.treatmentDetailId}::${row.prosthesisTypeCode}`; const selectionGroupId = row.selectionGroupId?.trim() || ''; const group = groups.get(key) ?? { treatmentDetailId: row.treatmentDetailId, treatmentType: row.detail.treatmentType, prosthesisTypeCode: row.prosthesisTypeCode, selectionGroupId, teeth: [], }; // Keep first non-empty selectionGroupId for persistence; grouping ignores it. if (!group.selectionGroupId && selectionGroupId) { group.selectionGroupId = selectionGroupId; } group.teeth.push(row.tooth); groups.set(key, group); } const taskRows: Prisma.LabCaseTaskCreateManyInput[] = []; for (const group of groups.values()) { const typeSteps = stepsByProsthesisCode.get(group.prosthesisTypeCode) ?? []; if (typeSteps.length === 0) { continue; } const teeth = sortTeeth(group.teeth); for (const step of typeSteps) { taskRows.push({ labCaseId, treatmentDetailId: group.treatmentDetailId, teeth, treatmentType: group.treatmentType, prosthesisTypeCode: group.prosthesisTypeCode, selectionGroupId: group.selectionGroupId, workflowStepCode: step.workflowStepCode, stepOrder: step.stepOrder, stepLabel: stepLabels.get(step.workflowStepCode) ?? step.workflowStepCode, status: LabTaskStatus.IN_PROGRESS, }); } } if (taskRows.length === 0) { return 0; } await tx.labCaseTask.createMany({ data: taskRows }); return taskRows.length; } function sortTeeth(teeth: string[]): string[] { return [...new Set(teeth)].sort((a, b) => { const na = Number(a); const nb = Number(b); if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb; return a.localeCompare(b); }); } async function resolveStepLabels( tx: TransactionClient, stepCodes: string[], locale: string, ): Promise> { if (stepCodes.length === 0) { return new Map(); } const rows = await tx.catalogTranslation.findMany({ where: { entityKind: CatalogEntityKind.LAB_WORKFLOW_STEP, entityCode: { in: stepCodes }, locale: { in: [locale, 'en'] }, }, select: { entityCode: true, locale: true, label: true }, }); const byCode = new Map(); for (const row of rows) { const entry = byCode.get(row.entityCode) ?? {}; if (row.locale === 'en') entry.en = row.label; if (row.locale === locale) entry.locale = row.label; byCode.set(row.entityCode, entry); } const result = new Map(); for (const code of stepCodes) { const entry = byCode.get(code); result.set(code, entry?.locale ?? entry?.en ?? code); } return result; }