mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r1.1): socle backend du référentiel — modèle, contrat, API, seed, tests
- migration r1_referentiel : Category (EQUIPMENT/COMPONENT_TYPE), Location (site → zone, lat/lng + colonne PostGIS générée geography(Point,4326) + index GIST), Asset (statut d'équipement), AssetComponent (organe sans emplacement PAR CONSTRUCTION), Team, invitation sur User ; migration autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis) - contrat : 21 nouvelles opérations (26 total), générateur OpenAPI étendu aux paramètres de chemin ; spec + client web régénérés dans ce commit - API : modules categories/locations/assets/teams + gestion des personnes (liste, rôles, invitation lien 7 j à usage unique, activation publique qui connecte directement, mise à jour rôle/équipes) — tout sous @RequirePermission ; invariants en service (profondeur 2, kinds, catégorie jamais supprimée) - seed : parc de la maquette validée (5 sites + 8 zones, 8 appareils, organes A1/B2, 9 catégories, 2 équipes) — idempotent - 36 tests verts (couverture 96 % stmts / 85 % branches) : recette site→zone→appareil→organes, matrice vivante, invitation→activation ; smoke test sur build de prod - CI : postgres → postgis/postgis:18-3.6 (la migration R1 l'exige) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,10 +50,74 @@ model User {
|
||||
| `demo-login` refuse tout compte `isDemo=false` | service auth (+ test e2e) |
|
||||
| Matrice complète : chaque rôle a une ligne par catégorie d'objet | seed idempotent (+ test) |
|
||||
|
||||
## R1 — Référentiel
|
||||
|
||||
```prisma
|
||||
enum CategoryKind { EQUIPMENT COMPONENT_TYPE } // référentiels administrables
|
||||
enum AssetStatus { IN_SERVICE OUT_OF_SERVICE UNDER_MAINTENANCE }
|
||||
|
||||
model Category { // renommable/désactivable, JAMAIS supprimée si utilisée
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
kind CategoryKind
|
||||
name String
|
||||
isActive Boolean @default(true)
|
||||
@@unique([kind, name])
|
||||
}
|
||||
|
||||
model Location { // site (parentId null) → zone (1 niveau max, vérifié service)
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String
|
||||
parentId String? @db.Uuid // auto-relation « LocationTree »
|
||||
address String? city String?
|
||||
guardianName String? guardianPhone String?
|
||||
latitude Float? longitude Float? // saisies par la carte
|
||||
// + colonne PostGIS générée (migration SQL) :
|
||||
// position geography(Point,4326) GENERATED ALWAYS AS (ST_Point(longitude,latitude)::geography) STORED
|
||||
}
|
||||
|
||||
model Asset { // l'appareil (ascenseur, monte-charge…)
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
reference String @unique // « A1 », « B2 » — imprimée sur le QR
|
||||
brand String model String? serialNumber String?
|
||||
commissionedAt DateTime? loadKg Int? floors Int?
|
||||
status AssetStatus @default(IN_SERVICE) // statut d'ÉQUIPEMENT ≠ statut d'OT
|
||||
categoryId String @db.Uuid // Category(kind=EQUIPMENT)
|
||||
locationId String @db.Uuid // rattachement obligatoire
|
||||
components AssetComponent[]
|
||||
}
|
||||
|
||||
model AssetComponent { // organe — PAS de colonne emplacement : la règle
|
||||
id String @id @default(uuid()) @db.Uuid // « un organe n'a pas d'emplacement
|
||||
assetId String @db.Uuid // propre » est garantie PAR
|
||||
typeId String @db.Uuid // CONSTRUCTION (table dédiée),
|
||||
designation String? // plus besoin du CHECK v1
|
||||
}
|
||||
|
||||
model Team { // équipes par zone — l'assignation d'OT (R2) s'appuiera dessus
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String @unique
|
||||
description String?
|
||||
members User[] // m2m implicite
|
||||
}
|
||||
|
||||
// User (R0) reçoit : phone?, teams Team[], et l'invitation (ADR maquettes R1) :
|
||||
// activationToken String? @unique + activationExpiresAt DateTime?
|
||||
// statut dérivé : invité = passwordHash null && token présent ; actif = hash présent
|
||||
```
|
||||
|
||||
**Invariants R1** :
|
||||
|
||||
| Invariant | Où il vit |
|
||||
| --- | --- |
|
||||
| Hiérarchie d'emplacements limitée à 2 niveaux (site → zone) | service locations (+ test) |
|
||||
| Un organe n'a pas d'emplacement propre | par construction (AssetComponent sans locationId) |
|
||||
| `Category(kind)` cohérente avec l'usage (EQUIPMENT sur Asset, COMPONENT_TYPE sur organe) | services (+ test) |
|
||||
| Catégorie utilisée : jamais supprimée (désactivation seulement) | service categories (+ test) |
|
||||
| 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 |
|
||||
|
||||
## À venir (référence v1 éprouvée, sera réintroduit release par release)
|
||||
|
||||
- **R1** : `Location` (hiérarchie + colonne PostGIS), `Asset` + organes (hiérarchie 2 niveaux,
|
||||
CHECK « un organe n'a pas d'emplacement propre »), `Category`, `Team`.
|
||||
- **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`.
|
||||
|
||||
@@ -4,6 +4,26 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 — Pr. Daaif (+ Claude) — R1.1 : socle backend du référentiel
|
||||
|
||||
**Actions**
|
||||
|
||||
- **Modèle R1** (doc + Prisma + migration `r1_referentiel`) : Category (kind EQUIPMENT/COMPONENT_TYPE), Location (site → zone, lat/lng + **colonne PostGIS générée** `geography(Point,4326)` + index GIST), Asset (statut d'équipement), AssetComponent (organe **sans emplacement par construction**), Team (m2m User), invitation sur User (token unique + expiration). Migration **autosuffisante** (`CREATE EXTENSION IF NOT EXISTS postgis`).
|
||||
- **Contrat** : 21 nouvelles opérations (26 au total) — categories/locations/assets+organes/teams/users/roles/invitations/activate ; générateur OpenAPI étendu aux paramètres de chemin ; spec + client web régénérés dans le même commit.
|
||||
- **API** : 4 nouveaux modules + gestion des personnes, tous sous `@RequirePermission` (la matrice décide) ; invariants en service : profondeur 2, kinds de catégories, catégorie jamais supprimée, lien d'activation 7 jours à usage unique qui connecte directement.
|
||||
- **Seed** : parc de la maquette (5 sites + 8 zones, 8 appareils dont B2 à l'arrêt et M1 en maintenance, organes d'A1/B2, 9 catégories, 2 équipes).
|
||||
- **36 tests verts** (couverture 96 % stmts / 85 % branches) : parcours de recette site→zone→appareil→organes, profondeur 3 refusée, matrice vivante (Technicien lit mais ne crée pas), invitation→activation complète (lien périmé/consommé/renvoyé). Smoke test sur build de prod : sites avec compteurs, fiche A1 et ses 4 organes.
|
||||
- **CI** : bascule sur `postgis/postgis:18-3.6` (la migration R1 l'exige) — le moment anticipé dans le commentaire du workflow.
|
||||
|
||||
**Leçons**
|
||||
|
||||
- Migration modifiée après application locale ⇒ réaligner son checksum dans `_prisma_migrations` (ou reset) — d'où la règle : rendre la migration autosuffisante AVANT de l'appliquer.
|
||||
- Un serveur `reuseExistingServer` de Playwright peut squatter :3000 et faire tester un dist périmé — tuer le port avant tout smoke test.
|
||||
|
||||
**Prochaine étape** : R1.2 `apps/web` — écrans Sites (+ carte Leaflet/OSM), Fiche site, Ascenseurs, Nouvel ascenseur, Étiquette QR, Personnes & équipes, Catégories, fidèles à maquette-r1.html.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 — Pr. Daaif (+ Claude) — R0 CLOSE (tag) · R1 ouverte : maquettes à valider
|
||||
|
||||
**Actions**
|
||||
@@ -20,6 +40,7 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
- Catégorie utilisée : renommage/désactivation seulement, jamais de suppression.
|
||||
|
||||
**⛔ Bloquant** : validation des maquettes R1 par le référent avant toute ligne de code applicatif R1.
|
||||
**→ Levé le 16/07/2026 : maquettes R1 et les 4 décisions de conception VALIDÉES par le référent.** Lancement R1.1 (socle backend).
|
||||
|
||||
---
|
||||
|
||||
|
||||
2265
docs/openapi.json
2265
docs/openapi.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user