Commit Graph
5 Commits
Author SHA1 Message Date
MichaelandClaude Opus 5 0a916ff73e Retirer la barre latérale au profit d'onglets de section
Deux défauts la condamnaient. Elle rognait la largeur utile au moment précis où
la grille de planning en manque le plus. Et elle donnait à la navigation deux
grammaires : des onglets sur la fiche salarié, une liste verticale partout
ailleurs. Une grammaire se retient, deux se cherchent.

Les entrées de la section courante s'affichent donc en onglets, dans la même
forme que la fiche salarié. Une entrée sans écran construit reste visible et
mène à l'écran d'attente : la masquer ferait croire le produit plus étroit que
ce qu'il vise, la laisser inerte ferait croire la navigation cassée.

La colonne centrale passe en largeur fixe. Une largeur qui suit la fenêtre fait
bouger les colonnes du planning d'un poste à l'autre : la même semaine ne se lit
pas au même endroit sur deux écrans, et l'œil doit se réorienter à chaque
ouverture. Une largeur arrêtée rend la page reconnaissable ; sur une fenêtre
étroite, elle défile horizontalement plutôt que de comprimer la grille.

À l'impression, cette largeur n'a plus de sens et déborderait de la feuille :
elle y est relâchée, comme les autres largeurs minimales de la grille.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:31:23 +02:00
Claude 2047d7f5cc Exiger un second facteur des rôles administrateur et RH
La matrice n° 15 l'impose ; les colonnes existaient en base depuis WP-01 mais
rien ne les remplissait. TOTP (RFC 6238) plutôt qu'un code envoyé par message :
le second facteur ne doit pas dépendre du canal qui sert déjà à réinitialiser le
mot de passe, faute de quoi un accès à la boîte donnerait les deux d'un coup.

L'algorithme est écrit ici plutôt qu'emprunté — trente lignes, et une dépendance
de moins sur le chemin d'authentification. Il est éprouvé contre les vecteurs
publiés de la RFC, ce qui distingue « le code change toutes les trente
secondes » de « le code est celui qu'attend l'application du téléphone ».

L'obligation est adossée aux capacités, pas à des rôles nommés qu'un client
renomme librement : distribuer les droits, ou lire les rémunérations. Elle est
tenue au point de passage de toutes les routes applicatives, où l'écran
d'enrôlement remplace le contenu. Il remplace plutôt qu'il ne redirige : une
redirection depuis un layout se joue aussi pendant la navigation qui suit la
connexion, et Next y répond par une page vide.

Le secret n'est enregistré qu'après qu'un code en a été tiré — l'enregistrer
d'avance laisserait des comptes porteurs d'un facteur que leur détenteur ne sait
pas produire, c'est-à-dire des comptes fermés. Le rejeu d'un code est refusé
dans sa propre fenêtre : sans cela le facteur protège d'un mot de passe volé,
pas d'un code lu par-dessus l'épaule.

Dix codes de secours accompagnent chaque activation, conservés hachés. Et parce
que PlanFlow est auto-hébergé et qu'il n'y a pas d'éditeur à appeler, un retrait
depuis le serveur existe — l'accès « break glass » que demande la même ligne de
la matrice. Sans lui, le second facteur deviendrait le risque principal plutôt
que la protection.

Un défaut trouvé par les tests, et il aurait été grave : la réactualisation de
la route après activation remplaçait l'écran par celui d'un compte déjà enrôlé,
emportant les codes de secours avant que leur destinataire ait pu les noter.

La suite de tests suit le produit : la mise en place enrôle réellement le compte
de direction et calcule les codes comme le ferait un téléphone. Les écrans qui
n'éprouvent pas l'authentification ont migré vers la session commune — deux
connexions parallèles sur un même compte se heurtent au refus de rejeu, ce qui
est le comportement voulu et non un défaut à contourner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-08 22:05:57 +00:00
Claude 023f236881 WP-04: planning sur données réelles — grille semaine, vue jour, publication par équipe
Le planning quitte le module de démonstration : la grille lit `WeeklySchedule`
et `Shift`, et les quatre derniers fichiers de `src/lib/demo/` qui portaient des
salariés fictifs disparaissent.

Modèle
- `TeamMember` : rattachement d'un salarié à une équipe, distinct de
  `MembershipScope` (qui dit ce qu'un manager a le droit de voir). Sans lui, un
  salarié sans créneau n'apparaîtrait pas dans la grille — c'est-à-dire l'état
  de départ de toute semaine en construction.
- RLS sur `WeeklySchedule`, `Shift`, `Rest`, `DailyNote` et `TeamMember`.

Temps
- `src/domain/planning/week.ts` : repérage par couple année ISO + semaine ISO,
  jamais par date de début. Le lundi 29 décembre 2025 appartient à la semaine 1
  de 2026 ; une clé fondée sur la date ferait apparaître deux semaines 1.
- `zonedInstant` / `zonedMidnight` corrigent le décalage mesuré **à l'instant
  visé**. Le 29 mars 2026, minuit est en UTC+1 et 09 h en UTC+2 : ajouter neuf
  heures à minuit donnerait 10 h locales.
- La semaine du retour à l'heure d'hiver dure 169 h, celle du passage à l'heure
  d'été 167 — vérifié par test.

Écritures
- Création, modification, suppression de créneau ; publication et dépublication
  **par équipe**, avec verrou optimiste sur `version` : deux managers sur la
  même grille est le cas normal, pas l'exception.
- Chevauchement refusé en transaction, pas seulement dans le formulaire.
- Modifier une semaine publiée exige `planning.edit_published`, capacité que le
  rôle manager n'a pas : un salarié a organisé sa semaine sur ce qu'il a lu.
- Toute mutation laisse une entrée d'audit ; la suppression écrit sa trace
  **avant** l'effacement, sinon l'état supprimé serait perdu.

Lecture
- `planning.view_unpublished` filtre en base : sans cette capacité, les
  brouillons ne sont pas chargés du tout. Un test vérifie que les horaires
  n'apparaissent pas dans le HTML servi — un masquage CSS les y laisserait.
- Vue jour reconstruite sur les mêmes données, amplitude déduite de la journée
  réelle plutôt que figée à 06 h–21 h.

Vérification
- 26 tests unitaires sur le repérage des semaines et la mise en grille.
- Parcours e2e : poser un créneau, refus de chevauchement, publier, dépublier ;
  et ce que voient un salarié et un manager sur la même semaine.
- `scripts/dev-db.sh` : la base de développement est éphémère dans cet
  environnement, la remonter ne doit pas être une redécouverte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-07 23:43:48 +00:00
Claude 8a314991d3 WP-03: employee records and contracts on real data
Replaces the demo module behind the team directory and the employee
record with scoped queries, and adds contracts, amendments, work
permits and the forfait-jours fields.

Writing the end-to-end test exposed a modelling error worth naming:
first and last names lived only on User, so an employee without an
application account had no name at all — the directory rendered
"— Salarié E0007". Most sales staff never sign in, and the personnel
register requires their name, so the name belongs to the record, not
to the login. Moved to EmployeeProfile with a data migration that
carries the existing names down from User.

Contract rules are pure functions tested at the boundaries. The case
that matters is an open-ended contract: a CDI with no end date overlaps
every later period, which a naive comparison of two date pairs misses,
and two overlapping active contracts would count one employee twice in
payroll. The check runs inside the transaction, not only in the form.

Forfait jours is refused without a written individual agreement and a
dated employee consent: without them the arrangement is unenforceable,
and enabling it would also switch off every weekly-duration control.

Salary and bank details are not merely hidden when the capability is
missing — they are never loaded. A field absent from the response
cannot leak through HTML, a log or an error message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-07 23:17:16 +00:00
Claude 92667b16b9 WP-02: locations, teams and the legal configuration register
Adds the referential models, the first database-backed settings
screens, and the register the compliance matrix requires before any
parameter is enforceable.

The register is the point of the lot. The matrix is explicit that
copying another product's configuration is not enough — each parameter
must carry its value, source, effective date, population and an
approver. Approval records the session's actor, never a form field: a
signature you can type yourself is worth nothing. The screen names the
domains that have no approved parameter yet, so the gap is visible
rather than assumed closed.

Two bugs of the same family, both now structurally impossible:

- The Prisma scoping extension read a hand-written list of models
  carrying accountId. The four models added here were missing from it,
  so writes failed with an opaque Prisma error — and a read would have
  silently returned every account's rows. The list is now derived from
  the schema itself.
- The RLS policies were likewise per-table. A new integration test
  fails if any table with an accountId column lacks forced RLS and both
  policies, which is the failure mode that hides best: nobody writes a
  wrong rule, someone forgets to write one.

An end-to-end test signs in as a manager and confirms the settings
screens refuse to render — the sidebar hiding them is a convenience,
the server check is the control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-07 23:04:26 +00:00