initial commit

This commit is contained in:
2026-04-04 22:13:55 -04:00
commit 5d77e207c9
10181 changed files with 522212 additions and 0 deletions

1
packages/db/prisma/.env Executable file
View File

@@ -0,0 +1 @@
DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp

View File

@@ -0,0 +1 @@
DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp

View File

@@ -0,0 +1,505 @@
-- CreateEnum
CREATE TYPE "PatientStatus" AS ENUM ('ACTIVE', 'INACTIVE', 'UNKNOWN');
-- CreateEnum
CREATE TYPE "ProcedureSource" AS ENUM ('COMBO', 'MANUAL');
-- CreateEnum
CREATE TYPE "ClaimStatus" AS ENUM ('PENDING', 'APPROVED', 'CANCELLED', 'REVIEW', 'VOID');
-- CreateEnum
CREATE TYPE "MissingTeethStatus" AS ENUM ('No_missing', 'endentulous', 'Yes_missing');
-- CreateEnum
CREATE TYPE "ServiceLineStatus" AS ENUM ('PENDING', 'PARTIALLY_PAID', 'PAID', 'UNPAID', 'ADJUSTED', 'OVERPAID', 'DENIED');
-- CreateEnum
CREATE TYPE "PdfTitleKey" AS ENUM ('INSURANCE_CLAIM', 'INSURANCE_CLAIM_PREAUTH', 'ELIGIBILITY_STATUS', 'CLAIM_STATUS', 'OTHER');
-- CreateEnum
CREATE TYPE "PaymentStatus" AS ENUM ('PENDING', 'PARTIALLY_PAID', 'PAID', 'OVERPAID', 'DENIED', 'VOID');
-- CreateEnum
CREATE TYPE "PaymentMethod" AS ENUM ('EFT', 'CHECK', 'CASH', 'CARD', 'OTHER');
-- CreateEnum
CREATE TYPE "NotificationTypes" AS ENUM ('BACKUP', 'CLAIM', 'PAYMENT', 'ETC');
-- CreateEnum
CREATE TYPE "CommunicationChannel" AS ENUM ('sms', 'voice');
-- CreateEnum
CREATE TYPE "CommunicationDirection" AS ENUM ('outbound', 'inbound');
-- CreateEnum
CREATE TYPE "CommunicationStatus" AS ENUM ('queued', 'sent', 'delivered', 'failed', 'completed', 'busy', 'no_answer');
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Patient" (
"id" SERIAL NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"dateOfBirth" DATE NOT NULL,
"gender" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"email" TEXT,
"address" TEXT,
"city" TEXT,
"zipCode" TEXT,
"insuranceProvider" TEXT,
"insuranceId" TEXT,
"groupNumber" TEXT,
"policyHolder" TEXT,
"allergies" TEXT,
"medicalConditions" TEXT,
"status" "PatientStatus" NOT NULL DEFAULT 'UNKNOWN',
"userId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Patient_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Appointment" (
"id" SERIAL NOT NULL,
"patientId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"staffId" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"date" DATE NOT NULL,
"startTime" TEXT NOT NULL,
"endTime" TEXT NOT NULL,
"type" TEXT NOT NULL,
"notes" TEXT,
"status" TEXT NOT NULL DEFAULT 'scheduled',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"eligibilityStatus" "PatientStatus" NOT NULL DEFAULT 'UNKNOWN',
CONSTRAINT "Appointment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Staff" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT,
"role" TEXT NOT NULL,
"phone" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Staff_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AppointmentProcedure" (
"id" SERIAL NOT NULL,
"appointmentId" INTEGER NOT NULL,
"patientId" INTEGER NOT NULL,
"procedureCode" TEXT NOT NULL,
"procedureLabel" TEXT,
"fee" DECIMAL(10,2),
"category" TEXT,
"toothNumber" TEXT,
"toothSurface" TEXT,
"oralCavityArea" TEXT,
"source" "ProcedureSource" NOT NULL DEFAULT 'MANUAL',
"comboKey" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AppointmentProcedure_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Claim" (
"id" SERIAL NOT NULL,
"patientId" INTEGER NOT NULL,
"appointmentId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"staffId" INTEGER NOT NULL,
"patientName" TEXT NOT NULL,
"memberId" TEXT NOT NULL,
"dateOfBirth" DATE NOT NULL,
"remarks" TEXT NOT NULL,
"missingTeethStatus" "MissingTeethStatus" NOT NULL DEFAULT 'No_missing',
"missingTeeth" JSONB,
"serviceDate" TIMESTAMP(3) NOT NULL,
"insuranceProvider" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"status" "ClaimStatus" NOT NULL DEFAULT 'PENDING',
CONSTRAINT "Claim_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ServiceLine" (
"id" SERIAL NOT NULL,
"claimId" INTEGER,
"paymentId" INTEGER,
"procedureCode" TEXT NOT NULL,
"procedureDate" DATE NOT NULL,
"oralCavityArea" TEXT,
"toothNumber" TEXT,
"toothSurface" TEXT,
"totalBilled" DECIMAL(10,2) NOT NULL,
"totalPaid" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"totalAdjusted" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"totalDue" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"status" "ServiceLineStatus" NOT NULL DEFAULT 'UNPAID',
CONSTRAINT "ServiceLine_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ClaimFile" (
"id" SERIAL NOT NULL,
"claimId" INTEGER NOT NULL,
"filename" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
CONSTRAINT "ClaimFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InsuranceCredential" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"siteKey" TEXT NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
CONSTRAINT "InsuranceCredential_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PdfGroup" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"titleKey" "PdfTitleKey" NOT NULL DEFAULT 'OTHER',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"patientId" INTEGER NOT NULL,
CONSTRAINT "PdfGroup_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PdfFile" (
"id" SERIAL NOT NULL,
"filename" TEXT NOT NULL,
"pdfData" BYTEA NOT NULL,
"uploadedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"groupId" INTEGER NOT NULL,
CONSTRAINT "PdfFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Payment" (
"id" SERIAL NOT NULL,
"claimId" INTEGER,
"patientId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"updatedById" INTEGER,
"totalBilled" DECIMAL(10,2) NOT NULL,
"totalPaid" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"totalAdjusted" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"totalDue" DECIMAL(10,2) NOT NULL,
"status" "PaymentStatus" NOT NULL DEFAULT 'PENDING',
"notes" TEXT,
"icn" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ServiceLineTransaction" (
"id" SERIAL NOT NULL,
"paymentId" INTEGER NOT NULL,
"serviceLineId" INTEGER NOT NULL,
"transactionId" TEXT,
"paidAmount" DECIMAL(10,2) NOT NULL,
"adjustedAmount" DECIMAL(10,2) NOT NULL DEFAULT 0.00,
"method" "PaymentMethod" NOT NULL,
"receivedDate" TIMESTAMP(3) NOT NULL,
"payerName" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ServiceLineTransaction_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DatabaseBackup" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "DatabaseBackup_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BackupDestination" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"path" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "BackupDestination_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Notification" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"type" "NotificationTypes" NOT NULL,
"message" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"read" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CloudFolder" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"parentId" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CloudFolder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CloudFile" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"mimeType" TEXT,
"fileSize" BIGINT NOT NULL,
"folderId" INTEGER,
"isComplete" BOOLEAN NOT NULL DEFAULT false,
"totalChunks" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CloudFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CloudFileChunk" (
"id" SERIAL NOT NULL,
"fileId" INTEGER NOT NULL,
"seq" INTEGER NOT NULL,
"data" BYTEA NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CloudFileChunk_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "communications" (
"id" SERIAL NOT NULL,
"patientId" INTEGER NOT NULL,
"userId" INTEGER,
"channel" "CommunicationChannel" NOT NULL,
"direction" "CommunicationDirection" NOT NULL,
"status" "CommunicationStatus" NOT NULL,
"body" TEXT,
"callDuration" INTEGER,
"twilioSid" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "communications_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE INDEX "Patient_insuranceId_idx" ON "Patient"("insuranceId");
-- CreateIndex
CREATE INDEX "Patient_createdAt_idx" ON "Patient"("createdAt");
-- CreateIndex
CREATE INDEX "Appointment_patientId_idx" ON "Appointment"("patientId");
-- CreateIndex
CREATE INDEX "Appointment_date_idx" ON "Appointment"("date");
-- CreateIndex
CREATE INDEX "AppointmentProcedure_appointmentId_idx" ON "AppointmentProcedure"("appointmentId");
-- CreateIndex
CREATE INDEX "AppointmentProcedure_patientId_idx" ON "AppointmentProcedure"("patientId");
-- CreateIndex
CREATE INDEX "InsuranceCredential_userId_idx" ON "InsuranceCredential"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "InsuranceCredential_userId_siteKey_key" ON "InsuranceCredential"("userId", "siteKey");
-- CreateIndex
CREATE INDEX "PdfGroup_patientId_idx" ON "PdfGroup"("patientId");
-- CreateIndex
CREATE INDEX "PdfGroup_titleKey_idx" ON "PdfGroup"("titleKey");
-- CreateIndex
CREATE INDEX "PdfFile_groupId_idx" ON "PdfFile"("groupId");
-- CreateIndex
CREATE UNIQUE INDEX "Payment_claimId_key" ON "Payment"("claimId");
-- CreateIndex
CREATE INDEX "Payment_claimId_idx" ON "Payment"("claimId");
-- CreateIndex
CREATE INDEX "Payment_patientId_idx" ON "Payment"("patientId");
-- CreateIndex
CREATE INDEX "Payment_createdAt_idx" ON "Payment"("createdAt");
-- CreateIndex
CREATE INDEX "ServiceLineTransaction_paymentId_idx" ON "ServiceLineTransaction"("paymentId");
-- CreateIndex
CREATE INDEX "ServiceLineTransaction_serviceLineId_idx" ON "ServiceLineTransaction"("serviceLineId");
-- CreateIndex
CREATE INDEX "DatabaseBackup_userId_idx" ON "DatabaseBackup"("userId");
-- CreateIndex
CREATE INDEX "DatabaseBackup_createdAt_idx" ON "DatabaseBackup"("createdAt");
-- CreateIndex
CREATE INDEX "Notification_userId_idx" ON "Notification"("userId");
-- CreateIndex
CREATE INDEX "Notification_createdAt_idx" ON "Notification"("createdAt");
-- CreateIndex
CREATE INDEX "CloudFolder_parentId_idx" ON "CloudFolder"("parentId");
-- CreateIndex
CREATE UNIQUE INDEX "CloudFolder_userId_parentId_name_key" ON "CloudFolder"("userId", "parentId", "name");
-- CreateIndex
CREATE INDEX "CloudFile_folderId_idx" ON "CloudFile"("folderId");
-- CreateIndex
CREATE INDEX "CloudFileChunk_fileId_seq_idx" ON "CloudFileChunk"("fileId", "seq");
-- CreateIndex
CREATE UNIQUE INDEX "CloudFileChunk_fileId_seq_key" ON "CloudFileChunk"("fileId", "seq");
-- AddForeignKey
ALTER TABLE "Patient" ADD CONSTRAINT "Patient_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_staffId_fkey" FOREIGN KEY ("staffId") REFERENCES "Staff"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Staff" ADD CONSTRAINT "Staff_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AppointmentProcedure" ADD CONSTRAINT "AppointmentProcedure_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AppointmentProcedure" ADD CONSTRAINT "AppointmentProcedure_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Claim" ADD CONSTRAINT "Claim_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Claim" ADD CONSTRAINT "Claim_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Claim" ADD CONSTRAINT "Claim_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Claim" ADD CONSTRAINT "Claim_staffId_fkey" FOREIGN KEY ("staffId") REFERENCES "Staff"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ServiceLine" ADD CONSTRAINT "ServiceLine_claimId_fkey" FOREIGN KEY ("claimId") REFERENCES "Claim"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ServiceLine" ADD CONSTRAINT "ServiceLine_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ClaimFile" ADD CONSTRAINT "ClaimFile_claimId_fkey" FOREIGN KEY ("claimId") REFERENCES "Claim"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InsuranceCredential" ADD CONSTRAINT "InsuranceCredential_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PdfGroup" ADD CONSTRAINT "PdfGroup_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PdfFile" ADD CONSTRAINT "PdfFile_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "PdfGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_claimId_fkey" FOREIGN KEY ("claimId") REFERENCES "Claim"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ServiceLineTransaction" ADD CONSTRAINT "ServiceLineTransaction_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ServiceLineTransaction" ADD CONSTRAINT "ServiceLineTransaction_serviceLineId_fkey" FOREIGN KEY ("serviceLineId") REFERENCES "ServiceLine"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DatabaseBackup" ADD CONSTRAINT "DatabaseBackup_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BackupDestination" ADD CONSTRAINT "BackupDestination_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CloudFolder" ADD CONSTRAINT "CloudFolder_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "CloudFolder"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CloudFolder" ADD CONSTRAINT "CloudFolder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CloudFile" ADD CONSTRAINT "CloudFile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CloudFile" ADD CONSTRAINT "CloudFile_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "CloudFolder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CloudFileChunk" ADD CONSTRAINT "CloudFileChunk_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "CloudFile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "communications" ADD CONSTRAINT "communications_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "Patient"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "communications" ADD CONSTRAINT "communications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,22 @@
-- AlterTable
ALTER TABLE "Appointment" ADD COLUMN "procedureCodeNotes" TEXT;
-- CreateTable
CREATE TABLE "NpiProvider" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"npiNumber" TEXT NOT NULL,
"providerName" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "NpiProvider_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "NpiProvider_userId_idx" ON "NpiProvider"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "NpiProvider_userId_npiNumber_key" ON "NpiProvider"("userId", "npiNumber");
-- AddForeignKey
ALTER TABLE "NpiProvider" ADD CONSTRAINT "NpiProvider_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@@ -0,0 +1,12 @@
import dotenv from "dotenv";
import path from "path";
import { defineConfig, env } from "prisma/config";
dotenv.config({ path: path.resolve(__dirname, ".env") });
export default defineConfig({
schema: "schema.prisma",
datasource: {
url: env("DATABASE_URL"),
},
});

506
packages/db/prisma/schema.prisma Executable file
View File

@@ -0,0 +1,506 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
generator zod {
provider = "prisma-zod-generator"
output = "../shared/" // Zod schemas will be generated here inside `db/shared`
}
datasource db {
provider = "postgresql"
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
patients Patient[]
appointments Appointment[]
staff Staff[]
npiProviders NpiProvider[]
claims Claim[]
insuranceCredentials InsuranceCredential[]
updatedPayments Payment[] @relation("PaymentUpdatedBy")
backups DatabaseBackup[]
backupDestinations BackupDestination[]
notifications Notification[]
cloudFolders CloudFolder[]
cloudFiles CloudFile[]
communications Communication[]
}
model Patient {
id Int @id @default(autoincrement())
firstName String
lastName String
dateOfBirth DateTime @db.Date
gender String
phone String
email String?
address String?
city String?
zipCode String?
insuranceProvider String?
insuranceId String?
groupNumber String?
policyHolder String?
allergies String?
medicalConditions String?
status PatientStatus @default(UNKNOWN)
userId Int
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
appointments Appointment[]
procedures AppointmentProcedure[]
claims Claim[]
groups PdfGroup[]
payment Payment[]
communications Communication[]
documents PatientDocument[]
@@index([insuranceId])
@@index([createdAt])
}
enum PatientStatus {
ACTIVE
INACTIVE
UNKNOWN
}
model Appointment {
id Int @id @default(autoincrement())
patientId Int
userId Int
staffId Int
title String
date DateTime @db.Date
startTime String // Store time as "hh:mm"
endTime String // Store time as "hh:mm"
type String // e.g., "checkup", "cleaning", "filling", etc.
notes String?
procedureCodeNotes String?
status String @default("scheduled") // "scheduled", "completed", "cancelled", "no-show"
createdAt DateTime @default(now())
eligibilityStatus PatientStatus @default(UNKNOWN)
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
staff Staff? @relation(fields: [staffId], references: [id])
procedures AppointmentProcedure[]
claims Claim[]
@@index([patientId])
@@index([date])
}
model Staff {
id Int @id @default(autoincrement())
userId Int
name String
email String?
role String // e.g., "Dentist", "Hygienist", "Assistant"
phone String?
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
appointments Appointment[]
claims Claim[] @relation("ClaimStaff")
}
model NpiProvider {
id Int @id @default(autoincrement())
userId Int
npiNumber String
providerName String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, npiNumber])
@@index([userId])
}
enum ProcedureSource {
COMBO
MANUAL
}
model AppointmentProcedure {
id Int @id @default(autoincrement())
appointmentId Int
patientId Int
procedureCode String
procedureLabel String?
fee Decimal? @db.Decimal(10, 2)
category String?
toothNumber String?
toothSurface String?
oralCavityArea String?
source ProcedureSource @default(MANUAL)
comboKey String?
createdAt DateTime @default(now())
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
@@index([appointmentId])
@@index([patientId])
}
model Claim {
id Int @id @default(autoincrement())
patientId Int
appointmentId Int
userId Int
staffId Int
patientName String
memberId String
dateOfBirth DateTime @db.Date
remarks String
missingTeethStatus MissingTeethStatus @default(No_missing)
missingTeeth Json? // { "T_14": "X", "T_G": "O", ... }
serviceDate DateTime
insuranceProvider String // e.g., "Delta MA"
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status ClaimStatus @default(PENDING)
claimNumber String?
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id])
staff Staff? @relation("ClaimStaff", fields: [staffId], references: [id])
serviceLines ServiceLine[]
claimFiles ClaimFile[]
payment Payment?
}
enum ClaimStatus {
PENDING
APPROVED
CANCELLED
REVIEW
VOID
}
enum MissingTeethStatus {
No_missing
endentulous
Yes_missing
}
model ServiceLine {
id Int @id @default(autoincrement())
claimId Int?
paymentId Int?
procedureCode String
procedureDate DateTime @db.Date
quad String?
arch String?
toothNumber String?
toothSurface String?
totalBilled Decimal @db.Decimal(10, 2)
totalPaid Decimal @default(0.00) @db.Decimal(10, 2)
totalAdjusted Decimal @default(0.00) @db.Decimal(10, 2)
totalDue Decimal @default(0.00) @db.Decimal(10, 2)
status ServiceLineStatus @default(UNPAID)
claim Claim? @relation(fields: [claimId], references: [id], onDelete: Cascade)
payment Payment? @relation(fields: [paymentId], references: [id], onDelete: Cascade)
serviceLineTransactions ServiceLineTransaction[]
}
enum ServiceLineStatus {
PENDING
PARTIALLY_PAID
PAID
UNPAID
ADJUSTED
OVERPAID
DENIED
}
model ClaimFile {
id Int @id @default(autoincrement())
claimId Int
filename String
mimeType String
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
}
model InsuranceCredential {
id Int @id @default(autoincrement())
userId Int
siteKey String
username String
password String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, siteKey])
@@index([userId])
}
model PdfGroup {
id Int @id @default(autoincrement())
title String
titleKey PdfTitleKey @default(OTHER)
createdAt DateTime @default(now())
patientId Int
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
pdfs PdfFile[]
@@index([patientId])
@@index([titleKey])
}
model PdfFile {
id Int @id @default(autoincrement())
filename String
pdfData Bytes
uploadedAt DateTime @default(now())
groupId Int
group PdfGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
@@index([groupId])
}
enum PdfTitleKey {
INSURANCE_CLAIM
INSURANCE_CLAIM_PREAUTH
ELIGIBILITY_STATUS
CLAIM_STATUS
OTHER
}
model Payment {
id Int @id @default(autoincrement())
claimId Int? @unique
patientId Int
userId Int
updatedById Int?
totalBilled Decimal @db.Decimal(10, 2)
totalPaid Decimal @default(0.00) @db.Decimal(10, 2)
totalAdjusted Decimal @default(0.00) @db.Decimal(10, 2)
totalDue Decimal @db.Decimal(10, 2)
status PaymentStatus @default(PENDING)
notes String?
icn String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
claim Claim? @relation(fields: [claimId], references: [id], onDelete: Cascade)
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
updatedBy User? @relation("PaymentUpdatedBy", fields: [updatedById], references: [id])
serviceLineTransactions ServiceLineTransaction[]
serviceLines ServiceLine[]
@@index([claimId])
@@index([patientId])
@@index([createdAt])
}
model ServiceLineTransaction {
id Int @id @default(autoincrement())
paymentId Int
serviceLineId Int
transactionId String?
paidAmount Decimal @db.Decimal(10, 2)
adjustedAmount Decimal @default(0.00) @db.Decimal(10, 2)
method PaymentMethod
receivedDate DateTime
payerName String?
notes String?
createdAt DateTime @default(now())
payment Payment @relation(fields: [paymentId], references: [id], onDelete: Cascade)
serviceLine ServiceLine @relation(fields: [serviceLineId], references: [id], onDelete: Cascade)
@@index([paymentId])
@@index([serviceLineId])
}
enum PaymentStatus {
PENDING
PARTIALLY_PAID
PAID
OVERPAID
DENIED
VOID
}
enum PaymentMethod {
EFT
CHECK
CASH
CARD
OTHER
}
// Database management page
model DatabaseBackup {
id Int @id @default(autoincrement())
userId Int
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([createdAt])
}
model BackupDestination {
id Int @id @default(autoincrement())
userId Int
path String
isActive Boolean @default(true)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model Notification {
id Int @id @default(autoincrement())
userId Int
type NotificationTypes
message String
createdAt DateTime @default(now())
read Boolean @default(false)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([createdAt])
}
enum NotificationTypes {
BACKUP
CLAIM
PAYMENT
ETC
}
model CloudFolder {
id Int @id @default(autoincrement())
userId Int
name String
parentId Int?
parent CloudFolder? @relation("FolderChildren", fields: [parentId], references: [id], onDelete: Cascade)
children CloudFolder[] @relation("FolderChildren")
user User @relation(fields: [userId], references: [id])
files CloudFile[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, parentId, name]) // prevents sibling folder name duplicates
@@index([parentId])
}
model CloudFile {
id Int @id @default(autoincrement())
userId Int
name String
mimeType String?
fileSize BigInt @db.BigInt
folderId Int? // optional: null => root
isComplete Boolean @default(false) // upload completed?
totalChunks Int? // optional: expected number of chunks
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
folder CloudFolder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
chunks CloudFileChunk[]
@@index([folderId])
}
model CloudFileChunk {
id Int @id @default(autoincrement())
fileId Int
seq Int
data Bytes
createdAt DateTime @default(now())
file CloudFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
@@unique([fileId, seq])
@@index([fileId, seq])
}
// patient-connection-
enum CommunicationChannel {
sms
voice
}
enum CommunicationDirection {
outbound
inbound
}
enum CommunicationStatus {
queued
sent
delivered
failed
completed
busy
no_answer
}
model Communication {
id Int @id @default(autoincrement())
patientId Int
userId Int?
channel CommunicationChannel
direction CommunicationDirection
status CommunicationStatus
body String?
callDuration Int?
twilioSid String?
createdAt DateTime @default(now())
// Relations
patient Patient @relation(fields: [patientId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@map("communications")
}
model PatientDocument {
id Int @id @default(autoincrement())
patientId Int
filename String
originalName String
mimeType String
fileSize BigInt
filePath String
uploadedAt DateTime @default(now())
updatedAt DateTime @updatedAt
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
@@index([patientId])
@@index([uploadedAt])
}

93
packages/db/prisma/seed.ts Executable file
View File

@@ -0,0 +1,93 @@
import { PrismaClient } from "../generated/prisma";
const prisma = new PrismaClient();
function formatTime(date: Date): string {
return date.toTimeString().slice(0, 5); // "HH:MM"
}
async function main() {
// Create multiple users
const users = await prisma.user.createMany({
data: [
{ username: "admin2", password: "123456" },
{ username: "bob", password: "123456" },
],
});
const createdUsers = await prisma.user.findMany();
// Creatin staff
await prisma.staff.createMany({
data: [
{ name: "Dr. Kai Gao", role: "Doctor" },
{ name: "Dr. Jane Smith", role: "Doctor" },
],
});
const staffMembers = await prisma.staff.findMany();
// Create multiple patients
const patients = await prisma.patient.createMany({
data: [
{
firstName: "Emily",
lastName: "Clark",
dateOfBirth: new Date("1985-06-15"),
gender: "female",
phone: "555-0001",
email: "emily@example.com",
address: "101 Apple Rd",
city: "Newtown",
zipCode: "10001",
userId: createdUsers[0].id,
},
{
firstName: "Michael",
lastName: "Brown",
dateOfBirth: new Date("1979-09-10"),
gender: "male",
phone: "555-0002",
email: "michael@example.com",
address: "202 Banana Ave",
city: "Oldtown",
zipCode: "10002",
userId: createdUsers[1].id,
},
],
});
const createdPatients = await prisma.patient.findMany();
// Create multiple appointments
await prisma.appointment.createMany({
data: [
{
patientId: createdPatients[0].id,
userId: createdUsers[0].id,
title: "Initial Consultation",
date: new Date("2025-06-01"),
startTime: formatTime(new Date("2025-06-01T10:00:00")),
endTime: formatTime(new Date("2025-06-01T10:30:00")),
type: "consultation",
},
{
patientId: createdPatients[1].id,
userId: createdUsers[1].id,
title: "Follow-up",
date: new Date("2025-06-02"),
startTime: formatTime(new Date("2025-06-01T10:00:00")),
endTime: formatTime(new Date("2025-06-01T10:30:00")),
type: "checkup",
},
],
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});