From 5e2c6c219bd8eca42d3b631af223f0c732075b29 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 24 Jun 2026 15:41:24 +0200 Subject: [PATCH] docs : document contract end notification feature --- CLAUDE.md | 4 +++ doc/contract-end-notifications.md | 35 ++++++++++++++++++++++++++ doc/functional-rules.md | 7 ++++++ frontend/data/documentation-content.ts | 1 + 4 files changed, 47 insertions(+) create mode 100644 doc/contract-end-notifications.md diff --git a/CLAUDE.md b/CLAUDE.md index 7a7ca49..dad7bff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -212,6 +212,10 @@ - **Écran Journal refondu** (`frontend/pages/audit-logs.vue` + `useAuditLogsList`) : tableau en `MalioDataTable` (1er usage SIRH), **drawer de filtre** façon STARSEED (`MalioDrawer` + `MalioAccordion`, état brouillon/appliqué, badge compteur, Réinitialiser/Appliquer), **drawer de détail** au clic ligne. Filtres backend : `employee` (LIKE nom/prénom de l'employé affecté, via join), `username`/`ip`/`device` (LIKE insensible casse), `entityType[]`/`action[]` (IN), `perPage` (10/25/50/100, défaut 10). Filtres du drawer = champs texte (recherche libre), période en `MalioDateRange`, type/action en cases à cocher. Logique dans `useAuditLogsList` ; libellés FR en dur ; filtres hors URL. Provider/`AuditLogReadRepositoryInterface`/repository portent les nouveaux critères. - Documentation: `doc/audit-logging.md` +## Notifications +- Système : entité `Notification` (table `notifications`, `recipient`/`actor`/`message`/`category`/`target`/`isRead`), cloche **admin-only** dans `AppTopNav.vue`, providers `/notifications/{unread,today,history}` + `POST /notifications/mark-all-read`. Création historique : `WorkHourSiteValidationProcessor` (1 notif/admin via `UserRepository::findAllAdmins`). +- **Fin de contrat (J-1 ouvré)** : commande cron quotidienne `app:contract:end-notifications` (crontab prod, ~6h ; option `--date`). Notifie les admins sur le **dernier jour ouvré avant** `endDate` (inclusif) de la **dernière** période d'un employé (changement de contrat enchaîné exclu). Week-ends + fériés sautés (`WorkingDayCalculator`). Fenêtre couverte un jour J = `]J ; prochain_jour_ouvré(J)]`. Message « Fin de {nature} de {Nom} le {date} », catégorie `Contrat`, target `/employees/{id}`, acteur null. Idempotent (`NotificationRepository::existsForRecipientCategoryTargetMessage`). Logique pure testée : `ContractEndNotificationPlanner` + `WorkingDayCalculator`. Front : `AppTopNav.vue` masque le span acteur si `actorName` vide. Doc : `doc/contract-end-notifications.md`. + ## Backend Conventions - Prefer explicit DTOs over associative arrays - Business rules in backend (providers/processors/services), frontend is display/interaction only diff --git a/doc/contract-end-notifications.md b/doc/contract-end-notifications.md new file mode 100644 index 0000000..1dfa67a --- /dev/null +++ b/doc/contract-end-notifications.md @@ -0,0 +1,35 @@ +# 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é). +- **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`. diff --git a/doc/functional-rules.md b/doc/functional-rules.md index 25d4be6..7e8ae6e 100644 --- a/doc/functional-rules.md +++ b/doc/functional-rules.md @@ -486,6 +486,13 @@ Seuls les employés dont au moins une période de contrat intersecte la période - condition: plus aucune ligne `work_hours` du site à la date concernée avec `isSiteValid = false` - destinataires: utilisateurs `ROLE_ADMIN` +- **Fin de contrat (J-1 ouvré)** : une commande quotidienne (`app:contract:end-notifications`) + notifie tous les admins, sur le dernier jour ouvré précédant la fin d'un contrat, qu'un + salarié arrive au terme de son emploi. Cible = **dernière** période de l'employé (un + changement de contrat enchaîné ne notifie pas). Week-ends et fériés sautés. Message + « Fin de {nature} de {Nom} le {date} », catégorie `Contrat`, lien vers la fiche employé, + sans acteur. Idempotente. Détail : `doc/contract-end-notifications.md`. + ## 16) Export PDF des heures annuelles - Accessible depuis la fiche employé (bouton imprimante à droite du nom) diff --git a/frontend/data/documentation-content.ts b/frontend/data/documentation-content.ts index d42404a..92ac7a2 100644 --- a/frontend/data/documentation-content.ts +++ b/frontend/data/documentation-content.ts @@ -268,6 +268,7 @@ export const documentationSections: DocSection[] = [ { type: 'paragraph', content: 'Deux tâches automatiques s\'exécutent quotidiennement pour gérer le report des compteurs.' }, { type: 'list', content: 'Report congés (02h10) : déclenche le report des congés payés le 1er juin (CDI/CDD) et le 1er janvier (forfait)\nReport RTT (02h15) : déclenche le report du solde RTT le 1er juin' }, { type: 'note', content: 'Ces tâches sont idempotentes : si elles s\'exécutent plusieurs fois, aucun doublon n\'est créé.' }, + { type: 'paragraph', content: 'Notification fin de contrat : chaque jour ouvré, les administrateurs sont prévenus (cloche en haut à droite) lorsqu\'un salarié atteint le dernier jour ouvré avant la fin de son contrat. Le message indique la nature du contrat, le nom du salarié et la date de fin, et renvoie vers sa fiche. Les week-ends et jours fériés sont pris en compte : une fin de contrat le lundi est signalée dès le vendredi.' }, ], }, ],