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
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test';
|
||
|
||
import {
|
||
formatWeekParam,
|
||
isoWeekOf,
|
||
previousIsoWeek,
|
||
} from '../../src/domain/planning/week';
|
||
|
||
/**
|
||
* Ce que chaque rôle voit du planning.
|
||
*
|
||
* Ces tests se connectent eux-mêmes : ils ne peuvent pas réutiliser la session
|
||
* partagée de la direction, d'où le projet « anonyme ».
|
||
*/
|
||
|
||
async function signIn(page: Page, email: string) {
|
||
await page.goto('/connexion');
|
||
await page.getByLabel('Adresse électronique').fill(email);
|
||
await page.getByLabel('Mot de passe').fill('planflow-demo-2026');
|
||
await page.getByRole('button', { name: 'Se connecter' }).click();
|
||
// Attendre la sortie de l'écran de connexion : naviguer tout de suite
|
||
// annulerait la redirection en cours, et le test repartirait déconnecté.
|
||
await expect(page).not.toHaveURL(/\/connexion/);
|
||
}
|
||
|
||
const current = isoWeekOf(new Date());
|
||
const CURRENT_WEEK = formatWeekParam(current);
|
||
const PUBLISHED_WEEK = formatWeekParam(previousIsoWeek(current));
|
||
|
||
test('un salarié ne voit pas une semaine non publiée', async ({ page }) => {
|
||
await signIn(page, 'salarie@example.test');
|
||
|
||
// Le seed laisse la semaine courante en brouillon et publie la précédente.
|
||
await page.goto(`/planning/semaine?semaine=${CURRENT_WEEK}`);
|
||
await expect(page.getByText(/n’est pas encore publié/).first()).toBeVisible();
|
||
|
||
// Le masquage n'est pas cosmétique : les créneaux ne sont pas chargés, donc
|
||
// absents du HTML. Un simple `display: none` les y laisserait.
|
||
expect(await page.content()).not.toContain('09:00–17:00');
|
||
|
||
// Ni bouton de publication ni ajout de créneau : les capacités manquent.
|
||
await expect(page.getByRole('button', { name: 'Publier' })).toHaveCount(0);
|
||
await expect(page.getByRole('button', { name: /\+ Créneau/ })).toHaveCount(0);
|
||
});
|
||
|
||
test('un salarié voit la semaine publiée', async ({ page }) => {
|
||
await signIn(page, 'salarie@example.test');
|
||
await page.goto(`/planning/semaine?semaine=${PUBLISHED_WEEK}`);
|
||
|
||
await expect(page.getByText('Publiée').first()).toBeVisible();
|
||
await expect(page.getByText('09:00–17:00').first()).toBeVisible();
|
||
});
|
||
|
||
test('un manager voit le brouillon et peut le publier', async ({ page }) => {
|
||
await signIn(page, 'manager.nantes@example.test');
|
||
await page.goto(`/planning/semaine?semaine=${CURRENT_WEEK}`);
|
||
|
||
await expect(page.getByText('Brouillon').first()).toBeVisible();
|
||
await expect(page.getByRole('button', { name: 'Publier' }).first()).toBeVisible();
|
||
});
|