Commit Graph
4 Commits
Author SHA1 Message Date
Claude b184109e23 Réparer l'intégration continue, et poser des ports internes non communs
**Le test des heures ne pouvait que tomber.** Il interrogeait le mois
précédent, alors que le seed ne pose que deux semaines : la courante et la
précédente. Hors des premiers jours d'un mois, le mois précédent est donc
vide. Mesuré sur une base semée à neuf : un seul mois porte des créneaux,
le mois courant. Le défaut ne se voyait pas en développement, où la base
garde les créneaux des exécutions antérieures — 39 créneaux de juillet
survivaient chez moi à des semis d'il y a plusieurs semaines.

**Le seed ne remettait pas l'état de publication.** Son `update` était vide,
si bien qu'une semaine déjà semée gardait le statut qu'elle avait alors : la
semaine précédente, publiée par définition, restait en brouillon dès qu'elle
avait été semée du temps où elle était la semaine courante. D'où des tests
qui échouent en local et passent en intégration continue — l'écart le plus
coûteux à diagnostiquer. Le statut est désormais réimposé.

**Ports internes.** L'application écoute sur 9317 et la base sur 5439,
jusque dans l'image. Sur un réseau Docker deux conteneurs peuvent écouter le
même port sans se gêner — ce n'est donc pas une correction de collision —
mais une valeur unique de bout en bout lève l'ambiguïté quand plusieurs
piles cohabitent derrière le même proxy, et la configuration du
reverse-proxy porte partout le même nombre. Publication, sonde de santé,
serveur, chaîne de connexion et scripts d'amorçage sont alignés sur une
seule variable par service.

`pnpm verify` : 450 tests. Playwright : 77/77 après remise à zéro du semis.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-10 08:10:35 +00:00
Claude db43356919 Corriger la construction de l'image, pour de bon
`pnpm db:generate` échouait avant même le build : `prisma.config.ts` résolvait
DATABASE_URL par `env()`, qui lève dès le **chargement** du fichier de
configuration quand la variable manque. Or ce fichier est lu par toutes les
commandes du CLI, y compris `generate`, qui ne se connecte à rien — la
construction de l'image exigeait donc une base.

Ma vérification précédente ne valait rien : j'avais éprouvé `pnpm build` seul,
pas `pnpm db:generate && pnpm build`, la ligne qui échoue. Cette fois c'est la
commande exacte du Dockerfile qui a été jouée, sans .env, et elle sort en 0.

Corrigé au passage, du même genre que la troncature de l'écran de conservation :
la liste des périodes de paie s'arrête aux 24 plus récentes sans le dire. Une
période créée hors de cette fenêtre semblait ne pas s'être enregistrée. Le total
est désormais rapporté.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 10:55:36 +00:00
Claude 92a0ebf853 Configurer les rôles et leurs capacités
Critère d'acceptation de WP-01 resté sans écran : « un rôle personnalisé créé
par un client modifie effectivement l'accès, sans changement de code ». Le
catalogue de capacités existait, l'autorisation s'y adossait déjà, mais seul le
semis pouvait attribuer quoi que ce soit.

Deux dangers opposés, et il faut se protéger des deux. L'escalade d'abord : on
ne peut accorder qu'une capacité qu'on détient soi-même, sans quoi la première
personne autorisée à éditer un rôle s'accorde l'accès aux rémunérations en
trois clics et le catalogue ne sert plus à rien. Le retrait, lui, reste permis
même sur une capacité qu'on n'a pas — réduire un droit n'a jamais élargi le
sien, et l'interdire empêcherait de corriger un rôle trop large.

Le verrouillage ensuite : au moins un rôle doit conserver la gestion des
droits. La condition se juge sur l'ensemble des rôles après coup, non sur celui
qu'on édite — ce qui compte n'est pas que ce rôle-ci garde la capacité, mais
qu'un rôle la garde. Sans cela une organisation se fermerait dehors, et le seul
recours serait une intervention en base.

Un rôle naît sans aucune capacité : en hériter de celles de son créateur
distribuerait des droits que personne n'a demandés. Le niveau propriétaire se
délègue à part. Un rôle système ne se supprime pas, un rôle attribué non plus —
ses membres se retrouveraient dehors sans que personne l'ait décidé pour eux.

Le test va jusqu'au bout : créer le rôle, l'attribuer, se connecter, constater
qu'un écran s'ouvre et qu'un autre reste fermé. S'arrêter à « la case est
cochée » n'aurait rien dit du critère.

Trois défauts d'isolation des tests corrigés au passage, tous du même genre —
un état qui s'accumule d'une exécution à l'autre :

- la file des congés désignait « la première ligne de ce salarié » pour son
  nettoyage, et tombait sur une ligne laissée par un passage antérieur ;
- l'écran de conservation tronquait à quarante pièces triées par ancienneté,
  faisant disparaître les pièces échues derrière celles dont il n'y a rien à
  dire — corrigé dans le produit, pas seulement dans le test ;
- le test de verrouillage de période consommait un mois par exécution sans
  jamais le rendre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 08:07:36 +00:00
Claude 2e9a4c5efe WP-07 : heures prévu/réalisé/payé et périodes de paie verrouillables
Les trois grandeurs
Sans pointeuse, le réalisé est saisi par le manager. La matrice impose de
distinguer trois grandeurs et non deux, avec deux règles qui ne se négocient
pas :
- **Sans heures réelles, le prévu fait foi.** Attendre une saisie qui ne
  viendra pas ne produirait aucune paie. Une saisie partielle — un début sans
  fin — ne suffit pas : elle donnerait une durée fantaisiste.
- **Le paiement ne dépend jamais de la validation.** Une ligne non validée part
  en paie sur la base du réalisé. Bloquer le paiement d'heures accomplies faute
  de validation est précisément ce que la matrice interdit ; l'écran l'énonce
  pour qu'aucun manager ne croie l'inverse.

Toute correction d'heures déjà saisies exige un motif — sans lui, une
correction est indistinguable d'une erreur de saisie. Une première saisie n'en
demande pas : il n'y a rien à corriger, et exiger un motif à chaque ligne le
ferait remplir machinalement.

Le verrou de période
- Verrouiller fige les instantanés **et** ferme le mois : plus aucun créneau,
  aucune absence, aucune heure réelle ne peut être écrit sur ces dates. Le
  contrôle passe avant l'écriture, dans planning, absences et heures.
- Déverrouiller exige une justification. Rouvrir périme les fichiers déjà
  transmis au cabinet, et six mois plus tard personne ne saurait pourquoi.
- La **péremption d'un export est déduite** de `generatedAt < unlockedAt`,
  jamais stockée : la stocker obligerait à réécrire une trace qui doit rester
  append-only, et une trace réécrite ne prouve plus rien.
- Supprimer une période demande de **retaper son libellé** : un « êtes-vous
  sûr » se clique sans lire, et la suppression efface des instantanés sur
  lesquels un export a pu être bâti.

Un seul calcul pour trois écrans
Les instantanés viennent de `buildPayrollPeriod`, la même fonction que le
rapport de paie et l'export. C'est ce qui satisfait le critère croisé : trois
calculs séparés divergeraient, et l'écart ne se verrait qu'au bulletin. Un test
d'intégration compare instantané et rapport sur le même jeu de données.

Tests enfin idempotents
La suite e2e échouait au second passage : les absences acceptées d'une
exécution bloquaient les demandes de la suivante. Trois corrections, dans
l'ordre d'importance — chaque test travaille sur **son** salarié (le
chevauchement se juge par personne), chaque test libère ce qu'il a créé, et les
fenêtres de dates sont écartées d'une exécution à l'autre. Trois passages
consécutifs passent désormais.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-08 12:25:02 +00:00