From a510b2ca73a31ecc5fe3bc8cb19e9b76d1cf5736 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 19 Jun 2026 10:50:14 +0200 Subject: [PATCH] docs : add modular monolith migration roadmap and socle design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan de migration complet Lesstime vers modular monolith DDD (archi Starseed) : roadmap en 14 tickets ordonnés par dépendances + design technique détaillé du socle (Shared/, contrats, endpoints modules/sidebar, plan strangler). --- ...26-06-19-lst-56-modular-monolith-design.md | 192 ++++++++++++++++++ ...6-19-migration-modular-monolith-roadmap.md | 161 +++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-19-lst-56-modular-monolith-design.md create mode 100644 docs/superpowers/specs/2026-06-19-migration-modular-monolith-roadmap.md diff --git a/docs/superpowers/specs/2026-06-19-lst-56-modular-monolith-design.md b/docs/superpowers/specs/2026-06-19-lst-56-modular-monolith-design.md new file mode 100644 index 0000000..5f58cb6 --- /dev/null +++ b/docs/superpowers/specs/2026-06-19-lst-56-modular-monolith-design.md @@ -0,0 +1,192 @@ +# LST-56 — Socle modular monolith DDD + pilote « Projets/Tâches » + +> Ticket Lesstime **#56** (1/5 — groupe « Refonte / Alignement Starseed »). +> Design validé le 2026-06-19. Référence vivante : repo **Starseed** (`.claude/rules/*.md` + implémentation réelle), et `Starseed/doc/architecture-modulaire-malio.md` (vision cible théorique — **non contraignante** là où elle diverge du code réel). + +## 1. Objectif & contraintes + +Poser dans Lesstime l'**infrastructure d'un modular monolith DDD** calquée sur Starseed, et **migrer un premier module pilote** (Projets/Tâches) de bout en bout comme preuve que la mécanique tient sur le cœur métier. + +Contraintes **non négociables** : + +- **Ne rien casser de l'existant.** Migration **strangler progressive** : le code legacy (`src/Entity/…`) et les modules (`src/Module/…`) coexistent ; l'application reste fonctionnelle et `make test` vert à **chaque** étape. +- **Prod = Docker, BDD peuplée** → uniquement des migrations **additives et nullable** (aucun `DROP`, aucun `NOT NULL` rétroactif, aucun déplacement de données). +- **Profondeur DDD : pragmatique**, alignée sur le **Starseed réel** (pas la doc théorique) : ORM attributs conservés dans les entités Domain, Repository = interface (Domain) + impl Doctrine (Infrastructure), Provider/Processor API Platform, contrats `Shared/Domain/Contract` pour le cross-module. **Pas de CQRS bus systématique, pas de multi-tenant.** + +### Décisions de cadrage (figées) + +| Sujet | Décision | +|-------|----------| +| Périmètre #56 | Socle complet + **1 module pilote** migré de bout en bout | +| Stratégie | **Strangler progressif** (legacy + modules en parallèle) | +| Profondeur DDD | **Pragmatique** (= Starseed réel) | +| Module pilote | **Projets/Tâches** (cœur métier) | +| Dépendances du pilote (User/Client/Notification) | Restent **legacy**, câblées via **contrats `Shared/Domain/Contract`** + `resolve_target_entities` | +| Infra d'audit Starseed | **Différée** → ticket Lesstime dédié (créé séparément) | +| Périmètre front #56 | **Câblage shell/shared/middlewares + migration du pilote en layer**, sans relooking (le relooking Malio reste #60) | +| Exposition API du pilote | **Garder les `#[ApiResource]` actuels** (étendre seulement les chemins de scan) — zéro régression API | +| Tâche → Notification | **Contrat `NotifierInterface`** (impl legacy crée la `Notification`) | +| Nom/ID du module | back `ProjectManagement` / front `project-management` / ID `project_management` | + +## 2. Garde-fous Starseed retenus pour #56 + +Repris : `declare(strict_types=1)`, `src/Module//{Domain,Application,Infrastructure}`, `Shared/Domain/Contract` + `resolve_target_entities` (zéro import inter-modules), `config/modules.php` + `config/sidebar.php`, endpoints `/api/modules` + `/api/sidebar` + `/api/version`, `TimestampableBlamableTrait` + subscriber, pagination obligatoire, `COMMENT ON COLUMN` (helper `ColumnCommentsCatalog`), front layers auto-détectés + `useSidebar`/`useModules` + `auth.global.ts`/`modules.global.ts`. + +Reportés (hors #56) : **infra d'audit** (`#[Auditable]`/`#[AuditIgnore]`, table `audit_log`, listener, resource) → ticket dédié. **RBAC fin** (`module.resource.action`) → #57 ; en #56 la sidebar filtre **par module actif** (au plus un gate `ROLE_ADMIN`). + +## 3. Backend — arborescence cible + +``` +src/Shared/ +├── Domain/ +│ ├── Contract/ UserInterface, UserResolverInterface, ClientInterface, NotifierInterface +│ ├── Event/ DomainEventInterface +│ └── Trait/ TimestampableBlamableTrait +├── Infrastructure/ +│ ├── Doctrine/ TimestampableBlamableSubscriber +│ ├── Database/ ColumnCommentsCatalog (helper COMMENT ON COLUMN + 4 colonnes std) +│ └── ApiPlatform/ +│ ├── Resource/ ModulesResource, SidebarResource +│ └── State/ ModulesProvider, SidebarProvider +│ +src/Module/ProjectManagement/ +├── ProjectManagementModule.php ID='project_management', LABEL='Projets', REQUIRED=false, permissions()=[] (stub, RBAC réel #57) +├── Domain/ +│ ├── Entity/ Project, Task, Workflow, TaskStatus, TaskGroup, TaskEffort, +│ │ TaskPriority, TaskTag, TaskRecurrence, TaskDocument +│ └── Repository/ *RepositoryInterface (une interface par agrégat consommé) +├── Application/ RecurrenceCalculator/RecurrenceHandler + services task-centric déplacés +└── Infrastructure/ + ├── Doctrine/ Doctrine*Repository + Migrations/ (additif Timestampable) + ├── ApiPlatform/ State/Provider + State/Processor déplacés (TaskNumber, TaskCalendar, + │ TaskDocument*, SwitchProjectWorkflow, WorkflowDelete, ActiveTimeEntry resté legacy…) + └── Mcp/Tool/ MCP tools Project/, Task/, TaskMeta/, Workflow/ déplacés +``` + +`src/Entity/` conserve **intacts** : `User`, `Client`, `Notification`, `TimeEntry`, `AbsenceRequest`/`AbsencePolicy`/`AbsenceBalance`, `Mail*`, `Gitea*`/`BookStack*`/`Zimbra*`/`Share*Configuration`. Ces domaines seront modularisés dans des tickets ultérieurs. + +> **Note de découpage** : `TimeEntry` reste legacy en #56 (domaine Time tracking séparé). Le lien `Task ↔ TimeEntry` est porté côté `TimeEntry` (FK nullable vers la table `task`) ; aucune contrainte ne casse car la table `task` ne change pas de nom. + +## 4. Câblage des dépendances (zéro import inter-modules) + +1. Interfaces dans `src/Shared/Domain/Contract/` : + - `UserInterface` (id + identifiants nécessaires aux entités du module : assignee, collaborators, createdBy/updatedBy), + - `ClientInterface` (id + nom, pour `Project.client`), + - `UserResolverInterface` (résoudre un user par id, pour les State/MCP du module), + - `NotifierInterface` (créer une notification — impl legacy). +2. Les entités du module **type-hintent les interfaces**, jamais `App\Entity\*`. +3. `config/packages/doctrine.yaml → orm.resolve_target_entities` : + ```yaml + resolve_target_entities: + App\Shared\Domain\Contract\UserInterface: App\Entity\User + App\Shared\Domain\Contract\ClientInterface: App\Entity\Client + ``` +4. `App\Entity\User` `implements UserInterface`, `App\Entity\Client` `implements ClientInterface` (legacy modifié à minima, additif). +5. Notifications : `App\Module\ProjectManagement\…` appelle `NotifierInterface` ; impl `App\…\LegacyNotifier` (wrappe le `NotificationService` actuel). Le `TaskNotificationListener` est déplacé/adapté pour passer par le contrat. + +## 5. Config backend (toutes additives) + +- **`doctrine.yaml`** — ajouter un mapping module (garder `App → src/Entity`) : + ```yaml + mappings: + App: { type: attribute, is_bundle: false, dir: '%kernel.project_dir%/src/Entity', prefix: 'App\Entity', alias: App } + ProjectManagement: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Module/ProjectManagement/Domain/Entity' + prefix: 'App\Module\ProjectManagement\Domain\Entity' + ``` + Les entités déplacées **gardent leur `#[ORM\Table(name: '…')]` actuel** (table inchangée → aucune donnée déplacée). `#[ORM\Entity(repositoryClass: DoctrineXxxRepository::class)]` mis à jour vers la nouvelle classe. +- **`doctrine_migrations.yaml`** — ajouter le namespace module (garder `DoctrineMigrations`) : + ```yaml + migrations_paths: + DoctrineMigrations: '%kernel.project_dir%/migrations' + 'App\Module\ProjectManagement\Infrastructure\Doctrine\Migrations': '%kernel.project_dir%/src/Module/ProjectManagement/Infrastructure/Doctrine/Migrations' + ``` + > ⚠️ Doctrine Migrations trie par FQCN entre namespaces : le legacy `DoctrineMigrations` (setup initial) passe avant les migrations modulaires sur base vide. Sur la prod déjà migrée, seules les **nouvelles** migrations additives s'appliquent → pas d'impact d'ordre. +- **`api_platform.yaml`** — déclarer les chemins de mapping (entités + resources legacy **et** module) pour que les `#[ApiResource]` du pilote restent découverts : + ```yaml + mapping: + paths: + - '%kernel.project_dir%/src/Entity' + - '%kernel.project_dir%/src/ApiResource' + - '%kernel.project_dir%/src/Shared/Infrastructure/ApiPlatform/Resource' + - '%kernel.project_dir%/src/Module/ProjectManagement/Domain/Entity' + ``` +- **`services.yaml`** — mettre à jour les FQCN explicites déplacés : `App\EventListener\TaskDocumentListener`, `App\State\TaskDocumentProcessor`, `App\Controller\TaskDocumentDownloadController`, `App\Mcp\Tool\Task\AddTaskDocumentTool`, `App\Mcp\Tool\Task\UpdateTaskDocumentTool` → nouveaux namespaces module. Le glob `App\: '../src/'` continue d'autowire les classes déplacées. + +## 6. Garde-fous portés dans #56 + +- **TimestampableBlamable** : trait `Shared/Domain/Trait/TimestampableBlamableTrait` (4 colonnes `created_at`, `updated_at`, `created_by`, `updated_by` — toutes **nullable**), rempli par `TimestampableBlamableSubscriber` (prePersist/preUpdate). Appliqué aux entités du pilote → **1 migration additive** par table concernée, avec `COMMENT ON COLUMN` via `ColumnCommentsCatalog::addStandardTimestampableBlamableComments()`. +- **Pagination** : conserver le standard API Platform actuel (les collections du pilote restent paginées comme aujourd'hui). +- **`COMMENT ON COLUMN`** : appliqué sur les colonnes ajoutées par #56 (pas de rétro-commentaire forcé sur le legacy). + +## 7. Endpoints modules / sidebar / version + +- `GET /api/modules` (public) — `ModulesResource` + `ModulesProvider` lisant `config/modules.php` (renvoie `{ modules: ["project_management", …] }`). +- `GET /api/sidebar` (auth) — `SidebarResource` + `SidebarProvider` lisant `config/sidebar.php` ; filtrage **par module actif** (item `module` absent de la liste active → masqué + route ajoutée à `disabledRoutes`) ; gate de section optionnel `ROLE_ADMIN`. Le filtrage par **permissions fines** est explicitement reporté à #57. +- `GET /api/version` — **déjà présent** (`AppVersion`) ; vérifier le format `{ version }`, ré-aligner si besoin (déplacement optionnel vers `Shared/`). +- `config/modules.php` : `return [ ProjectManagementModule::class ];` (Core viendra plus tard ; pas de module REQUIRED bloquant en #56). +- `config/sidebar.php` : sections « Projets » / « Mes tâches » avec `module => 'project_management'` ; les entrées des domaines encore legacy (Time tracking, Absences, Mail, Admin…) listées **sans** clé `module` (donc toujours visibles) pour ne rien masquer. + +## 8. Frontend — câblage + pilote en layer (sans relooking) + +``` +frontend/app/ +├── layouts/default.vue shell : sidebar (depuis /api/sidebar) + main +├── middleware/auth.global.ts protège routes, charge sidebar+modules après login +└── middleware/modules.global.ts redirige si route ∈ disabledRoutes +frontend/shared/ +├── composables/ useApi (déplacé), useSidebar, useModules, + existants réutilisés +├── stores/ auth, ui, timer (timer reste partagé : Time tracking encore legacy) +├── utils/ api.ts (extractHydraMembers/fetchAllHydra), … +└── types/ +frontend/modules/project-management/ +├── nuxt.config.ts defineNuxtConfig({}) +├── pages/ my-tasks.vue, projects/index.vue, projects/[id]/* (déplacés tels quels) +├── components/ task/*, project/* (déplacés) +├── services/ tasks.ts, projects.ts, task-*.ts, workflows.ts (déplacés) +└── stores/ (si spécifiques au domaine) +``` + +- **`nuxt.config.ts`** : auto-détection des layers `modules/*/` (scan `readdirSync` comme Starseed) ajoutés à `extends`, + dirs d'auto-import des composables/stores par layer. `extends: ['@malio/layer-ui']` conservé en tête. +- **`useSidebar`/`useModules`** : état singleton, `loadSidebar()`/`loadModules()` appelés dans `auth.global.ts`, `reset*()` au logout. +- **`modules.global.ts`** : `isRouteDisabled(to.path)` → `navigateTo('/')`. +- **Migration des pages** : déplacement **sans réécriture visuelle** ; les pages des autres domaines (time-tracking, absences, mail, admin, profile…) **restent dans `frontend/pages/`** (legacy) tant que leurs modules ne sont pas migrés. Nuxt fusionne les routes du shell + des layers → cohabitation transparente. + +> Point de vigilance front : vérifier que la cohabitation `frontend/pages/` (legacy) + `frontend/modules/*/pages/` (layer) ne crée pas de collision de routes ; `my-tasks`/`projects` sont déplacés **et retirés** de `frontend/pages/` pour éviter le doublon. + +## 9. Plan strangler (ordre d'exécution — app verte à chaque palier) + +1. **Shared/ + garde-fous** : trait, subscriber, `ColumnCommentsCatalog`. Neutre (rien ne les consomme encore). +2. **Endpoints modules/sidebar** + `config/modules.php` + `config/sidebar.php` (toutes entrées legacy sans `module` → rien masqué). Additif. +3. **Contrats `Shared/Domain/Contract`** + `resolve_target_entities` + `User`/`Client` `implements …Interface`. Neutre. +4. **Déplacement back du module** ProjectManagement (entités → Domain/Entity, repos → Infra/Doctrine + interfaces Domain, State, MCP) + mises à jour `doctrine.yaml`/`api_platform.yaml`/`doctrine_migrations.yaml`/`services.yaml`. **`make test` vert.** +5. **Migration additive Timestampable** sur les tables du pilote (+ `COMMENT ON COLUMN`). +6. **Front shell** : `app/` + `shared/` + middlewares + auto-détection `nuxt.config.ts`. App encore en pages plates. +7. **Déplacement front du pilote** vers `modules/project-management/` (pages/components/services), retrait des doublons de `frontend/pages/`. +8. **Vérification bout-en-bout** : commenter `ProjectManagementModule::class` dans `config/modules.php` → `/api/modules` ne le liste plus, `/api/sidebar` masque ses entrées + peuple `disabledRoutes`, le front redirige `/my-tasks`→`/`. Décommenter → tout revient. Documenter le test. + +## 10. Critères d'acceptation (repris du ticket, raffinés) + +- [ ] `src/Shared/` + `src/Module/ProjectManagement/{Domain,Application,Infrastructure}` en place. +- [ ] `/api/modules`, `/api/sidebar` fonctionnels ; `/api/version` aligné. +- [ ] Aucun import direct `App\Entity\User`/`Client` depuis le module (contrats + `resolve_target_entities`). +- [ ] Front : layers `frontend/modules/*/` auto-détectés ; `useSidebar`/`useModules` + `auth.global.ts`/`modules.global.ts` opérationnels ; pilote migré sans régression visuelle. +- [ ] Garde-fous : TimestampableBlamable (migration additive + `COMMENT ON COLUMN`) ; pagination conservée. **Audit explicitement hors périmètre** (ticket dédié). +- [ ] `make test` vert ; activation/désactivation du module validée de bout en bout. +- [ ] Aucune migration destructive ; prod déployable sans perte. + +## 11. Risques & points de vigilance + +- **Prod peuplée** : seules migrations additives nullable. `created_by`/`updated_by` non backfillés (historique) — conforme Starseed. +- **Changement de namespace des entités** : sans impact DB (Doctrine mappe par table). Vérifier qu'aucun code legacy ne référence en dur `App\Entity\Task` etc. → grep + remplacement (le pilote tire Task/Project, consommés par TimeEntry/Mail/BookStack links restés legacy : ces liens passeront par les contrats ou un type-hint relâché). +- **Collision de routes front** legacy vs layer (cf. §8). +- **MCP tools** (spécificité Lesstime) : déplacés sous `Module/*/Infrastructure/Mcp/` ; confirmer que `McpSchemaGeneratorPass` les redécouvre (scan `src/`). +- **`auto_mapping: true`** : valider que l'ajout d'un mapping explicite ne perturbe pas la résolution (sinon désactiver `auto_mapping` et lister explicitement). + +## 12. Suite + +- Ticket **audit** dédié à créer (infra `#[Auditable]` + `audit_log` + listener + resource), prérequis souple de #57. +- #57 RBAC fin (permissions `module.resource.action`, sidebar filtrée par permission). +- #58 Répertoire (Clients/Prospects), #59 Reporting, #60 Refonte front Malio. diff --git a/docs/superpowers/specs/2026-06-19-migration-modular-monolith-roadmap.md b/docs/superpowers/specs/2026-06-19-migration-modular-monolith-roadmap.md new file mode 100644 index 0000000..bfd5fd5 --- /dev/null +++ b/docs/superpowers/specs/2026-06-19-migration-modular-monolith-roadmap.md @@ -0,0 +1,161 @@ +# Roadmap — Migration Lesstime → modular monolith DDD (archi Starseed) + +> Plan de migration **complet** validé le 2026-06-19. Référence architecture : repo **Starseed** +> (`.claude/rules/*.md` + implémentation réelle). Détail technique du socle : voir +> `2026-06-19-lst-56-modular-monolith-design.md`. + +## Principes directeurs + +- **Strangler progressif** : legacy (`src/Entity/…`) et modules (`src/Module/…`) coexistent ; l'app + reste fonctionnelle et `make test` vert à **chaque** merge. Aucune migration destructive (prod Docker, BDD peuplée → migrations **additives nullable** uniquement). +- **DDD pragmatique** (= Starseed réel) : ORM attrs dans l'entité Domain, Repository interface (Domain) + + impl Doctrine (Infra), Provider/Processor API Platform, contrats `Shared/Domain/Contract` pour le + cross-module. **Pas de CQRS bus, pas de multi-tenant.** +- **Tranches verticales** : chaque module de Phase 2 est livré **back + front (layer Malio) + MCP** + d'un coup → fonctionnel de bout en bout à son merge. L'ancienne idée d'un « ticket refonte front » + global est dissoute : chaque module arrive déjà en Malio ; un ticket de finition harmonise à la fin. +- **Ordre par dépendances** : socle → Core (identité/RBAC/audit) → modules métier → transverse/finition. +- **Zéro import inter-modules** : interfaces `Shared/Domain/Contract` + `resolve_target_entities`, + ou domain events / contrat `NotifierInterface`. + +## Garde-fous Starseed (appliqués à chaque entité migrée) + +`declare(strict_types=1)` · `TimestampableBlamableTrait` (4 colonnes nullable) + subscriber · +pagination obligatoire · `COMMENT ON COLUMN` (helper `ColumnCommentsCatalog`) · +`#[Auditable]`/`#[AuditIgnore]` (dès que 1.3 est livré) · front `Malio*` + `usePaginatedList` + +`useFormErrors` · RBAC `module.resource.action` (dès 1.2). + +--- + +## Phase 0 — Socle (fondations, ne touche aucun métier) + +### 0.1 · Socle back — infrastructure modulaire *(réécrit depuis #56)* +**Dépend de** : — +`src/Shared/Domain/Contract/` (UserInterface, UserResolverInterface, ClientInterface, NotifierInterface), +`Shared/Domain/Event/DomainEventInterface`, `Shared/Domain/Trait/TimestampableBlamableTrait`, +`Shared/Infrastructure/Doctrine/TimestampableBlamableSubscriber`, +`Shared/Infrastructure/Database/ColumnCommentsCatalog`, +`Shared/Infrastructure/ApiPlatform/{Resource,State}` (`ModulesResource`/`ModulesProvider`, +`SidebarResource`/`SidebarProvider`), `config/modules.php`, `config/sidebar.php`, `/api/version` aligné. +Config additive : mapping Doctrine module prêt, `migrations_paths` modulaire, `api_platform.mapping.paths`. +**AC** : `/api/modules` + `/api/sidebar` répondent ; app verte ; aucune migration destructive. + +### 0.2 · Socle front — shell + auto-détection des layers +**Dépend de** : 0.1 +`frontend/app/` (shell `layouts/default.vue`), `frontend/shared/` (`useApi` déplacé, `useSidebar`, +`useModules`, stores), middlewares `auth.global.ts` + `modules.global.ts`, auto-détection des layers +`modules/*/` dans `nuxt.config.ts`. **Aucune page métier déplacée** (app encore plate). +**AC** : sidebar dynamique depuis `/api/sidebar` ; routes désactivées redirigées ; app verte. + +--- + +## Phase 1 — Module Core (identité, sécurité, traçabilité — transverse) + +### 1.1 · Core — Identité & Notifications +**Dépend de** : 0.1, 0.2 +Migrer `User` + Auth/JWT dans `src/Module/Core/` (Domain/Entity, Repository interface + Doctrine impl, +`MeProvider`, password hasher), `User implements UserInterface`, `resolve_target_entities → Core\User`. +`Notification` exposée via `NotifierInterface`. `CoreModule.php` (**REQUIRED=true**). Front : layer +`modules/core/` (login, profile, admin users). +**AC** : login/JWT OK ; app verte ; aucun import direct `App\Entity\User` hors Core. + +### 1.2 · RBAC fin *(réécrit depuis #57)* +**Dépend de** : 1.1 +`Role`/`Permission`, `permissions()` par module, commande `app:sync-permissions`, `PermissionVoter`, +`SidebarProvider` filtrant **par permission** (en plus du module actif), seed RBAC. Front : gestion des +rôles + `usePermissions`. +**AC** : permissions `module.resource.action` ; sidebar gated par permission. + +### 1.3 · Audit log *(réécrit depuis #61)* +**Dépend de** : 1.1 +`#[Auditable]`/`#[AuditIgnore]` (`Shared/Domain/Attribute`), table `audit_log` (migration additive + +`COMMENT ON COLUMN`), `AuditListener`/`AuditLogWriter`/`RequestIdProvider`, `AuditLogResource` + +`/api/audit-logs` paginé/filtrable, page front + labels i18n `audit.entity.*`. +**AC** : CRUD des entités `#[Auditable]` tracé ; endpoint paginé ; aucune migration destructive. + +--- + +## Phase 2 — Modules métier (tranches verticales back + front + MCP, strangler) + +### 2.1 · Module TimeTracking *(premier module — rodage)* +**Dépend de** : 1.1 +Migrer `TimeEntry` → `src/Module/TimeTracking/` (Domain/Entity, repo, `ActiveTimeEntryProvider`, +`TimeEntryExportService`/controller, MCP TimeEntry tools), front layer `modules/time-tracking/` +(`time-tracking.vue`, components, services, store `timer`). Timestampable additif. **Rode toute la +mécanique modulaire à risque quasi nul.** +**AC** : time tracking fonctionnel en module ; activation/désactivation testée ; app verte. + +### 2.2 · Module ProjectManagement *(cœur métier — réécrit depuis #56 pilote)* +**Dépend de** : 2.1, 1.1 +`Project, Task, Workflow, TaskStatus, TaskGroup, TaskEffort, TaskPriority, TaskTag, TaskRecurrence, +TaskDocument` → `src/Module/ProjectManagement/` (vertical back + MCP Task/Project/TaskMeta/Workflow + +front layer `modules/project-management/`). User/Client via contrats (Client encore legacy jusqu'à 2.4). +Notifications via `NotifierInterface`. `#[ApiResource]` conservés (étendre le scan). Timestampable additif. +**AC** : cœur en module sans régression API ; app verte. + +### 2.3 · Module Absence +**Dépend de** : 1.1 +`AbsenceRequest/AbsencePolicy/AbsenceBalance` + services (`AbsenceBalanceService`, `AbsenceDayCalculator`, +`PublicHolidayProvider`) + controllers (calendar, preview, justificatif) + MCP absence tools → +`src/Module/Absence/`, front layer `modules/absence/`. +**AC** : module absences complet ; app verte. + +### 2.4 · Module Directory — Clients + Prospects *(réécrit depuis #58)* +**Dépend de** : 1.1 (et après 2.2 qui référence Client via contrat) +`Client` → `src/Module/Directory/` + nouvelle entité `Prospect`. L'impl de `ClientInterface` migre du +legacy vers le module (`resolve_target_entities` mis à jour). Front répertoire (clients + prospects). +**AC** : Clients + Prospects en module ; contrats à jour ; app verte. + +### 2.5 · Module Mail +**Dépend de** : 1.1, 2.2 (TaskMailLink → Task) +`Mail*` + `TaskMailLink` + `MailSyncService` + controllers + settings → `src/Module/Mail/`, front layer. +Intègre le WIP `feat/mail-integration`. +**AC** : mail en module ; app verte. + +### 2.6 · Module Integration — Gitea / BookStack / Zimbra / Share +**Dépend de** : 1.1, 2.2 (liens Task) +Configs + services API (`GiteaApiService`, `BookStackApiService`, `CalDavService`, Share) + controllers + +liens → `src/Module/Integration/`, front (onglets admin + sections task). +**AC** : intégrations en module ; app verte. + +--- + +## Phase 3 — Transverse & finition + +### 3.1 · Module Reporting *(réécrit depuis #59)* +**Dépend de** : Phase 2 (consomme les modules) +Reporting natif transverse (agrège time tracking, tâches, absences) via contrats / API. Module +`src/Module/Reporting/` + front. +**AC** : rapports natifs ; aucune dépendance directe inter-modules. + +### 3.2 · Module Portail client +**Dépend de** : 1.1, 2.2, 2.4 +Portail client (accès restreint), module `src/Module/ClientPortal/` + front layer + RBAC dédié. +**AC** : portail fonctionnel ; gated RBAC. + +### 3.3 · Finition Malio + nettoyage legacy *(réécrit depuis #60)* +**Dépend de** : tout +Harmonisation visuelle Malio finale, **vidage de `src/Entity/` legacy résiduel**, suppression du mapping +Doctrine legacy + des pages plates `frontend/pages/` résiduelles, durcissement `resolve_target_entities`. +**AC** : `src/Entity` vide ; 100 % modulaire ; app verte ; aucune route/legacy orpheline. + +--- + +## Ordre d'exécution recommandé + +`0.1 → 0.2 → 1.1 → 1.2 → 1.3 → 2.1 → 2.2 → 2.3 → 2.4 → 2.5 → 2.6 → 3.1 → 3.2 → 3.3` + +Les tickets 1.2 et 1.3 peuvent se paralléliser après 1.1. Les modules 2.3 (Absence) et 2.4 (Directory) +peuvent se paralléliser après 2.2. Mail (2.5) et Integration (2.6) suivent 2.2. + +## Mapping avec les tickets Lesstime existants + +| Ancien | Devient | +|--------|---------| +| #56 (1/5 Aligner archi) | **0.1 Socle back** (le reste éclaté en 0.2 + 2.2) | +| #57 (2/5 RBAC) | **1.2 RBAC fin** | +| #58 (3/5 Répertoire) | **2.4 Directory** | +| #59 (4/5 Reporting) | **3.1 Reporting** | +| #60 (5/5 Front Malio) | **3.3 Finition Malio + nettoyage** (le front se fait par module) | +| #61 (Audit) | **1.3 Audit log** | +| *(créés)* | 0.2, 1.1, 2.1, 2.2, 2.3, 2.5, 2.6, 3.2 |