TreatmentType and ProsthesisType database shcema and data updated. Lab dispatch wired through new data.
This commit is contained in:
276
backend/prisma/catalog-seed-data.ts
Normal file
276
backend/prisma/catalog-seed-data.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { CatalogEntityKind } from '@prisma/client';
|
||||
|
||||
export type CatalogTranslationSeed = {
|
||||
entityKind: CatalogEntityKind;
|
||||
entityCode: string;
|
||||
locale: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const TREATMENT_TYPES = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
/** Legacy codes kept for historical rows; hidden from catalog. */
|
||||
export const LEGACY_TREATMENT_TYPES = [
|
||||
{ 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: 'design', sortOrder: 2 },
|
||||
{ code: 'milling_dry', sortOrder: 3 },
|
||||
{ code: 'milling_wet', sortOrder: 4 },
|
||||
{ code: 'printer_resin', sortOrder: 5 },
|
||||
{ code: 'printer_metal', sortOrder: 6 },
|
||||
{ code: 'sinter', sortOrder: 7 },
|
||||
{ code: 'build_up', sortOrder: 8 },
|
||||
{ code: 'stain', sortOrder: 9 },
|
||||
{ code: 'glaze', sortOrder: 10 },
|
||||
{ code: 'polish_prep', sortOrder: 11 },
|
||||
{ code: 'packing', sortOrder: 12 },
|
||||
{ code: 'shipping', sortOrder: 13 },
|
||||
{ code: 'pressing', sortOrder: 14 },
|
||||
] as const;
|
||||
|
||||
export type ProsthesisTypeSeed = {
|
||||
code: string;
|
||||
sortOrder: number;
|
||||
skipPackingShipping?: boolean;
|
||||
/** Manufacturing steps between design and packing (exclusive of universal scan/design/pack/ship). */
|
||||
manufacturingSteps: readonly string[];
|
||||
};
|
||||
|
||||
export const PROSTHESIS_TYPES: ProsthesisTypeSeed[] = [
|
||||
{
|
||||
code: 'pfm_crown',
|
||||
sortOrder: 1,
|
||||
manufacturingSteps: ['milling_wet', 'build_up', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'pfz_crown',
|
||||
sortOrder: 2,
|
||||
manufacturingSteps: ['milling_dry', 'sinter', 'build_up', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'monolithic_zirconia',
|
||||
sortOrder: 3,
|
||||
manufacturingSteps: ['milling_dry', 'sinter', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'glass_ceramic_crown',
|
||||
sortOrder: 4,
|
||||
manufacturingSteps: ['milling_wet', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'full_metal_crown',
|
||||
sortOrder: 5,
|
||||
manufacturingSteps: ['milling_wet', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'temporary_resin_crown',
|
||||
sortOrder: 6,
|
||||
manufacturingSteps: ['milling_wet', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'pmma',
|
||||
sortOrder: 7,
|
||||
manufacturingSteps: ['milling_dry', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'peek_crown',
|
||||
sortOrder: 8,
|
||||
manufacturingSteps: ['milling_dry', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'veneer_zirconia',
|
||||
sortOrder: 9,
|
||||
manufacturingSteps: ['milling_dry', 'sinter', 'build_up', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'veneer_ips_press',
|
||||
sortOrder: 10,
|
||||
manufacturingSteps: [
|
||||
'printer_resin',
|
||||
'build_up',
|
||||
'stain',
|
||||
'glaze',
|
||||
'polish_prep',
|
||||
'pressing',
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'veneer_ips_cad',
|
||||
sortOrder: 11,
|
||||
manufacturingSteps: ['milling_wet', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'soft_structure',
|
||||
sortOrder: 12,
|
||||
manufacturingSteps: ['milling_dry', 'sinter'],
|
||||
},
|
||||
{
|
||||
code: 'customized_abutment',
|
||||
sortOrder: 13,
|
||||
manufacturingSteps: ['milling_wet', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'prefabricated_abutment',
|
||||
sortOrder: 14,
|
||||
manufacturingSteps: ['polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'ti_base_abutment',
|
||||
sortOrder: 15,
|
||||
manufacturingSteps: ['polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'multi_unit_abutment',
|
||||
sortOrder: 16,
|
||||
manufacturingSteps: ['polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'zirconia_abutment',
|
||||
sortOrder: 17,
|
||||
manufacturingSteps: ['milling_dry', 'sinter', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'screw_retained',
|
||||
sortOrder: 18,
|
||||
manufacturingSteps: [
|
||||
'milling_wet',
|
||||
'printer_metal',
|
||||
'sinter',
|
||||
'build_up',
|
||||
'stain',
|
||||
'glaze',
|
||||
'polish_prep',
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'zirconia_overlay',
|
||||
sortOrder: 19,
|
||||
manufacturingSteps: ['milling_dry', 'sinter', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'ips_overlay',
|
||||
sortOrder: 20,
|
||||
manufacturingSteps: ['milling_wet', 'stain', 'glaze', 'polish_prep'],
|
||||
},
|
||||
{
|
||||
code: 'smile_design',
|
||||
sortOrder: 21,
|
||||
skipPackingShipping: true,
|
||||
manufacturingSteps: ['printer_resin'],
|
||||
},
|
||||
{
|
||||
code: 'mockup',
|
||||
sortOrder: 22,
|
||||
manufacturingSteps: ['printer_resin'],
|
||||
},
|
||||
];
|
||||
|
||||
const UNIVERSAL_PREFIX = ['intraoral_scan', 'design'] as const;
|
||||
const UNIVERSAL_SUFFIX = ['packing', 'shipping'] as const;
|
||||
|
||||
export function buildProsthesisStepCodes(type: ProsthesisTypeSeed): string[] {
|
||||
const steps = [...UNIVERSAL_PREFIX, ...type.manufacturingSteps];
|
||||
if (!type.skipPackingShipping) {
|
||||
steps.push(...UNIVERSAL_SUFFIX);
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
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' },
|
||||
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' },
|
||||
};
|
||||
|
||||
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' },
|
||||
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' },
|
||||
soft_structure: { en: 'Soft Structure', fa: 'ساختار نرم', nl: 'Zachte Structuur' },
|
||||
customized_abutment: { en: 'Customized Abutment', fa: 'اباتمنت سفارشی', nl: 'Aangepast Abutment' },
|
||||
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: 'Zirconia Abutment', fa: 'اباتمنت زیرکونیا', nl: 'Zirconia Abutment' },
|
||||
screw_retained: { en: 'Screw Retained', fa: 'پیچی', nl: 'Schroefgehouden' },
|
||||
zirconia_overlay: { en: 'Zirconia Overlay', fa: 'اورلی زیرکونیا', nl: 'Zirconia Overlay' },
|
||||
ips_overlay: { en: 'IPS Overlay', fa: 'اورلی IPS', nl: 'IPS Overlay' },
|
||||
smile_design: { en: 'Smile Design', fa: 'طراحی لبخند', nl: 'Smile Design' },
|
||||
mockup: { en: 'Mockup', fa: 'ماکاپ', nl: 'Mockup' },
|
||||
};
|
||||
|
||||
const WORKFLOW_STEP_LABELS: Record<string, Record<string, string>> = {
|
||||
intraoral_scan: { en: 'Intraoral Scan', fa: 'اسکن داخل دهان', nl: 'Intraorale Scan' },
|
||||
design: { en: 'Design', fa: 'طراحی', nl: 'Ontwerp' },
|
||||
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),
|
||||
];
|
||||
@@ -0,0 +1,95 @@
|
||||
-- Prosthesis catalog refactor: drop treatment workflow steps, add prosthesis catalog tables
|
||||
|
||||
CREATE TYPE "CatalogEntityKind" AS ENUM ('TREATMENT_TYPE', 'PROSTHESIS_TYPE', 'LAB_WORKFLOW_STEP');
|
||||
|
||||
ALTER TABLE "treatment_types" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
DROP TABLE IF EXISTS "treatment_workflow_steps";
|
||||
|
||||
CREATE TABLE "catalog_translations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"entityKind" "CatalogEntityKind" NOT NULL,
|
||||
"entityCode" TEXT NOT NULL,
|
||||
"locale" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "catalog_translations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "catalog_translations_entityKind_entityCode_locale_key"
|
||||
ON "catalog_translations"("entityKind", "entityCode", "locale");
|
||||
|
||||
CREATE TABLE "prosthesis_types" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"skipPackingShipping" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "prosthesis_types_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "prosthesis_types_code_key" ON "prosthesis_types"("code");
|
||||
|
||||
CREATE TABLE "lab_workflow_steps" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "lab_workflow_steps_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "lab_workflow_steps_code_key" ON "lab_workflow_steps"("code");
|
||||
|
||||
CREATE TABLE "prosthesis_type_steps" (
|
||||
"id" TEXT NOT NULL,
|
||||
"prosthesisTypeId" TEXT NOT NULL,
|
||||
"labWorkflowStepId" TEXT NOT NULL,
|
||||
"stepOrder" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "prosthesis_type_steps_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "prosthesis_type_steps_prosthesisTypeId_stepOrder_key"
|
||||
ON "prosthesis_type_steps"("prosthesisTypeId", "stepOrder");
|
||||
|
||||
CREATE UNIQUE INDEX "prosthesis_type_steps_prosthesisTypeId_labWorkflowStepId_key"
|
||||
ON "prosthesis_type_steps"("prosthesisTypeId", "labWorkflowStepId");
|
||||
|
||||
ALTER TABLE "prosthesis_type_steps"
|
||||
ADD CONSTRAINT "prosthesis_type_steps_prosthesisTypeId_fkey"
|
||||
FOREIGN KEY ("prosthesisTypeId") REFERENCES "prosthesis_types"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "prosthesis_type_steps"
|
||||
ADD CONSTRAINT "prosthesis_type_steps_labWorkflowStepId_fkey"
|
||||
FOREIGN KEY ("labWorkflowStepId") REFERENCES "lab_workflow_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "lab_case_tooth_prosthesis" (
|
||||
"id" TEXT NOT NULL,
|
||||
"labCaseId" TEXT NOT NULL,
|
||||
"treatmentDetailId" TEXT NOT NULL,
|
||||
"tooth" TEXT NOT NULL,
|
||||
"prosthesisTypeCode" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "lab_case_tooth_prosthesis_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "lab_case_tooth_prosthesis_labCaseId_treatmentDetailId_tooth_key"
|
||||
ON "lab_case_tooth_prosthesis"("labCaseId", "treatmentDetailId", "tooth");
|
||||
|
||||
ALTER TABLE "lab_case_tooth_prosthesis"
|
||||
ADD CONSTRAINT "lab_case_tooth_prosthesis_labCaseId_fkey"
|
||||
FOREIGN KEY ("labCaseId") REFERENCES "lab_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "lab_case_tooth_prosthesis"
|
||||
ADD CONSTRAINT "lab_case_tooth_prosthesis_treatmentDetailId_fkey"
|
||||
FOREIGN KEY ("treatmentDetailId") REFERENCES "treatment_details"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Lab case tasks: add prosthesis fields and update unique constraint
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "prosthesisTypeCode" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "workflowStepCode" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
DROP INDEX IF EXISTS "lab_case_tasks_labCaseId_tooth_treatmentType_stepOrder_key";
|
||||
|
||||
CREATE UNIQUE INDEX "lab_case_tasks_labCaseId_treatmentDetailId_tooth_stepOrder_key"
|
||||
ON "lab_case_tasks"("labCaseId", "treatmentDetailId", "tooth", "stepOrder");
|
||||
41
backend/prisma/reset-treatment-data.ts
Normal file
41
backend/prisma/reset-treatment-data.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Dev-only: truncate treatment and lab case data (preserves catalog tables).
|
||||
* Usage: npx ts-node prisma/reset-treatment-data.ts
|
||||
*/
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { config } from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
config({ path: envPath });
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.error('reset-treatment-data is not allowed in production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('Truncating treatment and lab case data...');
|
||||
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "lab_case_tasks" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "lab_case_sends" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "lab_case_tooth_prosthesis" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "lab_case_details" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "lab_cases" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "treatment_detail_attachments" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "treatment_details" CASCADE');
|
||||
await prisma.$executeRawUnsafe('TRUNCATE TABLE "treatments" CASCADE');
|
||||
|
||||
console.log('Done.');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -155,6 +155,7 @@ model TreatmentDetail {
|
||||
attachments TreatmentDetailAttachment[]
|
||||
labCaseLink LabCaseDetail?
|
||||
labCaseTasks LabCaseTask[]
|
||||
toothProsthesis LabCaseToothProsthesis[]
|
||||
|
||||
@@index([treatmentId, sortOrder])
|
||||
@@map("treatment_details")
|
||||
@@ -188,10 +189,11 @@ model LabCase {
|
||||
labComment String?
|
||||
sentAt DateTime?
|
||||
|
||||
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
|
||||
details LabCaseDetail[]
|
||||
sends LabCaseSend[]
|
||||
tasks LabCaseTask[]
|
||||
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
|
||||
details LabCaseDetail[]
|
||||
sends LabCaseSend[]
|
||||
tasks LabCaseTask[]
|
||||
toothProsthesis LabCaseToothProsthesis[]
|
||||
|
||||
@@index([treatmentId, sortOrder])
|
||||
@@map("lab_cases")
|
||||
@@ -226,36 +228,92 @@ model TreatmentType {
|
||||
code String @unique
|
||||
labDependent Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
workflowSteps TreatmentWorkflowStep[]
|
||||
isActive Boolean @default(true)
|
||||
|
||||
@@map("treatment_types")
|
||||
}
|
||||
|
||||
model TreatmentWorkflowStep {
|
||||
id String @id @default(uuid())
|
||||
treatmentTypeId String
|
||||
stepOrder Int
|
||||
label String
|
||||
enum CatalogEntityKind {
|
||||
TREATMENT_TYPE
|
||||
PROSTHESIS_TYPE
|
||||
LAB_WORKFLOW_STEP
|
||||
}
|
||||
|
||||
treatmentType TreatmentType @relation(fields: [treatmentTypeId], references: [id], onDelete: Cascade)
|
||||
model CatalogTranslation {
|
||||
id String @id @default(uuid())
|
||||
entityKind CatalogEntityKind
|
||||
entityCode String
|
||||
locale String
|
||||
label String
|
||||
|
||||
@@unique([treatmentTypeId, stepOrder])
|
||||
@@map("treatment_workflow_steps")
|
||||
@@unique([entityKind, entityCode, locale])
|
||||
@@map("catalog_translations")
|
||||
}
|
||||
|
||||
model ProsthesisType {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
skipPackingShipping Boolean @default(false)
|
||||
|
||||
steps ProsthesisTypeStep[]
|
||||
|
||||
@@map("prosthesis_types")
|
||||
}
|
||||
|
||||
model LabWorkflowStep {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
sortOrder Int @default(0)
|
||||
|
||||
prosthesisSteps ProsthesisTypeStep[]
|
||||
|
||||
@@map("lab_workflow_steps")
|
||||
}
|
||||
|
||||
model ProsthesisTypeStep {
|
||||
id String @id @default(uuid())
|
||||
prosthesisTypeId String
|
||||
labWorkflowStepId String
|
||||
stepOrder Int
|
||||
|
||||
prosthesisType ProsthesisType @relation(fields: [prosthesisTypeId], references: [id], onDelete: Cascade)
|
||||
labWorkflowStep LabWorkflowStep @relation(fields: [labWorkflowStepId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([prosthesisTypeId, stepOrder])
|
||||
@@unique([prosthesisTypeId, labWorkflowStepId])
|
||||
@@map("prosthesis_type_steps")
|
||||
}
|
||||
|
||||
model LabCaseToothProsthesis {
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
treatmentDetailId String
|
||||
tooth String
|
||||
prosthesisTypeCode String
|
||||
|
||||
labCase LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
|
||||
detail TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([labCaseId, treatmentDetailId, tooth])
|
||||
@@map("lab_case_tooth_prosthesis")
|
||||
}
|
||||
|
||||
model LabCaseTask {
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
treatmentDetailId String
|
||||
tooth String
|
||||
treatmentType String
|
||||
stepOrder Int
|
||||
stepLabel String
|
||||
assigneeUserId String?
|
||||
assignedAt DateTime?
|
||||
priority Int @default(3)
|
||||
status LabTaskStatus @default(PENDING)
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
treatmentDetailId String
|
||||
tooth String
|
||||
treatmentType String
|
||||
prosthesisTypeCode String
|
||||
workflowStepCode String
|
||||
stepOrder Int
|
||||
stepLabel String
|
||||
assigneeUserId String?
|
||||
assignedAt DateTime?
|
||||
priority Int @default(3)
|
||||
status LabTaskStatus @default(PENDING)
|
||||
|
||||
labCase LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
|
||||
detail TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)
|
||||
@@ -264,7 +322,7 @@ model LabCaseTask {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([labCaseId, tooth, treatmentType, stepOrder])
|
||||
@@unique([labCaseId, treatmentDetailId, tooth, stepOrder])
|
||||
@@index([labCaseId, status])
|
||||
@@index([assigneeUserId, priority, createdAt])
|
||||
@@index([assignedAt, labCaseId, priority])
|
||||
|
||||
@@ -3,6 +3,14 @@ import { PrismaClient } from '@prisma/client';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { config } from 'dotenv';
|
||||
import path from 'path';
|
||||
import {
|
||||
TREATMENT_TYPES,
|
||||
LEGACY_TREATMENT_TYPES,
|
||||
LAB_WORKFLOW_STEPS,
|
||||
PROSTHESIS_TYPES,
|
||||
CATALOG_TRANSLATIONS,
|
||||
buildProsthesisStepCodes,
|
||||
} from './catalog-seed-data';
|
||||
|
||||
// Load environment variables from the correct path
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
@@ -128,57 +136,118 @@ async function main() {
|
||||
}
|
||||
console.log('✅ Created features and permissions');
|
||||
|
||||
const workflowSteps = [
|
||||
{ code: 'endo', stepOrder: 1, label: 'Access review' },
|
||||
{ code: 'endo', stepOrder: 2, label: 'Fabrication' },
|
||||
] as const;
|
||||
|
||||
const treatmentTypes = [
|
||||
{ code: 'consultation', labDependent: false, sortOrder: 1 },
|
||||
{ code: 'filling', labDependent: false, sortOrder: 2 },
|
||||
{ code: 'endo', labDependent: true, sortOrder: 3 },
|
||||
{ code: 'visit', labDependent: false, sortOrder: 4 },
|
||||
{ code: 'hygiene', labDependent: false, sortOrder: 5 },
|
||||
] as const;
|
||||
|
||||
for (const type of treatmentTypes) {
|
||||
for (const type of [...TREATMENT_TYPES, ...LEGACY_TREATMENT_TYPES]) {
|
||||
const isActive = TREATMENT_TYPES.some((t) => t.code === type.code);
|
||||
await prisma.treatmentType.upsert({
|
||||
where: { code: type.code },
|
||||
update: { labDependent: type.labDependent, sortOrder: type.sortOrder },
|
||||
update: {
|
||||
labDependent: type.labDependent,
|
||||
sortOrder: type.sortOrder,
|
||||
isActive,
|
||||
},
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
code: type.code,
|
||||
labDependent: type.labDependent,
|
||||
sortOrder: type.sortOrder,
|
||||
isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('✅ Seeded treatment type catalog');
|
||||
|
||||
for (const step of workflowSteps) {
|
||||
const treatmentType = await prisma.treatmentType.findUniqueOrThrow({
|
||||
for (const step of LAB_WORKFLOW_STEPS) {
|
||||
await prisma.labWorkflowStep.upsert({
|
||||
where: { code: step.code },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
await prisma.treatmentWorkflowStep.upsert({
|
||||
where: {
|
||||
treatmentTypeId_stepOrder: {
|
||||
treatmentTypeId: treatmentType.id,
|
||||
stepOrder: step.stepOrder,
|
||||
},
|
||||
},
|
||||
update: { label: step.label },
|
||||
update: { sortOrder: step.sortOrder },
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
treatmentTypeId: treatmentType.id,
|
||||
stepOrder: step.stepOrder,
|
||||
label: step.label,
|
||||
code: step.code,
|
||||
sortOrder: step.sortOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('✅ Seeded lab workflow steps');
|
||||
|
||||
const workflowStepByCode = new Map(
|
||||
(
|
||||
await prisma.labWorkflowStep.findMany({
|
||||
select: { id: true, code: true },
|
||||
})
|
||||
).map((s) => [s.code, s.id]),
|
||||
);
|
||||
|
||||
for (const type of PROSTHESIS_TYPES) {
|
||||
const prosthesisType = await prisma.prosthesisType.upsert({
|
||||
where: { code: type.code },
|
||||
update: {
|
||||
sortOrder: type.sortOrder,
|
||||
skipPackingShipping: type.skipPackingShipping ?? false,
|
||||
isActive: true,
|
||||
},
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
code: type.code,
|
||||
sortOrder: type.sortOrder,
|
||||
skipPackingShipping: type.skipPackingShipping ?? false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
const stepCodes = buildProsthesisStepCodes(type);
|
||||
for (const [index, stepCode] of stepCodes.entries()) {
|
||||
const labWorkflowStepId = workflowStepByCode.get(stepCode);
|
||||
if (!labWorkflowStepId) {
|
||||
throw new Error(`Unknown workflow step code: ${stepCode}`);
|
||||
}
|
||||
|
||||
await prisma.prosthesisTypeStep.upsert({
|
||||
where: {
|
||||
prosthesisTypeId_stepOrder: {
|
||||
prosthesisTypeId: prosthesisType.id,
|
||||
stepOrder: index + 1,
|
||||
},
|
||||
},
|
||||
update: { labWorkflowStepId },
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
prosthesisTypeId: prosthesisType.id,
|
||||
labWorkflowStepId,
|
||||
stepOrder: index + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log('✅ Seeded prosthesis types and workflow mappings');
|
||||
|
||||
for (const tr of CATALOG_TRANSLATIONS) {
|
||||
const existing = await prisma.catalogTranslation.findFirst({
|
||||
where: {
|
||||
entityKind: tr.entityKind,
|
||||
entityCode: tr.entityCode,
|
||||
locale: tr.locale,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
await prisma.catalogTranslation.update({
|
||||
where: { id: existing.id },
|
||||
data: { label: tr.label },
|
||||
});
|
||||
} else {
|
||||
await prisma.catalogTranslation.create({
|
||||
data: {
|
||||
id: randomUUID(),
|
||||
entityKind: tr.entityKind,
|
||||
entityCode: tr.entityCode,
|
||||
locale: tr.locale,
|
||||
label: tr.label,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log('✅ Seeded catalog translations');
|
||||
|
||||
console.log('🌱 Seeding completed successfully!');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user