/opt/mawid/packages/db/prisma
NameSizeModeActions
migrations/-0755rm
schema.prisma80530644editdlrm
seed.ts37720644editdlrm
tsconfig.json2010644editdlrm
Edit: /opt/mawid/packages/db/prisma/schema.prisma (8053B)
// Full data model per PROJECT_PLAN.md §3. // Multi-tenancy rule: every query must be scoped by clinicId — enforced via // the repository pattern in the API (services require clinicId on every call), // with an explicit cross-tenant isolation test. generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum AppointmentStatus { pending confirmed cancelled no_show completed } enum AppointmentSource { whatsapp dashboard } enum MessageDirection { inbound @map("in") outbound @map("out") } enum ReminderKind { h24 @map("24h") h2 @map("2h") } enum ReminderStatus { pending sent cancelled failed } enum WaitlistStatus { active notified fulfilled cancelled expired } enum SlotHoldStatus { offered accepted declined expired cancelled } model Clinic { id String @id @default(cuid()) name String phone String // WhatsApp business number (display) waPhoneNumberId String? @unique // Meta phone_number_id — routes inbound webhooks to the clinic timezone String @default("Europe/Istanbul") defaultLanguage String @default("tr") workingHours Json @default("{}") // per-weekday intervals settings Json @default("{}") // reminder offsets, deposit on/off, ... createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] staff Staff[] services Service[] patients Patient[] appointments Appointment[] waitlistEntries WaitlistEntry[] conversations Conversation[] auditLogs AuditLog[] slotHolds SlotHold[] } // Owner accounts only for MVP (PROJECT_PLAN §2 Auth). model User { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) email String @unique passwordHash String name String role String @default("owner") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([clinicId]) } model Staff { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) name String role String @default("practitioner") workingHours Json @default("{}") active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt services Service[] @relation("StaffServices") appointments Appointment[] @@index([clinicId]) } model Service { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) name Json // i18n: { ar, tr, en } durationMinutes Int price Decimal? @db.Decimal(10, 2) active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt staff Staff[] @relation("StaffServices") appointments Appointment[] waitlistEntries WaitlistEntry[] @@index([clinicId]) } model Patient { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) waPhone String name String? language String @default("tr") notes String? tags String[] @default([]) lastVisitAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt appointments Appointment[] waitlistEntries WaitlistEntry[] conversations Conversation[] @@unique([clinicId, waPhone]) @@index([clinicId]) } model Appointment { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) patientId String patient Patient @relation(fields: [patientId], references: [id]) staffId String staff Staff @relation(fields: [staffId], references: [id]) serviceId String service Service @relation(fields: [serviceId], references: [id]) startsAt DateTime endsAt DateTime status AppointmentStatus @default(pending) source AppointmentSource @default(whatsapp) cancelReason String? unconfirmedRisk Boolean @default(false) // no reply to reminders by T-4h createdAt DateTime @default(now()) updatedAt DateTime @updatedAt reminderJobs ReminderJob[] @@index([clinicId, startsAt]) @@index([clinicId, status]) @@index([staffId, startsAt]) } model WaitlistEntry { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) patientId String patient Patient @relation(fields: [patientId], references: [id]) serviceId String service Service @relation(fields: [serviceId], references: [id]) preferredWindow Json // date ranges / times status WaitlistStatus @default(active) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt slotHolds SlotHold[] @@index([clinicId, status]) } // A freed slot offered to one waitlisted patient at a time (Phase 5). model SlotHold { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) waitlistEntryId String waitlistEntry WaitlistEntry @relation(fields: [waitlistEntryId], references: [id], onDelete: Cascade) staffId String serviceId String startsAt DateTime endsAt DateTime status SlotHoldStatus @default(offered) expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([clinicId, status]) @@index([staffId, startsAt]) } model Conversation { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) patientId String patient Patient @relation(fields: [patientId], references: [id]) state Json @default("{}") // agent memory / slots (compact facts, not transcripts) lastMessageAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt messages Message[] @@index([clinicId, patientId]) } model Message { id String @id @default(cuid()) conversationId String conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) direction MessageDirection waMessageId String? @unique type String // text | interactive | template | ... body String? payload Json? createdAt DateTime @default(now()) @@index([conversationId, createdAt]) } model ReminderJob { id String @id @default(cuid()) appointmentId String appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade) kind ReminderKind scheduledFor DateTime status ReminderStatus @default(pending) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([appointmentId]) @@index([status, scheduledFor]) } model AuditLog { id String @id @default(cuid()) clinicId String clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade) actor String // "agent" | "owner:" | "system" action String meta Json @default("{}") createdAt DateTime @default(now()) @@index([clinicId, createdAt]) }