feature: a minimal v1 backend implemented for treatments feature.

This commit is contained in:
2026-05-19 22:29:29 +03:30
parent 935bb8c214
commit f743046b38
27 changed files with 1636 additions and 656 deletions

View File

@@ -0,0 +1,154 @@
-- CreateEnum
CREATE TYPE "TreatmentStatus" AS ENUM ('DRAFT', 'COMPLETED');
-- CreateTable
CREATE TABLE "treatments" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"patientId" TEXT NOT NULL,
"appointmentId" TEXT,
"providerUserId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" "TreatmentStatus" NOT NULL DEFAULT 'DRAFT',
"treatmentAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "treatments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "treatment_cases" (
"id" TEXT NOT NULL,
"treatmentId" TEXT NOT NULL,
"clientKey" TEXT,
"sortOrder" INTEGER NOT NULL,
"treatmentType" TEXT NOT NULL,
"teeth" JSONB NOT NULL,
"comment" TEXT,
"sentAt" TIMESTAMP(3),
CONSTRAINT "treatment_cases_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "treatment_case_attachments" (
"id" TEXT NOT NULL,
"caseId" TEXT,
"appointmentId" TEXT,
"caseClientKey" TEXT,
"fileName" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"sizeBytes" INTEGER NOT NULL,
"storagePath" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "treatment_case_attachments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "treatment_case_sends" (
"id" TEXT NOT NULL,
"caseId" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"sentAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "treatment_case_sends_pkey" PRIMARY KEY ("id")
);
-- Migrate legacy patient_treatment_histories into treatments + single case per row
INSERT INTO "treatments" (
"id",
"organizationId",
"patientId",
"appointmentId",
"providerUserId",
"title",
"status",
"treatmentAt",
"createdAt",
"updatedAt"
)
SELECT
h."id",
p."organizationId",
h."patientId",
NULL,
o."ownerId",
h."title",
'COMPLETED'::"TreatmentStatus",
h."treatmentAt",
h."createdAt",
h."updatedAt"
FROM "patient_treatment_histories" h
JOIN "patients" p ON p."id" = h."patientId"
JOIN "organizations" o ON o."id" = p."organizationId";
INSERT INTO "treatment_cases" (
"id",
"treatmentId",
"clientKey",
"sortOrder",
"treatmentType",
"teeth",
"comment",
"sentAt"
)
SELECT
h."id" || '-case',
h."id",
NULL,
0,
'visit',
CASE
WHEN h."tooth" IS NOT NULL AND btrim(h."tooth") <> '' THEN jsonb_build_array(h."tooth")
ELSE '[]'::jsonb
END,
h."notes",
NULL
FROM "patient_treatment_histories" h;
-- Drop legacy table
DROP TABLE "patient_treatment_histories";
-- CreateIndex
CREATE UNIQUE INDEX "treatments_appointmentId_key" ON "treatments"("appointmentId");
-- CreateIndex
CREATE INDEX "treatments_patientId_treatmentAt_idx" ON "treatments"("patientId", "treatmentAt");
-- CreateIndex
CREATE INDEX "treatments_organizationId_status_idx" ON "treatments"("organizationId", "status");
-- CreateIndex
CREATE INDEX "treatment_cases_treatmentId_sortOrder_idx" ON "treatment_cases"("treatmentId", "sortOrder");
-- CreateIndex
CREATE INDEX "treatment_case_attachments_appointmentId_caseClientKey_idx" ON "treatment_case_attachments"("appointmentId", "caseClientKey");
-- CreateIndex
CREATE INDEX "treatment_case_attachments_caseId_idx" ON "treatment_case_attachments"("caseId");
-- CreateIndex
CREATE UNIQUE INDEX "treatment_case_sends_caseId_organizationId_key" ON "treatment_case_sends"("caseId", "organizationId");
-- AddForeignKey
ALTER TABLE "treatments" ADD CONSTRAINT "treatments_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatments" ADD CONSTRAINT "treatments_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "patients"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatments" ADD CONSTRAINT "treatments_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "appointments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatment_cases" ADD CONSTRAINT "treatment_cases_treatmentId_fkey" FOREIGN KEY ("treatmentId") REFERENCES "treatments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatment_case_attachments" ADD CONSTRAINT "treatment_case_attachments_caseId_fkey" FOREIGN KEY ("caseId") REFERENCES "treatment_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatment_case_sends" ADD CONSTRAINT "treatment_case_sends_caseId_fkey" FOREIGN KEY ("caseId") REFERENCES "treatment_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "treatment_case_sends" ADD CONSTRAINT "treatment_case_sends_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -61,6 +61,8 @@ model Organization {
sentOrganizationInvitations OrganizationInvitation[] @relation("OrganizationInvitationInviter")
patients Patient[]
appointments Appointment[]
treatments Treatment[]
caseSends TreatmentCaseSend[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -81,8 +83,8 @@ model Patient {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id])
treatments PatientTreatmentHistory[]
organization Organization @relation(fields: [organizationId], references: [id])
treatments Treatment[]
appointments Appointment[]
@@index([organizationId, createdAt])
@@ -90,24 +92,6 @@ model Patient {
@@map("patients")
}
model PatientTreatmentHistory {
id String @id @default(uuid())
patientId String
title String
status String
treatmentAt DateTime
tooth String?
notes String?
totalCost Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
@@index([patientId, treatmentAt])
@@map("patient_treatment_histories")
}
model Appointment {
id String @id @default(uuid())
organizationId String
@@ -119,6 +103,7 @@ model Appointment {
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
treatment Treatment?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -128,6 +113,84 @@ model Appointment {
@@map("appointments")
}
enum TreatmentStatus {
DRAFT
COMPLETED
}
model Treatment {
id String @id @default(uuid())
organizationId String
patientId String
appointmentId String? @unique
providerUserId String
title String
status TreatmentStatus @default(DRAFT)
treatmentAt DateTime
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
appointment Appointment? @relation(fields: [appointmentId], references: [id], onDelete: SetNull)
cases TreatmentCase[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([patientId, treatmentAt])
@@index([organizationId, status])
@@map("treatments")
}
model TreatmentCase {
id String @id @default(uuid())
treatmentId String
clientKey String?
sortOrder Int
treatmentType String
teeth Json
comment String?
sentAt DateTime?
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
attachments TreatmentCaseAttachment[]
sends TreatmentCaseSend[]
@@index([treatmentId, sortOrder])
@@map("treatment_cases")
}
model TreatmentCaseAttachment {
id String @id @default(uuid())
caseId String?
appointmentId String?
caseClientKey String?
fileName String
mimeType String
sizeBytes Int
storagePath String
case TreatmentCase? @relation(fields: [caseId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@index([appointmentId, caseClientKey])
@@index([caseId])
@@map("treatment_case_attachments")
}
model TreatmentCaseSend {
id String @id @default(uuid())
caseId String
organizationId String
sentAt DateTime @default(now())
case TreatmentCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
@@unique([caseId, organizationId])
@@map("treatment_case_sends")
}
model Plan {
id String @id @default(uuid())
name String @unique // "Solo", "Small", "Medium", "Large", "Enterprise"