feat: Calculer automatiquement les moyennes après chaque saisie de notes
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

Les enseignants ont besoin de moyennes à jour immédiatement après la
publication ou modification des notes, sans attendre un batch nocturne.

Le système recalcule via Domain Events synchrones : statistiques
d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées
(normalisation /20), et moyenne générale par élève. Les résultats sont
stockés dans des tables dénormalisées avec cache Redis (TTL 5 min).

Trois endpoints API exposent les données avec contrôle d'accès par rôle.
Une commande console permet le backfill des données historiques au
déploiement.
This commit is contained in:
2026-03-30 06:22:03 +02:00
parent b70d5ec2ad
commit e745cf326a
733 changed files with 113156 additions and 286 deletions

View File

@@ -70,7 +70,7 @@ async function loginAsStudent(page: import('@playwright/test').Page) {
await page.locator('#email').fill(STUDENT_EMAIL);
await page.locator('#password').fill(STUDENT_PASSWORD);
await Promise.all([
page.waitForURL(/\/dashboard/, { timeout: 30000 }),
page.waitForURL(/\/dashboard/, { timeout: 60000 }),
page.getByRole('button', { name: /se connecter/i }).click()
]);
}
@@ -357,7 +357,7 @@ test.describe('Student Homework Consultation (Story 5.7)', () => {
await page.locator('.filter-chip', { hasText: /maths/i }).click();
const cards = page.locator('.homework-card');
await expect(cards).toHaveCount(1, { timeout: 5000 });
await expect(cards).toHaveCount(1);
await expect(cards.first().locator('.card-title')).toContainText('Exercices chapitre 3');
});
@@ -369,10 +369,10 @@ test.describe('Student Homework Consultation (Story 5.7)', () => {
// Filter then unfilter
await page.locator('.filter-chip', { hasText: /maths/i }).click();
await expect(page.locator('.homework-card')).toHaveCount(1, { timeout: 5000 });
await expect(page.locator('.homework-card')).toHaveCount(1);
await page.locator('.filter-chip', { hasText: /tous/i }).click();
await expect(page.locator('.homework-card')).toHaveCount(2, { timeout: 5000 });
await expect(page.locator('.homework-card')).toHaveCount(2);
});
});
@@ -413,7 +413,7 @@ test.describe('Student Homework Consultation (Story 5.7)', () => {
// Reload the page
await page.reload();
await expect(page.locator('.homework-card').first()).toBeVisible({ timeout: 10000 });
await expect(page.locator('.homework-card').first()).toBeVisible({ timeout: 15000 });
// Done state should persist (localStorage)
await expect(page.locator('.homework-card.done')).toBeVisible({ timeout: 5000 });