// Modèle R0 — identité & permissions (docs/03-architecture/modele-donnees.md).
// La matrice rôles × objets × droits vit EN BASE ; le JWT ne porte jamais de droits.

generator client {
  provider = "prisma-client-js"
  // Cibles explicites : poste de dev (native) + conteneur node:24-slim
  // (OpenSSL 3) en x64 (serveur partenaire) et arm64 (répétition locale).
  binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Role {
  id          String       @id @default(uuid()) @db.Uuid
  name        String       @unique // 7 rôles seedés — voir @siop/shared ROLE_NAMES
  users       User[]
  permissions Permission[]
}

model Permission {
  id             String  @id @default(uuid()) @db.Uuid
  roleId         String  @db.Uuid
  role           Role    @relation(fields: [roleId], references: [id], onDelete: Cascade)
  objectCategory String // enum applicatif — voir @siop/shared OBJECT_CATEGORIES
  canView        Boolean @default(false)
  canViewOther   Boolean @default(false) // « voir autre » : au-delà de ses propres objets
  canCreate      Boolean @default(false)
  canEdit        Boolean @default(false)
  canDelete      Boolean @default(false)

  @@unique([roleId, objectCategory])
}

model User {
  id           String   @id @default(uuid()) @db.Uuid
  email        String   @unique
  displayName  String
  passwordHash String? // null tant que le compte n'est pas activé (R1)
  phone        String?
  roleId       String   @db.Uuid
  role         Role     @relation(fields: [roleId], references: [id])
  isActive     Boolean  @default(true)
  isDemo       Boolean  @default(false) // seul un compte isDemo est empruntable (ADR-002)
  // Invitation (R1) : lien d'activation 7 jours, usage unique.
  // Statut dérivé : invité = passwordHash null && token présent.
  activationToken     String?   @unique
  activationExpiresAt DateTime?
  teams        Team[]
  // R6.6 — sites autorisés en signalement (Demandeur) ; vide = aucune restriction
  assignedSites Location[]
  // R2 — exploitation
  workOrdersAssigned WorkOrder[]      @relation("WorkOrderAssignees")
  workOrdersCreated  WorkOrder[]      @relation("WorkOrderCreator")
  workOrderEvents    WorkOrderEvent[]
  requests           Request[]
  checklistDone      ChecklistItem[]
  meterReadings      MeterReading[]
  // R3 — gestion : taux horaire COURANT (le taux d'une saisie est figé dans LaborTime)
  hourlyRate     Decimal?        @db.Decimal(8, 2)
  laborTimes     LaborTime[]
  stockMovements StockMovement[]
  purchaseOrders PurchaseOrder[]
  documents      Document[]
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@index([roleId])
}

// ————— R1 — Référentiel (docs/03-architecture/modele-donnees.md §R1) —————

enum CategoryKind {
  EQUIPMENT
  COMPONENT_TYPE
}

enum AssetStatus {
  IN_SERVICE
  OUT_OF_SERVICE
  UNDER_MAINTENANCE
}

model Category {
  id         String           @id @default(uuid()) @db.Uuid
  kind       CategoryKind
  name       String
  isActive   Boolean          @default(true) // désactivable, jamais supprimée si utilisée
  assets     Asset[]
  components AssetComponent[]
  taskTemplates TaskTemplate[]

  @@unique([kind, name])
}

model Location {
  id            String     @id @default(uuid()) @db.Uuid
  name          String
  parentId      String?    @db.Uuid // site (null) → zone ; profondeur max 2 (service)
  parent        Location?  @relation("LocationTree", fields: [parentId], references: [id])
  children      Location[] @relation("LocationTree")
  address       String?
  city          String?
  guardianName  String?
  guardianPhone String?
  latitude      Float?
  longitude     Float?
  // + colonne PostGIS générée (voir migration r1_referentiel) :
  // position geography(Point,4326) GENERATED ALWAYS AS (…) STORED
  // Client/syndic gérant le site (R3 — colonne « Rattachements » des Tiers)
  partnerId     String?    @db.Uuid
  partner       Partner?   @relation(fields: [partnerId], references: [id])
  assets        Asset[]
  assignedUsers User[] // reverse de User.assignedSites (R6.6)
  createdAt     DateTime   @default(now())
  updatedAt     DateTime   @updatedAt

  @@index([parentId])
  @@index([partnerId])
}

model Asset {
  id             String           @id @default(uuid()) @db.Uuid
  reference      String           @unique // « A1 » — imprimée sur l'étiquette QR
  brand          String
  model          String?
  serialNumber   String?
  commissionedAt DateTime?
  loadKg         Int?
  floors         Int?
  status         AssetStatus      @default(IN_SERVICE) // statut d'ÉQUIPEMENT ≠ statut d'OT
  underContract  Boolean          @default(true) // contrat préventif (grille mensuelle)
  categoryId     String           @db.Uuid
  category       Category         @relation(fields: [categoryId], references: [id])
  locationId     String           @db.Uuid
  location       Location         @relation(fields: [locationId], references: [id])
  components     AssetComponent[]
  workOrders     WorkOrder[]
  requests       Request[]
  meters         Meter[]
  documents      Document[]
  createdAt      DateTime         @default(now())
  updatedAt      DateTime         @updatedAt

  @@index([locationId])
  @@index([categoryId])
}

// Organe : PAS de colonne emplacement — « un organe n'a pas d'emplacement
// propre » est garanti par construction (décision maquettes R1).
model AssetComponent {
  id          String   @id @default(uuid()) @db.Uuid
  assetId     String   @db.Uuid
  asset       Asset    @relation(fields: [assetId], references: [id], onDelete: Cascade)
  typeId      String   @db.Uuid
  type        Category @relation(fields: [typeId], references: [id])
  designation String?

  @@index([assetId])
}

model Team {
  id          String  @id @default(uuid()) @db.Uuid
  name        String  @unique
  description String?
  members     User[]
}

// ————— R2 — Exploitation (docs/03-architecture/modele-donnees.md §R2) —————

enum WorkOrderType {
  CORRECTIVE // Dépannage
  PREVENTIVE // Maintenance (grille du mois)
  WORKS // Travaux
}

enum WorkOrderStatus {
  OPEN
  IN_PROGRESS
  ON_HOLD
  DONE
  CANCELLED
}

enum WorkOrderPriority {
  NONE
  LOW
  MEDIUM
  HIGH
  PERSON_TRAPPED // personne bloquée — urgence absolue
}

enum RequestStatus {
  RECEIVED
  APPROVED
  REJECTED
}

enum ChecklistState {
  PENDING
  DONE
  NA
}

enum BilanField {
  DOOR_STATE
  CABIN_POSITION
  ANOMALY
  EXTERNAL_CAUSE
  ACTION_TAKEN
  COMPONENT_CONCERNED
}

enum MeterKind {
  RUNNING_HOURS
  STARTS
}

model WorkOrder {
  id          String            @id @default(uuid()) @db.Uuid
  reference   String            @unique // OT-2026-0341
  title       String
  description String?
  type        WorkOrderType
  status      WorkOrderStatus   @default(OPEN) // machine à états stricte (service)
  priority    WorkOrderPriority @default(NONE)
  assetId     String            @db.Uuid
  asset       Asset             @relation(fields: [assetId], references: [id])
  dueDate     DateTime?
  assignees   User[]            @relation("WorkOrderAssignees")
  createdById String?           @db.Uuid
  createdBy   User?             @relation("WorkOrderCreator", fields: [createdById], references: [id])
  startedAt   DateTime?
  completedAt DateTime?
  cancelledAt DateTime?
  events      WorkOrderEvent[]
  ragChunks   RagChunk[]
  checklist   ChecklistItem[]
  report      InterventionReport?
  request     Request?
  stockMovements StockMovement[]
  laborTimes     LaborTime[]
  documents      Document[]
  // Grille du mois : « AAAA-MM » — l'unicité [assetId, periodKey] EST
  // l'idempotence de la génération (les OT correctifs restent à null).
  periodKey   String?
  createdAt   DateTime          @default(now())
  updatedAt   DateTime          @updatedAt

  @@unique([assetId, periodKey])
  @@index([assetId])
  @@index([status])
}

model WorkOrderEvent {
  id          String    @id @default(uuid()) @db.Uuid
  workOrderId String    @db.Uuid
  workOrder   WorkOrder @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  kind        String // COMMENT · STATUS_CHANGED · ASSIGNED · CREATED · FROM_REQUEST
  message     String?
  byId        String?   @db.Uuid
  by          User?     @relation(fields: [byId], references: [id])
  createdAt   DateTime  @default(now())

  @@index([workOrderId])
}

model Request {
  id              String        @id @default(uuid()) @db.Uuid
  reference       String        @unique // DEM-2026-0112
  description     String
  isPersonTrapped Boolean       @default(false)
  status          RequestStatus @default(RECEIVED)
  rejectionReason String? // REQUIS au rejet (service)
  assetId         String        @db.Uuid
  asset           Asset         @relation(fields: [assetId], references: [id])
  requestedById   String?       @db.Uuid
  requestedBy     User?         @relation(fields: [requestedById], references: [id])
  requesterName   String? // portail public via QR (R2.4)
  // Suivi SANS COMPTE (portail) : le téléphone du gardien garde ce jeton,
  // seul moyen de lire l'avancement — jamais listé, jamais devinable.
  publicToken     String?       @unique
  workOrderId     String?       @unique @db.Uuid // lien 1-1 — jamais de doublon
  workOrder       WorkOrder?    @relation(fields: [workOrderId], references: [id])
  createdAt       DateTime      @default(now())
  updatedAt       DateTime      @updatedAt

  @@index([assetId])
}

// Référentiels administrables du bilan codé (un par champ)
model ReferenceValue {
  id       String     @id @default(uuid()) @db.Uuid
  field    BilanField
  label    String
  isActive Boolean    @default(true)

  doorStates          InterventionReport[] @relation("BilanDoorState")
  cabinPositions      InterventionReport[] @relation("BilanCabinPosition")
  anomalies           InterventionReport[] @relation("BilanAnomaly")
  externalCauses      InterventionReport[] @relation("BilanExternalCause")
  actionsTaken        InterventionReport[] @relation("BilanActionTaken")
  componentsConcerned InterventionReport[] @relation("BilanComponentConcerned")

  @@unique([field, label])
}

model InterventionReport {
  id          String    @id @default(uuid()) @db.Uuid
  workOrderId String    @unique @db.Uuid
  workOrder   WorkOrder @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  note        String?

  doorStateId          String?         @db.Uuid
  doorState            ReferenceValue? @relation("BilanDoorState", fields: [doorStateId], references: [id])
  cabinPositionId      String?         @db.Uuid
  cabinPosition        ReferenceValue? @relation("BilanCabinPosition", fields: [cabinPositionId], references: [id])
  anomalyId            String?         @db.Uuid
  anomaly              ReferenceValue? @relation("BilanAnomaly", fields: [anomalyId], references: [id])
  externalCauseId      String?         @db.Uuid
  externalCause        ReferenceValue? @relation("BilanExternalCause", fields: [externalCauseId], references: [id])
  actionTakenId        String?         @db.Uuid
  actionTaken          ReferenceValue? @relation("BilanActionTaken", fields: [actionTakenId], references: [id])
  componentConcernedId String?         @db.Uuid
  componentConcerned   ReferenceValue? @relation("BilanComponentConcerned", fields: [componentConcernedId], references: [id])

  updatedAt DateTime @updatedAt
}

model TaskTemplate {
  id              String          @id @default(uuid()) @db.Uuid
  label           String          @unique
  componentTypeId String?         @db.Uuid
  componentType   Category?       @relation(fields: [componentTypeId], references: [id])
  periodMonths    Int // 1, 3, 6, 12…
  isRegulatory    Boolean         @default(false) // essai parachute
  isActive        Boolean         @default(true)
  checklistItems  ChecklistItem[]
}

model ChecklistItem {
  id          String         @id @default(uuid()) @db.Uuid
  workOrderId String         @db.Uuid
  workOrder   WorkOrder      @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  label       String
  state       ChecklistState @default(PENDING)
  templateId  String?        @db.Uuid
  template    TaskTemplate?  @relation(fields: [templateId], references: [id])
  doneById    String?        @db.Uuid
  doneBy      User?          @relation(fields: [doneById], references: [id])
  doneAt      DateTime?

  @@index([workOrderId])
}

model Meter {
  id       String         @id @default(uuid()) @db.Uuid
  assetId  String         @db.Uuid
  asset    Asset          @relation(fields: [assetId], references: [id], onDelete: Cascade)
  kind     MeterKind
  readings MeterReading[]

  @@unique([assetId, kind])
}

model MeterReading {
  id        String   @id @default(uuid()) @db.Uuid
  meterId   String   @db.Uuid
  meter     Meter    @relation(fields: [meterId], references: [id], onDelete: Cascade)
  value     Int // strictement croissant (service)
  readById  String?  @db.Uuid
  readBy    User?    @relation(fields: [readById], references: [id])
  createdAt DateTime @default(now())

  @@index([meterId])
}

// ————— R3 — Gestion (docs/03-architecture/modele-donnees.md §R3) —————

enum PartnerKind {
  SUPPLIER
  CLIENT // syndic / propriétaire
}

enum PurchaseOrderStatus {
  DRAFT
  SENT
  RECEIVED
  CANCELLED
}

enum StockMovementKind {
  RECEIPT // réception de BC (+, PU figé)
  ENTRY // entrée manuelle (+)
  CONSUMPTION // consommation d'OT (−, PU figé)
  ADJUSTMENT // inventaire (±, motif REQUIS)
}

enum DocumentKind {
  NOTICE
  CERTIFICATE
  PHOTO
  OTHER
}

model Partner {
  id             String          @id @default(uuid()) @db.Uuid
  name           String          @unique
  kind           PartnerKind
  contactName    String?
  phone          String?
  email          String?
  city           String?
  isActive       Boolean         @default(true)
  parts          Part[]
  purchaseOrders PurchaseOrder[]
  sites          Location[] // sites gérés (kind CLIENT)
  createdAt      DateTime        @default(now())
  updatedAt      DateTime        @updatedAt
}

model Part {
  id            String          @id @default(uuid()) @db.Uuid
  reference     String          @unique // P-0113
  designation   String
  threshold     Int             @default(0) // seuil d'alerte
  // Dernier prix d'achat — figé sur chaque mouvement au moment T.
  lastUnitPrice Decimal?        @db.Decimal(10, 2)
  compatible    String?
  supplierId    String?         @db.Uuid
  supplier      Partner?        @relation(fields: [supplierId], references: [id])
  isActive      Boolean         @default(true)
  movements     StockMovement[]
  orderLines    PurchaseOrderLine[]
  createdAt     DateTime        @default(now())
  updatedAt     DateTime        @updatedAt
}

// Le stock EST la somme des mouvements — aucune colonne de quantité.
model StockMovement {
  id              String            @id @default(uuid()) @db.Uuid
  partId          String            @db.Uuid
  part            Part              @relation(fields: [partId], references: [id], onDelete: Cascade)
  kind            StockMovementKind
  quantity        Int // signé : + entrée, − sortie
  unitPrice       Decimal?          @db.Decimal(10, 2) // figé (réception, consommation)
  reason          String? // ajustement : motif requis (service)
  workOrderId     String?           @db.Uuid
  workOrder       WorkOrder?        @relation(fields: [workOrderId], references: [id])
  purchaseOrderId String?           @db.Uuid
  purchaseOrder   PurchaseOrder?    @relation(fields: [purchaseOrderId], references: [id])
  byId            String?           @db.Uuid
  by              User?             @relation(fields: [byId], references: [id])
  createdAt       DateTime          @default(now())

  @@index([partId])
  @@index([workOrderId])
}

model PurchaseOrder {
  id          String              @id @default(uuid()) @db.Uuid
  reference   String              @unique // BC-2026-0024
  status      PurchaseOrderStatus @default(DRAFT)
  supplierId  String              @db.Uuid
  supplier    Partner             @relation(fields: [supplierId], references: [id])
  lines       PurchaseOrderLine[]
  movements   StockMovement[]
  sentAt      DateTime?
  receivedAt  DateTime?
  cancelledAt DateTime?
  createdById String?             @db.Uuid
  createdBy   User?               @relation(fields: [createdById], references: [id])
  createdAt   DateTime            @default(now())
  updatedAt   DateTime            @updatedAt
}

model PurchaseOrderLine {
  id              String        @id @default(uuid()) @db.Uuid
  purchaseOrderId String        @db.Uuid
  purchaseOrder   PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade)
  partId          String        @db.Uuid
  part            Part          @relation(fields: [partId], references: [id])
  quantity        Int
  unitPrice       Decimal       @db.Decimal(10, 2)

  @@index([purchaseOrderId])
}

// Main-d'œuvre : le taux est FIGÉ à la saisie (le coût d'un OT ne bouge plus).
model LaborTime {
  id          String    @id @default(uuid()) @db.Uuid
  workOrderId String    @db.Uuid
  workOrder   WorkOrder @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  userId      String    @db.Uuid
  user        User      @relation(fields: [userId], references: [id])
  minutes     Int
  hourlyRate  Decimal   @db.Decimal(8, 2)
  note        String?
  createdAt   DateTime  @default(now())

  @@index([workOrderId])
}

model Document {
  id           String       @id @default(uuid()) @db.Uuid
  kind         DocumentKind
  fileName     String
  storageKey   String       @unique // clé MinIO (FileStorage)
  size         Int
  contentType  String
  assetId      String?      @db.Uuid
  asset        Asset?       @relation(fields: [assetId], references: [id])
  workOrderId  String?      @db.Uuid
  workOrder    WorkOrder?   @relation(fields: [workOrderId], references: [id])
  uploadedById String?      @db.Uuid
  uploadedBy   User?        @relation(fields: [uploadedById], references: [id])
  createdAt    DateTime     @default(now())
  // R5 (D3) : le corpus est visible et réversible, document par document.
  inCorpus     Boolean      @default(true)
  indexedAt    DateTime? // null = jamais indexé
  chunkCount   Int          @default(0)
  chunks       RagChunk[]

  @@index([assetId])
  @@index([workOrderId])
}

/// R5 — extraits indexés du corpus (ADR-004) : bibliothèque + bilans codés,
/// ANONYMISÉS À L'INGESTION (D4). Écrit par apps/ai, schéma possédé par Prisma.
model RagChunk {
  id          String                       @id @default(uuid()) @db.Uuid
  sourceType  RagSourceType
  documentId  String?                      @db.Uuid
  document    Document?                    @relation(fields: [documentId], references: [id], onDelete: Cascade)
  workOrderId String?                      @db.Uuid
  workOrder   WorkOrder?                   @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  /// Repère humain de la source : « p. 42 », « bilan du 17/07/2026 »…
  locator     String
  content     String
  embedding   Unsupported("vector(768)")
  createdAt   DateTime                     @default(now())

  @@index([documentId])
  @@index([workOrderId])
}

enum RagSourceType {
  DOCUMENT
  WORK_ORDER
}
