import { CatalogEntityKind } from '@prisma/client'; export type CatalogTranslationSeed = { entityKind: CatalogEntityKind; entityCode: string; locale: string; label: string; }; export type TreatmentTypeSeed = { code: string; labDependent: boolean; sortOrder: number; /** Selectable when booking an appointment. Defaults to true. */ availableInAppointments?: boolean; /** Selectable as a treatment plan detail. Defaults to true. */ availableInTreatment?: boolean; }; export const TREATMENT_TYPES: readonly TreatmentTypeSeed[] = [ { code: 'restoration', labDependent: false, sortOrder: 1 }, { code: 'specialized_restoration', labDependent: false, sortOrder: 2 }, { code: 'radiography', labDependent: false, sortOrder: 3 }, { code: 'endo', labDependent: false, sortOrder: 4 }, { code: 'surgery', labDependent: false, sortOrder: 5 }, { code: 'prosthesis', labDependent: true, sortOrder: 6 }, { code: 'implant', labDependent: false, sortOrder: 7 }, { code: 'orthodontics', labDependent: false, sortOrder: 8 }, { code: 'perio', labDependent: false, sortOrder: 9 }, { code: 'pediatrics', labDependent: false, sortOrder: 10 }, { code: 'extraction', labDependent: false, sortOrder: 11 }, { code: 'clinic_visit', labDependent: false, sortOrder: 12, availableInTreatment: false, }, { code: 'continue_treatment', labDependent: false, sortOrder: 13, availableInTreatment: false, }, ] as const; /** Legacy codes kept for historical rows; hidden from catalog. */ export const LEGACY_TREATMENT_TYPES: readonly TreatmentTypeSeed[] = [ { code: 'consultation', labDependent: false, sortOrder: 99 }, { code: 'filling', labDependent: false, sortOrder: 100 }, { code: 'visit', labDependent: false, sortOrder: 101 }, { code: 'hygiene', labDependent: false, sortOrder: 102 }, ] as const; export const LAB_WORKFLOW_STEPS = [ { code: 'intraoral_scan', sortOrder: 1 }, { code: 'choosing_abutment', sortOrder: 2 }, { code: 'design', sortOrder: 3 }, { code: 'model_create', sortOrder: 4 }, { code: 'milling_dry', sortOrder: 5 }, { code: 'milling_wet', sortOrder: 6 }, { code: 'printer_resin', sortOrder: 7 }, { code: 'printer_metal', sortOrder: 8 }, { code: 'pressing', sortOrder: 9 }, { code: 'sinter', sortOrder: 10 }, { code: 'build_up', sortOrder: 11 }, { code: 'stain', sortOrder: 12 }, { code: 'glaze', sortOrder: 13 }, { code: 'polish_prep', sortOrder: 14 }, { code: 'packing', sortOrder: 15 }, { code: 'shipping', sortOrder: 16 }, ] as const; export type ProsthesisChartRegion = 'crown' | 'root' | 'arch'; export type ProsthesisStackGroup = 'restoration' | 'implant' | 'post_core' | 'arch'; export type ProsthesisAddonKind = '' | 'implant' | 'post_core'; export type ProsthesisTypeSeed = { code: string; sortOrder: number; skipPackingShipping?: boolean; isActive?: boolean; category: string; subcategory?: string; chartRegion: ProsthesisChartRegion; stackGroup: ProsthesisStackGroup; addonKind?: ProsthesisAddonKind; /** Manufacturing sequence. Packing and shipping are appended except for smile_design. */ manufacturingSteps: readonly string[]; }; const SCAN_DESIGN_MODEL = ['intraoral_scan', 'design', 'model_create'] as const; const INDIRECT_FULL = [...SCAN_DESIGN_MODEL, 'milling_wet', 'sinter', 'stain', 'glaze'] as const; const INDIRECT_LAYERED = [ ...SCAN_DESIGN_MODEL, 'milling_wet', 'sinter', 'build_up', 'stain', 'glaze', ] as const; /** Unlisted types keep scan+design plus today’s manufacturing (no model-create). */ const KEEP_FULL_CONTOUR = [ 'intraoral_scan', 'design', 'milling_wet', 'stain', 'glaze', 'polish_prep', ] as const; const KEEP_LAYERED = [ 'intraoral_scan', 'design', 'milling_dry', 'sinter', 'build_up', 'stain', 'glaze', 'polish_prep', ] as const; const DENTURE = ['intraoral_scan', 'design', 'printer_resin', 'polish_prep'] as const; const APPLIANCE = ['intraoral_scan', 'design', 'printer_resin', 'polish_prep'] as const; const POST_CORE = ['intraoral_scan', 'design', 'milling_wet', 'polish_prep'] as const; function crown( code: string, sortOrder: number, manufacturingSteps: readonly string[], extra?: Partial, ): ProsthesisTypeSeed { return { code, sortOrder, category: 'crown', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps, ...extra, }; } function implantAddon( code: string, sortOrder: number, manufacturingSteps: readonly string[], extra?: Partial, ): ProsthesisTypeSeed { return { code, sortOrder, category: 'implant', chartRegion: 'root', stackGroup: 'implant', addonKind: 'implant', manufacturingSteps, ...extra, }; } function indirect( indication: string, technique: 'full_contour' | 'layered', sortOrder: number, ): ProsthesisTypeSeed { return { code: `${indication}_${technique}`, sortOrder, category: 'indirect', subcategory: indication, chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: technique === 'layered' ? INDIRECT_LAYERED : INDIRECT_FULL, }; } export const PROSTHESIS_TYPES: ProsthesisTypeSeed[] = [ crown('pfm_crown', 1, [ ...SCAN_DESIGN_MODEL, 'milling_wet', 'sinter', 'build_up', 'stain', 'glaze', ]), crown('pfz_crown', 2, [ ...SCAN_DESIGN_MODEL, 'milling_dry', 'sinter', 'build_up', 'stain', 'glaze', ]), crown('monolithic_zirconia', 3, [ ...SCAN_DESIGN_MODEL, 'milling_dry', 'sinter', 'stain', 'glaze', ]), crown('glass_ceramic_crown', 4, [ ...SCAN_DESIGN_MODEL, 'milling_dry', 'sinter', 'stain', 'glaze', ]), crown('full_metal_crown', 5, [...SCAN_DESIGN_MODEL, 'milling_wet', 'polish_prep']), crown('temporary_resin_crown', 6, ['intraoral_scan', 'design', 'milling_wet', 'polish_prep']), crown('pmma', 7, ['intraoral_scan', 'design', 'milling_dry', 'polish_prep']), crown('peek_crown', 8, ['intraoral_scan', 'design', 'milling_dry', 'polish_prep']), crown('press_ceramic', 9, [ 'intraoral_scan', 'design', 'printer_resin', 'pressing', 'stain', 'glaze', 'polish_prep', ]), indirect('veneer', 'full_contour', 20), indirect('veneer', 'layered', 21), indirect('inlay', 'full_contour', 22), indirect('inlay', 'layered', 23), indirect('onlay', 'full_contour', 24), indirect('onlay', 'layered', 25), indirect('overlay', 'full_contour', 26), indirect('overlay', 'layered', 27), { code: 'cast_post_core', sortOrder: 30, category: 'post_core', chartRegion: 'root', stackGroup: 'post_core', addonKind: 'post_core', manufacturingSteps: POST_CORE, }, { code: 'fiber_post_core', sortOrder: 31, category: 'post_core', chartRegion: 'root', stackGroup: 'post_core', addonKind: 'post_core', manufacturingSteps: POST_CORE, }, implantAddon('prefabricated_abutment', 40, [ 'intraoral_scan', 'choosing_abutment', 'model_create', 'polish_prep', ]), implantAddon('ti_base_abutment', 41, ['intraoral_scan', 'model_create', 'polish_prep']), implantAddon('multi_unit_abutment', 42, [ 'intraoral_scan', 'choosing_abutment', 'model_create', 'polish_prep', ]), implantAddon('customized_abutment', 43, [ ...SCAN_DESIGN_MODEL, 'milling_wet', 'polish_prep', ]), implantAddon('zirconia_abutment', 44, [...SCAN_DESIGN_MODEL, 'milling_dry', 'sinter']), implantAddon( 'screw_retained', 45, [...SCAN_DESIGN_MODEL, 'milling_wet', 'polish_prep'], { chartRegion: 'crown' }, ), { code: 'complete_denture', sortOrder: 50, category: 'removable', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: DENTURE, }, { code: 'partial_denture', sortOrder: 51, category: 'removable', chartRegion: 'crown', stackGroup: 'arch', manufacturingSteps: DENTURE, }, { code: 'overdenture', sortOrder: 52, category: 'removable', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: DENTURE, }, { code: 'night_guard_soft', sortOrder: 60, category: 'appliance', subcategory: 'night_guard', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'night_guard_hard', sortOrder: 61, category: 'appliance', subcategory: 'night_guard', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'night_guard_dual', sortOrder: 62, category: 'appliance', subcategory: 'night_guard', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'bleaching_tray', sortOrder: 63, category: 'appliance', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'clear_aligner', sortOrder: 64, category: 'appliance', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'soft_structure', sortOrder: 65, category: 'appliance', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: ['intraoral_scan', 'design', 'milling_dry', 'sinter'], }, { code: 'surgical_guide', sortOrder: 70, category: 'digital', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: APPLIANCE, }, { code: 'smile_design', sortOrder: 71, category: 'digital', chartRegion: 'arch', stackGroup: 'arch', skipPackingShipping: true, manufacturingSteps: ['intraoral_scan', 'design'], }, { code: 'mockup', sortOrder: 72, category: 'digital', chartRegion: 'arch', stackGroup: 'arch', manufacturingSteps: ['intraoral_scan', 'design', 'model_create'], }, // Legacy leaves — labels for historical cases; hidden from the picker. { code: 'veneer_zirconia', sortOrder: 200, isActive: false, category: 'indirect', subcategory: 'veneer', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: KEEP_LAYERED, }, { code: 'veneer_ips_press', sortOrder: 201, isActive: false, category: 'indirect', subcategory: 'veneer', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: [ 'intraoral_scan', 'design', 'printer_resin', 'build_up', 'stain', 'glaze', 'polish_prep', 'pressing', ], }, { code: 'veneer_ips_cad', sortOrder: 202, isActive: false, category: 'indirect', subcategory: 'veneer', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: KEEP_FULL_CONTOUR, }, { code: 'zirconia_overlay', sortOrder: 203, isActive: false, category: 'indirect', subcategory: 'overlay', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: [ 'intraoral_scan', 'design', 'milling_dry', 'sinter', 'stain', 'glaze', 'polish_prep', ], }, { code: 'ips_overlay', sortOrder: 204, isActive: false, category: 'indirect', subcategory: 'overlay', chartRegion: 'crown', stackGroup: 'restoration', manufacturingSteps: KEEP_FULL_CONTOUR, }, ]; const PACK_SHIP = ['packing', 'shipping'] as const; export function buildProsthesisStepCodes(type: ProsthesisTypeSeed): string[] { const steps = [...type.manufacturingSteps]; if (!type.skipPackingShipping) { steps.push(...PACK_SHIP); } return steps; } export { unionWorkflowStepCodes, prosthesisGroupKey, splitProsthesisGroupKey, } from '../src/common/prosthesis-group'; const TREATMENT_LABELS: Record> = { restoration: { en: 'Restoration', fa: 'ترمیم', nl: 'Restauratie' }, specialized_restoration: { en: 'Specialized Restoration', fa: 'ترمیم تخصصی', nl: 'Gespecialiseerde Restauratie' }, radiography: { en: 'Radiography', fa: 'رادیوگرافی', nl: 'Röntgen' }, endo: { en: 'Endo', fa: 'اندو', nl: 'Endo' }, surgery: { en: 'Surgery', fa: 'جراحی', nl: 'Chirurgie' }, prosthesis: { en: 'Prosthesis', fa: 'پروتز', nl: 'Prothese' }, implant: { en: 'Implant', fa: 'ایمپلنت', nl: 'Implantaat' }, orthodontics: { en: 'Orthodontics', fa: 'ارتودنسی', nl: 'Orthodontie' }, perio: { en: 'Perio', fa: 'پریو', nl: 'Paro' }, pediatrics: { en: 'Pediatrics', fa: 'اطفال', nl: 'Kinderen' }, extraction: { en: 'Extraction', fa: 'کشیدن', nl: 'Extractie' }, clinic_visit: { en: 'Clinic Visit', fa: 'ویزیت درمانگاه', nl: 'Kliniekbezoek' }, continue_treatment: { en: 'Continue Treatment', fa: 'ادامه درمان', nl: 'Behandeling Voortzetten' }, consultation: { en: 'Consultation', fa: 'مشاوره', nl: 'Consult' }, filling: { en: 'Filling', fa: 'پر کردن', nl: 'Vulling' }, visit: { en: 'Visit', fa: 'ویزیت', nl: 'Bezoek' }, hygiene: { en: 'Hygiene', fa: 'بهداشت', nl: 'Hygiëne' }, }; export const PROSTHESIS_LABELS: Record> = { pfm_crown: { en: 'PFM Crown', fa: 'روکش PFM', nl: 'PFM Kroon' }, pfz_crown: { en: 'PFZ Crown', fa: 'روکش PFZ', nl: 'PFZ Kroon' }, monolithic_zirconia: { en: 'Monolithic Zirconia', fa: 'زیرکونیا مونولیتیک', nl: 'Monolithisch Zirconia' }, glass_ceramic_crown: { en: 'Glass Ceramic Crown', fa: 'روکش سرامیک شیشه‌ای', nl: 'Glaskeramische Kroon' }, full_metal_crown: { en: 'Full Metal Crown', fa: 'روکش تمام فلز', nl: 'Volledige Metalen Kroon' }, temporary_resin_crown: { en: 'Temporary Resin Crown', fa: 'روکش موقت رزینی', nl: 'Tijdelijke Harskroon' }, pmma: { en: 'PMMA', fa: 'PMMA', nl: 'PMMA' }, peek_crown: { en: 'PEEK Crown', fa: 'روکش PEEK', nl: 'PEEK Kroon' }, press_ceramic: { en: 'Pressed Ceramic / IPS e.max', fa: 'سرامیک پرس / IPS e.max', nl: 'Perskeramiek / IPS e.max' }, veneer_full_contour: { en: 'Veneer · Full contour', fa: 'ونیر · تمام‌کانتور', nl: 'Veneer · Full contour' }, veneer_layered: { en: 'Veneer · Layered ceramic', fa: 'ونیر · سرامیک لایه‌ای', nl: 'Veneer · Gelaagd keramiek' }, inlay_full_contour: { en: 'Inlay · Full contour', fa: 'اینلی · تمام‌کانتور', nl: 'Inlay · Full contour' }, inlay_layered: { en: 'Inlay · Layered ceramic', fa: 'اینلی · سرامیک لایه‌ای', nl: 'Inlay · Gelaagd keramiek' }, onlay_full_contour: { en: 'Onlay · Full contour', fa: 'آنلی · تمام‌کانتور', nl: 'Onlay · Full contour' }, onlay_layered: { en: 'Onlay · Layered ceramic', fa: 'آنلی · سرامیک لایه‌ای', nl: 'Onlay · Gelaagd keramiek' }, overlay_full_contour: { en: 'Overlay · Full contour', fa: 'اورلی · تمام‌کانتور', nl: 'Overlay · Full contour' }, overlay_layered: { en: 'Overlay · Layered ceramic', fa: 'اورلی · سرامیک لایه‌ای', nl: 'Overlay · Gelaagd keramiek' }, cast_post_core: { en: 'Cast Post & Core', fa: 'پست و کور ریختگی', nl: 'Gegoten Stiftopbouw' }, fiber_post_core: { en: 'Fiber Post & Core', fa: 'پست و کور فایبر', nl: 'Fiber Stiftopbouw' }, customized_abutment: { en: 'Custom Abutment (Titanium)', fa: 'اباتمنت سفارشی (تیتانیوم)', nl: 'Aangepast abutment (titanium)' }, prefabricated_abutment: { en: 'Prefabricated Abutment', fa: 'اباتمنت آماده', nl: 'Prefab Abutment' }, ti_base_abutment: { en: 'Ti Base Abutment', fa: 'اباتمنت پایه تیتانیوم', nl: 'Ti Basis Abutment' }, multi_unit_abutment: { en: 'Multi Unit Abutment', fa: 'اباتمنت مولتی یونیت', nl: 'Multi Unit Abutment' }, zirconia_abutment: { en: 'Custom Abutment (Zirconia)', fa: 'اباتمنت سفارشی (زیرکونیا)', nl: 'Aangepast abutment (zirconia)' }, screw_retained: { en: 'Screw Retained', fa: 'پیچی', nl: 'Schroefgehouden' }, complete_denture: { en: 'Complete Denture', fa: 'دنچر کامل', nl: 'Volledige prothese' }, partial_denture: { en: 'Partial Denture', fa: 'دنچر پارسیل', nl: 'Partiële prothese' }, overdenture: { en: 'Overdenture', fa: 'اوردنچر', nl: 'Overkappingsprothese' }, night_guard_soft: { en: 'Night Guard · Soft', fa: 'نایت گارد · نرم', nl: 'Nachtbeugel · Zacht' }, night_guard_hard: { en: 'Night Guard · Hard', fa: 'نایت گارد · سخت', nl: 'Nachtbeugel · Hard' }, night_guard_dual: { en: 'Night Guard · Dual laminate', fa: 'نایت گارد · دو لایه', nl: 'Nachtbeugel · Dual laminate' }, bleaching_tray: { en: 'Bleaching Tray', fa: 'تری بلیچینگ', nl: 'Bleeklepel' }, clear_aligner: { en: 'Clear Aligner', fa: 'الاینر شفاف', nl: 'Clear aligner' }, soft_structure: { en: 'Soft Structure', fa: 'ساختار نرم', nl: 'Zachte Structuur' }, surgical_guide: { en: 'Surgical Guide', fa: 'گاید جراحی', nl: 'Chirurgische mal' }, smile_design: { en: 'Smile Design', fa: 'طراحی لبخند', nl: 'Smile Design' }, mockup: { en: 'Mockup', fa: 'ماکاپ', nl: 'Mockup' }, veneer_zirconia: { en: 'Veneer Zirconia', fa: 'ونیر زیرکونیا', nl: 'Veneer Zirconia' }, veneer_ips_press: { en: 'Veneer IPS Press', fa: 'ونیر IPS پرس', nl: 'Veneer IPS Press' }, veneer_ips_cad: { en: 'Veneer IPS CAD', fa: 'ونیر IPS CAD', nl: 'Veneer IPS CAD' }, zirconia_overlay: { en: 'Zirconia Overlay', fa: 'اورلی زیرکونیا', nl: 'Zirconia Overlay' }, ips_overlay: { en: 'IPS Overlay', fa: 'اورلی IPS', nl: 'IPS Overlay' }, }; export const WORKFLOW_STEP_LABELS: Record> = { intraoral_scan: { en: 'Intraoral Scan', fa: 'اسکن داخل دهان', nl: 'Intraorale Scan' }, choosing_abutment: { en: 'Choosing Abutment', fa: 'انتخاب اباتمنت', nl: 'Abutment kiezen', }, design: { en: 'Design', fa: 'طراحی', nl: 'Ontwerp' }, model_create: { en: 'Model Create', fa: 'ساخت مدل', nl: 'Model maken' }, milling_dry: { en: 'Milling Dry', fa: 'فرز خشک', nl: 'Droog Frezen' }, milling_wet: { en: 'Milling Wet', fa: 'فرز تر', nl: 'Nat Frezen' }, printer_resin: { en: 'Printer Resin', fa: 'پرینتر رزین', nl: 'Harsprinter' }, printer_metal: { en: 'Printer Metal', fa: 'پرینتر فلز', nl: 'Metaalprinter' }, sinter: { en: 'Sinter', fa: 'سینتر', nl: 'Sinteren' }, build_up: { en: 'Build Up', fa: 'بیلدآپ', nl: 'Opbouw' }, stain: { en: 'Stain', fa: 'رنگ‌آمیزی', nl: 'Kleuren' }, glaze: { en: 'Glaze', fa: 'گلیز', nl: 'Glazuur' }, polish_prep: { en: 'Polish/Prep', fa: 'پولیش/آماده‌سازی', nl: 'Polijsten/Voorbereiding' }, packing: { en: 'Packing', fa: 'بسته‌بندی', nl: 'Verpakken' }, shipping: { en: 'Shipping', fa: 'ارسال', nl: 'Verzending' }, pressing: { en: 'Pressing', fa: 'پرس', nl: 'Persen' }, }; function labelsToTranslations( entityKind: CatalogEntityKind, labels: Record>, ): CatalogTranslationSeed[] { const out: CatalogTranslationSeed[] = []; for (const [entityCode, locales] of Object.entries(labels)) { for (const [locale, label] of Object.entries(locales)) { out.push({ entityKind, entityCode, locale, label }); } } return out; } export const CATALOG_TRANSLATIONS: CatalogTranslationSeed[] = [ ...labelsToTranslations(CatalogEntityKind.TREATMENT_TYPE, TREATMENT_LABELS), ...labelsToTranslations(CatalogEntityKind.PROSTHESIS_TYPE, PROSTHESIS_LABELS), ...labelsToTranslations(CatalogEntityKind.LAB_WORKFLOW_STEP, WORKFLOW_STEP_LABELS), ];