improvement: new prosthesis type data structure implemented and finally working!
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { CatalogEntityKind, LabTaskStatus, Prisma } from '@prisma/client';
|
||||
import { normalizeCatalogLocale } from '../catalog/catalog-label.service';
|
||||
import {
|
||||
prosthesisGroupKey,
|
||||
unionWorkflowStepCodes,
|
||||
} from '../../common/prosthesis-group';
|
||||
|
||||
type TransactionClient = Prisma.TransactionClient;
|
||||
|
||||
@@ -30,7 +34,7 @@ export async function generateLabCaseTasks(
|
||||
const prosthesisCodes = [...new Set(toothProsthesisRows.map((r) => r.prosthesisTypeCode))];
|
||||
|
||||
const prosthesisTypes = await tx.prosthesisType.findMany({
|
||||
where: { code: { in: prosthesisCodes }, isActive: true },
|
||||
where: { code: { in: prosthesisCodes } },
|
||||
include: {
|
||||
steps: {
|
||||
orderBy: { stepOrder: 'asc' },
|
||||
@@ -59,21 +63,18 @@ export async function generateLabCaseTasks(
|
||||
|
||||
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,
|
||||
{
|
||||
sourceKey: string;
|
||||
treatmentDetailId: string | null;
|
||||
lineId: string | null;
|
||||
treatmentType: string;
|
||||
prosthesisTypeCode: string;
|
||||
selectionGroupId: string;
|
||||
teeth: string[];
|
||||
}
|
||||
>();
|
||||
// Per tooth: stacked types become one group key; steps are the union.
|
||||
// Teeth are not merged even when they share the same type-set.
|
||||
type ToothBucket = {
|
||||
sourceKey: string;
|
||||
treatmentDetailId: string | null;
|
||||
lineId: string | null;
|
||||
treatmentType: string;
|
||||
selectionGroupId: string;
|
||||
codes: Set<string>;
|
||||
};
|
||||
|
||||
const byTooth = new Map<string, ToothBucket>();
|
||||
|
||||
for (const row of toothProsthesisRows) {
|
||||
const sourceKey =
|
||||
@@ -82,9 +83,9 @@ export async function generateLabCaseTasks(
|
||||
row.lineId ||
|
||||
'';
|
||||
if (!sourceKey) continue;
|
||||
const key = `${sourceKey}::${row.prosthesisTypeCode}`;
|
||||
const toothKey = `${sourceKey}::${row.tooth}`;
|
||||
const selectionGroupId = row.selectionGroupId?.trim() || '';
|
||||
const group = groups.get(key) ?? {
|
||||
const bucket = byTooth.get(toothKey) ?? {
|
||||
sourceKey,
|
||||
treatmentDetailId: row.treatmentDetailId,
|
||||
lineId: row.lineId,
|
||||
@@ -93,44 +94,50 @@ export async function generateLabCaseTasks(
|
||||
row.line?.treatmentType ??
|
||||
row.treatmentType ??
|
||||
'prosthesis',
|
||||
prosthesisTypeCode: row.prosthesisTypeCode,
|
||||
selectionGroupId,
|
||||
teeth: [],
|
||||
codes: new Set<string>(),
|
||||
};
|
||||
// Keep first non-empty selectionGroupId for persistence; grouping ignores it.
|
||||
if (!group.selectionGroupId && selectionGroupId) {
|
||||
group.selectionGroupId = selectionGroupId;
|
||||
if (!bucket.selectionGroupId && selectionGroupId) {
|
||||
bucket.selectionGroupId = selectionGroupId;
|
||||
}
|
||||
group.teeth.push(row.tooth);
|
||||
groups.set(key, group);
|
||||
bucket.codes.add(row.prosthesisTypeCode);
|
||||
byTooth.set(toothKey, bucket);
|
||||
}
|
||||
|
||||
// One lab item per tooth (or arch sentinel): stacked types on that
|
||||
// tooth become one prosthesisTypeCode group key; steps are the union.
|
||||
const taskRows: Prisma.LabCaseTaskCreateManyInput[] = [];
|
||||
|
||||
for (const group of groups.values()) {
|
||||
const typeSteps = stepsByProsthesisCode.get(group.prosthesisTypeCode) ?? [];
|
||||
if (typeSteps.length === 0) {
|
||||
for (const [toothKey, bucket] of byTooth) {
|
||||
const tooth = toothKey.slice(bucket.sourceKey.length + 2);
|
||||
const codes = [...bucket.codes];
|
||||
const lists = codes.map((code) =>
|
||||
(stepsByProsthesisCode.get(code) ?? []).map((s) => s.workflowStepCode),
|
||||
);
|
||||
const unioned = unionWorkflowStepCodes(lists);
|
||||
if (unioned.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const teeth = sortTeeth(group.teeth);
|
||||
const groupCode = prosthesisGroupKey(codes);
|
||||
|
||||
for (const step of typeSteps) {
|
||||
unioned.forEach((workflowStepCode, index) => {
|
||||
taskRows.push({
|
||||
labCaseId,
|
||||
treatmentDetailId: group.treatmentDetailId,
|
||||
lineId: group.lineId,
|
||||
sourceKey: group.sourceKey,
|
||||
teeth,
|
||||
treatmentType: group.treatmentType,
|
||||
prosthesisTypeCode: group.prosthesisTypeCode,
|
||||
selectionGroupId: group.selectionGroupId,
|
||||
workflowStepCode: step.workflowStepCode,
|
||||
stepOrder: step.stepOrder,
|
||||
stepLabel: stepLabels.get(step.workflowStepCode) ?? step.workflowStepCode,
|
||||
treatmentDetailId: bucket.treatmentDetailId,
|
||||
lineId: bucket.lineId,
|
||||
sourceKey: bucket.sourceKey,
|
||||
tooth,
|
||||
teeth: [tooth],
|
||||
treatmentType: bucket.treatmentType,
|
||||
prosthesisTypeCode: groupCode,
|
||||
selectionGroupId: bucket.selectionGroupId,
|
||||
workflowStepCode,
|
||||
stepOrder: index + 1,
|
||||
stepLabel: stepLabels.get(workflowStepCode) ?? workflowStepCode,
|
||||
status: LabTaskStatus.IN_PROGRESS,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (taskRows.length === 0) {
|
||||
@@ -141,15 +148,6 @@ export async function generateLabCaseTasks(
|
||||
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[],
|
||||
|
||||
Reference in New Issue
Block a user