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>
7.5 KiB
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.yamlscannesrc/(exclutDataFixtures). Aucune config à ajouter — créer la classe suffit. - Constructeur : injection de repos/services +
Security. __invoke(...): check de rôle en première ligne (AccessDeniedExceptionsinon), validation des IDs (InvalidArgumentExceptionsi introuvable), retourjson_encode(...).- Sérialisation centralisée dans
App\Mcp\Tool\Serializer. - Rôle :
ROLE_USERpour la lecture/écriture courante ;ROLE_ADMINpour les opérations sensibles (déjà le cas pourlist-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) —workflowIdrequiscategory(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 decreate-user, pas de modification depasswordnirolesvia 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,
userIdpour agir au nom d'un employé).
Découpage du plan d'implémentation (jalons)
- Absences : Serializer + 10 outils + tests de cohérence des soldes.
- Métadonnées tâches : delete-project/group, CRUD tag/effort/priority/status.
- 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.