feature: Phase3 - Task templates + generation on send
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
-- Treatment workflow steps + lab case tasks
|
||||
|
||||
CREATE TYPE "LabTaskStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED');
|
||||
|
||||
CREATE TABLE "treatment_workflow_steps" (
|
||||
"id" TEXT NOT NULL,
|
||||
"treatmentType" TEXT NOT NULL,
|
||||
"stepOrder" INTEGER NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "treatment_workflow_steps_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "treatment_workflow_steps_treatmentType_stepOrder_key"
|
||||
ON "treatment_workflow_steps"("treatmentType", "stepOrder");
|
||||
|
||||
CREATE TABLE "lab_case_tasks" (
|
||||
"id" TEXT NOT NULL,
|
||||
"labCaseId" TEXT NOT NULL,
|
||||
"treatmentDetailId" TEXT NOT NULL,
|
||||
"tooth" TEXT NOT NULL,
|
||||
"treatmentType" TEXT NOT NULL,
|
||||
"stepOrder" INTEGER NOT NULL,
|
||||
"stepLabel" TEXT NOT NULL,
|
||||
"assigneeUserId" TEXT,
|
||||
"status" "LabTaskStatus" NOT NULL DEFAULT 'PENDING',
|
||||
|
||||
CONSTRAINT "lab_case_tasks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "lab_case_tasks_labCaseId_tooth_treatmentType_stepOrder_key"
|
||||
ON "lab_case_tasks"("labCaseId", "tooth", "treatmentType", "stepOrder");
|
||||
|
||||
CREATE INDEX "lab_case_tasks_labCaseId_status_idx" ON "lab_case_tasks"("labCaseId", "status");
|
||||
|
||||
ALTER TABLE "lab_case_tasks"
|
||||
ADD CONSTRAINT "lab_case_tasks_labCaseId_fkey"
|
||||
FOREIGN KEY ("labCaseId") REFERENCES "lab_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "lab_case_tasks"
|
||||
ADD CONSTRAINT "lab_case_tasks_treatmentDetailId_fkey"
|
||||
FOREIGN KEY ("treatmentDetailId") REFERENCES "treatment_details"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "lab_case_tasks"
|
||||
ADD CONSTRAINT "lab_case_tasks_assigneeUserId_fkey"
|
||||
FOREIGN KEY ("assigneeUserId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,49 @@
|
||||
-- Treatment type catalog (data-driven; business logic reads from here)
|
||||
|
||||
CREATE TABLE "treatment_types" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"labDependent" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "treatment_types_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "treatment_types_code_key" ON "treatment_types"("code");
|
||||
|
||||
-- Temporary catalog (will be replaced with 14 real-world types later)
|
||||
INSERT INTO "treatment_types" ("id", "code", "labDependent", "sortOrder") VALUES
|
||||
('tt-consultation', 'consultation', false, 1),
|
||||
('tt-filling', 'filling', false, 2),
|
||||
('tt-endo', 'endo', true, 3),
|
||||
('tt-visit', 'visit', false, 4),
|
||||
('tt-hygiene', 'hygiene', false, 5);
|
||||
|
||||
-- Re-link workflow steps to catalog rows
|
||||
ALTER TABLE "treatment_workflow_steps" ADD COLUMN "treatmentTypeId" TEXT;
|
||||
|
||||
UPDATE "treatment_workflow_steps" AS w
|
||||
SET "treatmentTypeId" = t."id"
|
||||
FROM "treatment_types" AS t
|
||||
WHERE t."code" = w."treatmentType";
|
||||
|
||||
-- Drop steps for clinic-only types; only lab-dependent types keep workflows
|
||||
DELETE FROM "treatment_workflow_steps" AS w
|
||||
USING "treatment_types" AS t
|
||||
WHERE w."treatmentTypeId" = t."id" AND t."labDependent" = false;
|
||||
|
||||
DELETE FROM "treatment_workflow_steps" WHERE "treatmentTypeId" IS NULL;
|
||||
|
||||
ALTER TABLE "treatment_workflow_steps" DROP CONSTRAINT IF EXISTS "treatment_workflow_steps_treatmentType_stepOrder_key";
|
||||
DROP INDEX IF EXISTS "treatment_workflow_steps_treatmentType_stepOrder_key";
|
||||
|
||||
ALTER TABLE "treatment_workflow_steps" DROP COLUMN "treatmentType";
|
||||
|
||||
ALTER TABLE "treatment_workflow_steps" ALTER COLUMN "treatmentTypeId" SET NOT NULL;
|
||||
|
||||
ALTER TABLE "treatment_workflow_steps"
|
||||
ADD CONSTRAINT "treatment_workflow_steps_treatmentTypeId_fkey"
|
||||
FOREIGN KEY ("treatmentTypeId") REFERENCES "treatment_types"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE UNIQUE INDEX "treatment_workflow_steps_treatmentTypeId_stepOrder_key"
|
||||
ON "treatment_workflow_steps"("treatmentTypeId", "stepOrder");
|
||||
@@ -23,6 +23,7 @@ model User {
|
||||
sessions Session[] // 👈 ADD THIS - opposite relation for Session
|
||||
sentStaffInvites StaffInvitation[]
|
||||
sentOrganizationInvitations OrganizationInvitation[]
|
||||
assignedLabCaseTasks LabCaseTask[] @relation("LabCaseTaskAssignee")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -118,6 +119,12 @@ enum TreatmentStatus {
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
enum LabTaskStatus {
|
||||
PENDING
|
||||
IN_PROGRESS
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
model Treatment {
|
||||
id String @id @default(uuid())
|
||||
organizationId String
|
||||
@@ -154,6 +161,7 @@ model TreatmentDetail {
|
||||
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
|
||||
attachments TreatmentDetailAttachment[]
|
||||
labCaseLink LabCaseDetail?
|
||||
labCaseTasks LabCaseTask[]
|
||||
|
||||
@@index([treatmentId, sortOrder])
|
||||
@@map("treatment_details")
|
||||
@@ -190,6 +198,7 @@ model LabCase {
|
||||
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
|
||||
details LabCaseDetail[]
|
||||
sends LabCaseSend[]
|
||||
tasks LabCaseTask[]
|
||||
|
||||
@@index([treatmentId, sortOrder])
|
||||
@@map("lab_cases")
|
||||
@@ -219,6 +228,49 @@ model LabCaseSend {
|
||||
@@map("lab_case_sends")
|
||||
}
|
||||
|
||||
model TreatmentType {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
labDependent Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
workflowSteps TreatmentWorkflowStep[]
|
||||
|
||||
@@map("treatment_types")
|
||||
}
|
||||
|
||||
model TreatmentWorkflowStep {
|
||||
id String @id @default(uuid())
|
||||
treatmentTypeId String
|
||||
stepOrder Int
|
||||
label String
|
||||
|
||||
treatmentType TreatmentType @relation(fields: [treatmentTypeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([treatmentTypeId, stepOrder])
|
||||
@@map("treatment_workflow_steps")
|
||||
}
|
||||
|
||||
model LabCaseTask {
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
treatmentDetailId String
|
||||
tooth String
|
||||
treatmentType String
|
||||
stepOrder Int
|
||||
stepLabel String
|
||||
assigneeUserId String?
|
||||
status LabTaskStatus @default(PENDING)
|
||||
|
||||
labCase LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
|
||||
detail TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)
|
||||
assignee User? @relation("LabCaseTaskAssignee", fields: [assigneeUserId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([labCaseId, tooth, treatmentType, stepOrder])
|
||||
@@index([labCaseId, status])
|
||||
@@map("lab_case_tasks")
|
||||
}
|
||||
|
||||
model Plan {
|
||||
id String @id @default(uuid())
|
||||
name String @unique // "Solo", "Small", "Medium", "Large", "Enterprise"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// backend/prisma/seed.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { config } from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
@@ -123,6 +124,57 @@ 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) {
|
||||
await prisma.treatmentType.upsert({
|
||||
where: { code: type.code },
|
||||
update: { labDependent: type.labDependent, sortOrder: type.sortOrder },
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
code: type.code,
|
||||
labDependent: type.labDependent,
|
||||
sortOrder: type.sortOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('✅ Seeded treatment type catalog');
|
||||
|
||||
for (const step of workflowSteps) {
|
||||
const treatmentType = await prisma.treatmentType.findUniqueOrThrow({
|
||||
where: { code: step.code },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
await prisma.treatmentWorkflowStep.upsert({
|
||||
where: {
|
||||
treatmentTypeId_stepOrder: {
|
||||
treatmentTypeId: treatmentType.id,
|
||||
stepOrder: step.stepOrder,
|
||||
},
|
||||
},
|
||||
update: { label: step.label },
|
||||
create: {
|
||||
id: randomUUID(),
|
||||
treatmentTypeId: treatmentType.id,
|
||||
stepOrder: step.stepOrder,
|
||||
label: step.label,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('✅ Seeded lab workflow steps');
|
||||
|
||||
console.log('🌱 Seeding completed successfully!');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user