Files
SIRH/doc/contract-end-notifications.md
T
tristan 029a09dc09
Auto Tag Develop / tag (push) Successful in 17s
feat : notification de fin de contrat (veille ouvrée du dernier jour) (#35)
## Objectif
Prévenir automatiquement les administrateurs, sur le **dernier jour ouvré précédant la fin d'un contrat**, qu'un salarié arrive au terme de son emploi.

## Fonctionnement
- Commande quotidienne `app:contract:end-notifications` (à brancher sur le crontab prod, ~6h ; option `--date=YYYY-MM-DD` pour test/rattrapage).
- Cible **la dernière période de contrat** d'un employé (un changement de contrat enchaîné, ex. CDD→CDI, ne notifie pas).
- Notifie sur le **dernier jour ouvré strictement avant** `endDate` (inclusif). Week-ends **et fériés** sautés → une fin de contrat le lundi est signalée dès le vendredi. Le Lundi de Pentecôte reste un jour ouvré (cohérent avec le reste de l'app).
- Une notification par admin : message « Fin de {nature} de {Nom} le {date} », catégorie `Contrat`, lien `/employees/{id}`, sans acteur.
- **Idempotent** : déduplication par `(recipient, category, target, message)`.
- Front : la cloche (déjà admin-only) affiche proprement les notifs sans acteur.
- **Aucune migration** (réutilise la table `notifications`).

## Architecture
Logique pure isolée et testée : `WorkingDayCalculator` (week-end + férié) + `ContractEndNotificationPlanner` (fenêtre + message). Persistance dans `ContractEndNotificationService`, exposée par `ContractEndNotificationCommand`. Méthodes repo `findLatestPeriodsForAllEmployees` + `existsForRecipientCategoryTargetMessage`.

## Tests & vérification
- 11 tests unitaires ajoutés ; suite complète verte (264 tests, 564 assertions).
- Vérif e2e manuelle : run du vendredi → 6 notifs/1 contrat finissant le lundi (saut de week-end OK), relance idempotente (0), contenu BDD correct.

## Documentation
`doc/contract-end-notifications.md`, `doc/functional-rules.md` (§15), doc in-app (`documentation-content.ts`), `CLAUDE.md`.

## ⚠️ Tâche infra
Ajouter la ligne crontab prod : `0 6 * * * … bin/console app:contract:end-notifications`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #35
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-24 14:04:50 +00:00

2.6 KiB

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\ContractEndNotificationCommandapp:contract:end-notifications.
  • EmployeeContractPeriodRepository::findLatestPeriodsForAllEmployees, NotificationRepository::existsForRecipientCategoryTargetMessage.
  • Pas de migration : réutilise la table notifications.