Complément du fix scalaire : certains proxies MCP sérialisent aussi les
arguments tableaux/objets en string JSON (ex: tagIds arrive en "[3]" au
lieu de [3]). Le schéma array les rejetait en 422, et castToArray du SDK
ne décode pas les strings JSON.
CoerceJsonEncodedArgumentsListener écoute le RequestEvent du SDK (dispatché
avant tout handler) et, piloté par le schéma du tool, décode les arguments
string dont le type cible est array/object. Les params string ne sont
jamais touchés (sûr pour les titres/descriptions ressemblant à du JSON).
Corrige le 422 'Expected array|null, but received string' sur tagIds /
collaboratorIds lors des appels depuis Claude.
Certains clients MCP sérialisent tous les arguments JSON-RPC en string
(ex: "22" au lieu de 22). Le SDK valide les arguments contre le schéma
JSON AVANT de les caster (CallToolHandler), donc un schéma integer strict
rejetait "22" en 422 alors que ReferenceHandler::castArgumentType sait
le coercer ensuite.
CoercingSchemaGenerator enveloppe le SchemaGenerator du SDK et ajoute
"string" aux types scalaires integer/number/boolean (et aux items de
tableaux), de sorte que opis accepte la valeur stringifiée ; le type PHP
réel du paramètre pilote toujours la coercition. Branché sur le builder
MCP via McpSchemaGeneratorPass (enregistrée dans Kernel::build).
Corrige le rejet 422 sur groupId/effortId/priorityId/statusId/etc. lors
de l'appel des tools depuis Claude.
Sur une demande d'un seul jour, le formulaire recopie la demi-journée de
début sur la fin (même date), si bien que les deux bornes portaient une
demi-journée et 0,5 était soustrait deux fois (1 - 0,5 - 0,5 = 0).
Quand start et end tombent le même jour, les deux bornes se confondent :
on ne soustrait désormais 0,5 qu'une seule fois. Comparaison par
getTimestamp() pour rester compatible strict_comparison (=== sur deux
DateTimeImmutable distincts teste l'identité d'instance, pas la valeur).
Couverture complétée : mono-jour plein, week-end, férié, inversion de
dates, demi-journée de fin seule, demi sur samedi en mode ouvrables.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AbsenceBalanceService::availableForRequest() : jours disponibles (acquis N-1
+ en cours N − pris) pour la période de la demande, null si type non suivi.
- Blocage de l'approbation si countedDays > disponible, dans les deux chemins
(REST AbsenceReviewProcessor + MCP ReviewAbsenceRequestTool), comme le motif
décès. Les CP en cours d'acquisition restent posables, mais pas au-delà du
droit total (plus de solde négatif silencieux à l'approbation).
- Fixture : demande pending CP d'alice replacée dans sa période de référence
2025-2026 (26→29/05/2026, 4 j ouvrés) et solde pending aligné (5 → 4) ;
plus de "en attente" orphelin non lié à une demande.
- Test fonctionnel testApproveBeyondAvailableBalanceIsBlocked + employé de test
doté d'un droit pour que les approbations existantes passent le garde-fou.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Périmètre 1-6 du design 2026-05-22-absence-legal-compliance-fixes (points
lourds — ancienneté, CP pendant maladie, rétention — reportés en backlog).
- Événements familiaux sans solde : AbsenceType::decrementsBalance() ne vaut
true que pour les CP. Mariage/PACS, naissance, décès = droits par événement ;
congé parental = suspension ; maladie = Sécu. Plus de solde fantôme.
- Décès : daysPerEvent = null (selon lien de parenté) + motif obligatoire à la
création (REST + MCP), les minimums légaux étant rappelés dans l'aide.
- Ajout du congé naissance (type, policy 3 j, justificatif, libellés/couleur front).
- Garde-fou demi-journée : -0,5 appliqué uniquement si le jour-borne est
réellement décompté (corrige un sous-décompte week-end/férié) — TDD.
- CCN documentée : paramètre app.absence.convention = "Syntec (IDCC 1486)",
rappelée en sous-titre admin et dans l'aide /help.
Tests : AbsenceDayCalculatorTest (garde-fou demi-journée), AbsenceRequestLifecycle
(motif décès obligatoire + aucun solde touché). make test 52/52, build Nuxt OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- Décode les encoded-words MIME (RFC 2047) des sujets et noms d'expéditeur
via App\Mail\MimeHeaderDecoder, appliqué dans ImapMailProvider (sync propre)
- Commande app:mail:redecode-headers (--dry-run) pour re-décoder l'existant en base
- Aperçu inline images + PDF en visionneuse modale plein écran (MailAttachmentPreview),
téléchargement conservé pour les autres types
- Tests unitaires du décodeur + maj docs/mail-integration.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois causes racines révélées par une vraie synchro complète (139 dossiers) :
- contrainte UNIQUE globale sur message_id : fausse pour IMAP (un même Message-ID
existe dans plusieurs dossiers) → violation → fermeture de l'EntityManager →
cascade qui tuait tous les dossiers suivants. Migration : index simple à la place.
- 139 connexions IMAP (une par dossier) → throttling OVH (failed to authenticate) :
réutilisation d'une seule connexion (closeConnection() ajouté à l'interface).
- état de connexion corrompu après un dossier en erreur (must be in SELECTED state) :
reconnexion ciblée après chaque dossier en échec.
- garde anti-cascade : reset du ManagerRegistry + arrêt propre si l'EM se ferme.
Résultat : 456 messages sur 57 dossiers (avant : 188/30 puis crash). Les rares
dossiers à encodage spécial sont skippés proprement et réessayés au cycle suivant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dispatch MailSyncRequested au bus Messenger, retourne 202 immediat
- folderPath optionnel via body JSON pour sync ciblee
- en test : transport in-memory route le message en sync
- securite via MailAccessChecker
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- POST /api/mail/messages/{id}/link-task body {taskId} : cree TaskMailLink (idempotent)
- DELETE /api/mail/messages/{id}/link-task/{taskId} : supprime le lien (204)
- GET /api/tasks/{id}/mails : liste les mails lies a une tache
- securite via MailAccessChecker, tests fonctionnels 401/403
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- recupere headers + body + attachments via ImapMailProvider::fetchMessage
- cache Symfony pool cache.app, cle mail_body_{md5(messageId)}, TTL 300s
- attachments serialises sans contenu binaire, avec downloadId base64url(messageDbId:partNumber)
- 503 si IMAP indisponible, 404 si message inconnu
- les tests read/flag ROLE_CLIENT/auth seront ajoutes en Task 10 (route deja existante)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ApiResource MailSettings expose les operations Get + Patch sur /api/mail/configuration
- Provider + Processor relient le DTO a l'entite MailConfiguration (singleton)
- password en write-only (jamais retourne) + hasPassword en lecture
- chiffrement password via TokenEncryptor (sodium)
- securite ROLE_ADMIN sur les deux operations
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>