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>
142 lines
7.5 KiB
Markdown
142 lines
7.5 KiB
Markdown
# 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.
|