# CHANGELOG — Socle de gestion

Une entrée par version livrée. Le détail de chaque lot — le pourquoi, la recette, et ce qu'il faut
vérifier après déploiement — vit dans son rapport : `docs/rapports/RAPPORT-Vx.y.z.md` à partir de
la v3.10.0, à la racine du dépôt pour les versions antérieures.

Sauf mention contraire, chaque version se livre **à l'identique aux deux installations**
(IRADA VENTURE et Ruwad Capital Group) : c'est un socle, pas deux applications.

---

## v3.14.1 — 25/08/2026

**HOTFIX : l'analyse IA d'un gros dossier d'AO expirait**
→ [`docs/rapports/RAPPORT-V3.14.1.md`](docs/rapports/RAPPORT-V3.14.1.md)

**Aucune migration, aucune dépendance, aucun `.env` dans le paquet.**

### Corrigé
- **L'analyse IA d'un gros dossier d'AO ne meurt plus en plein appel** (constaté en prod sur le
  CPS signalétique 37-SMIT : `TimeoutExceededException` dans `failed_jobs`, fenêtre d'analyse
  qui tourne sans fin) : le plafond du Job `RunAiAnalysis`, **codé en dur à 280 s**, est
  désormais lu dans la configuration — `AI_JOB_TIMEOUT_SECONDS`, défaut **840 s**, sous les
  900 s du worker. `$tries` reste à 1.
- **Un délai réseau dépassé affiche une raison, pas une trace de framework** : au point de
  sortie HTTP, une `ConnectionException` de délai (cURL 28) devient une `AiException` au message
  français (« L'analyse a pris trop de temps — dossier trop volumineux ou service momentanément
  lent. Réessayez, ou réduisez le dossier. »). Le run est marqué en échec avec sa raison, les
  pièces sont conservées pour une relance et l'utilisateur est notifié.

### Ajouté
- **Garde-fou de taille des analyses IA** : la masse **totale** de texte extrait transmise au
  modèle est bornée (`AI_MAX_DOCUMENT_CHARS`, défaut 200 000 caractères) et la coupure est
  signalée dans le contenu. Un dossier normal passe intact.
- **La chaîne des délais est documentée** (DEPLOIEMENT.md §3, tableau) : appel HTTP
  < plafond du Job < `--timeout` du worker < `retry_after` de la file. L'ordre n'est pas
  négociable — le maillon le plus court doit toujours être celui qui échoue proprement.

---

## v3.14.0 — 22/08/2026

**Cadre du cachet à taille réelle + le devis rejoint la facture**
→ [`docs/rapports/RAPPORT-V3.14.0.md`](docs/rapports/RAPPORT-V3.14.0.md)

**Une migration additive et idempotente** (`quotes` : marché lié, références, préférences
d'affichage). Aucune dépendance, pas de montée de vendor.

### Corrigé
- **Le cadre « Cachet et signature » reçoit un tampon encreur physique** (constaté sur le scan
  de la 16-2026 : le tampon débordait sous un cadre de 1,5–2,1 cm) : hauteur intérieure utile
  **≥ 3,8 cm**, identique en confort ET en compact — un espace de tamponnage ne se comprime
  pas — images cachet/signature agrandies en proportion, strictement contenues, ratio conservé.
  La densité adaptative absorbe le surcoût (compact resserré d'un cran sur les zones non
  critiques : les trois profils de recette gardent leur pagination).

### Ajouté
- **Le PDF du devis rejoint la facture** (gabarit refondu sur le modèle exact v3.13.6) :
  « DEVIS N° X » sur une ligne, bloc références (Réf. marché / N° AO / Objet / Validité),
  sections avec sous-totaux masquables et numéros de lignes masquables, marges 1,0 cm,
  désignation ≥ 58 %, montant en lettres, coordonnées bancaires, **deux cadres de signature à
  taille de tampon** (prestataire / bon pour accord client), **pied 2 lignes ancré au bas de
  chaque page**, **densité adaptative** (téléchargement ET e-mail au client, règle d'or
  v3.13.6 respectée).
- **Le devis se rattache à un marché** : sélecteur « Marché lié » au formulaire, références,
  n° d'AO, objet et client pré-remplis comme sur la facture (modifiables) ; colonnes miroir
  sur `quotes` (`project_id`, `market_reference`, `tender_reference`,
  `show_section_subtotals`, `show_line_numbers` — défauts = rendu actuel).
- **La conversion devis → facture emporte tout** : marché, références et préférences
  d'affichage suivent (les sections suivaient déjà).

## v3.13.6 — 22/08/2026

**HOTFIX : le PDF de facture sortait illisible en production**
→ [`docs/rapports/RAPPORT-V3.13.6.md`](docs/rapports/RAPPORT-V3.13.6.md)

**Aucune migration, aucune dépendance, pas de montée de version vendor.**

### Corrigé
- La densité adaptative v3.13.5 retournait au contrôleur une instance dompdf **déjà rendue**
  (rendue pour compter les pages) ; sur le vendor plus ancien du serveur, `stream()`/
  `download()` re-rendaient la même instance → **double rendu, glyphes corrompus** (texte
  illisible en prod, parfait en dev). Règle d'or désormais codée et testée : les rendus de
  mesure sont des instances **jetables**, `DensitePdf::resoudre()` retourne une instance
  **neuve jamais rendue** — le `stream()`/`download()`/`output()` de l'appelant fait l'unique
  rendu, quelle que soit la version du vendor. Au pire trois rendus par PDF (négligeable).

## v3.13.5 — 22/08/2026

**Facture PDF : mise en page intelligente + dernières retouches**
→ [`docs/rapports/RAPPORT-V3.13.5.md`](docs/rapports/RAPPORT-V3.13.5.md)

**Aucune migration, aucune dépendance.**

### Ajouté
- **Densité adaptative** (`App\Support\DensitePdf`) : deux préréglages du gabarit —
  « confort » (aéré, l'esprit d'avant v3.13.3) et « compact » (v3.13.3) — choisis par
  **mesure du rendu dompdf réel**, jamais au nombre de lignes : confort si la facture tient
  sur une page ; compact si c'est lui qui la fait tenir ; sinon confort multi-pages (mieux
  vaut deux pages aérées qu'une compression illisible). Jamais plus de deux rendus (mémoïsé).
  Appliqué au téléchargement ET au PDF joint aux e-mails du Courrier. Devis non concerné
  (gabarit distinct — voir rapport).

### Modifié
- **« FACTURE N° 16-2026 » sur une seule ligne** (numéro en gras taille 18 px — un
  « 05-2026 bis » tient) ; dessous ne restent que Date et Échéance.
- **Désignation élargie à 58 %** (62 % sans numéros de lignes), prise sur N°/Qté/Unité ;
  PU HT / Total HT à 13 %, toujours larges pour les plus gros montants.
- **Marges haut/gauche/droite réduites à 1,0 cm** (marge basse inchangée : le pied 2 lignes).
- **Cadre du cachet élargi à ~49 %** de la largeur, images cachet/signature agrandies en
  proportion ; bloc Règlement intact.

## v3.13.4 — 22/08/2026

**Encaisser une facture « pour compte de tiers » ne touche plus la trésorerie**
→ [`docs/rapports/RAPPORT-V3.13.4.md`](docs/rapports/RAPPORT-V3.13.4.md)

**Aucune migration, aucune dépendance.**

### Corrigé
- **Bug avec dégât réel** : « Marquer encaissée » sur une facture `hors_tresorerie` créait une
  vraie transaction de trésorerie (6 encaissements fictifs, 50 593,20 DH, constatés en prod).
  Désormais `markCollected()` passe la facture en « Encaissée » (date, mode de règlement sur la
  facture, référence consignée dans les notes internes) **sans créer aucune transaction** ni
  toucher aucun solde ; le décompte lié suit le même passage d'état (transaction liée nulle).
- **La modale « Marquer encaissée »** ne demande plus de compte crédité pour une facture pour
  compte de tiers (il n'y en a pas) et l'annonce clairement : « aucun mouvement de trésorerie
  ne sera créé ». Pour une facture normale, rien ne change (compte requis, transaction créée).
- **« Annuler l'encaissement »** d'une facture hors trésorerie remet « Émise » sans chercher de
  transaction à supprimer.

## v3.13.3 — 22/08/2026

**Facture PDF : pied en 2 lignes, désignation élargie, une page**
→ [`docs/rapports/RAPPORT-V3.13.3.md`](docs/rapports/RAPPORT-V3.13.3.md)

**Aucune migration, aucune dépendance.** Un seul fichier de vue (+ tests).

### Corrigé
- **Pied légal compacté en 2 lignes** (identité fiscale complète — société, Capital, CNSS,
  ICE, RC, IF, TP — sur la première, Banque — RIB sur la seconde, police 7,5 px) ; la marge
  basse de `@page` passe de 2,3 à 1,8 cm et rend la place au contenu. Le pied reste ancré au
  bas de chaque page, sans chevauchement (re-vérifié au rendu réel).
- **Désignation élargie à 52 %** (57–65 % selon colonnes masquées), prise sur N° (6→5 %),
  Qté (9→7 %) et Unité (12→8 %) ; PU HT et Total HT inchangés. Mécanique v3.13.2 conservée.
- **La facture type — profil de la 16-2026 réelle : 10 lignes, 2 sections, RAS TVA/IS, RIB,
  signature — tient sur une page A4** (fixture `InvoicePdfLayoutTest` ajustée, comptage de
  pages sur le rendu dompdf réel) ; densité encore resserrée d'un cran, multi-pages propre
  au-delà.

## v3.13.2 — 22/08/2026

**Facture PDF : derniers réglages avant déploiement**
→ [`docs/rapports/RAPPORT-V3.13.2.md`](docs/rapports/RAPPORT-V3.13.2.md)

**Une migration additive** (trois colonnes nullables/booléennes, aucune donnée modifiée).
Aucune commande de reprise. Aucune dépendance nouvelle.

### Ajouté
- **Deux options d'affichage par facture** (section « Options d'affichage », oui par défaut =
  rendu actuel) : « Afficher les sous-totaux de section » (à non, les bandeaux restent, les
  lignes « Sous-total HT — … » disparaissent — masquée si la facture n'a pas de sections) et
  « Afficher les numéros de lignes » (à non, la colonne N° disparaît, la largeur revient à la
  désignation).
- **N° d'appel d'offres** (`tender_reference`) : champ optionnel pré-rempli depuis l'AO du
  marché lié (repli : référence AO saisie sur le marché), modifiable, effaçable ; imprimé
  « N° AO : … » dans le bloc références, seulement s'il est renseigné.

### Corrigé
- **Le pied légal (ICE/RC/IF/TP + banque) descend au bas de la feuille**, sur chaque page :
  il est ancré dans la marge basse de `@page` (recette dompdf, `bottom` négatif) au lieu de
  coller au contenu ; le contenu ne peut plus le chevaucher, la marge lui est réservée.
- **Une facture courante tient sur une seule page A4** (vérifié au rendu réel : 8 lignes,
  2 sections, RAS TVA + IS, RIB, signature) : interlignes, paddings et marges verticales
  resserrés, bloc signature compacté — densité seule, contenu et hiérarchie inchangés. Le
  multi-pages reste propre (en-tête de tableau répété, pied partout).

## v3.13.1 — 22/08/2026

**Factures : bouton « Émettre » — sortir une facture du brouillon**
→ [`docs/rapports/RAPPORT-V3.13.1.md`](docs/rapports/RAPPORT-V3.13.1.md)

**Aucune migration. Aucune commande de reprise. Aucune dépendance nouvelle.**

### Corrigé
- **Une facture créée dans l'app pouvait rester bloquée en brouillon** : aucune action de
  l'interface ne permettait de la passer en « Émise », donc elle n'entrait jamais dans les
  registres de revenu, les créances ni les positions de marché. Les factures historiques
  venaient de la reprise déjà émises — le trou n'était jamais apparu.

### Ajouté
- **Action « Émettre »** dans le menu de ligne de la liste des factures ET en en-tête de la
  page d'édition — visible seulement sur un brouillon, gated par la même famille de permission
  que l'encaissement (`invoices.collect`), avec confirmation : le numéro devient définitif
  (art. 145 CGI). Passage en « Émise » par `InvoiceService::markIssued()` (totaux refigés).
- **Retour « Repasser en brouillon »** réservé au super-admin, et seulement tant que la
  facture n'est ni encaissée ni annulée (`InvoiceService::markDraft()`, policy `unissue`).

### Modifié
- **« Marquer encaissée » n'apparaît plus sur un brouillon** : une facture s'émet d'abord,
  s'encaisse ensuite. Le service refuse aussi le raccourci (`markCollected` sur un brouillon →
  message clair), quelle que soit la voie d'appel.

## v3.13.0 — 22/08/2026

**Factures : sections de lignes, éléments à la carte, numérotation assainie**
→ [`docs/rapports/RAPPORT-V3.13.0.md`](docs/rapports/RAPPORT-V3.13.0.md)

**Deux migrations additives** (aucune donnée modifiée). Aucune commande de reprise. Aucune
dépendance composer nouvelle.

### Corrigé
- **La suggestion de numéro de facture redevient la vraie suite de l'année.** La bande
  hors-série ≥ 900 (factures « bis » de la reprise) n'entre plus dans `nextSequence()` : avec
  {…, 15, 900, 905} en base, la suggestion est **16** — plus jamais 906. La bande 900+ reste
  intouchée.

### Ajouté
- **Numéro saisissable** à la création et modifiable tant que la facture n'est pas émise
  (format NN-AAAA, unicité contrôlée, message clair) ; une facture émise garde son numéro
  (art. 145 CGI — champ désactivé, rien ne change en base).
- **Sections de lignes** (factures ET devis) : un sous-titre facultatif par ligne ; les lignes
  consécutives de même titre forment une section — bandeau pleine largeur sur le PDF,
  « Sous-total HT » de section, numérotation continue, totaux généraux inchangés. La conversion
  devis → facture conserve les sections. Sans section, rien ne change.
- **Éléments « à la carte »** du gabarit de facture, chacun imprimé seulement s'il est renseigné
  (section « Options d'affichage » repliée par défaut) : période de la prestation ou date de
  livraison ; n° de bon de commande / décompte ; **remise** par ligne et/ou globale (HT brut,
  remise, HT net — TVA et RAS repartent du HT net) ; **acompte déjà facturé** (« Acompte déduit »,
  « Reste à payer ») ; **retenue de garantie** (% du TTC, déduite du net) ; mention d'exonération
  de TVA (régime sans TVA) ; notes libres en pied de facture. La colonne Unité se masque si
  aucune ligne n'en a. La variante acheteur masque toujours les retenues à la source.
- Non-régression de rendu : 24 factures fixtures représentatives, deux variantes, aucun marquage
  nouveau (`InvoicePdfNonRegressionTest`) ; chaîne de calcul au centime (`InvoiceOptionsTest`).

---

## v3.12.1 — 19/08/2026

**Correctif : l'avancement de facturation des marchés retombait à 0 %**
→ [`docs/rapports/RAPPORT-V3.12.1.md`](docs/rapports/RAPPORT-V3.12.1.md)

Aucune migration. **Aucune donnée n'avait bougé** — c'est un calcul en mémoire qui était faux.

### Corrigé
- **Régression de la v3.12.0.** L'optimisation de `Project::sommeDecomptes()` comparait, dans sa
  branche « en mémoire », un objet `MilestoneStatus` à la chaîne `'encaisse'` : jamais égaux. La
  somme valait 0, et **tous** les marchés affichaient « 0 % » d'avancement de facturation, décompte
  définitif encaissé compris. La branche base de données, elle, était correcte. Corrigé en
  comparant sur la **valeur** de l'enum, comme le fait la base. L'optimisation est conservée : la
  liste des marchés coûte toujours 6 requêtes, pas 105.
- **Antérieur à ce lot, trouvé pendant l'audit** : dans les suggestions de destinataires du
  Courrier, `$u->type === 'investor'` n'était jamais vrai (`User::$type` est casté en enum) —
  **aucun investisseur n'a jamais porté son libellé** dans cette liste. Portée : un libellé
  d'affichage, rien d'autre.

### Ajouté
- `PariteDesTotauxDecomptesTest` — les deux branches de `sommeDecomptes()` (base et mémoire)
  doivent rendre le **même montant au centime**, et non zéro des deux côtés. Une optimisation qui
  change un chiffre n'est pas une optimisation.

### L'audit
Le même piège — un attribut casté en enum comparé à une valeur brute dans un prédicat de
**Collection** — a été cherché dans tout `app/`, sur la soixantaine d'attributs concernés. **Une
seule autre occurrence**, celle du Courrier ci-dessus. Le détail de ce qui a été écarté (et
pourquoi) est dans le rapport.

---

## v3.12.0 — 19/08/2026

**Le blog reprend ses tableaux, l'admin cesse de tout recharger à chaque écran**
→ [`docs/rapports/RAPPORT-V3.12.0.md`](docs/rapports/RAPPORT-V3.12.0.md)

Aucune migration. Aucune commande de reprise. Aucune dépendance composer nouvelle.

### Corrigé
- **Un tableau saisi dans un article n'est plus détruit à l'enregistrement.** La liste blanche de
  `HtmlSanitizer` ignorait `table`/`tr`/`td` : HTMLPurifier les découpait, en silence, au save.
  ⚠️ Les tableaux des **anciens** articles ne sont pas récupérables (ils n'ont jamais été stockés) :
  ils sont à **re-saisir**.
- **Trois N+1** : la liste des marchés passe de **105 à 6** requêtes, la paie de **30 à 10**, les
  cautions de **19 à 5**. `Project::collectedAmountTtc()` et `Payroll::$paid_amount` lisaient la
  base ligne par ligne alors que la relation était déjà chargée ; les cautions et les tiers
  manquaient d'un `with()`.
- Les **logos partenaires** de la vitrine ne sont plus en noir et blanc : le `grayscale` au repos
  les laissait gris pour toujours sur mobile, où il n'y a pas de survol. Cartes agrandies
  (180×110 → 260×150), proportions gardées.

### Ajouté
- **Barre d'outils complète** pour les corps riches de la vitrine (`CmsForm::corps()`) : tableau,
  H2/H3/H4, alignements (dont justifié), listes, citation, code en ligne et bloc de code, filet
  horizontal, liens, images, annuler/rétablir. Règle de la barre : **un bouton n'y figure que si
  son résultat traverse le sanitizer** — un test le vérifie.
- **RTL arabe** : les champs arabes du back-office s'écrivent de droite à gauche (corps, titre,
  extrait, sur-titre, méta SEO) ; l'article rendu en locale `ar` porte `dir="rtl"` sur son entête
  et son corps. Le français reste LTR.
- **Mode SPA** sur le panneau d'administration : la navigation ne recharge plus ~1,7 Mo de CSS/JS
  à chaque clic. Les points de téléchargement (PDF, tableurs, justificatifs, sauvegardes) sont
  déclarés en exception dans `App\Support\NavigationSpa` — vingt-sept motifs, tenus par un test.
- Réglage **`cms.social_instagram_2`** (facultatif, Contenu → Vitrine) : un **second compte
  Instagram** dans le pied de page, la page Contact et le `sameAs` du JSON-LD. Vide, comportement
  inchangé. Valeurs par défaut du semis IRADA mises à jour (`irada_incubateur` + `irada_prod`).
- `CoutParEcranTest` — ouvrir une liste de trois fois plus de lignes ne doit pas coûter une
  requête de plus. Sept écrans.

### Sécurité
- La liste blanche s'élargit, **la garde ne bouge pas** : `style` n'est ouvert que sur les blocs
  alignables, et `CSS.AllowedProperties` n'y laisse passer que `text-align`. `<script>`, `<style>`,
  `<iframe>`, les attributs `on*` et les URL `javascript:`/`data:` restent retirés — y compris à
  l'intérieur d'un tableau.

### Déploiement
Rien à lancer. Deux gestes facultatifs : saisir le second Instagram dans Contenu → Vitrine, et
re-saisir les tableaux des anciens articles. Un rechargement complet du navigateur est recommandé
la première fois (le mode SPA change le chargement des scripts du panneau).

---

## v3.11.0 — 19/08/2026

**Le fonctionnement interne devient une dimension, plus un marché**
→ [`docs/rapports/RAPPORT-V3.11.0.md`](docs/rapports/RAPPORT-V3.11.0.md)

⚠️ **Migration de données. Sauvegarde de la base OBLIGATOIRE avant.** Pas de commande de reprise.

### Changé
- **« Fonctionnement interne » n'est plus un projet.** Une opération, une heure ou une charge
  **sans marché** (`project_id = null`) EST du fonctionnement interne, par définition. Il n'y a
  plus rien à sélectionner : le champ « marché » reste vide.
- Le faux marché disparaît de **tous** les sélecteurs — saisie d'opération, saisie rapide, devis,
  feuille de temps, affectations, KPI, listes de marchés — d'un seul coup, sans filtre par écran.
- `AllocationEngine` écrit `project_id = null` là où il écrivait sur le projet interne. **Aucune
  règle de calcul, aucune clé, aucun arrondi ne change.**
- Feuille de temps : la grille porte toujours, en tête, la ligne « Fonctionnement interne » ; une
  ligne sans marché s'affiche sous ce libellé partout (grilles, vues d'équipe, liste admin,
  export Excel). Le champ « Marché » du formulaire admin n'est plus obligatoire.
- « Répartition des coûts » : un **bloc « Fonctionnement interne » distinct** (coût RH des heures
  sans marché, charges non rattachées, **CA = 0**) et le contrôle
  `Σ marchés + interne = coût total de l'entreprise`.
- « Coût RH non affecté » raccordé à la même source de vérité, sans duplication.

### Ajouté
- `App\Services\Interne\FonctionnementInterneService` — le calcul du coût interne, en un seul
  endroit. `CostAllocation::scopeSansMarche()`.

### Retiré
- `Project::internal()` et `InternalProjectSeeder` : le concept n'existe plus, et un commentaire
  posé à leur place explique pourquoi ils ne doivent pas revenir.

### Corrigé
- `ProjectFactory` ne tire plus le type `interne` au hasard — une source d'échecs de tests
  intermittents depuis toujours.

### L'invariant
Le coût du fonctionnement interne calculé la nouvelle façon est **égal au centime** à ce que le
projet interne portait avant, et le coût de chaque marché est **strictement inchangé** :
`InvariantDesMontantsTest` le prouve en calculant un mois avant migration, puis après.

### Déploiement
1. **`php artisan backup:run --only-db`**, vérifiée présente et non vide ;
2. relever le « Coût total de l'entreprise » du mois en cours ;
3. `php artisan migrate --force` ;
4. rouvrir l'écran : **le total doit être identique au centime**. Sinon, restaurer la sauvegarde.

Aucun recalcul n'est nécessaire : les quotes-parts déjà calculées sont repointées elles aussi.

---

## v3.10.0 — 19/08/2026

**Jours fériés ↔ feuilles de temps · menu simplifié · UX unifiée**
→ [`docs/rapports/RAPPORT-V3.10.0.md`](docs/rapports/RAPPORT-V3.10.0.md)

### Ajouté
- **Jours fériés** (`holidays`) — nouvelle notion, nouvel écran **RH → Jours fériés**, liste
  groupée par année, statut « Validé » / « Proposé », validation unitaire et groupée, corbeille
  visible et restaurable (`date` est unique corbeille comprise : une ligne effacée continue
  d'occuper sa date).
- **Proposition du calendrier marocain** — action d'en-tête avec champ Année : 10 fêtes civiles à
  date fixe pour n'importe quelle année, 6 fêtes religieuses en dates **estimées** pour 2026 et
  2027 seulement. Tout naît **non validé**, l'action est rejouable sans doublon, et aucune date
  religieuse n'est extrapolée hors des années connues.
- `App\Services\Timesheets\HolidayService`, `App\Models\Holiday`, `App\Enums\HolidayType`,
  `App\Policies\HolidayPolicy`.
- Permissions `holidays.view / create / update / delete / confirm` — `confirm` est distincte :
  valider un férié décide qu'un jour cesse d'être réclamé à toute l'entreprise.

### Modifié
- `TimesheetService` — un jour férié **validé** n'est jamais « manquant » et ne déclenche aucune
  relance, exactement comme un congé approuvé. `monthGrid()` et `teamWeek()` portent désormais
  `on_holiday` et `holiday_label`. Un férié **proposé** reste sans effet.
- **La saisie reste possible un jour férié** : `saveDay()` et `dayLocked()` sont inchangés — le
  jour n'est pas verrouillé, il est seulement signalé.
- Affichage du férié (pastille + libellé de la fête) sur « Ma semaine », « Ma journée », « Suivi de
  mon équipe » et la « Matrice équipe » de l'admin.
- **Menu : 9 groupes → 7.** « Marchés », « Appels d'offres » et « Rapports » fusionnent en
  **« Marchés & appels d'offres »**. Aucun écran ne disparaît.
- **Trésorerie** et **RH** réordonnées : les écrans du quotidien remontent en tête de groupe.
- Deux libellés clarifiés : « Timesheets » → « Feuilles de temps », « Comptes » → « Comptes
  bancaires ».

### Déploiement
- **Une migration** : `php artisan migrate --force` (table `holidays`).
- **Puis** `php artisan db:seed --class="Database\Seeders\RolesAndPermissionsSeeder" --force`,
  sans quoi les permissions `holidays.*` n'existent pas et l'écran reste invisible à tous sauf au
  super-admin.
- Aucune commande de reprise. La Partie B est du code seul.
- ⚠️ Une installation qui avait masqué « Marchés », « Appels d'offres » ou « Rapports » verra ces
  écrans réapparaître : les libellés masqués n'existent plus. Rien n'est perdu, tout est re-rangé.

---

## Versions antérieures

Le détail de chacune vit dans son rapport, à la racine du dépôt.

| Version | Date | Sujet |
|---|---|---|
| v3.9.2 | 18/08/2026 | Les couleurs de marque quittent le semis, le garde anti-identité-en-dur redevient intact |
| v3.9.1 | 18/08/2026 | Semis de la vitrine IRADA — commande de reprise gardée, qui se refuse ailleurs |
| v3.9.0 | 15/08/2026 | Ordre de lecture aligné sur MOBDIE, PDF qui se referment, logos nets |
| v3.8.6 | 13/08/2026 | La fiche marché se lit en quatre blocs et cesse de se répéter |
| v3.8.5 | 13/08/2026 | Le dépôt rejoint ce qui tourne en production chez IRADA |
| v3.8.0 | 12/08/2026 | Deux leviers de répartition des alimentations |
| v3.7.0 | 12/08/2026 | La cible, c'est le journal ; une créance cesse de passer pour une perte |
| v3.6.x | 11/08/2026 | Cautions, trésorerie et répartition des alimentations |
| v3.5.0 | 10/08/2026 | Groupes de navigation masquables (masquer n'est pas supprimer) |
| v3.4.0 | 10/08/2026 | Notes de frais côté collaborateur, traçabilité des chiffres |
| v3.3.x | 10/08/2026 | GED, notes de frais signables, groupements d'AO |
| v3.2.x | 07–09/08/2026 | Module Courrier |
