Files
Lesstime/docs/superpowers/plans/2026-06-20-lst-65-module-projectmanagement.md
T
Matthieu f119ec30ca refactor(project-management) : introduce Project/Task/TaskTag/Client contracts
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.
2026-06-20 16:34:15 +02:00

9.7 KiB

LST-65 (2.2) — Module ProjectManagement : plan de migration

Migration strangler du cœur métier Projets/Tâches vers src/Module/ProjectManagement/. Additive, sans régression API. Exécution en 4 tranches incrémentalement vertes (chaque tranche compile + phpunit vert + commit ; aucun état cassé committé).

Branche : integration/modular-monolith-0.1-1.3 (empilement phase 2). Vérif container : docker exec -u www-data php-lesstime-fpm php bin/console cache:clear Tests : docker exec -u www-data php-lesstime-fpm php vendor/bin/phpunit (baseline = 159 verts). Style : make php-cs-fixer-allow-risky. PHP declare(strict_types=1). SQL colonnes minuscules.

Périmètre (10 entités + écosystème)

Entités : Project, Task, Workflow, TaskStatus, TaskGroup, TaskEffort, TaskPriority, TaskTag, TaskRecurrence, TaskDocument. Enums : StatusCategory, RecurrenceType. Repos (9), State (7), MCP (38), Controller (1), Services (2 : CalDavService, RecurrenceCalculator), Listeners (3), ApiResource (SwitchWorkflowOutput), fixtures, tests.

Décisions d'architecture (figées)

  1. Contrats inter-modules uniquement (src/Shared/Domain/Contract/), surface minimale :
    • ProjectInterface : getId(): ?int, getCode(): ?string, getName(): ?string
    • TaskInterface : getId(): ?int, getNumber(): ?int, getTitle(): ?string
    • TaskTagInterface : getId(): ?int, getLabel(): ?string, getColor(): ?string
    • ClientInterface : getId(): ?int, getName(): ?string
    • PAS de WorkflowInterface (Workflow est intra-module PM).
  2. Consommateur contractuel : seul le module TimeTracking (TimeEntry) bascule Project/Task/TaskTag → interfaces. Project (PM) bascule client → ClientInterface.
  3. Legacy non modularisé (Gitea/BookStack/Mail : src/Controller/Mail/*, src/State/Gitea*, src/State/BookStack*, src/Service/GiteaApiService.php, src/ApiResource/BookStack*, src/Entity/TaskMailLink.php, src/Entity/TaskBookStackLink.php), Serializer MCP partagé (src/Mcp/Tool/Serializer.php), fixtures, tests : bascule du FQCN concret App\Entity\XApp\Module\ProjectManagement\Domain\Entity\X. Couplage transitoire legacy→module, nettoyé en 2.4/2.5/2.6.
  4. Repos : pattern Core/TimeTracking — interface Domain/Repository/XxxRepositoryInterface + Infrastructure/Doctrine/DoctrineXxxRepository extends ServiceEntityRepository implements … + binding services.yaml. Conserver les méthodes métier (findMaxNumberByProjectForUpdate, findFirstNonFinal, findDefault).
  5. Services CalDavService + RecurrenceCalculatorInfrastructure/ du module (dépendance résiduelle ZimbraConfiguration legacy tolérée jusqu'à 2.6).
  6. Serializer.php reste à src/Mcp/Tool/ (helper multi-domaines), import concret PM.
  7. Timestampable additif : sur Task et Project uniquement (agrégats), pas les référentiels. Migration additive (4 colonnes nullable + FK SET NULL + COMMENT).
  8. Table inchangée (naming strategy → mêmes tables). Aucune migration destructive.
  9. resolve_target_entities final :
    UserInterface       -> App\Module\Core\Domain\Entity\User            (existant)
    ProjectInterface    -> App\Module\ProjectManagement\Domain\Entity\Project
    TaskInterface       -> App\Module\ProjectManagement\Domain\Entity\Task
    TaskTagInterface    -> App\Module\ProjectManagement\Domain\Entity\TaskTag
    ClientInterface     -> App\Entity\Client                              (Client legacy jusqu'à 2.4)
    

Tranche 1 — Découplage EN PLACE (entités non déplacées)

But : créer les contrats et basculer les consommateurs inter-modules, sans déplacer les entités → diff minimal, isole le risque architectural.

  1. Créer les 4 interfaces dans src/Shared/Domain/Contract/ (signatures ci-dessus).
  2. src/Entity/Project.php implements ProjectInterface ; Task.php implements TaskInterface ; TaskTag.php implements TaskTagInterface ; Client.php implements ClientInterface. (Méthodes déjà présentes — juste implements + use.)
  3. Project.php : client → type ?ClientInterface (targetEntity: ClientInterface::class, import, getter/setter).
  4. src/Module/TimeTracking/Domain/Entity/TimeEntry.php : project?ProjectInterface, task?TaskInterface, tagsCollection<TaskTagInterface> (targetEntity = interfaces, imports, getters/setters/addTag/removeTag). MAJ TimeEntryRepositoryInterface/DoctrineTimeEntryRepository/ActiveTimeEntryProvider/TimeEntryExportController si typage Project/Task.
  5. config/packages/doctrine.yaml : ajouter les 4 lignes resolve_target_entities (cibles = App\Entity\Project/Task/TaskTag + App\Entity\Client — encore legacy à ce stade).
  6. Vérif : cache:clear OK + phpunit vert. Commit refactor(project-management) : introduce Project/Task/TaskTag/Client contracts, decouple TimeTracking.

Tranche 2 — Move mécanique vers le module

But : déplacer entités + écosystème, bascule namespaces, sans changement de comportement.

  1. git mv entités → src/Module/ProjectManagement/Domain/Entity/ (namespace App\Module\ProjectManagement\Domain\Entity). Relations intra-module = concret ; client=ClientInterface ; assignee/collaborators/uploadedBy=UserInterface (inchangé). repositoryClassDoctrineXxxRepository::class.
  2. git mv enums → src/Module/ProjectManagement/Domain/Enum/ (namespace adapté).
  3. Repos → Infrastructure/Doctrine/DoctrineXxxRepository.php + interfaces Domain/Repository/XxxRepositoryInterface.php (méthodes métier dans l'interface). Bindings services.yaml (9).
  4. State (7), MCP (38), Controller (1), Services (2), Listeners (3), ApiResource SwitchWorkflowOutput → sous-dossiers Infrastructure/… du module, namespaces adaptés, injecter les interfaces de repo. services.yaml : repointer App\State\TaskDocumentProcessor, App\Controller\TaskDocumentDownloadController, App\Mcp\Tool\Task\AddTaskDocumentTool, App\Mcp\Tool\Task\UpdateTaskDocumentTool, App\EventListener\TaskDocumentListener vers les nouveaux FQCN (garder $uploadDir + tag doctrine.orm.entity_listener).
  5. resolve_target_entities : repointer ProjectInterface/TaskInterface/TaskTagInterface vers les FQCN module. (ClientInterface reste App\Entity\Client.)
  6. Swap FQCN concret legacy : remplacer App\Entity\{Task,Project,Workflow,TaskStatus,TaskGroup,TaskEffort,TaskPriority,TaskTag,TaskRecurrence,TaskDocument}App\Module\ProjectManagement\Domain\Entity\… et App\Enum\{StatusCategory,RecurrenceType}App\Module\ProjectManagement\Domain\Enum\… et App\Repository\Xxx → interfaces/Doctrine, dans : Serializer.php, Controller/Mail/, State/Gitea, State/BookStack*, ApiResource/BookStack*, Service/GiteaApiService.php, Entity/TaskMailLink.php, Entity/TaskBookStackLink.php, DataFixtures/AppFixtures.php, tests/*. (NE PAS toucher App\Entity\Client.)
  7. config/modules.php : ajouter ProjectManagementModule (id project-management, label Projets & Tâches, isRequired false, permissions project-management.projects.view/manage, project-management.tasks.view/manage — non recâblées, additif).
  8. config/packages/doctrine.yaml : mapping ProjectManagement (dir src/Module/ProjectManagement/Domain/Entity).
  9. config/sidebar.php : 'module' => 'project-management' sur items my-tasks et projects.
  10. Vérif : cache:clear OK + doctrine:schema:validate mapping OK + phpunit vert + cs-fixer. Commit feat(project-management) : migrate core Projects/Tasks domain into module (back).

Tranche 3 — Timestampable additif (Task + Project)

  1. Ajouter TimestampableBlamableTrait + interfaces à Task et Project.
  2. Migration additive manuscrite : created_at/updated_at (TIMESTAMP(0) null), created_by/updated_by (INT null, FK "user" ON DELETE SET NULL) + index + COMMENT, sur task et project. down() = DROP des ajouts.
  3. Champs hors groupes API existants (le trait porte ses propres groupes).
  4. Vérif : migrations:migrate -n (dev+test) + phpunit vert. Commit feat(project-management) : add timestampable/blamable to Task and Project (additive).

Tranche 4 — Front layer project-management

  1. git mv vers frontend/modules/project-management/ : pages (my-tasks, projects/index, projects/[id]/{index,groups,archives}), components/{project,task}/, services (projects, tasks, workflows, task-statuses, task-priorities, task-efforts, task-tags, task-groups, task-documents, task-recurrences) + services/dto/ correspondants. nuxt.config.ts = export default defineNuxtConfig({}).
  2. Réécrire imports explicites ~/services/<x> + ~/services/dto/<x>~/modules/project-management/... dans : les fichiers déplacés, components/admin/{AdminEffortTab,AdminPriorityTab,AdminTagTab,AdminWorkflowTab,WorkflowDrawer}.vue, components/mail/{MailCreateTaskModal,MailLinkTaskModal}.vue, pages/index.vue, pages/mail.vue, app/layouts/default.vue, et frontend/modules/time-tracking/ (dto/time-entry, stores/timer, pages/time-tracking, components/TimeEntryDrawer importent project/task/task-tag dto). clients.ts reste racine.
  3. Préserver routes /my-tasks, /projects, /projects/:id, /projects/:id/groups, /projects/:id/archives. i18n global inchangé.
  4. Vérif : cd frontend && npx nuxt build OK + routes présentes. Commit feat(project-management) : extract Projects/Tasks front into Nuxt module layer.

Critères d'acceptation (ticket)

  • Cœur Projets/Tâches en module sans régression API (opérations/securities/uriTemplates conservés).
  • Aucun import direct inter-modules établis (contrats) — legacy en transit toléré.
  • make test vert, aucune migration destructive.
  • Toggle module project-management (sidebar + routes) prouvé.