029a09dc09
Auto Tag Develop / tag (push) Successful in 17s
## 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>
2.6 KiB
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(endDateest inclusif = dernier jour travaillé). Les week-ends ET jours fériés (PublicHolidayService, zonemetropole) 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 appliqueEXCLUDED_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égorieContrat, 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.