# 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.