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:
@@ -39,12 +39,105 @@ model User {
|
||||
email String @unique
|
||||
displayName String
|
||||
passwordHash String? // null tant que le compte n'est pas activé (R1)
|
||||
phone String?
|
||||
roleId String @db.Uuid
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
isActive Boolean @default(true)
|
||||
isDemo Boolean @default(false) // seul un compte isDemo est empruntable (ADR-002)
|
||||
// Invitation (R1) : lien d'activation 7 jours, usage unique.
|
||||
// Statut dérivé : invité = passwordHash null && token présent.
|
||||
activationToken String? @unique
|
||||
activationExpiresAt DateTime?
|
||||
teams Team[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([roleId])
|
||||
}
|
||||
|
||||
// ————— R1 — Référentiel (docs/03-architecture/modele-donnees.md §R1) —————
|
||||
|
||||
enum CategoryKind {
|
||||
EQUIPMENT
|
||||
COMPONENT_TYPE
|
||||
}
|
||||
|
||||
enum AssetStatus {
|
||||
IN_SERVICE
|
||||
OUT_OF_SERVICE
|
||||
UNDER_MAINTENANCE
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
kind CategoryKind
|
||||
name String
|
||||
isActive Boolean @default(true) // désactivable, jamais supprimée si utilisée
|
||||
assets Asset[]
|
||||
components AssetComponent[]
|
||||
|
||||
@@unique([kind, name])
|
||||
}
|
||||
|
||||
model Location {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String
|
||||
parentId String? @db.Uuid // site (null) → zone ; profondeur max 2 (service)
|
||||
parent Location? @relation("LocationTree", fields: [parentId], references: [id])
|
||||
children Location[] @relation("LocationTree")
|
||||
address String?
|
||||
city String?
|
||||
guardianName String?
|
||||
guardianPhone String?
|
||||
latitude Float?
|
||||
longitude Float?
|
||||
// + colonne PostGIS générée (voir migration r1_referentiel) :
|
||||
// position geography(Point,4326) GENERATED ALWAYS AS (…) STORED
|
||||
assets Asset[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([parentId])
|
||||
}
|
||||
|
||||
model Asset {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
reference String @unique // « A1 » — imprimée sur l'étiquette 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 Category @relation(fields: [categoryId], references: [id])
|
||||
locationId String @db.Uuid
|
||||
location Location @relation(fields: [locationId], references: [id])
|
||||
components AssetComponent[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([locationId])
|
||||
@@index([categoryId])
|
||||
}
|
||||
|
||||
// Organe : PAS de colonne emplacement — « un organe n'a pas d'emplacement
|
||||
// propre » est garanti par construction (décision maquettes R1).
|
||||
model AssetComponent {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
assetId String @db.Uuid
|
||||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||
typeId String @db.Uuid
|
||||
type Category @relation(fields: [typeId], references: [id])
|
||||
designation String?
|
||||
|
||||
@@index([assetId])
|
||||
}
|
||||
|
||||
model Team {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String @unique
|
||||
description String?
|
||||
members User[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user