improvement: tasks and cases feature updated based on the new prosthesis types and their steps. the whole assignment proccess removed from the flow.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
-- Lab workflow refactor: remove task assignment/priority, group tasks by prosthesis,
|
||||
-- add importance flag, status timeline, and per-case comments.
|
||||
-- Local dev data only: existing tasks are truncated and regenerated on next dispatch/send.
|
||||
|
||||
-- 1. Clear existing task data (task shape changes: tooth -> teeth[]).
|
||||
TRUNCATE TABLE "lab_case_tasks" CASCADE;
|
||||
|
||||
-- 2. Drop assignment / priority machinery.
|
||||
ALTER TABLE "lab_case_tasks" DROP CONSTRAINT IF EXISTS "lab_case_tasks_assigneeUserId_fkey";
|
||||
DROP INDEX IF EXISTS "lab_case_tasks_assigneeUserId_priority_createdAt_idx";
|
||||
DROP INDEX IF EXISTS "lab_case_tasks_assignedAt_labCaseId_priority_idx";
|
||||
DROP INDEX IF EXISTS "lab_case_tasks_labCaseId_treatmentDetailId_tooth_stepOrder_key";
|
||||
|
||||
ALTER TABLE "lab_case_tasks"
|
||||
DROP COLUMN IF EXISTS "assigneeUserId",
|
||||
DROP COLUMN IF EXISTS "assignedAt",
|
||||
DROP COLUMN IF EXISTS "priority",
|
||||
DROP COLUMN IF EXISTS "tooth";
|
||||
|
||||
-- 3. Rebuild LabTaskStatus enum without PENDING.
|
||||
ALTER TABLE "lab_case_tasks" ALTER COLUMN "status" DROP DEFAULT;
|
||||
ALTER TYPE "LabTaskStatus" RENAME TO "LabTaskStatus_old";
|
||||
CREATE TYPE "LabTaskStatus" AS ENUM ('IN_PROGRESS', 'COMPLETED');
|
||||
ALTER TABLE "lab_case_tasks"
|
||||
ALTER COLUMN "status" TYPE "LabTaskStatus" USING ("status"::text::"LabTaskStatus");
|
||||
ALTER TABLE "lab_case_tasks" ALTER COLUMN "status" SET DEFAULT 'IN_PROGRESS';
|
||||
DROP TYPE "LabTaskStatus_old";
|
||||
|
||||
-- 4. New task columns.
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "teeth" JSONB NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE "lab_case_tasks" ALTER COLUMN "teeth" DROP DEFAULT;
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "isImportant" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "lastStatusChangedByUserId" TEXT;
|
||||
ALTER TABLE "lab_case_tasks" ADD COLUMN "lastStatusChangedAt" TIMESTAMP(3);
|
||||
|
||||
-- 5. New unique + indexes.
|
||||
CREATE UNIQUE INDEX "lab_case_tasks_labCaseId_treatmentDetailId_prosthesisTypeCode_stepOrder_key"
|
||||
ON "lab_case_tasks"("labCaseId", "treatmentDetailId", "prosthesisTypeCode", "stepOrder");
|
||||
CREATE INDEX "lab_case_tasks_labCaseId_isImportant_idx"
|
||||
ON "lab_case_tasks"("labCaseId", "isImportant");
|
||||
|
||||
ALTER TABLE "lab_case_tasks" ADD CONSTRAINT "lab_case_tasks_lastStatusChangedByUserId_fkey"
|
||||
FOREIGN KEY ("lastStatusChangedByUserId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- 6. Task status timeline.
|
||||
CREATE TABLE "lab_case_task_status_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"fromStatus" "LabTaskStatus",
|
||||
"toStatus" "LabTaskStatus" NOT NULL,
|
||||
"changedByUserId" TEXT,
|
||||
"changedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "lab_case_task_status_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "lab_case_task_status_events_taskId_changedAt_idx"
|
||||
ON "lab_case_task_status_events"("taskId", "changedAt");
|
||||
ALTER TABLE "lab_case_task_status_events" ADD CONSTRAINT "lab_case_task_status_events_taskId_fkey"
|
||||
FOREIGN KEY ("taskId") REFERENCES "lab_case_tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "lab_case_task_status_events" ADD CONSTRAINT "lab_case_task_status_events_changedByUserId_fkey"
|
||||
FOREIGN KEY ("changedByUserId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- 7. Per-case comments.
|
||||
CREATE TYPE "LabCaseCommentSide" AS ENUM ('LAB', 'CLINIC');
|
||||
CREATE TABLE "lab_case_comments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"labCaseId" TEXT NOT NULL,
|
||||
"authorUserId" TEXT,
|
||||
"authorOrganizationId" TEXT,
|
||||
"authorSide" "LabCaseCommentSide" NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"visibleToClinic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "lab_case_comments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "lab_case_comments_labCaseId_createdAt_idx"
|
||||
ON "lab_case_comments"("labCaseId", "createdAt");
|
||||
ALTER TABLE "lab_case_comments" ADD CONSTRAINT "lab_case_comments_labCaseId_fkey"
|
||||
FOREIGN KEY ("labCaseId") REFERENCES "lab_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "lab_case_comments" ADD CONSTRAINT "lab_case_comments_authorUserId_fkey"
|
||||
FOREIGN KEY ("authorUserId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "lab_case_comments" ADD CONSTRAINT "lab_case_comments_authorOrganizationId_fkey"
|
||||
FOREIGN KEY ("authorOrganizationId") REFERENCES "organizations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
60
backend/prisma/regenerate-lab-tasks.ts
Normal file
60
backend/prisma/regenerate-lab-tasks.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Dev-only: regenerate lab case tasks from existing LabCaseToothProsthesis rows.
|
||||
* Usage: npx ts-node prisma/regenerate-lab-tasks.ts
|
||||
*
|
||||
* The lab workflow refactor truncated lab_case_tasks. This rebuilds task sets
|
||||
* (grouped by treatment detail + prosthesis type, one set per workflow step)
|
||||
* for every already-sent case that still has prosthesis selections.
|
||||
*/
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { config } from 'dotenv';
|
||||
import path from 'path';
|
||||
import { generateLabCaseTasks } from '../src/modules/cases/lab-case-task.generator';
|
||||
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
config({ path: envPath });
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.error('regenerate-lab-tasks is not allowed in production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const cases = await prisma.labCase.findMany({
|
||||
where: {
|
||||
sentAt: { not: null },
|
||||
toothProsthesis: { some: {} },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
treatment: { select: { organization: { select: { owner: { select: { language: true } } } } } },
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Regenerating tasks for ${cases.length} sent case(s)...`);
|
||||
|
||||
let total = 0;
|
||||
for (const labCase of cases) {
|
||||
const locale = labCase.treatment.organization.owner.language ?? 'en';
|
||||
// Clear any stale tasks first so the generator's "already exists" guard passes.
|
||||
await prisma.labCaseTask.deleteMany({ where: { labCaseId: labCase.id } });
|
||||
const created = await prisma.$transaction((tx) =>
|
||||
generateLabCaseTasks(tx, labCase.id, locale),
|
||||
);
|
||||
total += created;
|
||||
console.log(` - ${labCase.id}: ${created} task(s)`);
|
||||
}
|
||||
|
||||
console.log(`Done. ${total} task(s) created.`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -21,6 +21,8 @@ const prisma = new PrismaClient();
|
||||
|
||||
// FK-safe order: children before parents.
|
||||
const TABLES_IN_ORDER = [
|
||||
'lab_case_task_status_events',
|
||||
'lab_case_comments',
|
||||
'lab_case_tasks',
|
||||
'lab_case_sends',
|
||||
'lab_case_tooth_prosthesis',
|
||||
|
||||
@@ -23,7 +23,9 @@ model User {
|
||||
sessions Session[] // 👈 ADD THIS - opposite relation for Session
|
||||
sentStaffInvites StaffInvitation[]
|
||||
sentOrganizationInvitations OrganizationInvitation[]
|
||||
assignedLabCaseTasks LabCaseTask[] @relation("LabCaseTaskAssignee")
|
||||
statusChangedLabCaseTasks LabCaseTask[] @relation("LabCaseTaskLastStatusChangedBy")
|
||||
labCaseTaskStatusEvents LabCaseTaskStatusEvent[]
|
||||
labCaseComments LabCaseComment[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -65,6 +67,7 @@ model Organization {
|
||||
appointments Appointment[]
|
||||
treatments Treatment[]
|
||||
labCaseSends LabCaseSend[]
|
||||
labCaseComments LabCaseComment[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -115,11 +118,15 @@ model Appointment {
|
||||
}
|
||||
|
||||
enum LabTaskStatus {
|
||||
PENDING
|
||||
IN_PROGRESS
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
enum LabCaseCommentSide {
|
||||
LAB
|
||||
CLINIC
|
||||
}
|
||||
|
||||
model Treatment {
|
||||
id String @id @default(uuid())
|
||||
organizationId String
|
||||
@@ -194,6 +201,7 @@ model LabCase {
|
||||
sends LabCaseSend[]
|
||||
tasks LabCaseTask[]
|
||||
toothProsthesis LabCaseToothProsthesis[]
|
||||
comments LabCaseComment[]
|
||||
|
||||
@@index([treatmentId, sortOrder])
|
||||
@@map("lab_cases")
|
||||
@@ -306,31 +314,66 @@ model LabCaseTask {
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
treatmentDetailId String
|
||||
tooth String
|
||||
teeth Json
|
||||
treatmentType String
|
||||
prosthesisTypeCode String
|
||||
workflowStepCode String
|
||||
stepOrder Int
|
||||
stepLabel String
|
||||
assigneeUserId String?
|
||||
assignedAt DateTime?
|
||||
priority Int @default(3)
|
||||
status LabTaskStatus @default(PENDING)
|
||||
isImportant Boolean @default(false)
|
||||
status LabTaskStatus @default(IN_PROGRESS)
|
||||
lastStatusChangedByUserId String?
|
||||
lastStatusChangedAt DateTime?
|
||||
|
||||
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)
|
||||
lastStatusChangedBy User? @relation("LabCaseTaskLastStatusChangedBy", fields: [lastStatusChangedByUserId], references: [id], onDelete: SetNull)
|
||||
statusEvents LabCaseTaskStatusEvent[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([labCaseId, treatmentDetailId, tooth, stepOrder])
|
||||
@@unique([labCaseId, treatmentDetailId, prosthesisTypeCode, stepOrder])
|
||||
@@index([labCaseId, status])
|
||||
@@index([assigneeUserId, priority, createdAt])
|
||||
@@index([assignedAt, labCaseId, priority])
|
||||
@@index([labCaseId, isImportant])
|
||||
@@map("lab_case_tasks")
|
||||
}
|
||||
|
||||
model LabCaseTaskStatusEvent {
|
||||
id String @id @default(uuid())
|
||||
taskId String
|
||||
fromStatus LabTaskStatus?
|
||||
toStatus LabTaskStatus
|
||||
changedByUserId String?
|
||||
changedAt DateTime @default(now())
|
||||
|
||||
task LabCaseTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
changedBy User? @relation(fields: [changedByUserId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([taskId, changedAt])
|
||||
@@map("lab_case_task_status_events")
|
||||
}
|
||||
|
||||
model LabCaseComment {
|
||||
id String @id @default(uuid())
|
||||
labCaseId String
|
||||
authorUserId String?
|
||||
authorOrganizationId String?
|
||||
authorSide LabCaseCommentSide
|
||||
body String
|
||||
visibleToClinic Boolean @default(false)
|
||||
|
||||
labCase LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
|
||||
authorUser User? @relation(fields: [authorUserId], references: [id], onDelete: SetNull)
|
||||
authorOrganization Organization? @relation(fields: [authorOrganizationId], references: [id], onDelete: SetNull)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([labCaseId, createdAt])
|
||||
@@map("lab_case_comments")
|
||||
}
|
||||
|
||||
model Plan {
|
||||
id String @id @default(uuid())
|
||||
name String @unique // "Solo", "Small", "Medium", "Large", "Enterprise"
|
||||
|
||||
Reference in New Issue
Block a user