====== 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