====== Phase 2 — Prisma Schema ======
===== Objectif =====
Construire le schéma Prisma qui deviendra la source de vérité unique du système.
À partir de ce schéma seront générés :
* PostgreSQL
* Prisma Client
* DTO
* Entités NestJS
* OpenAPI
* SDK TypeScript
* Migrations SQL
Le schéma doit refléter l'intégralité des domaines définis dans les 20 sprints.
----
====== Objectifs techniques ======
Le schéma Prisma doit supporter :
Multi-tenant
Audit
Historisation
Soft Delete
RBAC
CRM
Catalogue
Réservations
Paiements
Contrats
OTA
IA
Revenue Management
Internationalisation
Sécurité Enterprise
----
====== Ordre de construction recommandé ======
Ne jamais commencer par les 80 tables.
Construire par couches.
----
===== Couche 1 — Fondations =====
Construire en premier :
Tenant
User
Role
Permission
UserRole
RolePermission
Ces tables sont nécessaires partout.
----
===== Couche 2 — Référentiels =====
Créer :
Address
Country
Language
Currency
Timezone
----
===== Couche 3 — Catalogue =====
Créer :
Property
PropertyType
PropertyMedia
PropertyFeature
PropertyAvailability
PropertyRate
----
===== Couche 4 — Réservations =====
Créer :
Reservation
ReservationGuest
ReservationStatus
ReservationEvent
----
===== Couche 5 — Contrats =====
Créer :
Contract
ContractTemplate
ContractSignature
Document
----
===== Couche 6 — Paiements =====
Créer :
Payment
Invoice
Refund
AccountingEntry
----
===== Couche 7 — CRM =====
Créer :
Lead
Customer
Activity
Task
Pipeline
----
===== Couche 8 — Propriétaires =====
Créer :
Owner
OwnerProperty
OwnerDocument
----
===== Couche 9 — Notifications =====
Créer :
Notification
NotificationTemplate
Message
Campaign
----
===== Couche 10 — Administration =====
Créer :
AuditLog
FeatureFlag
CustomField
Workflow
----
===== Couche 11 — IA =====
Créer :
AiConversation
AiMessage
KnowledgeDocument
AutomationRule
----
===== Couche 12 — OTA =====
Créer :
Channel
ChannelConnection
PropertyDistribution
ChannelReservation
----
===== Couche 13 — Revenue Management =====
Créer :
PricingRule
DynamicPrice
RevenueSimulation
CompetitorSnapshot
----
===== Couche 14 — Sécurité =====
Créer :
Consent
SecurityPolicy
Risk
ComplianceAudit
----
===== Couche 15 — Internationalisation =====
Créer :
Translation
CurrencyRate
Region
EnterpriseLicense
----
====== Organisation du dossier Prisma ======
Au lieu d'un unique fichier géant :
prisma/
├── schema.prisma
│
├── models/
│
│ ├── tenant.prisma
│ ├── user.prisma
│ ├── property.prisma
│ ├── reservation.prisma
│ ├── contract.prisma
│ ├── payment.prisma
│ ├── crm.prisma
│ ├── owner.prisma
│ ├── notification.prisma
│ ├── audit.prisma
│ ├── ai.prisma
│ ├── channel.prisma
│ ├── revenue.prisma
│ ├── security.prisma
│ └── localization.prisma
----
====== Configuration initiale ======
===== schema.prisma =====
Version minimale :
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
----
====== Première implémentation ======
===== Étape 1 =====
Créer :
prisma/models/tenant.prisma
----
===== Tenant =====
model Tenant {
id String @id @default(uuid())
code String @unique
name String
status TenantStatus
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
properties Property[]
}
----
===== Enum =====
enum TenantStatus {
ACTIVE
SUSPENDED
TRIAL
ARCHIVED
}
----
====== Étape 2 ======
Créer :
prisma/models/user.prisma
----
===== User =====
model User {
id String @id @default(uuid())
tenantId String
email String @unique
passwordHash String
firstName String
lastName String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(
fields:[tenantId],
references:[id]
)
roles UserRole[]
}
----
===== Role =====
model Role {
id String @id @default(uuid())
code String @unique
name String
userRoles UserRole[]
permissions RolePermission[]
}
----
===== Permission =====
model Permission {
id String @id @default(uuid())
code String @unique
name String
rolePermissions RolePermission[]
}
----
===== UserRole =====
model UserRole {
userId String
roleId String
assignedAt DateTime @default(now())
user User @relation(
fields:[userId],
references:[id]
)
role Role @relation(
fields:[roleId],
references:[id]
)
@@id([userId, roleId])
}
----
===== RolePermission =====
model RolePermission {
roleId String
permissionId String
role Role @relation(
fields:[roleId],
references:[id]
)
permission Permission @relation(
fields:[permissionId],
references:[id]
)
@@id([roleId, permissionId])
}
----
====== Génération ======
Après chaque bloc important :
npx prisma format
----
===== Validation =====
npx prisma validate
----
===== Génération Client =====
npx prisma generate
----
===== Migration =====
npx prisma migrate dev \
--name init_security
----
====== Vérification ======
Ouvrir :
npx prisma studio
Vérifier la présence :
Tenant
User
Role
Permission
UserRole
RolePermission
----
====== Phase 2-A — Fondation complète (Tenant + RBAC + Propriétés de sécurité) ======
Vous avez raison.
La version précédente contient :
* Tenant
* User
* Role
* Permission
* UserRole
* RolePermission
mais il manque plusieurs éléments indispensables pour un véritable système Enterprise :
* propriétés du compte utilisateur
* préférences utilisateur
* sessions
* refresh tokens
* MFA
* audit de connexion
* catalogue complet des permissions
Sans ces éléments, le Sprint 1 (Authentification) et le Sprint 19 (Sécurité Enterprise) ne pourront pas être implémentés proprement.
----
====== Modèle User complet ======
===== User =====
model User {
id String @id @default(uuid())
tenantId String
email String @unique
passwordHash String
firstName String
lastName String
phone String?
avatarUrl String?
languageCode String?
timezoneCode String?
active Boolean @default(true)
emailVerified Boolean @default(false)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(
fields:[tenantId],
references:[id]
)
roles UserRole[]
sessions UserSession[]
refreshTokens RefreshToken[]
preferences UserPreference?
loginHistory LoginHistory[]
mfaMethods UserMfaMethod[]
}
----
====== Préférences utilisateur ======
===== UserPreference =====
model UserPreference {
id String @id @default(uuid())
userId String @unique
theme String?
language String?
timezone String?
notifications Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(
fields:[userId],
references:[id]
)
}
----
====== Gestion des sessions ======
===== UserSession =====
model UserSession {
id String @id @default(uuid())
userId String
ipAddress String?
userAgent String?
country String?
city String?
lastActivityAt DateTime
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
user User @relation(
fields:[userId],
references:[id]
)
}
----
====== Refresh Tokens ======
===== RefreshToken =====
model RefreshToken {
id String @id @default(uuid())
userId String
tokenHash String
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
user User @relation(
fields:[userId],
references:[id]
)
}
----
====== Historique des connexions ======
===== LoginHistory =====
model LoginHistory {
id String @id @default(uuid())
userId String
success Boolean
ipAddress String?
userAgent String?
country String?
city String?
createdAt DateTime @default(now())
user User @relation(
fields:[userId],
references:[id]
)
}
----
====== MFA ======
===== UserMfaMethod =====
model UserMfaMethod {
id String @id @default(uuid())
userId String
methodType String
secret String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
user User @relation(
fields:[userId],
references:[id]
)
}
----
====== RBAC Enterprise ======
===== Role =====
model Role {
id String @id @default(uuid())
code String @unique
name String
description String?
systemRole Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userRoles UserRole[]
permissions RolePermission[]
}
----
===== Permission =====
model Permission {
id String @id @default(uuid())
code String @unique
name String
description String?
module String
createdAt DateTime @default(now())
rolePermissions RolePermission[]
}
----
====== Catalogue initial des permissions ======
===== Auth =====
AUTH_LOGIN
AUTH_REGISTER
AUTH_RESET_PASSWORD
AUTH_MANAGE_USERS
----
===== Users =====
USER_READ
USER_CREATE
USER_UPDATE
USER_DELETE
----
===== Properties =====
PROPERTY_READ
PROPERTY_CREATE
PROPERTY_UPDATE
PROPERTY_DELETE
PROPERTY_PUBLISH
----
===== Reservations =====
RESERVATION_READ
RESERVATION_CREATE
RESERVATION_UPDATE
RESERVATION_CANCEL
----
===== Contracts =====
CONTRACT_READ
CONTRACT_CREATE
CONTRACT_SIGN
----
===== Payments =====
PAYMENT_READ
PAYMENT_CREATE
PAYMENT_REFUND
----
===== CRM =====
CRM_READ
CRM_WRITE
CRM_EXPORT
----
===== Administration =====
ADMIN_READ
ADMIN_WRITE
ADMIN_AUDIT
ADMIN_TENANT
----
====== Rôles système ======
===== SUPER_ADMIN =====
Accès complet plateforme.
----
===== TENANT_ADMIN =====
Administration d'agence.
----
===== AGENCY_MANAGER =====
Gestion opérationnelle.
----
===== OWNER =====
Extranet propriétaire.
----
===== AGENT =====
Collaborateur.
----
===== CUSTOMER =====
Client final.
----
====== Indexes recommandés ======
===== User =====
@@index([tenantId])
@@index([email])
@@index([active])
----
===== LoginHistory =====
@@index([userId])
@@index([createdAt])
----
===== UserSession =====
@@index([userId])
@@index([expiresAt])
----
====== Architecture du domaine Property ======
===== Entités =====
Property
PropertyType
PropertyStatus
PropertyAddress
PropertyFeature
PropertyMedia
PropertyAvailability
PropertyRate
PropertyOwner
----
====== Property ======
===== Table principale =====
model Property {
id String @id @default(uuid())
tenantId String
propertyTypeId String
code String @unique
reference String?
title String
slug String @unique
description String?
shortDescription String?
maxGuests Int
bedrooms Int
bathrooms Int
area Decimal? @db.Decimal(10,2)
floor Int?
constructionYear Int?
checkInTime String?
checkOutTime String?
active Boolean @default(true)
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(
fields:[tenantId],
references:[id]
)
propertyType PropertyType @relation(
fields:[propertyTypeId],
references:[id]
)
address PropertyAddress?
features PropertyFeature[]
media PropertyMedia[]
availabilities PropertyAvailability[]
rates PropertyRate[]
owners PropertyOwner[]
reservations Reservation[]
}
----
====== PropertyType ======
===== Référentiel =====
model PropertyType {
id String @id @default(uuid())
code String @unique
name String
properties Property[]
}
----
===== Valeurs =====
HOUSE
APARTMENT
VILLA
STUDIO
LOFT
CHALET
COTTAGE
MOBILE_HOME
----
====== PropertyAddress ======
===== Adresse du bien =====
model PropertyAddress {
id String @id @default(uuid())
propertyId String @unique
addressLine1 String
addressLine2 String?
postalCode String
city String
state String?
countryCode String
latitude Decimal? @db.Decimal(10,7)
longitude Decimal? @db.Decimal(10,7)
property Property @relation(
fields:[propertyId],
references:[id]
)
}
----
====== PropertyFeature ======
===== Équipements =====
model PropertyFeature {
id String @id @default(uuid())
propertyId String
featureCode String
featureValue String?
property Property @relation(
fields:[propertyId],
references:[id]
)
}
----
===== Exemples =====
POOL
WIFI
PARKING
AIR_CONDITIONING
TERRACE
SEA_VIEW
PET_ALLOWED
----
====== PropertyMedia ======
===== Médias =====
model PropertyMedia {
id String @id @default(uuid())
propertyId String
fileName String
fileUrl String
mediaType String
position Int
isCover Boolean @default(false)
createdAt DateTime @default(now())
property Property @relation(
fields:[propertyId],
references:[id]
)
}
----
===== MediaType =====
IMAGE
VIDEO
VIRTUAL_TOUR
DOCUMENT
----
====== PropertyAvailability ======
===== Calendrier =====
model PropertyAvailability {
id String @id @default(uuid())
propertyId String
startDate DateTime
endDate DateTime
status AvailabilityStatus
property Property @relation(
fields:[propertyId],
references:[id]
)
}
----
===== Enum =====
enum AvailabilityStatus {
AVAILABLE
RESERVED
BLOCKED
MAINTENANCE
}
----
====== PropertyRate ======
===== Tarification =====
model PropertyRate {
id String @id @default(uuid())
propertyId String
startDate DateTime
endDate DateTime
nightlyRate Decimal @db.Decimal(10,2)
weekendRate Decimal? @db.Decimal(10,2)
cleaningFee Decimal? @db.Decimal(10,2)
securityDeposit Decimal? @db.Decimal(10,2)
currencyCode String
property Property @relation(
fields:[propertyId],
references:[id]
)
}
----
====== PropertyOwner ======
===== Association propriétaire =====
model PropertyOwner {
propertyId String
ownerId String
ownershipRate Decimal @db.Decimal(5,2)
property Property @relation(
fields:[propertyId],
references:[id]
)
owner Owner @relation(
fields:[ownerId],
references:[id]
)
@@id([propertyId, ownerId])
}
----
====== Indexes ======
===== Property =====
@@index([tenantId])
@@index([propertyTypeId])
@@index([published])
@@index([active])
@@index([title])
@@index([slug])
----
===== PropertyAvailability =====
@@index([propertyId])
@@index([startDate])
@@index([endDate])
----
===== PropertyRate =====
@@index([propertyId])
@@index([startDate])
@@index([endDate])
----
====== Domaine Owner ======
===== Architecture =====
Owner
OwnerAddress
OwnerDocument
OwnerBankAccount
PropertyOwner
----
====== Owner ======
model Owner {
id String @id @default(uuid())
tenantId String
code String @unique
companyName String?
firstName String?
lastName String?
email String
phone String?
mobile String?
taxIdentifier String?
vatNumber String?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(
fields:[tenantId],
references:[id]
)
address OwnerAddress?
documents OwnerDocument[]
bankAccounts OwnerBankAccount[]
properties PropertyOwner[]
}
----
====== OwnerAddress ======
model OwnerAddress {
id String @id @default(uuid())
ownerId String @unique
addressLine1 String
addressLine2 String?
postalCode String
city String
state String?
countryCode String
owner Owner @relation(
fields:[ownerId],
references:[id]
)
}
----
====== OwnerDocument ======
model OwnerDocument {
id String @id @default(uuid())
ownerId String
documentType String
fileUrl String
createdAt DateTime @default(now())
owner Owner @relation(
fields:[ownerId],
references:[id]
)
}
----
===== Document Types =====
IDENTITY
TAX_DOCUMENT
MANDATE
BANK_DETAILS
INSURANCE
----
====== OwnerBankAccount ======
model OwnerBankAccount {
id String @id @default(uuid())
ownerId String
iban String
bic String?
accountHolder String
active Boolean @default(true)
owner Owner @relation(
fields:[ownerId],
references:[id]
)
}
----
====== Domaine Reservation ======
===== Architecture =====
Reservation
ReservationGuest
ReservationStatusHistory
ReservationEvent
ReservationPricing
----
====== Reservation ======
===== Table centrale =====
model Reservation {
id String @id @default(uuid())
tenantId String
propertyId String
customerId String?
reference String @unique
status ReservationStatus
checkInDate DateTime
checkOutDate DateTime
nights Int
adults Int
children Int
infants Int
totalGuests Int
notes String?
source ReservationSource
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cancelledAt DateTime?
tenant Tenant @relation(
fields:[tenantId],
references:[id]
)
property Property @relation(
fields:[propertyId],
references:[id]
)
guests ReservationGuest[]
events ReservationEvent[]
statusHistory ReservationStatusHistory[]
pricing ReservationPricing?
}
----
====== ReservationStatus ======
enum ReservationStatus {
DRAFT
PENDING
CONFIRMED
SIGNED
PAID
CHECKED_IN
COMPLETED
CANCELLED
}
----
====== ReservationSource ======
enum ReservationSource {
WEBSITE
BACKOFFICE
AIRBNB
BOOKING
VRBO
API
}
----
====== ReservationGuest ======
model ReservationGuest {
id String @id @default(uuid())
reservationId String
firstName String
lastName String
birthDate DateTime?
email String?
phone String?
isPrimary Boolean @default(false)
reservation Reservation @relation(
fields:[reservationId],
references:[id]
)
}
----
====== ReservationPricing ======
model ReservationPricing {
id String @id @default(uuid())
reservationId String @unique
nightlyAmount Decimal @db.Decimal(10,2)
cleaningFee Decimal @db.Decimal(10,2)
touristTax Decimal @db.Decimal(10,2)
discountAmount Decimal @db.Decimal(10,2)
totalAmount Decimal @db.Decimal(10,2)
currencyCode String
reservation Reservation @relation(
fields:[reservationId],
references:[id]
)
}
----
====== ReservationEvent ======
model ReservationEvent {
id String @id @default(uuid())
reservationId String
eventType String
payload Json?
createdAt DateTime @default(now())
reservation Reservation @relation(
fields:[reservationId],
references:[id]
)
}
----
===== Event Types =====
CREATED
CONFIRMED
SIGNED
PAID
CHECK_IN
CHECK_OUT
CANCELLED
----
====== ReservationStatusHistory ======
model ReservationStatusHistory {
id String @id @default(uuid())
reservationId String
previousStatus ReservationStatus?
newStatus ReservationStatus
changedAt DateTime @default(now())
reservation Reservation @relation(
fields:[reservationId],
references:[id]
)
}
----
====== Relations à ajouter ======
===== Property =====
Ajouter :
reservations Reservation[]
----
===== Tenant =====
Ajouter :
owners Owner[]
reservations Reservation[]
----
====== Indexes ======
===== Owner =====
@@index([tenantId])
@@index([email])
@@index([active])
----
===== Reservation =====
@@index([tenantId])
@@index([propertyId])
@@index([status])
@@index([checkInDate])
@@index([checkOutDate])
@@index([reference])
----
===== ReservationGuest =====
@@index([reservationId])
----
====== Validation ======
npx prisma format
npx prisma validate
npx prisma generate
----
====== Migration ======
npx prisma migrate dev \
--name owners_and_reservations
----
====== État actuel du schéma ======
À ce stade, le noyau fonctionnel est enfin présent :
Tenant
User
Role
Permission
Owner
Property
Reservation
avec :
Catalogue Immobilier
Propriétaires
Réservations
RBAC
Multi-tenant
----
====== Étape suivante ======
===== Phase 2-E =====
Domaines financiers et contractuels :
Contract
ContractTemplate
ContractSignature
Document
Payment
Invoice
Refund
AccountingEntry
Ces tables permettront d'implémenter intégralement :
* Sprint 5 — Contrats & Signature Électronique
* Sprint 6 — Paiements & Facturation
et de disposer du premier flux métier complet :
Property
↓
Reservation
↓
Contract
↓
Payment
↓
Invoice