Files
dyolink/backend/prisma/catalog-seed-data.ts

562 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 todays 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>,
): ProsthesisTypeSeed {
return {
code,
sortOrder,
category: 'crown',
chartRegion: 'crown',
stackGroup: 'restoration',
manufacturingSteps,
...extra,
};
}
function implantAddon(
code: string,
sortOrder: number,
manufacturingSteps: readonly string[],
extra?: Partial<ProsthesisTypeSeed>,
): 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<string, Record<string, string>> = {
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<string, Record<string, string>> = {
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<string, Record<string, string>> = {
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<string, Record<string, string>>,
): 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),
];