feat(absences) : outils MCP CRUD pour les absences

Expose le module Absences via le serveur MCP et comble les trous CRUD
existants (projets, groupes, métadonnées de tâches, clients, users RH).

Absences (réutilise AbsenceDayCalculator + AbsenceBalanceService pour ne
pas contourner la logique de soldes) :
- list/get/create/review/cancel/delete-absence-request
- list/update-absence-policy, list/update-absence-balance
- create-absence-request prend un userId explicite (agir au nom d'un employé) ;
  review/cancel maintiennent les soldes (pending/taken) cohérents
- AbsenceRequestRepository::findFiltered pour les filtres de liste

Trous CRUD comblés :
- delete-project, delete-group
- CRUD tag, effort, priority
- CRUD status (couplé au workflow, avec category)
- CRUD client, get/update-user (champs RH, sans password ni roles)

Sérialisation centralisée (Serializer::absenceRequest/Policy/Balance/client/userFull).
Instructions MCP (mcp.yaml) mises à jour : statuts par workflow + domaine absences.

Tests : tests/Functional/Mcp/AbsenceRequestLifecycleTest (création / approbation /
annulation admin) vérifient le cycle complet et la cohérence des soldes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-05-22 14:10:56 +02:00
parent 2a0b202d32
commit 2b148fa65a
36 changed files with 4536 additions and 1 deletions

View File

@@ -0,0 +1,141 @@
# Spec — Extension des outils MCP : module Absences + trous CRUD
Date : 2026-05-22
Branche : `feat/absence-management`
## Contexte & objectif
Le serveur MCP Lesstime (`src/Mcp/Tool/`) expose aujourd'hui les projets, tâches,
métadonnées de tâches, time tracking et workflows. Le nouveau **module Absences**
(`AbsenceRequest`, `AbsencePolicy`, `AbsenceBalance`) n'est pas exposé, et plusieurs
entités existantes n'ont qu'une couverture partielle (souvent `list` sans
create/update/delete).
Objectif : permettre de piloter l'app via MCP (assistant) sur ces domaines, en
respectant strictement la logique métier déjà en place.
## Conventions reprises de l'existant
- Une classe par outil, attribut `#[McpTool(name, description)]` sur la **classe**.
- Discovery automatique : `config/packages/mcp.yaml` scanne `src/` (exclut `DataFixtures`).
Aucune config à ajouter — créer la classe suffit.
- Constructeur : injection de repos/services + `Security`.
- `__invoke(...)` : check de rôle en première ligne (`AccessDeniedException` sinon),
validation des IDs (`InvalidArgumentException` si introuvable), retour `json_encode(...)`.
- Sérialisation centralisée dans `App\Mcp\Tool\Serializer`.
- Rôle : `ROLE_USER` pour la lecture/écriture courante ; `ROLE_ADMIN` pour les
opérations sensibles (déjà le cas pour `list-clients`/`list-users`, et pour les
opérations admin du module absences).
## Décision clé — réutilisation de la logique métier (pas les Processors)
Les Processors API Platform (`AbsenceRequestProcessor`, `AbsenceReviewProcessor`,
`AbsenceCancelProcessor`) sont liés à `Security::getUser()` (l'utilisateur courant)
et à l'`Operation` HTTP. En MCP, l'utilisateur courant est le **propriétaire du
token** (admin), or on veut pouvoir agir **au nom d'un employé**.
→ Les outils MCP **n'appellent pas les Processors** ; ils répliquent leur
orchestration en réutilisant les **services partagés** qui portent la vraie règle
métier :
- `AbsenceDayCalculator::countWorkingDays(...)` — calcul des jours décomptés.
- `AbsenceBalanceService``reservePending`, `applyApproval`, `release`, `periodFor`.
- `AbsencePolicyRepository::findOneByType(...)` — politique active du type.
- `AbsenceRequestRepository::hasOverlap(...)` — règle anti-chevauchement.
`create-absence-request` prend un `userId` explicite (l'employé cible) ;
`review`/`cancel` posent `reviewedBy` = utilisateur du token MCP.
Ainsi soldes (`pending`/`taken`/`acquired`) et statuts restent cohérents avec ce
que produit l'UI.
## Inventaire des outils
### Module Absences — `src/Mcp/Tool/Absence/` (10 outils)
| Outil | Rôle | Paramètres | Logique |
|---|---|---|---|
| `list-absence-requests` | USER | `userId?`, `status?`, `type?`, `from?`, `to?` | Filtre ; sans `userId` renvoie tout (token admin). |
| `get-absence-request` | USER | `id` | — |
| `create-absence-request` | USER | `userId`, `type`, `startDate`, `endDate`, `startHalfDay?`, `endHalfDay?`, `reason?` | Vérifie policy active + overlap, calcule `countedDays`, refuse si ≤ 0, statut `Pending`, `reservePending`. |
| `review-absence-request` | ADMIN | `id`, `decision` (`approve`\|`reject`), `rejectionReason?` | Seulement si `Pending`. Approve → `applyApproval` ; reject → `rejectionReason` requis + `release(false)`. Pose `reviewedAt`/`reviewedBy`. |
| `cancel-absence-request` | USER | `id` | `Pending``release(false)` ; `Approved` → ADMIN requis + `release(true)` ; sinon conflit. Statut `Cancelled`. |
| `delete-absence-request` | ADMIN | `id` | Suppression définitive. |
| `list-absence-policies` | USER | — | Toutes les policies (ordre `type`). |
| `update-absence-policy` | ADMIN | `id`, `daysPerYear?`, `daysPerEvent?`, `justificationRequired?`, `noticeDays?`, `countWorkingDaysOnly?`, `active?` | Seuls les champs fournis changent. |
| `list-absence-balances` | USER | `userId?`, `type?`, `period?` | Soldes filtrés. |
| `update-absence-balance` | ADMIN | `id`, `acquired?`, `acquiring?`, `taken?` | Ajustement manuel (régularisation). |
`type` et `status` acceptés en valeur d'enum string (ex. `cp`, `maladie`,
`pending`) ; erreur de validation explicite si invalide. `startDate`/`endDate`
au format `YYYY-MM-DD`.
### Trous CRUD sur l'existant
**Projets / groupes**
- `delete-project` (ADMIN) — `id`.
- `delete-group` (USER) — `id`.
**Métadonnées de tâches**`src/Mcp/Tool/TaskMeta/`
- `create-tag` / `update-tag` / `delete-tag` (USER) — `label`, `color?`.
- `create-effort` / `update-effort` / `delete-effort` (USER) — `label` (+ ordre éventuel).
- `create-priority` / `update-priority` / `delete-priority` (USER) — `label`, `color?`.
- `create-status` / `update-status` / `delete-status` (ADMIN) — **`workflowId` requis**
+ `category` (`todo`|`in_progress`|`blocked`|`review`|`done`), `label`, `color?`,
`position?`, `isFinal?`. (Les statuts ne sont PAS globaux : ils appartiennent à un workflow.)
**Clients**`src/Mcp/Tool/Reference/` (ADMIN, aligné sur `list-clients`)
- `get-client``id`.
- `create-client``name` (+ `email?`, `phone?`, `street?`, `city?`, `postalCode?`).
- `update-client``id` + champs optionnels.
- `delete-client``id`.
**Utilisateurs**`src/Mcp/Tool/Reference/` (ADMIN)
- `get-user``id` (profil complet RH).
- `update-user``id` + champs RH/profil : `isEmployee?`, `hireDate?`, `endDate?`,
`contractType?`, `workTimeRatio?`, `annualLeaveDays?`, `referencePeriodStart?`,
`initialLeaveBalance?`, `familySituation?`, `nbChildren?`.
**Hors périmètre (décision utilisateur) : pas de `create-user`, pas de modification
de `password` ni `roles` via MCP.**
## Sérialisation — ajouts à `Serializer.php`
- `absenceRequest(AbsenceRequest)` : id, user{id,username}, type{value,label},
startDate, endDate, startHalfDay, endHalfDay, countedDays, reason, status{value,label},
rejectionReason, createdAt, reviewedAt, reviewedBy, justificationFileName.
- `absencePolicy(AbsencePolicy)` : id, type{value,label}, daysPerYear, daysPerEvent,
justificationRequired, noticeDays, countWorkingDaysOnly, active.
- `absenceBalance(AbsenceBalance)` : id, user, type{value,label}, period, acquired,
acquiring, taken, pending, acquiredTotal, available.
- `client(Client)` : id, name, email, phone, street, city, postalCode.
- `userFull(User)` : id, username, roles, isEmployee, hireDate, endDate, contractType,
workTimeRatio, annualLeaveDays, referencePeriodStart, initialLeaveBalance,
familySituation, nbChildren.
## Mise à jour de la doc MCP
`config/packages/mcp.yaml` — bloc `instructions` :
- corriger la mention « statuses … are GLOBAL » (faux : par workflow) ;
- ajouter une phrase sur le domaine Absences (requests/policies/balances, lifecycle
approve/reject/cancel, `userId` pour agir au nom d'un employé).
## Découpage du plan d'implémentation (jalons)
1. **Absences** : Serializer + 10 outils + tests de cohérence des soldes.
2. **Métadonnées tâches** : delete-project/group, CRUD tag/effort/priority/status.
3. **Clients & users** : CRUD clients, get/update user + maj `mcp.yaml`.
Chaque jalon est livrable et testable indépendamment.
## Tests
Tests fonctionnels MCP (si une infra de test MCP existe) ou tests unitaires sur la
réplication de la logique de solde : créer → review(approve) → cancel et vérifier
`pending`/`taken` à chaque étape ; vérifier le refus sur chevauchement et sur
plage sans jour ouvré.
## Hors périmètre
- Mail, BookStack, Gitea, Zimbra, Notifications, TaskDocument (non demandés).
- Création d'utilisateurs et gestion mot de passe/rôles via MCP.
- Upload de justificatif d'absence via MCP.