Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 43 additions & 136 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,142 +4,49 @@ Toutes les évolutions notables de **Dalibi** sont consignées ici.
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le versionnage
respecte [SemVer](https://semver.org/lang/fr/).

## [1.2.0] — 2026-09-03
## [1.0.0] — 2026-09-06

Refonte du module **Statistiques** : chaque onglet gagne des graphiques plus lisibles,
accessibles (palette validée en vision déficiente, mode sombre), interactifs et, là où
c'est pertinent, ventilés par sexe et filtrables.

### Ajouté
- **Comparaisons** : cartes de variation vs année précédente, séparation en deux lectures
claires — *Performance* (réussite, recouvrement) et *Déperdition* (redoublement,
abandon) — l'année en cours n'étant pas tracée pour les taux de fin d'année encore
indécis. Le jeu de démonstration sème deux années passées pour donner de vraies tendances.
- **Géographie** : treemap hiérarchique région → préfecture, une teinte par région
(nuancée selon l'effectif) et une légende ; KPI de concentration (couverture, part du
Grand Lomé, régions, préfectures).
- **Réussite & examens** : examens officiels (CEPD, BEPC, BAC) avec résultats
admis / échoué / absent et taux d'admission ; **répartition des mentions** enfin
alimentée (quatre paliers).
- **Effectifs & parité** : **pyramide des âges** divergente garçons / filles, **origine
géographique** (top villes) ventilée par sexe, et **filtre par cycle** (Maternelle /
Primaire / Collège / Lycée) avec tri sur le graphe des classes.
- **Statistiques élèves** (module Élèves) refondue : vrais graphiques thémés, mode sombre,
donut de parité, histogramme d'âge, effectifs par classe filtrables et empilés par sexe,
KPI d'âge moyen et de sur-âge.
- Âge attendu par classe renseigné, ce qui active le calcul du **sur-âge** (retard scolaire).

### Corrigé
- **Répartition des mentions vide** : les libellés (« Très bien », « Assez bien »…) sont
désormais normalisés avant d'être agrégés sur les quatre paliers standard.
- **Lisibilité de la géographie** : couleurs par région (les faibles effectifs, en teinte
unique quasi blanche, étaient invisibles) et libellés du treemap en graisse normale.

## [1.1.0] — 2026-09-03

### Ajouté
- **Jeu de démonstration complet** (`DemoSeeder`) : une année scolaire vivante sur toutes
les classes actives (au moins 20 élèves par classe), avec dossiers d'élèves complets,
programme et affectations des enseignants, évaluations notées et moyennes par matière,
bulletins figés, facturation et paiements, présences, emploi du temps, calendrier,
cycles de paie et dossiers courants (justificatifs, réclamations, primes, documents).
Noms, prénoms et villes réels du Togo (divisions administratives officielles),
recrutement pondéré autour de Lomé. Seeder idempotent, déterministe, et interdit en
production. Lancement : `php artisan db:seed --class=DemoSeeder`.

### Corrigé
- **Bulletin — colonne Composition toujours vide** : la colonne de source « composition »
était résolue sur une clé absente du bulletin figé (la valeur y est stockée sous
« compo »). La colonne restait vide **même quand la note existait**, quelle que soit la
période ou le barème. Correctif d'affichage : aucun bulletin à régénérer.
- **Bulletin — logo de l'en-tête surdimensionné** : les styles de l'en-tête ministériel
(dont le plafond de taille du logo) n'étaient pas embarqués dans le CSS du bulletin ; le
logo s'affichait à sa taille naturelle et écrasait l'en-tête.
- **Types d'évaluation — catégorie de la « Composition »** : lorsqu'elle avait dérivé vers
« contrôle continu », la composition était diluée dans la note de classe et la
pondération Classe/Compo du barème était ignorée. Rejouer `ReferenceDataSeeder` corrige
la donnée, puis régénérer les bulletins concernés.

### Modifié
- **Bulletin — la colonne « Classe »** (contrôle continu) s'intitule désormais « Devoir »
par défaut. Seul l'affichage change : la colonne agrège toujours interro + devoir et
alimente la moyenne. Un libellé déjà personnalisé par une école n'est pas écrasé.

## [1.0.1] — 2026-09-02

### Corrigé
- **Couleur des catégories dans les graphiques** : la couleur suivait la *position*
dans le tableau et non la catégorie. Lorsqu'une catégorie vide était filtrée, les
suivantes changeaient de couleur — les filles pouvaient hériter du bleu des garçons,
« Très bien » perdre son vert.

### Accessibilité
- **Palette revue et validée** (bande de luminosité, chroma, séparation en vision
déficiente, contraste) dans les modes clair *et* sombre. L'ancien couple bleu/violet
était indistinguable en deutéranopie.
- **Légendes ajoutées** aux camemberts qui n'en avaient pas : l'identité ne repose plus
sur la seule couleur.
- Les répartitions à plus de trois catégories (modes de paiement, régions) passent en
**barres étiquetées** ; les mentions, données ordonnées, en **rampe séquentielle**.
- **Mode sombre** : couleurs d'axes et de grille désormais adaptées au thème, au lieu de
valeurs claires codées en dur (la grille était quasi invisible sur fond sombre).
- L'animation d'entrée des camemberts est retirée : elle laissait le graphique **vide**
lorsque les images d'animation sont ralenties (onglet en arrière-plan, économie
d'énergie, `prefers-reduced-motion`).
## [1.0.0] — 2026-09-02

Première version stable. Dalibi couvre le cycle complet de gestion d'un établissement
scolaire, de l'inscription de l'élève au bulletin, en passant par l'écolage et la paie.
Première version stable de **Dalibi**, système de gestion scolaire pensé pour les
établissements togolais 🇹🇬 — de l'inscription de l'élève au bulletin, en passant par
l'écolage, la paie et le pilotage.

### Scolarité
- **Élèves & inscriptions** : dossiers complets (état civil, parents, fiche médicale, pièces
justificatives sur disque privé), import CSV en masse, inscriptions rattachées à l'année
académique, effectifs et listes de classe, passage de classe en fin d'année, bourses.
- **Notes & bulletins** : saisie par matière et évaluation, calcul des moyennes paramétrable
(trimestre ou semestre **par type de classe**), bulletins PDF fidèles avec modèle et
en-tête configurables, réclamations de notes.
- **Examens** : modèles d'évaluation réutilisables, déploiement par classe, planning
imprimable, examens officiels (CEPD, BEPC, BAC).
- **Présences** : appel par classe et par séance, statistiques d'assiduité, demandes de
permission d'absence avec cycle de révision.
- **Emploi du temps** : grille hebdomadaire par classe, export PDF.

### Gestion
- **Comptabilité & écolage** : structures de frais, encaissements, reçus vérifiables par
code-barres, situation par classe, dépenses, caisses multiples (dont Mobile Money).
- **Personnel & Paie** : fiches employés, grilles salariales et rubriques, cycles de paie
mensuels avec bulletins PDF, CNSS et ITS calculés automatiquement, décaissement écrit
en comptabilité.
- **Archives & documents** : modèles de documents avec en-tête glisser-déposer et filigrane,
archivage avec tags et corbeille, fichiers sur disque privé.
- **Statistiques** : indicateurs alignés sur la carte scolaire (MEPSTA), exports PDF et Excel.

### Portail parents & élèves
- API REST (`/api/v1`) authentifiée par token, spécifiée en OpenAPI 3.1.
- Isolation stricte : un tuteur n'accède qu'à ses enfants, un élève qu'à ses propres données.
- Activable par établissement, accès délivré compte par compte.

### Sécurité
- Contrôle d'accès **piloté par les permissions** (~145), avec **une permission par verbe** :
un droit de lecture ne permet jamais de créer, modifier ou supprimer.
- **Cloisonnement enseignant** : notes et appels limités aux classes affectées.
- **Intégrité financière** : gardes anti trop-perçu et transitions du cycle de paie évaluées
sous verrou ; une inscription portant des paiements ne peut pas être supprimée.
- Protection des comptes à privilèges, 2FA, journal d'audit, fichiers sensibles hors du
dossier public.
- Données de démonstration **interdites en production**.

### Exploitation
- **Sauvegardes** conçues pour la durée : écriture en flux, compression gzip, `pg_dump` natif,
archives d'année scolaire verrouillées, empreinte SHA-256, sauvegarde optionnelle des médias,
restauration sélective par table.
- Déploiement **Docker** (app / worker / scheduler), observabilité optionnelle (logs JSON,
Sentry/GlitchTip).
- Intégration continue : tests, build et vérification des types à chaque contribution.

### Socle technique
Laravel 12 (PHP 8.3+), React 19 + TypeScript via Inertia.js, Tailwind CSS 4, PostgreSQL,
identifiants UUID. **502 tests** automatisés.

[1.0.1]: https://github.com/wearedalibi/dalibi/releases/tag/v1.0.1
[1.0.0]: https://github.com/wearedalibi/dalibi/releases/tag/v1.0.0
- **Élèves & inscriptions** : dossiers complets (état civil, parents, fiche médicale,
pièces justificatives sur disque privé), import CSV, inscriptions rattachées à l'année
académique, effectifs et listes de classe, passage de classe, bourses.
- **Notes & bulletins** : saisie par matière et évaluation, calcul des moyennes
paramétrable (trimestre ou semestre par type de classe), mentions, bulletins PDF
fidèles avec modèle et en-tête configurables, réclamations de notes.
- **Examens** : modèles d'évaluation réutilisables, planning imprimable, examens officiels
(CEPD, BEPC, BAC) avec inscriptions et résultats.
- **Présences** : appel par séance, statistiques d'assiduité, permissions d'absence avec
cycle de révision.
- **Emploi du temps & calendrier** : grille hebdomadaire par classe, calendrier scolaire.

### Finances & paie
- **Écolage** : structures de frais, factures, paiements (espèces, Mobile Money, virement,
chèque), reçus vérifiables, recouvrement par classe.
- **Comptabilité** : caisses, journal des transactions, dépenses, situation par classe.
- **Personnel & paie** : fiches employés, grilles et rubriques, cycles de paie et bulletins
(retenues légales CNSS et ITS).

### Statistiques
- Tableaux de bord par thème : **effectifs & parité** (pyramide des âges par sexe, origine
géographique par sexe, filtre par cycle), **finances & recouvrement** (par classe,
filtrable et détaillé), **réussite & examens**, **encadrement**, **assiduité**,
**comparaisons** pluriannuelles et **géographie** (treemap région → préfecture).
- Graphiques accessibles : palette validée en vision déficiente, mode sombre, légendes,
survols ; exports PDF et Excel.

### Documents & système
- **En-tête unique** : bulletins, bulletins de paie, certificats, attestations, listes,
emplois du temps, planning et exports statistiques partagent la même en-tête officielle.
- **Sauvegardes** : à la demande ou planifiées, base + médias, restauration sûre,
archives annuelles verrouillées.
- **Sécurité** : rôles et permissions (RBAC), authentification à deux facteurs,
changement de mot de passe imposé aux comptes de démonstration, journal d'audit.
- **Portail** : accès parents/élèves, API du portail.
- **Paramètres** : configuration complète de l'établissement, stockage local ou S3, et
une page **À propos** (version, licence, informations techniques).

Dalibi est un logiciel libre publié sous licence **GNU GPL v3**.
16 changes: 10 additions & 6 deletions app/Http/Controllers/Eleves/RosterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Models\Classroom;
use App\Models\Enrollment;
use App\Models\School;
use App\Services\DocumentRenderer;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -112,14 +113,17 @@ public function export(Request $request)
->sortBy(fn ($e) => $e->student?->lastname)
->values();

$school = School::where('active', true)->first() ?? School::query()->first();
$school = School::where('active', true)->first() ?? School::query()->first();
$renderer = app(DocumentRenderer::class);

$pdf = Pdf::loadView('exports.roster', [
'school' => $school,
'classroom' => $classroom,
'year' => $year,
'students' => $students,
'statuses' => Enrollment::ACADEMIC_STATUSES,
'school' => $school,
'headerHtml' => $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : '',
'headerCss' => $renderer->headerCss(),
'classroom' => $classroom,
'year' => $year,
'students' => $students,
'statuses' => Enrollment::ACADEMIC_STATUSES,
])->setPaper('a4', 'portrait');

return $pdf->stream('effectifs-' . Str::slug($classroom->name . '-' . $year->year) . '.pdf');
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Controllers/Eleves/TimetableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Models\AcademicYear;
use App\Models\Classroom;
use App\Models\School;
use App\Services\DocumentRenderer;
use App\Models\Subject;
use App\Models\TimetableSlot;
use App\Models\User;
Expand Down Expand Up @@ -84,8 +85,12 @@ public function export(Request $request, string $classId)
$grid[$range][$slot->day_of_week] = $slot;
}

$renderer = app(DocumentRenderer::class);

$pdf = Pdf::loadView('exports.timetable', [
'school' => $school,
'headerHtml' => $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : '',
'headerCss' => $renderer->headerCss(),
'classroom' => $classroom,
'days' => TimetableSlot::DAYS,
'timeRanges' => $timeRanges,
Expand Down
6 changes: 5 additions & 1 deletion app/Http/Controllers/Examens/EvaluationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ public function exportPlanning(Request $request, string $classroomId): \Illumina

$school = School::where('active', true)->first();

$html = view('exports.planning', compact('classroom', 'evaluations', 'school', 'activeYear'))->render();
$renderer = app(\App\Services\DocumentRenderer::class);
$headerHtml = $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : '';
$headerCss = $renderer->headerCss();

$html = view('exports.planning', compact('classroom', 'evaluations', 'school', 'activeYear', 'headerHtml', 'headerCss'))->render();

return response($html)->header('Content-Type', 'text/html');
}
Expand Down
31 changes: 31 additions & 0 deletions app/Http/Controllers/Parametres/AboutController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Http\Controllers\Parametres;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\File;
use Inertia\Inertia;
use Inertia\Response;

class AboutController extends Controller
{
public function index(): Response
{
// La version fait autorité dans package.json (celle publiée à chaque release).
$version = '—';
$packagePath = base_path('package.json');
if (File::exists($packagePath)) {
$version = json_decode(File::get($packagePath))->version ?? '—';
}

return Inertia::render('Parametres/About', [
'app' => [
'name' => config('app.name', 'Dalibi'),
'version' => $version,
'laravel' => app()->version(),
'php' => PHP_VERSION,
'environment' => app()->environment(),
],
]);
}
}
1 change: 1 addition & 0 deletions app/Http/Controllers/Statistics/StatisticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public function export(Request $request, string $section, string $format)

$html = view('statistics.report', [
'header' => $renderer->headerHtml($school, $vars),
'headerCss' => $renderer->headerCss(),
'section' => $section,
'data' => $data,
'year' => optional(AcademicYear::find($filters['academic_year_id']))->year,
Expand Down
24 changes: 8 additions & 16 deletions app/Services/DocumentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,10 @@ protected function renderSignature(DocumentTemplate $template, array $variables)
}

/**
* CSS de l'en-tête officiel (ministériel + logo), réutilisable par des
* documents qui n'embarquent pas tout le baseCss (ex. bulletin de paie).
* CSS de l'en-tête officiel (ministériel + logo). **Source unique** : tous les
* documents (bulletin, bulletin de paie, export statistiques, certificats et
* attestations) partagent exactement cette en-tête. Ne pas redéfinir les
* règles `.mh-*` ou `.doc-mheader` ailleurs — {@see baseCss()} la compose.
*/
public function headerCss(): string
{
Expand All @@ -393,22 +395,12 @@ public function headerCss(): string

protected function baseCss(): string
{
return <<<CSS
// L'en-tête ministérielle (règles .mh-* / .doc-mheader) vient d'une source
// unique — headerCss() — pour que les documents officiels partagent
// exactement l'en-tête du bulletin. Ici, seuls le corps et la signature.
return $this->headerCss() . <<<CSS
* { box-sizing: border-box; }
body { font-family: 'DejaVu Sans', sans-serif; color: #1a1a1a; font-size: 13px; line-height: 1.6; margin: 0; }
/* En-tête officiel ministériel (3 colonnes) */
.doc-mheader { margin-bottom: 32px; border-bottom: 1px solid #000; padding-bottom: 16px;
font-family: 'DejaVu Serif', 'Times New Roman', serif; }
.mh-table { width: 100%; border-collapse: collapse; }
.mh-col { vertical-align: middle; text-align: center; padding: 0 6px; }
.mh-logo { width: 24%; }
.mh-logo-img { max-width: 100px; max-height: 100px; object-fit: contain; }
.mh-ministere { font-size: 11px; text-transform: uppercase; font-weight: bold; letter-spacing: 0.4px; }
.mh-school { font-size: 17px; font-weight: bold; text-transform: uppercase; margin: 4px 0; }
.mh-info { font-size: 10.5px; }
.mh-republic { font-size: 13px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.8px; }
.mh-motto { font-size: 11px; font-style: italic; font-weight: bold; margin-top: 2px; }
.mh-sep { width: 50px; height: 1px; background: #000; margin: 5px auto; }
.doc-body { margin: 28px 0; }
.doc-body h1, .doc-body h2 { text-align: center; text-transform: uppercase; }
.doc-signature { margin-top: 48px; text-align: right; }
Expand Down
15 changes: 10 additions & 5 deletions app/Services/StatisticsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,24 @@ public function financeStats(array $filters): array
$billed = (float) ($stats->billed ?? 0);
$collected = (float) ($stats->collected ?? 0);

// Recouvrement par classe
// Recouvrement par classe (avec cycle + reste, pour filtre et charts détaillés)
$byClass = (clone $inv)
->join('classes', 'enrollments.class_id', '=', 'classes.id')
->selectRaw('classes.name AS name,
->leftJoin('classroom_types', 'classroom_types.id', '=', 'classes.classroom_type_id')
->selectRaw('classes.name AS name, classroom_types.name AS cycle,
COALESCE(SUM(invoices.total), 0) AS billed,
COALESCE(SUM(invoices.amount_paid), 0) AS collected')
->groupBy('classes.name')
->orderByDesc('billed')
COALESCE(SUM(invoices.amount_paid), 0) AS collected,
COALESCE(SUM(invoices.amount_remaining), 0) AS remaining')
->groupBy('classes.name', 'classroom_types.name', 'classes.expected_age')
->orderByRaw('classes.expected_age NULLS LAST')
->orderBy('classes.name')
->get()
->map(fn ($r) => [
'name' => $r->name,
'cycle' => $this->cycleLabel($r->cycle),
'billed' => (float) $r->billed,
'collected' => (float) $r->collected,
'remaining' => (float) $r->remaining,
'rate' => $r->billed > 0 ? round($r->collected / $r->billed * 100, 1) : 0.0,
]);

Expand Down
Loading
Loading