LST-58 (2.4), part 1/2 — Client move. Prospect + repertoire front are pending
the product spec and will be added on this branch afterward.
- Client entity moved to src/Module/Directory/Domain/Entity; repository split
into Domain/Repository/ClientRepositoryInterface + Doctrine impl (bound in
services.yaml). 5 client MCP tools moved to Infrastructure/Mcp/Tool, now
injecting the interface.
- resolve_target_entities ClientInterface repointed to Directory\Client;
Directory mapping added; DirectoryModule registered (id directory, 2 RBAC
perms). Client.projects relation now uses ProjectInterface -> Directory no
longer depends on ProjectManagement.
- ProjectManagement Create/UpdateProjectTool inject Directory's
ClientRepositoryInterface; Serializer and fixtures repointed.
- Garde-fous: #[Auditable] + Timestampable/Blamable on Client (additive
migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
NULL + COMMENT).
161 tests green, mapping valid, no API route regression, cs-fixer clean.
LST-66 (2.3) backend. Behaviour-preserving move of the absences domain into
src/Module/Absence/. API operations, securities, routes and the 10 MCP tool
names are unchanged.
- 3 entities + 3 enums moved to Domain/{Entity,Enum}; user relations stay on
UserInterface. 3 repositories split into Domain/Repository interfaces +
Doctrine impls (bound in services.yaml); find() kept off interfaces
(findById instead).
- Pure services (AbsenceDayCalculator, PublicHolidayProvider) -> Domain/Service;
AbsenceBalanceService -> Application/Service; State (5), controllers (5),
10 MCP tools and AccrueLeaveCommand -> Infrastructure/.
- New LeaveProfileInterface contract (Shared) exposes the HR getters used by
AbsenceBalanceService/AccrueLeaveCommand; User implements it -> Absence no
longer imports the concrete Core User. MCP tools/command inject
UserRepositoryInterface (findById) instead of the concrete repository.
- Timestampable/Blamable added to AbsenceBalance and AbsencePolicy (additive
migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
NULL + COMMENT). AbsenceRequest untouched (already has createdAt/reviewedAt).
- AbsenceModule registered (id absence, 4 RBAC perms, not re-wired); doctrine
mapping added; team-absences sidebar item gated by the module.
161 tests green, mapping valid, no API route regression, cs-fixer clean.
Tranche 2 of LST-65. Mechanical, behaviour-preserving move of the core
business domain into src/Module/ProjectManagement/. API operations,
securities, uriTemplates and the 38 MCP tool names are all unchanged.
- 10 entities + 2 enums moved to Domain/{Entity,Enum}; intra-module
relations stay concrete, cross-module relations go through contracts
(Project.client -> ClientInterface, Task/TaskDocument users ->
UserInterface).
- 9 repositories split into Domain/Repository interfaces + Doctrine impls,
bound in services.yaml; consumers inject the interfaces. find() kept off
the interfaces (ServiceEntityRepository ?object compat) -> findById().
- State (7), MCP tools (38), controller, CalDavService/RecurrenceCalculator,
3 Doctrine listeners and SwitchWorkflowOutput moved under Infrastructure/.
- doctrine.yaml: ProjectManagement mapping + resolve_target_entities of the
3 module contracts repointed to the module (ClientInterface stays legacy).
- ProjectManagementModule registered (id project-management, 4 RBAC perms,
not re-wired); sidebar my-tasks/projects gated by the module.
- Legacy not-yet-modularised consumers (Mail/Gitea/BookStack, Serializer,
fixtures, tests) swapped to the module FQCN — transitional coupling to be
cleaned in 2.4/2.5/2.6.
159 tests green, mapping valid, no API route regression, cs-fixer clean.
Tranche 1 of LST-65 (ProjectManagement module migration). Decouples the
TimeTracking module from the core-business entities before they move, with
no entity relocation yet — keeps the diff minimal and the risk isolated.
- New read contracts in Shared/Domain/Contract (minimal surface, aligned on
the entities' real nullable signatures): ProjectInterface (id/code/name),
TaskInterface (id/number/title), TaskTagInterface (id/label/color),
ClientInterface (id/name).
- Project/Task/TaskTag/Client implement their contract (entities stay in
src/Entity for now). Project.client typed as ClientInterface.
- TimeEntry (TimeTracking) now references ProjectInterface/TaskInterface/
TaskTagInterface instead of the concrete entities; repository + DQL
untouched in behaviour.
- resolve_target_entities maps the 4 contracts to the legacy entities (will
be repointed to the module in tranche 2).
- Adds the migration plan doc.
159 tests green, mapping valid, cs-fixer clean.
First business module of Phase 2 (LST-64, rodage). Strangler-style,
additive move — no behavioural change to the public API or MCP tools.
- New module App\Module\TimeTracking (TimeTrackingModule, id "time-tracking",
declares time-tracking.entries.view/export permissions in the RBAC catalog;
operation security left on ROLE_USER, not re-wired here).
- Move TimeEntry entity, repository (now interface + Doctrine impl bound in
services.yaml), ActiveTimeEntryProvider, export service/controller and the
4 MCP TimeEntry tools into the module. #[ApiResource] (operations, security,
uriTemplates /time_entries/*), filters and serialization groups preserved.
- Doctrine mapping "TimeTracking" added; table time_entry unchanged.
- Sidebar item gated with module "time-tracking" (SidebarFilter disables the
route when the module is inactive).
- Timestampable/Blamable adopted (first adopter): additive migration adds
created_at/updated_at/created_by/updated_by (nullable, FK SET NULL) +
COMMENT ON COLUMN. Functional test confirms created_at on persist and
updated_at refresh on update — the suspected preUpdate recompute issue does
not occur (Doctrine ORM 3.6.2 recomputes change sets after preUpdate).
159 tests green, schema mapping valid, php-cs-fixer clean.
API Platform pagine par défaut à 30 éléments/page et le helper front
extractHydraMembers ne lit que la première page (il ignore hydra:view.next),
ce qui tronque silencieusement toute liste de plus de 30 éléments.
- Back : paginationEnabled false sur les GetCollection consommées en entier
et à volume borné/modéré (Client, Project, User, TaskTag, TaskGroup,
TaskStatus, TaskPriority, TaskEffort, Workflow).
- Front : nouveau helper fetchAllHydra() qui parcourt toutes les pages ;
utilisé pour /notifications (volume non borné, reste paginé côté back).
- Doc : règle anti-troncature ajoutée au CLAUDE.md.
Déjà protégés (vérifiés) : Task, TimeEntry, TaskDocument, TaskRecurrence,
AbsenceRequest/Policy/Balance (paginationEnabled false) et /time_entries/range.
La vue suivi de temps tapait la GetCollection paginée de /time_entries
(30 items/page) et ne lisait que la première page : sur une semaine
chargée, les entrées les plus anciennes (triées startedAt DESC) étaient
tronquées tant qu'aucun filtre projet ne réduisait le total sous 30.
Ajout d'une GetCollection dédiée /time_entries/range non paginée, bornée
par date, vers laquelle pointe désormais getByDateRange.
Deux colonnes nullable firstName/lastName sur User (groupes me:read,
user:list, user:write), éditables dans le drawer utilisateur (admin).
L'affichage reste basé sur le username pour l'instant. Migration +
valeurs de démo dans les fixtures.
Suite à la revue de conformité du module absences.
Fuite corrigée : GET /api/users et /api/users/{id} n'avaient aucun contrôle
d'accès alors que le groupe user:list exposait les données RH/familiales
(date d'embauche, contrat, soldes de CP, rôles…). Tout utilisateur authentifié
pouvait donc lire ces informations sur tous ses collègues.
- chaque champ RH (isEmployee, hireDate, endDate, contractType, workTimeRatio,
annualLeaveDays, referencePeriodStart, initialLeaveBalance) ainsi que roles
est désormais exposé via #[ApiProperty(security: "is_granted('ROLE_ADMIN') or
object == user")] : visible uniquement par un admin ou par l'utilisateur
lui-même. id et username restent publics (sélecteurs d'assigné, avatars).
Minimisation : suppression de familySituation et nbChildren, collectés et
exposés (form RH, API, outil MCP) mais utilisés par aucun calcul.
- entité User + enum FamilySituation + migration de drop des colonnes
- Serializer MCP, update-user (MCP), EmployeeDrawer, DTO, fixtures, i18n
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>
- ajoute symfony/messenger ^8.0 et symfony/doctrine-messenger ^8.0 pour la sync mail async
- ajoute symfony/browser-kit + css-selector en dev pour tests fonctionnels WebTestCase
- ENCRYPTION_KEY ajoutee dans phpunit.dist.xml pour permettre le chiffrement en test
- MESSENGER_TRANSPORT_DSN configure (Doctrine), messenger.yaml minimal (sera enrichi en Task 12)
- fix(orm) : ClientTicket - migre uniqueConstraints en attribut separe (Doctrine ORM 4 deprecation)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend :
- POST /api/me/regenerate-api-token : nouveau controller, ROLE_USER (exclut CLIENT)
- User.apiToken exposé via groupe me:read sur GET /api/me
Frontend :
- Section 'Token API MCP' sur /profile (masquée pour les CLIENT du portail)
- Boutons Copier + Régénérer avec modal de confirmation
- Service api-token + DTO mis à jour + clés i18n fr
Client.id/name and Project.id/name were missing the user:list group,
causing them to be serialized as IRI strings instead of embedded objects.
This broke the user edit form which expected object properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GetCollection/Get required ROLE_USER which ROLE_CLIENT doesn't have.
Added TaskDocumentProvider to scope client access to their own tickets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handles Patch (persist + sync + recurrence check) and Delete (remove + cleanup Zimbra events).
Updates TaskNumberProcessor to sync newly created tasks to calendar.
Wires TaskCalendarProcessor as processor for Patch/Delete on Task entity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds taskCount virtual field on Project entity, delete button in ProjectDrawer
(visible only when taskCount === 0), and a reusable ConfirmDeleteProjectModal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Assert\Choice on ClientTicket type and status with typed constants
- Add Assert\Url on GiteaConfiguration, BookStackConfiguration, TaskBookStackLink, ClientTicket
- Fix concurrent task/ticket numbering: use pg_advisory_xact_lock instead of FOR UPDATE with MAX()
- Wrap CreateTaskTool numbering in transaction
- Harmonize repository contracts: both return max number, caller adds +1
Tickets: T-004, T-008, T-011, T-012
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add non-persisted plainPassword field to User entity (write-only via API)
- Remove direct write access to password field
- Update UserPasswordHasherProcessor to hash from plainPassword
- Update frontend DTO and UserDrawer component
Ticket: T-009
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ManyToOne relation from TimeEntry to ClientTicket entity.
MCP tools create-time-entry, update-time-entry, and list-time-entries
now support clientTicketId parameter for linking tickets to time entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use getMimeType() instead of getClientMimeType() to prevent MIME spoofing
- Change IsGranted to IS_AUTHENTICATED_FULLY so ROLE_CLIENT can access avatars
- Remove Groups from avatarFileName (only avatarUrl needed by frontend)
- Disable aggressive caching to prevent stale avatar images
- Add error handling to avatar upload in profile page
- Use i18n for "Mon profil" button text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>