# Notification de fin de contrat (veille du dernier jour)

## Objectif
Prévenir les administrateurs, sur le dernier jour ouvré précédant la fin d'un contrat, qu'un
salarié arrive au terme de son emploi.

## Déclenchement
Commande `app:contract:end-notifications`, lancée chaque jour par le crontab de production
(ex. `0 6 * * *`). Option `--date=YYYY-MM-DD` pour test/rattrapage. Logger `cron`.

## Règle métier
- **Cible** : la **dernière** période de contrat d'un employé (aucune période ne lui succède).
  Un changement de contrat enchaîné (ex. CDD → CDI) ne notifie pas.
- **Quand** : sur le **dernier jour ouvré strictement avant** `endDate` (`endDate` est inclusif
  = dernier jour travaillé). Les week-ends ET jours fériés (`PublicHolidayService`, zone
  `metropole`) sont sautés. Concrètement, le jour J ouvré couvre les fins de contrat dans
  l'intervalle `]J ; prochain_jour_ouvré(J)]` — un vendredi notifie ainsi les fins du
  samedi, dimanche et lundi (mardi si lundi férié).
- **Jour de solidarité (Lundi de Pentecôte)** : traité comme un **jour ouvré** (choix
  délibéré). Le calcul s'appuie sur `getHolidaysDayByYears`, qui applique
  `EXCLUDED_PUBLIC_HOLIDAYS` (défaut = `"Lundi de Pentecôte"`) — la même liste de fériés que
  le reste de l'app (heures, congés, RTT). On évite ainsi une définition de « férié »
  divergente pour ce seul calcul ; et le jour de solidarité est, par nature, un jour travaillé
  (admins présents → la cloche est vue). Une fin de contrat le mardi après Pentecôte est donc
  notifiée le Lundi de Pentecôte, pas le vendredi précédent.
- **Destinataires** : tous les `ROLE_ADMIN`.
- **Message** : `Fin de {CDI|CDD|Intérim} de {Prénom Nom} le {dd/mm/yyyy}`, catégorie
  `Contrat`, cible `/employees/{id}`, sans acteur.

## Idempotence
Avant création, on vérifie l'absence d'une notif identique
`(recipient, category='Contrat', target, message)`. Le message étant unique par
(employé + date + nature), relancer la commande le même jour ne crée aucun doublon.

## Implémentation
- `App\Service\Notification\WorkingDayCalculator` — jour ouvré / prochain jour ouvré.
- `App\Service\Notification\ContractEndNotificationPlanner` — sélection + message (pur, testé).
- `App\Service\Notification\ContractEndNotificationService` — persistance (1 notif/admin).
- `App\Command\ContractEndNotificationCommand` — `app:contract:end-notifications`.
- `EmployeeContractPeriodRepository::findLatestPeriodsForAllEmployees`,
  `NotificationRepository::existsForRecipientCategoryTargetMessage`.
- Pas de migration : réutilise la table `notifications`.
