# Rapport — v3.10.0

**Lot du socle : se livre aux DEUX installations (IRADA et RUWAD), à l'identique.**
Deux parties, deux commits. **Une migration** (Partie A). **Aucune commande de reprise.**

---

## Pourquoi ce lot

### Partie A — Les jours fériés n'existaient pas

Le socle savait déjà qu'un **congé approuvé** ne se réclame pas : `TimesheetService` retire ces
jours des « manquants », et aucune relance ne part dessus. Il ne savait **rien** des jours fériés.
Un 30 juillet, l'application réclamait donc des heures à toute l'entreprise, relançait chacun le
lendemain matin, et affichait le jour en rouge sur la matrice du gérant.

Ce lot fait pour les fériés exactement ce qui existait pour les congés — ni plus, ni moins.

**Deux règles, posées par l'exploitant, tenues par le code :**

1. **Le logiciel propose, l'exploitant valide.** L'application pré-remplit le calendrier marocain,
   mais **rien n'agit tant que ce n'est pas validé**. C'est la colonne `confirmed`. Un férié
   « proposé » n'a aucun effet : le jour reste réclamé comme avant.

   Ce n'est pas de la prudence de principe. Les fêtes religieuses marocaines suivent le calendrier
   lunaire : leurs dates ne sont officielles qu'après l'annonce du ministère, observation du
   croissant faite. Un férié qui agirait sans validation dispenserait toute l'entreprise de saisie
   sur une date que personne n'a confirmée — et l'erreur ne planterait pas, elle réussirait.

2. **Jamais réclamé, mais toujours saisissable.** Un jour férié validé sort des « manquants » et
   des relances, **sans être verrouillé** : qui a exceptionnellement travaillé saisit ses heures,
   exactement comme un samedi. Le verrou (`dayLocked`) reste réservé aux journées approuvées.

### Partie B — Le menu était devenu long

Neuf groupes de navigation, dont trois minuscules qui parlaient du même métier (**Marchés**,
**Appels d'offres**, **Rapports** : 8 écrans à eux trois) et deux surchargés (**Trésorerie** 18
écrans, **RH** 17), rangés dans un ordre historique où les écrans du quotidien se retrouvaient
derrière les référentiels.

On a fusionné les trois petits et réordonné les deux gros. **Aucun écran n'a disparu.**

---

## Ce que le lot contient

### Partie A — Jours fériés ↔ feuilles de temps

| | |
|---|---|
| Migration | `2026_08_19_100000_create_holidays_table` — table `holidays` : `date` (**unique**), `label`, `type`, `confirmed` (défaut **false**), timestamps, softDeletes |
| Modèle | `App\Models\Holiday` (audité, soft-deletes, scopes `confirmed()` / `proposed()`) + `App\Enums\HolidayType` (`national` / `religieux` / `manuel`) |
| Policy | `App\Policies\HolidayPolicy` — permissions `holidays.view / create / update / delete / confirm` |
| Service | `App\Services\Timesheets\HolidayService` — `confirmedHolidayDates()`, `confirmedHolidayLabels()`, `proposer(int $annee)` |
| Écran | **RH → Jours fériés**, liste groupée par année, badge « Validé » / « Proposé », filtres année / statut / type / corbeille |

**`holidays.confirm` est une permission à part.** Créer ou corriger une ligne proposée est du
travail de secrétariat ; la **valider**, c'est décider qu'un jour cesse d'être réclamé à toute
l'entreprise. Les deux gestes n'ont pas le même poids, ils n'ont pas la même permission. Le rôle
`gestionnaire` reçoit `view / create / update / confirm` ; effacer une ligne du calendrier reste un
geste de la gérance.

**Le branchement** (`TimesheetService`), calqué ligne pour ligne sur le traitement des congés :

- `missingWorkingDays()` — un férié **validé** n'est jamais « manquant » ;
- `monthGrid()` — chaque jour porte `on_holiday` (bool) et `holiday_label` (string|null) ;
- `teamWeek()` — mêmes deux clés, et le jour n'est plus compté « manquant » ;
- `employeesWithMissingDays()`, `leadsWithMissingTeam()`, `TimesheetReminder`,
  `TeamTimesheetReminder`, le widget « Timesheets manquants », le digest du gérant et les alertes
  du tableau de bord **héritent** tous de `missingWorkingDays()` — rien à modifier chez eux, et
  c'est vérifié par des tests plutôt que supposé ;
- `saveDay()` / `dayLocked()` — **inchangés**. Un férié ne verrouille rien.

**Affichage** — pastille orange + libellé de la fête, à côté du bleu déjà utilisé pour les congés :
« Ma semaine » (en-tête de colonne, champ de saisie laissé ouvert dessous), « Ma journée »
(bandeau d'information, **jamais** le bandeau 🔒 qui, lui, désactive les champs), « Suivi de mon
équipe » et la « Matrice équipe » de l'admin.

**La proposition du calendrier marocain** — action d'en-tête, champ Année :

- **10 fêtes civiles à date fixe**, générées pour n'importe quelle année (Nouvel An, Manifeste de
  l'Indépendance, Yennayer, Fête du Travail, Fête du Trône, Oued Ed-Dahab, Révolution du Roi et du
  Peuple, Fête de la Jeunesse, Marche Verte, Fête de l'Indépendance) ;
- **6 fêtes religieuses**, connues pour **2026 et 2027 seulement**, en dates **estimées** ;
- pour toute autre année : **seules les fixes** sont créées, et une notification le dit —
  *« Les fêtes religieuses de {année} ne sont pas connues à l'avance (calendrier lunaire) —
  ajoutez-les à la main, puis validez-les. »* **Aucune date religieuse n'est extrapolée** : une
  date lunaire déduite d'une formule serait fausse une année sur trois ;
- tout naît en `confirmed = false` ; **rejouable sans doublon** (les dates déjà présentes sont
  sautées, y compris celles mises à la corbeille : une ligne effacée à la main ne revient pas
  d'elle-même) ;
- Achoura, une éventuelle « Fête de l'Unité » (31/10) et le second jour d'Aïd al-Mawlid ne sont
  **pas** proposés (statut incertain) — ils s'ajoutent à la main, et naissent alors **validés**.

### Partie B — Menu simplifié, UX unifiée

**9 groupes → 7.** Fusion de **Marchés**, **Appels d'offres** et **Rapports** en
**« Marchés & appels d'offres »** : Marchés → Cautions immobilisées → Répartition des coûts →
Appels d'offres → Concurrents → Acheteurs publics → Dossier administratif → Reprise de la TVA.

**Trésorerie réordonnée** : Opérations → Comptes bancaires → Comptage de caisse → Devis →
Factures → Bons de livraison → Charges fixes → Comptes courants associés → Prêts externes →
Investissements → Prévisionnel → Échéances prévisionnelles → Cautions & RG → Contacts →
Catégories → TVA → IS → CPC.

**RH réordonnée** : Collaborateurs → Feuilles de temps → Congés → Calendrier des absences →
Droits à congés → Types de congés → **Jours fériés** → Paie du mois → Primes → Règles de primes →
Dépenses remboursables → Notes de frais → Affectations → Formations → Organigramme → Régimes →
Rapprochement CNSS → Coût RH non affecté.

**Deux libellés seulement** ont changé, ceux qui faisaient hésiter dans un menu français :
« Timesheets » → **« Feuilles de temps »**, « Comptes » → **« Comptes bancaires »** (il se
confondait avec « Comptes courants associés », juste en dessous). Rien d'autre n'a été renommé.

**UX unifiée** — le socle est un dépôt unique : la réorganisation part à l'identique aux deux
installations. Un test (`MemeInterfacePourToutesLesSocietesTest`) tient désormais la ligne :
aucune méthode `getNavigationGroup/Label/Sort` des trois panneaux ne lit un réglage, la liste des
sept groupes est écrite noir sur blanc dans le test, et aucun libellé de menu ne nomme une société.

**2FA** : rien n'a été touché. Elle est déjà optionnelle (`EnsureStaffTwoFactor`, réglage
`security.enforce_staff_2fa` / env `SECURITY_ENFORCE_STAFF_2FA`, **off par défaut, sans exception
super-admin**) et le reste.

---

## Recette

`php artisan test` — **1928 tests**, dont **28 nouveaux** :

- `tests/Feature/Holidays/JoursFeriesEtFeuillesDeTempsTest` (7) — le férié validé sort des
  manquants, le **même** férié non validé y reste ; `monthGrid` marque `on_holiday` +
  `holiday_label` ; `employeesWithMissingDays` et `leadsWithMissingTeam` ignorent les fériés
  validés ; `teamWeek` les marque ; **`saveDay()` fonctionne un jour férié et le jour n'est pas
  verrouillé** ; seules les dates validées remontent du service.
- `tests/Feature/Holidays/PropositionFeriesMarocainsTest` (7) — 16 fériés proposés pour 2026,
  **tous en attente** ; rejouable sans doublon ; une validation manuelle survit à une nouvelle
  proposition ; une date mise à la corbeille ne revient pas ; une année inconnue ne reçoit que les
  10 fixes ; **aucune date religieuse n'est inventée** (2028, 2029, 2031 vérifiées).
- `tests/Feature/Holidays/EcranJoursFeriesTest` (6) — l'écran exige `holidays.view` (un staff sans
  droit : 403), un collaborateur ne reste pas sur le panneau d'administration, l'action de
  proposition est idempotente, un férié saisi à la main naît **validé**, validation unitaire /
  groupée / dé-validation, **et un férié mis à la corbeille reste atteignable** — `date` étant
  unique corbeille comprise, une ligne effacée continue d'occuper sa date : sans le filtre
  « Corbeille » et l'action « Restaurer », l'exploitant se heurterait à « un jour férié existe déjà
  à cette date » devant une liste vide.
- `tests/Feature/Ui/MenuSimplifieTest` (5) — les trois anciens groupes n'existent plus, le groupe
  fusionné contient bien ses 8 écrans dans l'ordre, **chacun répond toujours en 200 et garde sa
  policy**, les jours fériés sont entre les congés et la paie.
- `tests/Feature/Ui/MemeInterfacePourToutesLesSocietesTest` (3) — cf. ci-dessus.

**Non-régression congés** : `LeaveTimesheetIntegrationTest`, `LeaveServiceTest`,
`TimesheetServiceTest`, `RemindMissingTimesheetsTest`, `TeamTimesheetsLeadTest`,
`MaJourneeEtRelanceTest` — verts, comportement inchangé.

`./vendor/bin/pint` ✅ · `npm run build` ✅ · tag `v3.10.0`.

⚠️ **Une seule anomalie connue, locale au Mac et non liée à ce lot** : `IdentiteDuSocleTest`
(« livre un jeu d'icônes neutres ») échoue sur cette machine parce que `public/images/` porte deux
artefacts du Finder — un fichier `Icon\r` et un dossier `site/` vide qui n'en contient qu'un autre.
Les deux sont **hors dépôt** (motif `Icon?` du `.gitignore`) et absents du serveur. Rien à corriger
dans le code ; supprimer les deux artefacts sur le Mac suffit à rendre ce test vert.

---

## À vérifier après déploiement

1. **La migration est passée** : `php artisan migrate --force`, puis la table `holidays` existe.
2. **Les nouvelles permissions existent** — sinon l'écran reste invisible à tout le monde sauf au
   super-admin :

   ```bash
   php artisan db:seed --class="Database\\Seeders\\RolesAndPermissionsSeeder" --force
   ```

   (Le seeder ne fait qu'ajouter et resynchroniser rôles et permissions ; il ne touche aucune
   donnée métier.)
3. **Le menu** : sept groupes, et **« Marchés & appels d'offres »** contient bien ses 8 écrans.
   ⚠️ Si l'installation avait masqué « Marchés », « Appels d'offres » ou « Rapports » dans
   *Réglages → Groupes de navigation*, ces libellés n'existent plus : les écrans **réapparaissent**
   dans le menu. C'est voulu — rien n'a été perdu, seulement re-rangé. Re-décocher le nouveau
   groupe si besoin.
4. **RH → Jours fériés** s'ouvre, entre « Types de congés » et « Paie du mois ».
5. **Proposer l'année en cours**, puis **valider** les dates retenues. Tant qu'un férié reste
   « Proposé », il n'a **aucun** effet — c'est normal, ce n'est pas une panne.
6. **Contrôle de bout en bout** : un jour férié validé et passé ne doit plus apparaître en rouge
   sur *RH → Feuilles de temps → Matrice équipe*, ni déclencher la relance du lendemain. Et un
   collaborateur doit toujours pouvoir y saisir des heures depuis « Ma journée ».
7. **Les fêtes religieuses de 2028 et au-delà** ne seront pas proposées : elles s'ajoutent à la
   main dès l'annonce officielle du ministère. C'est le fonctionnement attendu, pas un oubli.
