c6abbaadc55624c02c723ba337d0b572f20024d9
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c6abbaadc5 |
Faire de la fiche salarié un dossier à onglets, et le rendre saisissable
La fiche empilait tout sur une page, en lecture seule. Le dossier personnel existait pourtant en base — date et lieu de naissance, nationalité, adresse, contact d'urgence, NIR et IBAN chiffrés — sans qu'aucun écran ne l'écrive : un salarié créé restait un dossier vide que rien ne permettait de remplir. Cinq onglets, cinq routes : une fiche s'envoie par lien et se rouvre au même endroit. Le bandeau porte ce qui ne change pas d'un onglet à l'autre, et le gabarit le tient une fois pour toutes. Deux points d'écriture et non un seul. L'état civil et les coordonnées demandent `members.edit` ; le NIR, l'IBAN et le BIC exigent en plus de pouvoir les lire. Fondus dans un même formulaire, un profil habilité à modifier mais pas à lire aurait renvoyé des champs vides — et effacé ce qui ne lui avait jamais été montré. Le journal retient qu'ils ont été renseignés, jamais leur valeur. Les parcours qui déposaient une pièce ou ouvraient un accès passent désormais par l'onglet qui les porte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b243df4772 |
Étendre la préservation des formulaires, et corriger la publication
Suite de PR #31. `PersistentForm` couvre désormais les formulaires où la saisie compte : ajout d'un salarié, d'un établissement, d'une équipe, d'une entrée de registre, demande d'absence, heures réelles, correspondances de paie, réglages d'envoi, conservation, dépôt de pièce. Le composant gère `select`, `textarea`, cases et boutons radio, et reçoit `resetAfter` : passé l'état retourné par l'action en cas de succès, le formulaire revient aux valeurs rendues par le serveur — vide pour un ajout, la valeur enregistrée pour un champ d'édition. C'est ce qui rend visible une normalisation faite côté serveur. Les champs cachés sont exclus. Ils ne portent jamais une saisie mais l'état de l'application, et les rétablir écrase ce que le serveur vient de renvoyer. Le cas s'est produit sur `expectedVersion`, le verrou optimiste d'une semaine de planning. En éprouvant cela, un défaut plus grave est apparu, antérieur et sans rapport avec la préservation : le formulaire de publication recevait tantôt l'action de publication, tantôt celle de dépublication selon l'état, et cessait de suivre le changement. Lecture de l'en-tête `Next-Action` sur trois envois consécutifs : 1er envoi 60b8097e (dépublier) → brouillon 2e envoi 6012d40f (publier) → publiée 3e envoi 6012d40f (publier) → publiée Le bouton affichait « Dépublier » et republiait, sans message d'erreur puisque l'action réellement exécutée réussissait. Une seule action reçoit maintenant l'intention, portée par le bouton déclencheur — un champ caché serait remis à sa valeur d'origine par la réinitialisation que React applique après chaque action, un bouton ne l'est jamais. Le test du planning fait désormais deux allers-retours dans le même chargement : le premier envoi d'une page emploie toujours la bonne action, si bien qu'un seul aller-retour laissait passer le défaut selon l'état où la semaine avait été trouvée. Vérifié : le test échoue sur l'ancien câblage, passe sur le nouveau. 450 tests unitaires et d'intégration, 77 de bout en bout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv |
||
|
|
29369d4a4e |
Déposer et consulter les pièces du dossier salarié
Rien ne pouvait être téléversé jusqu'ici : ni pièce d'identité, ni relevé d'identité bancaire, ni arrêt de travail. Le dossier RH n'existait qu'en champs de formulaire. Les fichiers vivent sur disque, chiffrés avec la clé qui protège déjà le NIR et l'IBAN. Le plan n'exige le chiffrement que des pièces jointes de santé ; les chiffrer toutes supprime une branche dont l'oubli serait silencieux et ne coûte rien de plus. Leur emplacement est tiré au sort, jamais dérivé du nom déposé : la traversée de chemin devient impossible par construction plutôt que par filtrage, et un filtre s'oublie. Le caractère sensible se déduit de la catégorie et n'est jamais saisi : laisser déclarer qu'un arrêt de travail n'est pas une donnée de santé reviendrait à laisser désactiver la journalisation de sa lecture. Cette lecture est inscrite au journal avant d'être servie, et l'écran l'annonce — celui qui ouvre la pièce doit savoir que sa consultation laisse une trace nominative. Les liens sont signés et durent deux minutes, comme l'exige le plan. La signature ne remplace pas le contrôle d'accès : la route revérifie session, capacité et périmètre. Elle s'y ajoute pour qu'un lien recopié dans un message cesse de fonctionner de lui-même, sans attendre qu'une session expire. La signature est éprouvée avant l'échéance, sans quoi répondre « expiré » à un lien fabriqué indiquerait qu'il aurait pu marcher. L'empreinte du clair est conservée et revérifiée à chaque lecture : servir un contenu qui ne correspond plus reviendrait à présenter comme authentique une pièce altérée. Retirer une pièce efface le contenu mais garde la ligne : le dossier doit conserver trace qu'elle a existé et qui l'a retirée. Aucune durée de conservation n'est appliquée par défaut — le plan l'interdit explicitement (§12.5). L'échéance reste nulle et l'écran le dit, plutôt que d'inventer « cinq ans partout ». Deux pièges d'outillage rencontrés et documentés dans les tests : le cookie de session étant marqué Secure, le client HTTP de Playwright ne l'émet pas sur http et faisait passer les refus pour de bonnes raisons sans rien prouver ; et la visionneuse PDF intégrée de Chromium ne restitue pas le corps d'une navigation, ce qui masquait la vérification d'intégrité du contenu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv |
||
|
|
874c3fcd80 |
Ouvrir un accès à un salarié par invitation
Jusqu'ici personne ne pouvait entrer dans l'application : le modèle Invitation existait en base mais aucun code ne s'en servait, et le seul moyen d'obtenir un compte était le jeu de données de démonstration. Le lien vaut autant qu'un mot de passe le temps de sa validité, d'où trois règles : sept jours, un seul usage, et une révocation possible sans attendre l'expiration. Seule l'empreinte du jeton est conservée — renvoyer une invitation émet donc un nouveau lien et invalide le précédent, ce qui est aussi la bonne réponse à « il a perdu le message ». Deux liens vivants pour un même accès, ce sont deux portes dont une seule est tracée comme ayant servi. Le compte est porté par le lien lui-même, préfixé au secret. La table est protégée par RLS, laquelle exige de connaître le compte avant toute lecture : sans ce préfixe il aurait fallu ouvrir la politique aux requêtes sans compte — c'est-à-dire la vider de son sens. Divulguer un identifiant opaque à qui est membre du compte ne coûte rien. Le lien est aussi rendu une fois, à l'écran de celui qui l'émet. Sans cela un déploiement neuf ne peut inviter personne : configurer le serveur d'envoi demande d'être connecté, et être connecté demande une invitation. Il n'est ni conservé ni journalisé. Deux cas à l'acceptation, un seul demande un mot de passe. Si aucun compte n'existe pour l'adresse, il est créé ; s'il en existe un, le salarié est rattaché sans qu'on touche à son mot de passe — détenir le lien prouve l'accès à la boîte, ce qui suffit à rattacher un accès mais ne justifie pas de réinitialiser l'authentification d'un compte existant. Pas de connexion automatique non plus : un mot de passe qu'on vient de choisir se fixe en s'en servant. `members.invite` est une capacité distincte de `members.edit` : ouvrir un accès n'est pas modifier un dossier, et tel client voudra confier l'un sans l'autre. Deux défauts trouvés par les tests plutôt qu'en production : le contrôle qui refuse un mot de passe contenant le nom du salarié laissait passer « riviere » pour « Rivière » faute de replier les accents — soit exactement la variante qu'on tape au clavier ; et les contextes « visiteur » des tests héritaient de la session du responsable, si bien que le parcours anonyme n'était pas éprouvé. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv |
||
|
|
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 |
||
|
|
4012f32f7f |
Complete the six application screens
Adds the day timeline, team list, employee record and leave calendar. The design export shipped data for these but no markup, so their layout is designed here from the data shapes and the established style; only Aperçu and the week grid are ports. The day view carries a per-hour headcount band. Where the week view answers "who works how much", this one answers "who is on the floor at 14:00", and the band makes coverage gaps visible without reading every lane. Two fixes the tests and linter caught: - The poste derivation variables were missing from globals.css. Hues and tiers were ported but not the --post-*-bg/fg/edge that consume them, so every shift chip would have rendered colourless. The palette test now asserts all 36 exist. - ThemeToggle synced state in an effect. The document's data-theme attribute is the real store — it is set by the inline script before React exists — so it now subscribes with useSyncExternalStore instead of keeping a copy that is wrong on first render. Counter tests cover the DST cases in both directions: a 22:00–06:00 shift lasts 7 or 9 hours on the changeover nights, never 8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv |