pr-daaif
902efa6192
feat(r2.2): préventif — génération mensuelle idempotente — et compteurs
...
- migration : Asset.underContract + WorkOrder.periodKey avec unicité
[assetId, periodKey] — l'idempotence de la génération est EN BASE
(deux appels concurrents ne doublent jamais une grille)
- génération : une grille par appareil sous contrat, périodicités ancrées
sur la mise en service (mensuelles toujours dues), premier contrôle
(toutes les tâches) pour un appareil sans historique, échéance fin de
mois, événement GENERATED ; déclenchement manuel (cron au durcissement)
- gabarits administrables (désactivé = exclu des générations suivantes) ;
GET /preventive/status (générées, terminées, en retard, premiers contrôles)
- compteurs : GET meters (2 compteurs, récents d'abord) ; relevé
strictement croissant, refus motivé sinon
- contrat +7 opérations (48) ; 50 tests verts (94,7 % / 78 %) ; smoke test
prod : juillet généré (9 grilles, 8 premiers contrôles), 2e appel à zéro
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 15:15:57 +01:00
pr-daaif
f7702e4252
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 >
2026-07-16 13:56:07 +01:00
pr-daaif
9778629827
feat(r1.2): écrans web du référentiel — 7 écrans fidèles à maquette-r1
...
- Sites : liste + carte Leaflet/OSM réelle (pins maquette, positions
PostGIS), création en modale avec position posée au clic sur la carte
- Fiche site : arbre site→zones, appareils rattachés, ajout de zone
- Ascenseurs : table dense, filtres site/statut, strie rouge à l'arrêt ;
fiche appareil (organes ± ajout/retrait, statut, QR réel) ; création
identité → rattachement → organes ; étiquette A6 imprimable (objet
papier, window.print n'imprime qu'elle)
- Personnes & équipes : invitation → lien d'activation affiché à copier
(pas d'email en R1, décision explicite), renvoi de lien, équipes ;
page /activation (choix du mot de passe, connexion directe)
- Catégories : ajout, renommage inline, désactivation
- navigation et actions pilotées par la matrice (usePermissions) —
l'API re-vérifie chaque requête
- e2e Playwright : recette R1 officielle rejouée intégralement (site →
zone → appareil+organe → étiquette → invitation → activation) +
parcours « la matrice pilote l'UI » ; purge idempotente en globalSetup
- alias Vite @siop/shared → source TS (exports nommés CJS ↯ workspace lié)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 13:06:45 +01:00
pr-daaif
266ffaaf1b
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 >
2026-07-16 12:27:48 +01:00
pr-daaif
ce24d9c32b
build(r0.13): Dockerfiles api/web + compose Dokploy + runbook — R0 prêt à déployer
...
- image api : multi-stage (pnpm deploy --legacy --prod), client Prisma
régénéré dans l'arborescence déployée, binaryTargets explicites
(debian/arm64 openssl-3), entrypoint migrate deploy → seed optionnel
(SEED_ON_START, compilé dist/seed) → API ; non-root, healthcheck /health
- image web : nginx alpine, statique Vite, proxy /api résolu À LA REQUÊTE
(resolver Docker + variable — nginx démarre sans l'API), fallback SPA,
cache immuable /assets, healthcheck IPv4 (127.0.0.1)
- infra/docker-compose.dokploy.yml : 5 services préfixés siop2-, secrets
exigés, seul siop2-web rejoint dokploy-network (API jamais exposée)
- runbook docs/06-production/runbook-dokploy.md : topologie, profils
d'environnement, checklist première prod, rollback, répétition locale
- répétition locale validée de bout en bout : migrate+seed au boot,
parcours demo-login → /users/me à travers nginx conteneurisé, conteneurs
healthy ; le double verrou ADR-002 (DEMO_MODE sans I_KNOW en production)
a refusé de démarrer — observé en situation réelle
- prisma passe en dépendance de production (migrations au boot)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-15 22:58:42 +01:00
pr-daaif
36de93bb7e
test(r0.12): ESLint monorepo + parcours démo Playwright — R0.12 clos
...
- ESLint 10 flat config racine (typescript-eslint, react-hooks) ; règle
ADR-001 codée : no-restricted-imports interdit « minio » partout sauf
minio-storage.service.ts (violation vérifiée) ; jobs lint par paquet
- Playwright apps/web/e2e : redirection sans jeton, connexion démo 1 clic,
coquille (rail actif, chip DÉMO), bascule de rôle chronométrée < 3 s
(ADR-002), /design, déconnexion avec purge ; webServer api dist + vite,
seed idempotent en globalSetup, PW_CHANNEL=chrome en local
- CI : jobs lint + e2e (services PostgreSQL/Redis, artefact
playwright-report sur échec) ; vitest restreint à src/
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-15 22:36:17 +01:00
pr-daaif
4f3af5af4f
feat(r0.11): apps/web — connexion + sélecteur démo, coquille, /design, client typé
...
- React 19 + Vite + Tailwind v4 : tokens.css copié tel quel (source de
vérité), classes composants extraites de la maquette validée, Manrope
auto-hébergée, primitives shadcn-style possédées (Button cva, menu Radix)
- connexion fidèle à la maquette ; liste démo affichée seulement si
/auth/demo-accounts répond (404 ⇒ rien — ADR-002, l'API seule source)
- coquille : sidebar 4 groupes avec rail safran actif (écrans futurs marqués
R1-R3), topbar recherche/thème/chip DÉMO + sélecteur de rôle (bascule 101 ms,
critère < 3 s) ; page /design = référence vivante des revues pixel
- règle d'or : schema.d.ts généré depuis docs/openapi.json et committé
(openapi-typescript + openapi-fetch)
- tokens.css : tokens --nav-* ajoutés (valeurs de la maquette validée) ;
seed réaligné sur les personas de la maquette
- vérifié en navigateur réel (Playwright + Chrome headless) : 7 captures,
parcours démo complet, bi-thème, zéro erreur console ; typecheck + vitest + build OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-15 22:09:40 +01:00