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).

View File

@@ -4,6 +4,24 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
---
## 2026-07-16 — Pr. Daaif (+ Claude) — R3.1 : socle backend de la gestion
**Actions**
- **Maquettes R3 et les 4 décisions VALIDÉES par le référent** → lancement du socle.
- **Modèle** (migration `r3_gestion`, 7 tables) : Partner, Part (SANS colonne de quantité), StockMovement (signé, tracé, PU figé), PurchaseOrder/Line, LaborTime (taux figé), Document (préparé pour R3.2) ; `User.hourlyRate` (taux courant, administrable dans Personnes).
- **API** (66 opérations au contrat) : tiers, pièces (stock = Σ mouvements calculé en `groupBy`, alerte sous seuil), entrée/ajustement (motif requis, **stock jamais négatif** — vérifié 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é**) et main-d'œuvre (**taux figé**, refus motivé si taux non défini) ; `WorkOrderDetail.costs` (lignes + total immuables) ; coûts verrouillés après clôture/annulation (409).
- **Seed** : 5 tiers, 5 pièces de la maquette (P-0113 sous seuil via son histoire de mouvements), BC reçu + BC envoyé, taux horaires, et **la carte maquette rejouée : OT-0341 = 505 MAD** (240 + 85 + 180) — vérifiée par test.
- **55 tests verts** (92 % / 74,9 %) dont la recette officielle : consommer sous seuil → alerte → BC → réception → réappro, prix d'hier intact sur l'OT pendant que le prix courant change ; hausse du taux d'Ahmed sans effet sur les OT passés.
**Leçon majeure (durcissement)**
- Les références « max+1 » (OT/DEM/BC/P) étaient une **course sous charge parallèle** (500 sporadiques malgré les retries). Remplacées par des **séquences Postgres** (`nextval`) initialisées au max existant : la classe de bugs disparaît — 3 runs Jest complets consécutifs verts. La numérotation ne se remet pas à zéro chaque année (l'unicité prime), consigné.
**Prochaine étape** : R3.2 — bibliothèque de documents (upload multipart 20 Mo → `FileStorage.putObject`, téléchargement streamé via l'API — MinIO jamais exposé) + analytics (`GET /analytics/summary`), puis R3.3 écrans web.
---
## 2026-07-16 — Pr. Daaif (+ Claude) — R3 ouverte : maquettes de la gestion à valider
**Actions**

File diff suppressed because it is too large Load Diff