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:
@@ -116,11 +116,78 @@ model Team { // équipes par zone — l'assignation d'OT (R2) s
|
||||
| Lien d'activation : 7 jours, usage unique, aucun compte actif avant | service users (+ test e2e) |
|
||||
| Position : lat/lng saisis, colonne PostGIS **générée** pour les requêtes spatiales futures | migration SQL |
|
||||
|
||||
## R2 — Exploitation
|
||||
|
||||
```prisma
|
||||
enum WorkOrderType { CORRECTIVE PREVENTIVE WORKS } // Dépannage / Maintenance / Travaux
|
||||
enum WorkOrderStatus { OPEN IN_PROGRESS ON_HOLD DONE CANCELLED }
|
||||
enum WorkOrderPriority { NONE LOW MEDIUM HIGH PERSON_TRAPPED } // personne bloquée = priorité, pas statut
|
||||
enum RequestStatus { RECEIVED APPROVED REJECTED } // « Résolue » = dérivé (OT lié DONE)
|
||||
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 { // machine à états STRICTE (service, table des transitions)
|
||||
id String @id … reference String @unique // OT-2026-0341 (séquence + retry)
|
||||
title String description String?
|
||||
type WorkOrderType status WorkOrderStatus @default(OPEN)
|
||||
priority WorkOrderPriority @default(NONE)
|
||||
assetId → Asset dueDate DateTime?
|
||||
assignees User[] ("WorkOrderAssignees") createdById → User?
|
||||
startedAt? completedAt? cancelledAt?
|
||||
events WorkOrderEvent[] checklist ChecklistItem[]
|
||||
report InterventionReport? request Request?
|
||||
}
|
||||
|
||||
model WorkOrderEvent { // activité chronologique : commentaires + traces de transition
|
||||
id, workOrderId (cascade), kind String, message String?, byId → User?, createdAt
|
||||
}
|
||||
|
||||
model Request { // demande — converge vers l'OT (lien 1-1, jamais de doublon)
|
||||
id, reference @unique // DEM-2026-0112
|
||||
description, isPersonTrapped Boolean @default(false)
|
||||
status RequestStatus @default(RECEIVED) rejectionReason String? // motif REQUIS au rejet
|
||||
assetId → Asset requestedById → User? requesterName String? // portail public R2.4
|
||||
workOrderId String? @unique → WorkOrder
|
||||
}
|
||||
|
||||
model ReferenceValue { // référentiels ADMINISTRABLES du bilan codé (un par champ)
|
||||
id, field BilanField, label String, isActive Boolean @default(true)
|
||||
@@unique([field, label])
|
||||
}
|
||||
|
||||
model InterventionReport { // bilan codé 6 champs — 1-1 avec l'OT
|
||||
id, workOrderId @unique (cascade), note String?
|
||||
doorStateId? cabinPositionId? anomalyId? externalCauseId? actionTakenId? componentConcernedId?
|
||||
// requis pour clôturer : DOOR_STATE, ACTION_TAKEN, COMPONENT_CONCERNED (maquette)
|
||||
}
|
||||
|
||||
model TaskTemplate { // gabarit du préventif (période calendaire en mois)
|
||||
id, label, componentTypeId? → Category, periodMonths Int, isRegulatory Boolean, isActive
|
||||
}
|
||||
model ChecklistItem { // la grille du mois d'un OT préventif
|
||||
id, workOrderId (cascade), label, state ChecklistState @default(PENDING)
|
||||
templateId? → TaskTemplate doneById? → User doneAt?
|
||||
}
|
||||
|
||||
model Meter { id, assetId → Asset, kind MeterKind, @@unique([assetId, kind]) }
|
||||
model MeterReading { id, meterId (cascade), value Int, readById? → User, createdAt }
|
||||
```
|
||||
|
||||
**Invariants R2** :
|
||||
|
||||
| Invariant | Où il vit |
|
||||
| --- | --- |
|
||||
| Transitions : OPEN→(IN_PROGRESS·CANCELLED) ; IN_PROGRESS→(ON_HOLD·DONE·CANCELLED) ; ON_HOLD→(IN_PROGRESS·CANCELLED) ; DONE/CANCELLED terminaux | service work-orders (+ tests) |
|
||||
| **Garde de clôture** : bilan (3 champs requis) + checklist sans PENDING | service (transition → DONE) |
|
||||
| Demande approuvée = **un seul** OT (1-1), rejet ⇒ motif obligatoire | contrainte @unique + service |
|
||||
| « Voir autre » : sans `canViewOther`, un rôle ne voit que SES objets (assigné/créateur/demandeur) | services (scoping des listes + accès) |
|
||||
| ReferenceValue/TaskTemplate utilisés : désactivables, jamais supprimés | services (même règle que Category) |
|
||||
| Relevé de compteur strictement croissant | service meters |
|
||||
| Génération mensuelle idempotente ; appareil sans historique ⇒ premier contrôle (toutes tâches) | service préventif (R2.2, + tests) |
|
||||
|
||||
## À venir (référence v1 éprouvée, sera réintroduit release par release)
|
||||
|
||||
- **R2** : `WorkOrder` (machine à états stricte, priorité « personne bloquée »), `Request`
|
||||
(lien 1-1 vers OT), `InterventionReport` (bilan codé 6 champs → `ReferenceValue`),
|
||||
`TaskTemplate`/`PreventivePlan`/`ChecklistItem` (périodicité calendrier), `Meter`.
|
||||
- **R3** : `Part`/`StockMovement` (stock **dérivé des mouvements**), `PurchaseOrder`,
|
||||
`Partner`, `LaborTime` (taux figé), `Document`.
|
||||
- **R4** : `WorkOrder.version` (verrou optimiste de la synchro mobile).
|
||||
|
||||
@@ -4,6 +4,25 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2.1 : socle backend de l'exploitation
|
||||
|
||||
**Actions**
|
||||
|
||||
- **Modèle R2** (doc + migration `r2_exploitation`, 10 tables) : WorkOrder (référence séquentielle, horodatages par état), WorkOrderEvent (activité), Request (1-1 vers OT, motif de rejet), ReferenceValue (référentiels du bilan par champ), InterventionReport (6 FK nommées), TaskTemplate/ChecklistItem, Meter/MeterReading.
|
||||
- **Contrat** : 15 opérations (41 total) — la **table des transitions** et les champs requis du bilan vivent dans `@siop/shared` (une seule loi pour l'API et l'UI) ; la fiche OT expose `allowedTransitions` et `closureBlockers` (messages métier).
|
||||
- **API** : machine à états stricte (transition → DONE refusée si bilan incomplet OU checklist avec tâche sans réponse — messages « quoi faire », charte §7) ; approbation de demande = création d'OT lié en 1-1 (double traitement → 409) ; rejet à motif obligatoire ; **scoping « voir autre »** appliqué aux listes ET aux accès directs (404, pas de fuite) ; valeurs de bilan validées champ par champ.
|
||||
- **Seed** : 31 valeurs de référentiels (6 champs), 8 gabarits de préventif (parachute réglementaire), 4 OT et 4 demandes rejouant la maquette, compteurs d'A1.
|
||||
- **45 tests verts** (couverture 95 % / 79 % branches) dont la **recette officielle rejouée** : demande Karim → approbation Salma → OT assigné Ahmed → démarrage → clôture refusée sans bilan → bilan codé → clôture → Karim voit sa demande résolue. Smoke test build prod.
|
||||
|
||||
**Décisions**
|
||||
|
||||
- `POST` de transition/rejet répondent **200** (le contrat prime sur le défaut 201 de Nest — c'est le contrat qui a raison).
|
||||
- La priorité « personne bloquée » trie en tête côté API : aucun client ne peut l'oublier.
|
||||
|
||||
**Prochaine étape** : R2.2 — génération mensuelle du préventif (BullMQ, idempotente, premier contrôle) + API compteurs, puis R2.3 écrans web.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 — Pr. Daaif (+ Claude) — R2 ouverte : maquettes des écrans manquants à valider
|
||||
|
||||
**Actions**
|
||||
@@ -19,6 +38,7 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
- Relevé de compteur strictement croissant (refus sinon).
|
||||
|
||||
**⛔ Bloquant** : validation des maquettes R2 par le référent avant toute ligne de code R2.
|
||||
**→ Levé le 16/07/2026 : maquettes R2 et les 4 décisions VALIDÉES par le référent** (statuts de demande = extension cohérente, actée). Lancement R2.1 (socle backend exploitation).
|
||||
|
||||
---
|
||||
|
||||
|
||||
2057
docs/openapi.json
2057
docs/openapi.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user