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>
43 lines
2.6 KiB
Markdown
43 lines
2.6 KiB
Markdown
# 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`.
|