improvement: new prosthesis type data structure implemented and finally working!

This commit is contained in:
2026-09-01 21:03:04 +03:30
parent dc71c73ced
commit a3c14a18c1
51 changed files with 3306 additions and 1117 deletions

View File

@@ -133,7 +133,7 @@ describe('generateLabCaseTasks', () => {
expect(stepCodes).toContain('milling_wet');
});
it('groups teeth sharing a prosthesis in one detail, and keeps other prosthesis separate', async () => {
it('keeps one task set per tooth even when they share a prosthesis', async () => {
const pfmSteps = stepsFromSeed('pfm_crown');
const zirconiaSteps = stepsFromSeed('monolithic_zirconia');
const { tx, created } = buildMockTx({
@@ -150,16 +150,24 @@ describe('generateLabCaseTasks', () => {
const count = await generateLabCaseTasks(tx as never, 'lab-case-group', 'en');
expect(count).toBe(pfmSteps.length + zirconiaSteps.length);
expect(count).toBe(pfmSteps.length * 2 + zirconiaSteps.length);
const rows = created as Array<{ teeth: string[]; prosthesisTypeCode: string }>;
const pfmRows = rows.filter((r) => r.prosthesisTypeCode === 'pfm_crown');
const pfm14 = rows.filter(
(r) => r.prosthesisTypeCode === 'pfm_crown' && r.teeth.join() === '14',
);
const pfm15 = rows.filter(
(r) => r.prosthesisTypeCode === 'pfm_crown' && r.teeth.join() === '15',
);
const zirconiaRows = rows.filter((r) => r.prosthesisTypeCode === 'monolithic_zirconia');
expect(pfmRows).toHaveLength(pfmSteps.length);
expect(pfm14).toHaveLength(pfmSteps.length);
expect(pfm15).toHaveLength(pfmSteps.length);
expect(zirconiaRows).toHaveLength(zirconiaSteps.length);
// Teeth sharing the prosthesis in the same detail are merged and sorted.
expect(pfmRows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['14', '15']))).toBe(true);
expect(zirconiaRows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['16']))).toBe(true);
const uniqueKeys = new Set(
rows.map((r) => `${r.teeth.join()}:${r.prosthesisTypeCode}`),
);
expect(uniqueKeys.size).toBe(3);
});
it('omits packing and shipping for smile_design', async () => {
@@ -181,12 +189,13 @@ describe('generateLabCaseTasks', () => {
const stepCodes = (created as Array<{ workflowStepCode: string }>).map(
(row) => row.workflowStepCode,
);
expect(stepCodes).toEqual(['intraoral_scan', 'design']);
expect(stepCodes).not.toContain('packing');
expect(stepCodes).not.toContain('shipping');
expect(stepCodes).toContain('printer_resin');
expect(stepCodes).not.toContain('printer_resin');
});
it('merges teeth with the same prosthesis type across selection groups', async () => {
it('keeps connected and single teeth as separate per-tooth task sets', async () => {
const pfmSteps = stepsFromSeed('pfm_crown');
const { tx, created } = buildMockTx({
toothProsthesisRows: [
@@ -213,12 +222,10 @@ describe('generateLabCaseTasks', () => {
});
const count = await generateLabCaseTasks(tx as never, 'lab-case-merge', 'en');
expect(count).toBe(pfmSteps.length);
expect(count).toBe(pfmSteps.length * 3);
const rows = created as Array<{ teeth: string[]; prosthesisTypeCode: string }>;
expect(rows).toHaveLength(pfmSteps.length);
expect(rows.every((r) => JSON.stringify(r.teeth) === JSON.stringify(['14', '15', '21']))).toBe(
true,
);
const teethSets = new Set(rows.map((r) => r.teeth.join(',')));
expect(teethSets).toEqual(new Set(['14', '15', '21']));
});
it('skips generation when tasks already exist', async () => {
@@ -255,12 +262,56 @@ describe('generateLabCaseTasks', () => {
});
const count = await generateLabCaseTasks(tx as never, 'lab-internal-1', 'en');
expect(count).toBe(pfmSteps.length);
expect(count).toBe(pfmSteps.length * 2);
const rows = created as Array<{ teeth: string[]; lineId: string }>;
const teethSets = new Set(rows.map((r) => r.teeth.join(',')));
expect(teethSets).toEqual(new Set(['11', '21']));
expect(created[0]).toMatchObject({
lineId: 'line-1',
sourceKey: 'line-1',
treatmentDetailId: null,
teeth: ['11', '21'],
});
});
it('unions stacked types on the same tooth and de-dupes scan/pack/ship', async () => {
const tiSteps = stepsFromSeed('ti_base_abutment');
const zrSteps = stepsFromSeed('monolithic_zirconia');
const { tx, created } = buildMockTx({
toothProsthesisRows: [
{
treatmentDetailId: 'detail-1',
tooth: '46',
prosthesisTypeCode: 'ti_base_abutment',
},
{
treatmentDetailId: 'detail-1',
tooth: '46',
prosthesisTypeCode: 'monolithic_zirconia',
},
],
prosthesisTypes: [
{ code: 'ti_base_abutment', steps: tiSteps },
{ code: 'monolithic_zirconia', steps: zrSteps },
],
});
const count = await generateLabCaseTasks(tx as never, 'lab-case-stack', 'en');
const rows = created as Array<{
workflowStepCode: string;
prosthesisTypeCode: string;
teeth: string[];
}>;
expect(count).toBe(rows.length);
expect(rows.every((r) => r.teeth.join() === '46')).toBe(true);
expect(rows[0].prosthesisTypeCode).toBe('monolithic_zirconia+ti_base_abutment');
const stepCodes = rows.map((r) => r.workflowStepCode);
expect(stepCodes.filter((c) => c === 'intraoral_scan')).toHaveLength(1);
expect(stepCodes.filter((c) => c === 'packing')).toHaveLength(1);
expect(stepCodes.filter((c) => c === 'shipping')).toHaveLength(1);
expect(stepCodes[0]).toBe('intraoral_scan');
expect(stepCodes[stepCodes.length - 1]).toBe('shipping');
expect(stepCodes).toContain('milling_dry');
expect(stepCodes).toContain('sinter');
expect(stepCodes).toContain('polish_prep');
});
});