feat: Permettre à l'enseignant de saisir les notes dans une grille inline
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled

L'enseignant avait besoin d'un moyen rapide de saisir les notes après
une évaluation. La grille inline permet de compléter 30 élèves en moins
de 3 minutes grâce à la navigation clavier (Tab/Enter/Shift+Tab),
la validation temps réel, l'auto-save debounced (500ms) et les
raccourcis /abs et /disp pour marquer absents/dispensés.

Les notes restent en brouillon jusqu'à publication explicite (avec
confirmation modale). Une fois publiées, les élèves les voient
immédiatement ; les parents après un délai de 24h (VisibiliteNotesPolicy).
Le mode offline stocke les notes en IndexedDB et synchronise
automatiquement au retour de la connexion.

Chaque modification est auditée dans grade_events via un event
subscriber qui écoute NoteSaisie/NoteModifiee sur le bus d'événements.
This commit is contained in:
2026-03-29 09:55:45 +02:00
parent 98be1951bf
commit b70d5ec2ad
45 changed files with 3902 additions and 11 deletions

View File

@@ -5,21 +5,41 @@ import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
/**
* Global setup for E2E tests.
*
* - Resets rate limiter to ensure tests start with clean state
* - Cleans transactional data that could cause FK constraint failures
* (each test file handles its own specific cleanup in beforeAll/beforeEach)
* - Resets rate limiter cache
* - Token creation is handled per-browser in test files using beforeAll hooks
*/
async function globalSetup() {
console.warn('🎭 E2E Global setup - tokens are created per browser project');
const projectRoot = join(__dirname, '../..');
const composeFile = join(projectRoot, 'compose.yaml');
function runSql(sql: string) {
execSync(
`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "${sql}" 2>&1`,
{ encoding: 'utf-8' }
);
}
// Clean grade data (Story 6.2) to prevent FK constraint failures
// when other tests try to DELETE FROM evaluations
try {
runSql(`DELETE FROM grade_events WHERE grade_id IN (SELECT id FROM grades WHERE tenant_id = '${TENANT_ID}')`);
runSql(`DELETE FROM grades WHERE tenant_id = '${TENANT_ID}'`);
console.warn('✅ Grade data cleaned');
} catch {
// Tables may not exist yet
}
// Reset rate limiter to prevent failed login tests from blocking other tests
try {
const projectRoot = join(__dirname, '../..');
const composeFile = join(projectRoot, 'compose.yaml');
// Use Symfony cache:pool:clear for more reliable cache clearing
execSync(
`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`,
{ encoding: 'utf-8' }