mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r2.1): socle backend exploitation — machine à états, bilan codé, demandes 1-1
- migration r2_exploitation (10 tables) : WorkOrder (référence séquentielle, horodatages), WorkOrderEvent, Request (1-1, motif de rejet), ReferenceValue, InterventionReport (6 FK), TaskTemplate/ChecklistItem, Meter/MeterReading - contrat : 15 opérations (41 total) ; la table des transitions et les champs requis du bilan vivent dans @siop/shared ; la fiche OT expose allowedTransitions + closureBlockers (messages métier) - API : machine à états stricte ; garde de clôture (bilan 3 champs requis + checklist sans tâche en attente) ; approbation → OT lié 1-1 (409 si déjà traitée) ; rejet à motif obligatoire ; scoping « voir autre » sur listes et accès directs (404 sans fuite) ; validation des valeurs de bilan par champ ; « personne bloquée » triée en tête côté API - seed : 31 valeurs de référentiels, 8 gabarits (parachute réglementaire), OT/demandes/compteurs de la maquette — idempotent - 45 tests verts (95 % stmts / 79 % branches) dont la recette officielle rejouée de bout en bout ; smoke test sur build de prod Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,13 @@ model User {
|
||||
activationToken String? @unique
|
||||
activationExpiresAt DateTime?
|
||||
teams Team[]
|
||||
// R2 — exploitation
|
||||
workOrdersAssigned WorkOrder[] @relation("WorkOrderAssignees")
|
||||
workOrdersCreated WorkOrder[] @relation("WorkOrderCreator")
|
||||
workOrderEvents WorkOrderEvent[]
|
||||
requests Request[]
|
||||
checklistDone ChecklistItem[]
|
||||
meterReadings MeterReading[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -75,6 +82,7 @@ model Category {
|
||||
isActive Boolean @default(true) // désactivable, jamais supprimée si utilisée
|
||||
assets Asset[]
|
||||
components AssetComponent[]
|
||||
taskTemplates TaskTemplate[]
|
||||
|
||||
@@unique([kind, name])
|
||||
}
|
||||
@@ -115,6 +123,9 @@ model Asset {
|
||||
locationId String @db.Uuid
|
||||
location Location @relation(fields: [locationId], references: [id])
|
||||
components AssetComponent[]
|
||||
workOrders WorkOrder[]
|
||||
requests Request[]
|
||||
meters Meter[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -141,3 +152,201 @@ model Team {
|
||||
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[]
|
||||
checklist ChecklistItem[]
|
||||
report InterventionReport?
|
||||
request Request?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@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)
|
||||
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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user