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:
pr-daaif
2026-07-16 13:56:07 +01:00
parent 54d926e9f4
commit f7702e4252
23 changed files with 5568 additions and 4 deletions

View File

@@ -0,0 +1,93 @@
/** Vocabulaires de l'exploitation (R2) — partagés API / web / seed. */
export const WORK_ORDER_TYPES = ['CORRECTIVE', 'PREVENTIVE', 'WORKS'] as const;
export type WorkOrderType = (typeof WORK_ORDER_TYPES)[number];
export const WORK_ORDER_TYPE_LABELS: Record<WorkOrderType, string> = {
CORRECTIVE: 'Dépannage',
PREVENTIVE: 'Maintenance',
WORKS: 'Travaux',
};
export const WORK_ORDER_STATUSES = [
'OPEN',
'IN_PROGRESS',
'ON_HOLD',
'DONE',
'CANCELLED',
] as const;
export type WorkOrderStatus = (typeof WORK_ORDER_STATUSES)[number];
export const WORK_ORDER_STATUS_LABELS: Record<WorkOrderStatus, string> = {
OPEN: 'Ouvert',
IN_PROGRESS: 'En cours',
ON_HOLD: 'En attente',
DONE: 'Terminé',
CANCELLED: 'Annulé',
};
/** Machine à états STRICTE — la table est la loi, côté API comme côté UI. */
export const WORK_ORDER_TRANSITIONS: Record<WorkOrderStatus, WorkOrderStatus[]> = {
OPEN: ['IN_PROGRESS', 'CANCELLED'],
IN_PROGRESS: ['ON_HOLD', 'DONE', 'CANCELLED'],
ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],
DONE: [],
CANCELLED: [],
};
export const WORK_ORDER_PRIORITIES = [
'NONE',
'LOW',
'MEDIUM',
'HIGH',
'PERSON_TRAPPED',
] as const;
export type WorkOrderPriority = (typeof WORK_ORDER_PRIORITIES)[number];
export const WORK_ORDER_PRIORITY_LABELS: Record<WorkOrderPriority, string> = {
NONE: 'Aucune',
LOW: 'Basse',
MEDIUM: 'Moyenne',
HIGH: 'Haute',
PERSON_TRAPPED: 'Personne bloquée',
};
export const REQUEST_STATUSES = ['RECEIVED', 'APPROVED', 'REJECTED'] as const;
export type RequestStatus = (typeof REQUEST_STATUSES)[number];
export const REQUEST_STATUS_LABELS: Record<RequestStatus, string> = {
RECEIVED: 'Reçue',
APPROVED: 'Approuvée',
REJECTED: 'Rejetée',
};
export const CHECKLIST_STATES = ['PENDING', 'DONE', 'NA'] as const;
export type ChecklistState = (typeof CHECKLIST_STATES)[number];
export const BILAN_FIELDS = [
'DOOR_STATE',
'CABIN_POSITION',
'ANOMALY',
'EXTERNAL_CAUSE',
'ACTION_TAKEN',
'COMPONENT_CONCERNED',
] as const;
export type BilanField = (typeof BILAN_FIELDS)[number];
export const BILAN_FIELD_LABELS: Record<BilanField, string> = {
DOOR_STATE: 'État des portes',
CABIN_POSITION: 'Position cabine',
ANOMALY: 'Anomalie constatée',
EXTERNAL_CAUSE: 'Cause extérieure',
ACTION_TAKEN: 'Action réalisée',
COMPONENT_CONCERNED: 'Élément concerné',
};
/** Champs du bilan REQUIS pour clôturer (maquette fiche OT validée R0). */
export const REQUIRED_BILAN_FIELDS: readonly BilanField[] = [
'DOOR_STATE',
'ACTION_TAKEN',
'COMPONENT_CONCERNED',
];
export const METER_KINDS = ['RUNNING_HOURS', 'STARTS'] as const;
export type MeterKind = (typeof METER_KINDS)[number];
export const METER_KIND_LABELS: Record<MeterKind, string> = {
RUNNING_HOURS: 'Heures de marche',
STARTS: 'Démarrages',
};