feat(r3.1): socle backend gestion — stock dérivé, BC, coûts figés sur OT

- migration r3_gestion (7 tables) : Partner, Part (SANS colonne de
  quantité), StockMovement (signé, tracé, PU figé), PurchaseOrder/Line,
  LaborTime (taux figé), Document (R3.2) ; User.hourlyRate administrable
- API (66 opérations) : tiers ; pièces (stock = Σ mouvements, alerte sous
  seuil) ; entrée/ajustement (motif requis, stock jamais négatif, en
  transaction) ; BC Brouillon→Envoyé→Reçu (la réception crée les RECEIPT
  et met à jour lastUnitPrice) ; consommation sur OT (stock suffisant,
  PRIX FIGÉ) ; main-d'œuvre (TAUX FIGÉ, refus si taux non défini) ;
  WorkOrderDetail.costs ; coûts verrouillés après clôture (409)
- seed : tiers/pièces/BC/taux de la maquette — OT-0341 = 505 MAD (testé)
- durcissement : références OT/DEM/BC/P par SÉQUENCES Postgres (nextval)
  — fin des courses « max+1 » (500 sporadiques sous charge parallèle) ;
  3 runs Jest complets consécutifs verts
- 55 tests (92 % stmts / 74,9 % branches) dont la recette officielle :
  consommer sous seuil → BC → réception → réappro, prix/taux figés prouvés

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-16 20:42:03 +01:00
parent d5041c6f05
commit dfdf8f7c18
33 changed files with 5361 additions and 717 deletions

View File

@@ -186,10 +186,63 @@ model MeterReading { id, meterId (cascade), value Int, readById? → User, creat
| 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) |
## R3 — Gestion
```prisma
enum PartnerKind { SUPPLIER CLIENT } // fournisseur / syndic
enum PurchaseOrderStatus { DRAFT SENT RECEIVED CANCELLED }
enum StockMovementKind { RECEIPT ENTRY CONSUMPTION ADJUSTMENT }
enum DocumentKind { NOTICE CERTIFICATE PHOTO OTHER }
model Partner { id, name @unique, kind, contact…, isActive }
model Part { // AUCUNE colonne de quantité : le stock EST la
id, reference @unique // somme des mouvements (décision v1 éprouvée)
designation, threshold Int // seuil d'alerte
lastUnitPrice Decimal? // dernier prix d'achat (mis à jour à la réception)
supplierId? → Partner
}
model StockMovement { // + entrée / sortie, TOUJOURS tracé
id, partId (cascade), kind, quantity Int (signé)
unitPrice Decimal? // FIGÉ (réception : PU du BC ; consommation : lastUnitPrice)
reason String? // ADJUSTMENT : motif REQUIS (service)
workOrderId? → WorkOrder // consommation
purchaseOrderId? → PurchaseOrder // réception
byId? → User
}
model PurchaseOrder { // DRAFT → SENT → RECEIVED ; annulable avant réception
id, reference @unique, status, supplierId → Partner
lines PurchaseOrderLine[] // partId, quantity, unitPrice Decimal
// la RÉCEPTION crée un mouvement RECEIPT par ligne et met à jour lastUnitPrice
}
model LaborTime { // heures × taux FIGÉ à la saisie
id, workOrderId (cascade), userId → User, minutes Int, hourlyRate Decimal, note?
}
// User reçoit hourlyRate Decimal? (taux COURANT, administrable)
model Document { // MinIO derrière FileStorage — jamais d'accès direct
id, kind, fileName, storageKey @unique, size, contentType
assetId? / workOrderId? // rattachement REQUIS à l'un des deux (service)
}
```
**Invariants R3** :
| Invariant | Où il vit |
| --- | --- |
| Stock = Σ mouvements ; jamais de saisie directe de quantité | par construction (pas de colonne) |
| Un ajustement exige un motif ; le stock ne devient jamais négatif | service parts (+ tests) |
| Consommation : PU figé = `lastUnitPrice` au moment T ; stock suffisant exigé | service (transaction) |
| Réception : BC `SENT` uniquement ; crée les RECEIPT + met à jour `lastUnitPrice` | service (transaction) |
| Main-d'œuvre : taux figé = `User.hourlyRate` au moment T (refus si non défini) | service |
| Coût total d'un OT = Σ consommations + Σ main-d'œuvre — IMMUABLE après coup | dérivé des lignes figées |
| Document : rattaché à un appareil OU un OT ; types fermés ; 20 Mo max | service documents (R3.2) |
## À venir (référence v1 éprouvée, sera réintroduit release par release)
- **R3** : `Part`/`StockMovement` (stock **dérivé des mouvements**), `PurchaseOrder`,
`Partner`, `LaborTime` (taux figé), `Document`.
- **R4** : `WorkOrder.version` (verrou optimiste de la synchro mobile).
- **R5** : tables d'embeddings pgvector (côté service IA).