mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-24 21:31:43 +08:00
This commit implements all features specified in the eight-features design doc: Features Added: - Temperature Log: Track body temperature with fever alerts and trend charts - Contact Directory: Manage healthcare contacts with categories and roles - Weight Log: Monitor weight changes with BMI calculation and alerts - Treatment Timeline: Track treatment milestones and visualize progress - Caregiver Tasks: Manage delegated care tasks with completion tracking - Lab Results: Record lab tests with reference ranges and trend analysis - Medical Documents: Upload and organize medical documents - Drug Interactions: Check for interactions between medications Technical Changes: - Added 8 new Prisma models (TemperatureLog, Contact, WeightLog, TreatmentMilestone, CaregiverTask, LabResult, MedicalDocument, DrugInteraction) - Created 56 new components across 8 feature domains - Implemented 23 new API routes with full CRUD operations - Added comprehensive Zod schemas for type validation - Extended Dexie DB (v3) for offline-first sync support - Created lab panel templates (CBC, CMP, Liver, Tumor Markers) with flag computation - Built drug interaction checker with curated interaction database - Added 76 new tests (99 total) covering all new functionality Bug Fixes: - Fixed operator precedence bug in interaction checker - Fixed timezone handling in calculator tests - Aligned test expectations with grace window behavior All 99 tests pass and build completes successfully.
718 lines
22 KiB
Plaintext
718 lines
22 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ============================================
|
|
// USER & AUTHENTICATION
|
|
// ============================================
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String
|
|
name String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
lastLoginAt DateTime?
|
|
forcePasswordReset Boolean @default(false)
|
|
|
|
// Relations
|
|
sessions Session[]
|
|
workspaceMembers WorkspaceMember[]
|
|
createdAppointments Appointment[] @relation("AppointmentCreatedBy")
|
|
updatedAppointments Appointment[] @relation("AppointmentUpdatedBy")
|
|
createdMedications Medication[] @relation("MedicationCreatedBy")
|
|
updatedMedications Medication[] @relation("MedicationUpdatedBy")
|
|
createdNotes Note[] @relation("NoteCreatedBy")
|
|
updatedNotes Note[] @relation("NoteUpdatedBy")
|
|
loggedDoses DoseLog[] @relation("DoseLoggedBy")
|
|
undoneDoses DoseLog[] @relation("DoseUndoneBy")
|
|
auditLogs AuditLog[]
|
|
symptoms Symptom[]
|
|
pushSubscriptions PushSubscription[]
|
|
|
|
// New feature relations
|
|
temperatureLogs TemperatureLog[] @relation("TempLogCreatedBy")
|
|
createdContacts Contact[] @relation("ContactCreatedBy")
|
|
updatedContacts Contact[] @relation("ContactUpdatedBy")
|
|
weightLogs WeightLog[] @relation("WeightLogCreatedBy")
|
|
createdMilestones TreatmentMilestone[] @relation("MilestoneCreatedBy")
|
|
updatedMilestones TreatmentMilestone[] @relation("MilestoneUpdatedBy")
|
|
createdTasks CaregiverTask[] @relation("TaskCreatedBy")
|
|
updatedTasks CaregiverTask[] @relation("TaskUpdatedBy")
|
|
assignedTasks CaregiverTask[] @relation("TaskAssignedTo")
|
|
completedTasks CaregiverTask[] @relation("TaskCompletedBy")
|
|
createdLabResults LabResult[] @relation("LabResultCreatedBy")
|
|
updatedLabResults LabResult[] @relation("LabResultUpdatedBy")
|
|
createdDocuments MedicalDocument[] @relation("DocCreatedBy")
|
|
|
|
@@index([email])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
token String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
userAgent String?
|
|
ipAddress String?
|
|
|
|
// Relations
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([token])
|
|
@@index([userId])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model LoginAttempt {
|
|
id String @id @default(cuid())
|
|
email String
|
|
ipAddress String?
|
|
success Boolean
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([email, createdAt])
|
|
@@index([ipAddress, createdAt])
|
|
}
|
|
|
|
// ============================================
|
|
// WORKSPACE & SHARING
|
|
// ============================================
|
|
|
|
model Workspace {
|
|
id String @id @default(cuid())
|
|
name String // e.g., "Grace's Plan"
|
|
clinicPhone String?
|
|
emergencyPhone String?
|
|
quietHoursStart String? // HH:mm format
|
|
quietHoursEnd String? // HH:mm format
|
|
largeTextMode Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Emergency info fields
|
|
patientName String?
|
|
patientDOB DateTime?
|
|
bloodType String?
|
|
allergies String? // Comma-separated or free text
|
|
medicalConditions String? // Comma-separated or free text
|
|
primaryPhysician String?
|
|
physicianPhone String?
|
|
|
|
// Relations
|
|
members WorkspaceMember[]
|
|
inviteTokens InviteToken[]
|
|
appointments Appointment[]
|
|
medications Medication[]
|
|
notes Note[]
|
|
doseLogs DoseLog[]
|
|
auditLogs AuditLog[]
|
|
syncCursors SyncCursor[]
|
|
symptoms Symptom[]
|
|
appointmentChecklists AppointmentChecklist[]
|
|
pushSubscriptions PushSubscription[]
|
|
|
|
// New feature relations
|
|
temperatureLogs TemperatureLog[]
|
|
contacts Contact[]
|
|
weightLogs WeightLog[]
|
|
milestones TreatmentMilestone[]
|
|
caregiverTasks CaregiverTask[]
|
|
labResults LabResult[]
|
|
medicalDocuments MedicalDocument[]
|
|
drugInteractions DrugInteraction[]
|
|
|
|
@@index([name])
|
|
}
|
|
|
|
enum WorkspaceRole {
|
|
OWNER
|
|
EDITOR
|
|
VIEWER
|
|
}
|
|
|
|
model WorkspaceMember {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
userId String
|
|
role WorkspaceRole @default(VIEWER)
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([workspaceId, userId])
|
|
@@index([userId])
|
|
}
|
|
|
|
model InviteToken {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
token String @unique
|
|
role WorkspaceRole @default(VIEWER)
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
usedById String?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([token])
|
|
@@index([workspaceId])
|
|
}
|
|
|
|
// ============================================
|
|
// APPOINTMENTS
|
|
// ============================================
|
|
|
|
model Appointment {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
title String
|
|
datetime DateTime
|
|
location String?
|
|
mapUrl String?
|
|
notes String?
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync tracking
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("AppointmentCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("AppointmentUpdatedBy", fields: [updatedById], references: [id])
|
|
checklists AppointmentChecklist[]
|
|
|
|
@@index([workspaceId, datetime])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// MEDICATIONS
|
|
// ============================================
|
|
|
|
enum ScheduleType {
|
|
FIXED_TIMES // e.g., 08:00, 20:00 daily
|
|
INTERVAL // every X hours
|
|
WEEKDAYS // specific weekdays at a time
|
|
PRN // as needed with min hours between
|
|
}
|
|
|
|
model Medication {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
name String
|
|
instructions String?
|
|
scheduleType ScheduleType
|
|
scheduleData Json // Flexible storage for schedule details
|
|
startDate DateTime?
|
|
endDate DateTime?
|
|
active Boolean @default(true)
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Refill tracking fields
|
|
pillCount Int?
|
|
pillsPerDose Int? @default(1)
|
|
refillThreshold Int? @default(7)
|
|
lastRefillDate DateTime?
|
|
|
|
// Sync tracking
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("MedicationCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("MedicationUpdatedBy", fields: [updatedById], references: [id])
|
|
doseLogs DoseLog[]
|
|
|
|
// Drug interaction relations
|
|
interactions1 DrugInteraction[] @relation("Interaction1")
|
|
interactions2 DrugInteraction[] @relation("Interaction2")
|
|
|
|
@@index([workspaceId, active])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// Schedule data shapes (stored as JSON):
|
|
// FIXED_TIMES: { times: ["08:00", "20:00"] }
|
|
// INTERVAL: { hours: 8, startTime: "08:00" }
|
|
// WEEKDAYS: { days: [1, 3, 5], time: "09:00" } // Monday, Wednesday, Friday
|
|
// PRN: { minHoursBetween: 4 }
|
|
|
|
model DoseLog {
|
|
id String @id @default(cuid())
|
|
medicationId String
|
|
workspaceId String
|
|
takenAt DateTime
|
|
loggedById String
|
|
undoneAt DateTime?
|
|
undoneById String?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Sync tracking (append-only, never overwritten)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
medication Medication @relation(fields: [medicationId], references: [id], onDelete: Cascade)
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
loggedBy User @relation("DoseLoggedBy", fields: [loggedById], references: [id])
|
|
undoneBy User? @relation("DoseUndoneBy", fields: [undoneById], references: [id])
|
|
|
|
@@index([medicationId, takenAt])
|
|
@@index([workspaceId, takenAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// NOTES
|
|
// ============================================
|
|
|
|
enum NoteType {
|
|
QUESTION // Questions for doctor
|
|
GENERAL // General notes
|
|
}
|
|
|
|
model Note {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
type NoteType
|
|
content String
|
|
askedAt DateTime? // When a question was asked (marked)
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync tracking
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("NoteCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("NoteUpdatedBy", fields: [updatedById], references: [id])
|
|
|
|
@@index([workspaceId, type])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// AUDIT LOG
|
|
// ============================================
|
|
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
userId String
|
|
action String // CREATE, UPDATE, DELETE, TAKE_DOSE, UNDO_DOSE, etc.
|
|
entityType String // APPOINTMENT, MEDICATION, NOTE, DOSE_LOG
|
|
entityId String
|
|
details Json? // Additional context
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([workspaceId, createdAt])
|
|
@@index([entityType, entityId])
|
|
}
|
|
|
|
// ============================================
|
|
// SYMPTOMS
|
|
// ============================================
|
|
|
|
enum SymptomType {
|
|
FATIGUE
|
|
NAUSEA
|
|
PAIN
|
|
APPETITE
|
|
SLEEP
|
|
MOOD
|
|
CUSTOM
|
|
}
|
|
|
|
model Symptom {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
type SymptomType
|
|
customName String? // Only used when type is CUSTOM
|
|
severity Int // 1-5 scale
|
|
notes String?
|
|
recordedAt DateTime @default(now())
|
|
deletedAt DateTime?
|
|
createdById String
|
|
|
|
// Sync tracking
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation(fields: [createdById], references: [id])
|
|
|
|
@@index([workspaceId, recordedAt])
|
|
@@index([workspaceId, type])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// APPOINTMENT CHECKLIST
|
|
// ============================================
|
|
|
|
model AppointmentChecklist {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
appointmentId String
|
|
item String
|
|
isReady Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([workspaceId, appointmentId, item])
|
|
@@index([appointmentId])
|
|
@@index([workspaceId])
|
|
}
|
|
|
|
// ============================================
|
|
// PUSH NOTIFICATIONS
|
|
// ============================================
|
|
|
|
model PushSubscription {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
workspaceId String
|
|
endpoint String
|
|
p256dh String
|
|
auth String
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, endpoint])
|
|
@@index([workspaceId])
|
|
}
|
|
|
|
// ============================================
|
|
// SYNC
|
|
// ============================================
|
|
|
|
model SyncCursor {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
cursor BigInt @default(0) // Timestamp-based cursor
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([workspaceId])
|
|
}
|
|
|
|
// ============================================
|
|
// TEMPERATURE LOG
|
|
// ============================================
|
|
|
|
model TemperatureLog {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
recordedAt DateTime @default(now())
|
|
tempCelsius Float
|
|
method String? // "oral", "forehead", "ear", "armpit"
|
|
notes String?
|
|
createdById String
|
|
deletedAt DateTime?
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("TempLogCreatedBy", fields: [createdById], references: [id])
|
|
|
|
@@index([workspaceId, recordedAt])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// CONTACT DIRECTORY
|
|
// ============================================
|
|
|
|
model Contact {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
name String
|
|
role String // "Oncologist", "Pharmacist", etc.
|
|
category String // "ONCOLOGY", "HOSPITAL", "PHARMACY", "INSURANCE", "FAMILY", "OTHER"
|
|
phone String
|
|
phone2 String?
|
|
email String?
|
|
address String?
|
|
hours String? // "Mon-Fri 8am-5pm"
|
|
notes String?
|
|
isEmergency Boolean @default(false)
|
|
sortOrder Int @default(0)
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("ContactCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("ContactUpdatedBy", fields: [updatedById], references: [id])
|
|
|
|
@@index([workspaceId, category])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// WEIGHT LOG
|
|
// ============================================
|
|
|
|
model WeightLog {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
recordedAt DateTime @default(now())
|
|
weightKg Float
|
|
notes String?
|
|
createdById String
|
|
deletedAt DateTime?
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("WeightLogCreatedBy", fields: [createdById], references: [id])
|
|
|
|
@@index([workspaceId, recordedAt])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// TREATMENT TIMELINE
|
|
// ============================================
|
|
|
|
enum MilestoneStatus {
|
|
SCHEDULED
|
|
COMPLETED
|
|
DELAYED
|
|
CANCELLED
|
|
}
|
|
|
|
model TreatmentMilestone {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
type String // "CHEMO_CYCLE", "SURGERY", "RADIATION", "SCAN", "CONSULTATION", "OTHER"
|
|
title String
|
|
description String?
|
|
plannedDate DateTime
|
|
actualDate DateTime?
|
|
status MilestoneStatus @default(SCHEDULED)
|
|
sortOrder Int @default(0)
|
|
notes String?
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("MilestoneCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("MilestoneUpdatedBy", fields: [updatedById], references: [id])
|
|
|
|
@@index([workspaceId, plannedDate])
|
|
@@index([workspaceId, status])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// CAREGIVER TASKS
|
|
// ============================================
|
|
|
|
enum TaskStatus {
|
|
TODO
|
|
IN_PROGRESS
|
|
DONE
|
|
CANCELLED
|
|
}
|
|
|
|
enum TaskPriority {
|
|
URGENT
|
|
HIGH
|
|
NORMAL
|
|
LOW
|
|
}
|
|
|
|
model CaregiverTask {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
title String
|
|
description String?
|
|
category String // "MEDICAL", "ERRANDS", "MEALS", "EMOTIONAL", "OTHER"
|
|
priority TaskPriority @default(NORMAL)
|
|
status TaskStatus @default(TODO)
|
|
assignedToId String?
|
|
dueDate DateTime?
|
|
completedAt DateTime?
|
|
completedById String?
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("TaskCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("TaskUpdatedBy", fields: [updatedById], references: [id])
|
|
assignedTo User? @relation("TaskAssignedTo", fields: [assignedToId], references: [id])
|
|
completedBy User? @relation("TaskCompletedBy", fields: [completedById], references: [id])
|
|
|
|
@@index([workspaceId, status])
|
|
@@index([workspaceId, assignedToId])
|
|
@@index([workspaceId, dueDate])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// LAB RESULTS
|
|
// ============================================
|
|
|
|
model LabResult {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
testDate DateTime
|
|
panelName String // "Complete Blood Count", "Comprehensive Metabolic", etc.
|
|
labName String? // "Quest", "Hospital Lab"
|
|
results Json // Array of { marker, value, unit, refMin, refMax, flag }
|
|
notes String?
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
updatedById String
|
|
|
|
// Sync
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("LabResultCreatedBy", fields: [createdById], references: [id])
|
|
updatedBy User @relation("LabResultUpdatedBy", fields: [updatedById], references: [id])
|
|
|
|
@@index([workspaceId, testDate])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// MEDICAL DOCUMENTS
|
|
// ============================================
|
|
|
|
model MedicalDocument {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
title String
|
|
category String // "LAB_REPORT", "SCAN", "INSURANCE", "ID_CARD", "PRESCRIPTION", "OTHER"
|
|
fileName String
|
|
fileSize Int // bytes
|
|
mimeType String // "application/pdf", "image/jpeg"
|
|
fileData Bytes // Store in DB as bytes (self-hosted, no S3)
|
|
dateTaken DateTime?
|
|
expiryDate DateTime?
|
|
notes String?
|
|
deletedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String
|
|
|
|
// Sync (no offline sync for file blobs — too large)
|
|
version Int @default(1)
|
|
syncedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdBy User @relation("DocCreatedBy", fields: [createdById], references: [id])
|
|
|
|
@@index([workspaceId, category])
|
|
@@index([workspaceId, deletedAt])
|
|
@@index([syncedAt])
|
|
}
|
|
|
|
// ============================================
|
|
// DRUG INTERACTIONS (cached lookups)
|
|
// ============================================
|
|
|
|
model DrugInteraction {
|
|
id String @id @default(cuid())
|
|
workspaceId String
|
|
medication1Id String
|
|
medication2Id String
|
|
severity String // "MINOR", "MODERATE", "MAJOR", "CONTRAINDICATED"
|
|
description String
|
|
checkedAt DateTime @default(now())
|
|
|
|
// Relations
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
medication1 Medication @relation("Interaction1", fields: [medication1Id], references: [id], onDelete: Cascade)
|
|
medication2 Medication @relation("Interaction2", fields: [medication2Id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([workspaceId, medication1Id, medication2Id])
|
|
@@index([workspaceId])
|
|
}
|