2026-04-23 15:33:11 +03:30
|
|
|
// backend/prisma/schema.prisma
|
|
|
|
|
generator client {
|
|
|
|
|
provider = "prisma-client-js"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
datasource db {
|
|
|
|
|
provider = "postgresql"
|
|
|
|
|
url = env("DATABASE_URL")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model User {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
email String @unique
|
|
|
|
|
passwordHash String?
|
|
|
|
|
googleId String? @unique
|
|
|
|
|
facebookId String? @unique
|
|
|
|
|
name String
|
2026-04-29 20:19:40 +03:30
|
|
|
trialUsedAt DateTime?
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
memberships Membership[]
|
|
|
|
|
ownedOrganizations Organization[] @relation("OrganizationOwner")
|
|
|
|
|
sessions Session[] // 👈 ADD THIS - opposite relation for Session
|
2026-04-30 12:55:39 +03:30
|
|
|
sentStaffInvites StaffInvitation[]
|
2026-05-05 19:50:07 +03:30
|
|
|
sentOrganizationInvitations OrganizationInvitation[]
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@map("users")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model OrganizationType {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique // "CLINIC" or "LAB"
|
|
|
|
|
|
|
|
|
|
organizations Organization[]
|
|
|
|
|
features Feature[] // 👈 ADD THIS - opposite relation for Feature
|
|
|
|
|
|
|
|
|
|
@@map("organization_types")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Organization {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String
|
|
|
|
|
email String @unique
|
|
|
|
|
phone String?
|
|
|
|
|
address String?
|
|
|
|
|
|
|
|
|
|
typeId String
|
|
|
|
|
type OrganizationType @relation(fields: [typeId], references: [id])
|
|
|
|
|
|
|
|
|
|
ownerId String
|
|
|
|
|
owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
|
|
|
|
|
|
|
|
|
|
memberships Membership[]
|
2026-04-30 18:15:40 +03:30
|
|
|
planId String?
|
|
|
|
|
plan Plan? @relation(fields: [planId], references: [id])
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
sharedWithMe OrganizationLink[] @relation("OrganizationB")
|
|
|
|
|
sharedWithOthers OrganizationLink[] @relation("OrganizationA")
|
2026-05-05 19:50:07 +03:30
|
|
|
sentOrganizationInvitations OrganizationInvitation[] @relation("OrganizationInvitationInviter")
|
2026-04-29 13:32:22 +03:30
|
|
|
patients Patient[]
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@map("organizations")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 13:32:22 +03:30
|
|
|
model Patient {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
organizationId String
|
|
|
|
|
firstName String
|
|
|
|
|
lastName String
|
|
|
|
|
phone String?
|
|
|
|
|
email String?
|
|
|
|
|
dateOfBirth DateTime?
|
|
|
|
|
notes String?
|
|
|
|
|
isActive Boolean @default(true)
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id])
|
|
|
|
|
treatments PatientTreatmentHistory[]
|
|
|
|
|
|
|
|
|
|
@@index([organizationId, createdAt])
|
|
|
|
|
@@index([organizationId, lastName, firstName])
|
|
|
|
|
@@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")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 15:33:11 +03:30
|
|
|
model Plan {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique // "Solo", "Small", "Medium", "Large", "Enterprise"
|
|
|
|
|
maxUsers Int // 1, 5, 10, 15, 999999 for unlimited
|
|
|
|
|
price Float
|
|
|
|
|
features Json // Store feature flags as JSON
|
|
|
|
|
|
|
|
|
|
organizations Organization[]
|
|
|
|
|
|
|
|
|
|
@@map("plans")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Membership {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
userId String
|
|
|
|
|
organizationId String
|
|
|
|
|
|
|
|
|
|
isOwner Boolean @default(false)
|
2026-04-30 12:55:39 +03:30
|
|
|
isActive Boolean @default(true)
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id])
|
|
|
|
|
|
|
|
|
|
permissions MembershipPermission[]
|
2026-04-30 12:55:39 +03:30
|
|
|
invitations StaffInvitation[]
|
2026-04-23 15:33:11 +03:30
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@unique([userId, organizationId])
|
|
|
|
|
@@map("memberships")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-30 12:55:39 +03:30
|
|
|
model StaffInvitation {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
membershipId String
|
|
|
|
|
invitedById String
|
|
|
|
|
tokenHash String @unique
|
|
|
|
|
expiresAt DateTime
|
|
|
|
|
acceptedAt DateTime?
|
|
|
|
|
revokedAt DateTime?
|
|
|
|
|
|
|
|
|
|
membership Membership @relation(fields: [membershipId], references: [id], onDelete: Cascade)
|
|
|
|
|
invitedBy User @relation(fields: [invitedById], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@index([membershipId, createdAt])
|
|
|
|
|
@@map("staff_invitations")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 15:33:11 +03:30
|
|
|
model Permission {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique
|
|
|
|
|
description String?
|
|
|
|
|
|
|
|
|
|
memberships MembershipPermission[]
|
|
|
|
|
featureId String?
|
|
|
|
|
feature Feature? @relation(fields: [featureId], references: [id])
|
|
|
|
|
|
|
|
|
|
@@map("permissions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model MembershipPermission {
|
|
|
|
|
membershipId String
|
|
|
|
|
permissionId String
|
|
|
|
|
|
|
|
|
|
membership Membership @relation(fields: [membershipId], references: [id])
|
|
|
|
|
permission Permission @relation(fields: [permissionId], references: [id])
|
|
|
|
|
|
|
|
|
|
@@id([membershipId, permissionId])
|
|
|
|
|
@@map("membership_permissions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Feature {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique
|
|
|
|
|
description String?
|
|
|
|
|
|
|
|
|
|
permissions Permission[]
|
|
|
|
|
organizationTypeId String?
|
|
|
|
|
organizationType OrganizationType? @relation(fields: [organizationTypeId], references: [id])
|
|
|
|
|
|
|
|
|
|
@@map("features")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model OrganizationLink {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
organizationAId String
|
|
|
|
|
organizationBId String
|
|
|
|
|
status LinkStatus @default(PENDING)
|
|
|
|
|
sharedDataTypes Json
|
|
|
|
|
|
|
|
|
|
organizationA Organization @relation("OrganizationA", fields: [organizationAId], references: [id])
|
|
|
|
|
organizationB Organization @relation("OrganizationB", fields: [organizationBId], references: [id])
|
|
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@unique([organizationAId, organizationBId])
|
|
|
|
|
@@map("organization_links")
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 19:50:07 +03:30
|
|
|
model OrganizationInvitation {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
inviterOrganizationId String
|
|
|
|
|
inviterUserId String
|
|
|
|
|
|
|
|
|
|
invitedOrganizationId String?
|
|
|
|
|
invitedOrganizationName String
|
|
|
|
|
invitedOwnerEmail String
|
|
|
|
|
invitedOrganizationType String
|
|
|
|
|
tokenHash String @unique
|
|
|
|
|
expiresAt DateTime
|
|
|
|
|
acceptedAt DateTime?
|
|
|
|
|
revokedAt DateTime?
|
|
|
|
|
|
|
|
|
|
inviterOrganization Organization @relation("OrganizationInvitationInviter", fields: [inviterOrganizationId], references: [id], onDelete: Cascade)
|
|
|
|
|
inviterUser User @relation(fields: [inviterUserId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@index([inviterOrganizationId, createdAt])
|
|
|
|
|
@@index([invitedOwnerEmail, createdAt])
|
|
|
|
|
@@map("organization_invitations")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 15:33:11 +03:30
|
|
|
model Session {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
userId String
|
|
|
|
|
token String @unique
|
|
|
|
|
refreshToken String? @unique
|
|
|
|
|
expiresAt DateTime
|
|
|
|
|
userAgent String?
|
|
|
|
|
ipAddress String?
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
@@map("sessions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum LinkStatus {
|
|
|
|
|
PENDING
|
|
|
|
|
ACTIVE
|
|
|
|
|
REJECTED
|
|
|
|
|
BLOCKED
|
|
|
|
|
}
|