379 lines
12 KiB
Plaintext
379 lines
12 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model Clinic {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String @unique
|
|
email String @unique
|
|
passwordHash String?
|
|
phone String?
|
|
clinicType String @default("Medical Clinic")
|
|
wahaSessionId String?
|
|
wahaStatus String @default("disconnected")
|
|
wahaLastChecked DateTime?
|
|
timezone String @default("Asia/Riyadh")
|
|
quietHoursStart Int @default(22)
|
|
quietHoursEnd Int @default(8)
|
|
plan String @default("free")
|
|
planExpiresAt DateTime?
|
|
trialEndsAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
sessions AppSession[]
|
|
appointments Appointment[]
|
|
auditLogs AuditLog[]
|
|
cronLogs CronLog[]
|
|
leads Lead[]
|
|
patientNotes PatientNote[]
|
|
messages MessageLog[]
|
|
oauthAccounts OAuthAccount[]
|
|
patientProfiles PatientProfile[]
|
|
reminders ReminderTemplate[]
|
|
reminderTrees ReminderTree[]
|
|
treatments Treatment[]
|
|
treeExperiments TreeExperiment[]
|
|
campaigns Campaign[]
|
|
staff Staff[]
|
|
}
|
|
|
|
model OAuthAccount {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
provider String
|
|
providerId String
|
|
email String?
|
|
name String?
|
|
avatarUrl String?
|
|
accessToken String?
|
|
refreshToken String?
|
|
expiresAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([provider, providerId])
|
|
@@index([clinicId])
|
|
@@index([provider, email])
|
|
}
|
|
|
|
model AppSession {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
token String @unique
|
|
userAgent String?
|
|
ipAddress String?
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
lastSeenAt DateTime @default(now())
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clinicId])
|
|
@@index([token])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
action String
|
|
actor String?
|
|
resource String?
|
|
metadata String?
|
|
ipAddress String?
|
|
userAgent String?
|
|
createdAt DateTime @default(now())
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clinicId, createdAt])
|
|
@@index([action, createdAt])
|
|
}
|
|
|
|
model Treatment {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
duration Int @default(30)
|
|
price Float?
|
|
prepInstructions String?
|
|
aftercareInstructions String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
appointments Appointment[]
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Lead {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
phone String
|
|
email String?
|
|
source String @default("manual")
|
|
status String @default("new")
|
|
treatmentInterest String?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
appointments Appointment[]
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
patientNotes PatientNote[]
|
|
patientProfiles PatientProfile[]
|
|
}
|
|
|
|
model Appointment {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
leadId String
|
|
treatmentId String
|
|
dateTime DateTime
|
|
status String @default("scheduled")
|
|
confirmToken String?
|
|
confirmedAt DateTime?
|
|
arrivedAt DateTime?
|
|
completedAt DateTime?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
treatment Treatment @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
|
|
lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
messages MessageLog[]
|
|
scheduledReminders ScheduledReminder[]
|
|
userJourneys UserJourney[]
|
|
}
|
|
|
|
model ReminderTemplate {
|
|
id String @id @default(cuid())
|
|
clinicId String?
|
|
trigger String
|
|
offsetMinutes Int
|
|
message String
|
|
isActive Boolean @default(true)
|
|
sortOrder Int @default(0)
|
|
clinic Clinic? @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
scheduledReminders ScheduledReminder[]
|
|
}
|
|
|
|
model ScheduledReminder {
|
|
id String @id @default(cuid())
|
|
appointmentId String
|
|
templateId String
|
|
scheduledFor DateTime
|
|
status String @default("pending")
|
|
sentAt DateTime?
|
|
failureReason String?
|
|
retryCount Int @default(0)
|
|
nextRetryAt DateTime?
|
|
template ReminderTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
|
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([status, scheduledFor])
|
|
}
|
|
|
|
model MessageLog {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
appointmentId String?
|
|
phone String
|
|
direction String @default("outgoing")
|
|
message String
|
|
status String @default("queued")
|
|
wahaMessageId String?
|
|
sentAt DateTime?
|
|
deliveredAt DateTime?
|
|
readAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
appointment Appointment? @relation(fields: [appointmentId], references: [id])
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([wahaMessageId])
|
|
}
|
|
|
|
model CronLog {
|
|
id String @id @default(cuid())
|
|
clinicId String?
|
|
type String
|
|
sent Int @default(0)
|
|
skipped Int @default(0)
|
|
failed Int @default(0)
|
|
retried Int @default(0)
|
|
durationMs Int @default(0)
|
|
error String?
|
|
ranAt DateTime @default(now())
|
|
clinic Clinic? @relation(fields: [clinicId], references: [id])
|
|
|
|
@@index([type, ranAt])
|
|
}
|
|
|
|
model ReminderTree {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
description String?
|
|
triggerType String
|
|
isActive Boolean @default(true)
|
|
version Int @default(1)
|
|
metadata String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
variantFor TreeExperiment[] @relation("VariantTree")
|
|
controlFor TreeExperiment[] @relation("ControlTree")
|
|
nodes TreeNode[]
|
|
userJourneys UserJourney[]
|
|
|
|
@@index([clinicId, isActive])
|
|
}
|
|
|
|
model TreeNode {
|
|
id String @id @default(cuid())
|
|
treeId String
|
|
parentId String?
|
|
name String
|
|
nodeType String
|
|
content String?
|
|
offsetMinutes Int @default(0)
|
|
conditions String?
|
|
metadata String?
|
|
sortOrder Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
nextNodes NodeExecution[] @relation("NextNode")
|
|
executions NodeExecution[]
|
|
parent TreeNode? @relation("TreeNodeParent", fields: [parentId], references: [id], onDelete: Cascade)
|
|
children TreeNode[] @relation("TreeNodeParent")
|
|
tree ReminderTree @relation(fields: [treeId], references: [id], onDelete: Cascade)
|
|
userJourneys UserJourney[]
|
|
|
|
@@index([treeId, sortOrder])
|
|
@@index([parentId])
|
|
}
|
|
|
|
model UserJourney {
|
|
id String @id @default(cuid())
|
|
appointmentId String
|
|
treeId String
|
|
currentNodeId String?
|
|
status String @default("active")
|
|
journeyData String?
|
|
startedAt DateTime @default(now())
|
|
lastActiveAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
executions NodeExecution[]
|
|
currentNode TreeNode? @relation(fields: [currentNodeId], references: [id])
|
|
tree ReminderTree @relation(fields: [treeId], references: [id], onDelete: Cascade)
|
|
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([appointmentId, status])
|
|
@@index([currentNodeId, lastActiveAt])
|
|
}
|
|
|
|
model NodeExecution {
|
|
id String @id @default(cuid())
|
|
journeyId String
|
|
nodeId String
|
|
status String @default("pending")
|
|
executedAt DateTime?
|
|
response String?
|
|
metadata String?
|
|
nextNodeId String?
|
|
nextNode TreeNode? @relation("NextNode", fields: [nextNodeId], references: [id])
|
|
node TreeNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
journey UserJourney @relation(fields: [journeyId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([journeyId, executedAt])
|
|
}
|
|
|
|
model PatientProfile {
|
|
id String @id @default(cuid())
|
|
leadId String
|
|
clinicId String
|
|
responseStyle String @default("standard")
|
|
preferredTime String?
|
|
engagementScore Float @default(50.0)
|
|
noShowRisk Float @default(50.0)
|
|
conversationTone String @default("neutral")
|
|
languagePreference String @default("auto")
|
|
lastInteractionAt DateTime?
|
|
profileData String?
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([leadId, clinicId])
|
|
@@index([leadId])
|
|
}
|
|
|
|
model TreeExperiment {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
description String?
|
|
controlTreeId String
|
|
variantTreeId String
|
|
trafficSplit Float @default(50.0)
|
|
status String @default("draft")
|
|
startDate DateTime?
|
|
endDate DateTime?
|
|
metrics String?
|
|
results String?
|
|
createdAt DateTime @default(now())
|
|
variantTree ReminderTree @relation("VariantTree", fields: [variantTreeId], references: [id], onDelete: Cascade)
|
|
controlTree ReminderTree @relation("ControlTree", fields: [controlTreeId], references: [id], onDelete: Cascade)
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clinicId, status])
|
|
}
|
|
|
|
model PatientNote {
|
|
id String @id @default(cuid())
|
|
leadId String
|
|
clinicId String
|
|
type String @default("note")
|
|
content String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([leadId])
|
|
}
|
|
|
|
model Campaign {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
message String
|
|
targetType String
|
|
targetValue String?
|
|
sentCount Int @default(0)
|
|
status String @default("sent")
|
|
createdAt DateTime @default(now())
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clinicId, createdAt])
|
|
}
|
|
|
|
model Staff {
|
|
id String @id @default(cuid())
|
|
clinicId String
|
|
name String
|
|
email String
|
|
passwordHash String
|
|
role String @default("receptionist")
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
clinic Clinic @relation(fields: [clinicId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clinicId, email])
|
|
@@index([clinicId])
|
|
}
|