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
PlanFlow
Gestion du personnel, des plannings et des temps, multi-établissements, auto-hébergée. La paie est exportée vers Silae ; PlanFlow ne produit ni bulletin ni DSN.
La spécification de construction est PLAN.md. Elle est normative : en cas d'écart entre le code et le plan, c'est le plan qui a raison, ou le plan qui doit être corrigé — jamais l'écart qui s'installe.
| Document | Rôle |
|---|---|
PLAN.md |
Spécification : périmètre, modèle de données, règles, lots de travail |
matrice-conformite-rh-france-2026.md |
Exigences réglementaires françaises, cotées P0/P1/P2 |
Audit Combo/ |
Audit fonctionnel du produit de référence |
État
WP-00 — socle et implémentation du design : six écrans sur données de démonstration.
WP-01 — tenancy, identité, autorisation : en cours. Modèle de données, RLS, journal d'audit append-only, catalogue de 70 capacités, cinq rôles, sessions en base. Les écrans lisent encore src/lib/demo.
Démarrer
Avec Docker
cp .env.example .env
# Renseigner POSTGRES_PASSWORD et ENCRYPTION_KEY (voir ci-dessous)
docker compose up --build
L'application écoute sur http://localhost:3000. Les migrations s'appliquent au démarrage du conteneur.
En local
Nécessite Node 22, pnpm 10 et un PostgreSQL 16 accessible.
pnpm install
cp .env.example .env # renseigner DATABASE_URL et ENCRYPTION_KEY
pnpm db:generate
pnpm db:deploy
pnpm dev
Clé de chiffrement
ENCRYPTION_KEY chiffre au repos les colonnes sensibles exigées par le plan (§3.6) : NIR, IBAN, BIC.
openssl rand -base64 32
Elle vit hors de la base : une sauvegarde volée ne doit pas suffire à lire ces colonnes. La perdre rend ces données irrécupérables — la sauvegarder séparément et documenter sa rotation.
Vérifier
pnpm verify # typecheck + lint + tests unitaires
pnpm test:e2e # build, serveur standalone, tests de bout en bout
pnpm verify est ce que la CI exécute sur chaque pull request, suivi du build et des tests end-to-end.
Configuration de la base — à ne pas rater
L'application ne doit pas se connecter en superutilisateur PostgreSQL.
Un superutilisateur contourne la row-level security, y compris déclarée en FORCE. Connecter PlanFlow avec un tel compte désactive silencieusement la seconde couche d'isolation multi-tenant : les requêtes fonctionnent, les tests applicatifs passent, et rien n'indique que la protection a disparu — jusqu'au jour où quelqu'un lit les données d'un autre établissement.
CREATE ROLE planflow_app LOGIN PASSWORD '…' NOSUPERUSER NOBYPASSRLS;
GRANT USAGE ON SCHEMA public TO planflow_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO planflow_app;
Les migrations, elles, s'appliquent avec un compte propriétaire distinct.
L'application vérifie ce point au démarrage : elle refuse de démarrer en production sur une base mal configurée, et se contente d'un avertissement en développement. GET /api/sante expose l'état sous tenantIsolation.
Choix structurants
Aucun traceur tiers. L'audit du produit de référence a intercepté 2102 requêtes de traçage — Segment, LinkedIn Ads, Google Ads, DoubleClick, Clarity, Hotjar — et aucune requête métier. Une application RH ne doit pas envoyer un contexte de navigation portant sur des salariés identifiables à des régies publicitaires. Deux garde-fous rendent la règle vérifiable plutôt que déclarative :
- une Content-Security-Policy qui ne nomme aucune origine externe, posée par requête avec un nonce (
src/proxy.ts) ; - un test qui échoue si une dépendance de traçage apparaît dans
package.json.
Pour de la télémétrie technique, passer par une interface abstraite auto-hébergée.
Le serveur testé est celui qui est déployé. Les tests end-to-end lancent le serveur standalone, celui que l'image Docker exécute — pas next dev, dont la politique de sécurité est volontairement plus permissive.
Structure
src/
├── app/ écrans (App Router)
├── lib/
│ ├── env.ts contrat d'environnement, validé à l'import
│ └── security/csp.ts politique de sécurité, fonction pure et testable
├── server/
│ ├── db.ts client Prisma — le scoping multi-tenant s'y greffe au WP-01
│ └── health.ts
└── proxy.ts en-têtes de sécurité par requête
prisma/ schéma et migrations
tests/
├── unit/ Vitest
└── e2e/ Playwright
Écarts assumés par rapport au plan
Trois choix diffèrent de ce qu'annonçait PLAN.md §2, et le plan a été mis à jour en conséquence.
| Sujet | Plan initial | Retenu | Raison |
|---|---|---|---|
| Next.js | 15 | 16.3 | Version stable courante ; démarrer un greenfield une majeure en retard n'a pas de contrepartie. |
| Authentification | Auth.js v5 | Sessions maison en base | Auth.js v5 est encore en beta. Le besoin se limite à identifiants et invitation, sans OAuth, et la matrice de conformité (n° 23) impose la révocation de session — immédiate avec des sessions en base, malaisée avec des jetons JWT. |
| Convention Next | middleware.ts |
proxy.ts |
Next 16 a renommé la convention ; middleware est déprécié. |