Commit Graph

11 Commits

Author SHA1 Message Date
Matthieu 75fd737a4c fix(mcp) : décoder les arguments tableaux/objets sérialisés en string JSON
Auto Tag Develop / tag (push) Successful in 9s
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.
2026-05-27 10:53:42 +02:00
Matthieu c528067c79 fix(mcp) : accepter les arguments scalaires stringifiés (coercition string->int/bool)
Auto Tag Develop / tag (push) Successful in 11s
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.
2026-05-27 10:36:06 +02:00
Matthieu 45e86543a1 fix(absences) : demi-journée mono-jour décomptée 0,5 (et non 0)
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>
2026-05-26 10:05:42 +02:00
Matthieu f9773b3a5e feat(absences) : mise en conformité légale (événements familiaux, demi-journée, CCN)
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>
2026-05-22 16:00:28 +02:00
Matthieu de98924fd3 feat(absences) : fondation backend du module de gestion des absences
Module type Payfit (étapes 1+2 de la spec V1) : demande d'absence, validation
admin, soldes à jour.

- Enums : AbsenceType, AbsenceStatus, HalfDay, ContractType, FamilySituation
- Entités : AbsencePolicy, AbsenceBalance, AbsenceRequest + champs RH sur User
- Services : PublicHolidayProvider (fériés FR métropole en PHP pur, Computus),
  AbsenceDayCalculator (décompte jours ouvrés/ouvrables + demi-journées, TDD),
  AbsenceBalanceService (périodes + pending/taken/recrédit)
- API Platform : providers/processors (création, approve/reject/cancel) + RBAC
  me/admin, contrôleurs preview (dry-run), upload/download justificatif, calendrier
- Migrations : une par table + colonnes RH user (DEFAULT puis DROP DEFAULT)
- Fixtures : 5 policies par défaut, salariés démo, soldes et demandes
- Tests unitaires : PublicHolidayProvider, AbsenceDayCalculator (12 tests)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:45:14 +02:00
Matthieu bcbc04325e feat(mail) : décodage des en-têtes MIME + aperçu inline des pièces jointes
Auto Tag Develop / tag (push) Has been cancelled
- 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>
2026-05-21 11:42:38 +02:00
matthieu c75dfa0371 fix(mail) : synchro multi-dossiers fiable contre OVH
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>
2026-05-20 08:21:02 +02:00
matthieu c47434b502 feat(mail) : MailSyncService — syncAll/syncFolder/syncFolderStructure + lock + garde 50% 2026-05-19 23:37:31 +02:00
matthieu b546f528df feat(mail) : ImapMailProvider — implémentation complète MailProviderInterface 2026-05-19 23:35:12 +02:00
matthieu b5b4288cc0 feat(mail) : DTO MailSyncReport + test unitaire 2026-05-19 23:32:29 +02:00
matthieu 3cac87aa24 feat(mail) : MailConfiguration entity + repository + singleton test 2026-05-19 23:15:47 +02:00