# Workflows de statuts par projet (Kanban custom) **Date** : 2026-05-19 **Branche** : `feat/project-workflows` **Statut** : design validé (2026-05-19, par Matthieu), en attente de plan d'implémentation ## Reprise sur un autre poste > **Pour le prochain Claude qui ouvre cette branche :** > > 1. Branche `feat/project-workflows` checkout-ée, basée sur `develop` (commit `5585fa7` à l'origine). > 2. **Ce qui est fait** : design validé avec Matthieu et committé (ce fichier). > 3. **Aucun code applicatif n'a encore été écrit.** > 4. **Prochaine étape** : invoquer la skill `superpowers:writing-plans` pour transformer ce design en plan d'implémentation détaillé (découpage en tickets ordonnés, dépendances, estimations). > 5. **Validations Matthieu (2026-05-19)** : > - Hors scope (§8) → MCP `switch-project-workflow` **rapatrié dans la V1** (cf. §6). > - Fallback `in_progress` pour statuts non-mappables → **abandonné**. Seuls les 5 statuts standards existent ; la migration M2 échoue explicitement si elle rencontre autre chose. > - Suppression d'`AdminStatusTab` → **OK**. > - Ordre des étapes de livraison (§10) → **OK**. > 6. **Time tracking** : créer un nouveau timer Lesstime au reprise (projet=5 Lesstime, tags=[3 Backend, 9 Gestion projet]). > 7. **Fichiers déjà modifiés sur develop (orphelins, pas liés à cette feature)** à ne PAS toucher : `.mcp.json`, `config/reference.php`, `frontend/package-lock.json`, `frontend/pages/profile.vue`. ## 1. Contexte et besoin Aujourd'hui les `TaskStatus` sont globaux : tous les projets partagent le même jeu de 5 statuts (À faire / En cours / Bloqué / En attente de validation / Terminé). Pour les gros projets de dev, on veut pouvoir définir un kanban plus riche (ex : Backlog / To Do / In Dev / Code Review / QA / Blocked / Ready to deploy / Done) sans imposer ce détail aux projets simples. **Objectif** : permettre à chaque projet d'avoir son propre jeu de colonnes kanban, via des **templates de workflows réutilisables** définis en admin et assignés à un projet, sans casser les projets existants ni les vues transverses (`my-tasks`, time-tracking, dashboards, MCP). ## 2. Modèle de données ### Nouvelle entité : `Workflow` ``` Workflow - id int, PK - name string(255), unique - isDefault bool (un seul = true ; assigné aux projets sans workflow explicite ; unicité garantie par un listener Doctrine PrePersist/PreUpdate) - position int (pour l'ordre dans l'admin) - statuses OneToMany → TaskStatus (inverse côté Workflow) ``` ### Modifications : `TaskStatus` ``` TaskStatus + workflow_id int, FK → Workflow, NOT NULL, onDelete=CASCADE + category string, enum PHP : 'todo' | 'in_progress' | 'blocked' | 'review' | 'done', NOT NULL ~ position devient relatif au workflow (idéalement contrainte unique (workflow_id, position)) - isFinal conservé tel quel — distinct de category='done' (permet un statut "Annulé" final ≠ done) ``` ### Modifications : `Project` ``` Project + workflow_id int, FK → Workflow, NOT NULL, onDelete=RESTRICT ``` ### Choix de design - **Pas de partage de statuts entre workflows** : chaque workflow a SES PROPRES rows `TaskStatus`. "À faire" du workflow Standard ≠ "À faire" de Dev Kanban (IDs et couleurs distincts). Évite les bugs de couplage, simplifie le mapping lors du switch. - **`category` obligatoire** : pivot pour les vues transverses + mapping auto lors du switch. 5 valeurs : `todo`, `in_progress`, `blocked`, `review`, `done`. - **Plusieurs statuts peuvent partager la même catégorie** dans un workflow (ex : 3 statuts en `review` dans Dev Kanban). La catégorie n'est pas une contrainte, juste un bucket de regroupement. - **`onDelete=RESTRICT` sur `Project.workflow_id`** : un workflow ne peut pas être supprimé s'il a au moins un projet attaché. Protection à 3 niveaux (DB / API / UI). - **Suppression de TaskStatus** : reste protégée comme aujourd'hui via le flow `ConfirmDeleteStatusModal` (réassignation des tâches à un autre statut ou null). ## 3. Migrations BDD Trois migrations Doctrine successives : **M1 — `create_workflow_table`** - Crée la table `workflow` (id, name, is_default, position) - Insère le workflow par défaut `Standard` (is_default=true, position=0) **M2 — `add_workflow_to_task_status`** - Ajoute `task_status.workflow_id` nullable + `task_status.category` nullable - `UPDATE task_status SET workflow_id = ` pour toutes les lignes existantes - Backfill catégories (uniquement les 5 statuts standards existants — confirmé avec Matthieu 2026-05-19) : - "À faire" → `todo` - "En cours" → `in_progress` - "Bloqué" → `blocked` - "En attente de validation" → `review` - "Terminé" → `done` - La migration **échoue** (exception) si elle rencontre un label non listé → garde-fou explicite contre toute prod qui aurait dérivé. - Passe les 2 colonnes en `NOT NULL` **M3 — `add_workflow_to_project`** - Ajoute `project.workflow_id` nullable - `UPDATE project SET workflow_id = ` pour tous les projets existants - Passe en `NOT NULL` avec FK `ON DELETE RESTRICT` ## 4. Backend (Symfony / API Platform) ### Entités - `App\Entity\Workflow` — nouvelle entité, ApiResource avec `ROLE_ADMIN` pour Post/Patch/Delete - `App\Enum\StatusCategory` — enum PHP avec les 5 valeurs canoniques - `App\Entity\TaskStatus` — ajout des propriétés `workflow` (ManyToOne) et `category` (StatusCategory) - `App\Entity\Project` — ajout de la propriété `workflow` (ManyToOne, requise) ### Sérialisation - Groupe `workflow:read` pour l'API admin - `task_status:read` ajoute `workflow` et `category` - `project:read` embarque le workflow (ou son IRI) — décision à arbitrer dans le plan d'impl (vraisemblablement embarqué pour limiter les round-trips) ### Endpoint dédié au switch ``` POST /api/projects/{id}/switch-workflow Body: { workflowId: int, mapping: { "": | null, ... } } Security: ROLE_ADMIN ``` **Processor** : `App\State\SwitchProjectWorkflowProcessor` 1. Valide qu'il y a une entrée de mapping pour chaque `statusId` actuellement référencé par les tâches du projet (sinon 422 avec liste des sources manquantes) 2. Valide que chaque target appartient bien au workflow cible (ou est `null`) 3. Transaction unique : - Pour chaque entrée du mapping : `UPDATE task SET status_id = WHERE project_id = X AND status_id = ` - `UPDATE project SET workflow_id = ` 4. Retourne `{ project, migratedTaskCount }` ### Validation cross-entity - Sur `Task` (Post/Patch) : si `status` fourni, valider que `status.workflow === task.project.workflow`. Sinon 422 `"Status does not belong to this project's workflow"`. ### Suppression d'un Workflow - `WorkflowProcessor` (custom Delete) : compte les projets liés ; si > 0, renvoie 409 Conflict avec `{ linkedProjectIds: [...], message: "Workflow used by N project(s)" }` ## 5. Frontend (Nuxt / Vue) ### Nouveaux fichiers - `frontend/services/workflows.ts` — service API CRUD - `frontend/services/dto/workflow.ts` — type TS - `frontend/components/admin/AdminWorkflowTab.vue` — nouvel onglet dans `/admin` - `frontend/components/admin/WorkflowDrawer.vue` — drawer création/édition workflow (nom + liste éditable des statuts avec leur catégorie) - `frontend/components/project/ProjectWorkflowSwitchModal.vue` — modal de migration ### Modifications - `frontend/components/admin/AdminStatusTab.vue` : - **Supprimé.** Toute la gestion des statuts passe par l'onglet Workflows (un workflow = nom + sa liste de statuts éditable inline). Évite la confusion "où je crée un statut ?". - `frontend/components/project/ProjectDrawer.vue` : - Affiche le workflow actuel - Bouton "Changer de workflow" qui ouvre `ProjectWorkflowSwitchModal` - `frontend/pages/projects/[id]/index.vue` : - Charge `project.workflow.statuses` au lieu de `statusService.getAll()` - Le kanban a les colonnes du workflow du projet - `frontend/pages/projects/[id]/archives.vue` : - Filtre statut limité au workflow du projet - `frontend/pages/my-tasks.vue` : - **Kanban groupé par catégorie** : 5 colonnes (Todo / In Progress / Blocked / Review / Done) - Chaque card affiche le statut spécifique en badge - Vue liste : pas de changement - `frontend/components/task/TaskModal.vue` : - Reçoit `:statuses` filtrés par workflow du projet via les pages parentes (déjà la pattern actuelle) - `frontend/components/task/TaskBulkActions.vue` : - Dropdown statut filtré au workflow du projet de la tâche sélectionnée - Si tâches multi-projets : bouton "Changer le statut" désactivé avec tooltip explicatif ### `ProjectWorkflowSwitchModal.vue` — détails UX - Étape 1 : `MalioSelect` des workflows disponibles (sauf le workflow actuel) - Étape 2 (après sélection) : tableau de mapping - Une ligne par statut source effectivement utilisé par les tâches du projet (count > 0) + une ligne "Backlog" si des tâches `status=null` - Colonnes : Source (label + badge catégorie) → Cible (`MalioSelect` des statuts du workflow cible, pré-rempli intelligemment) → Nb de tâches concernées - Pré-remplissage : pour chaque source, on cherche dans le workflow cible le statut de **même catégorie** avec la plus petite `position`. Si aucune correspondance par catégorie, l'utilisateur doit choisir manuellement. - Option "Mapper vers le backlog" sur chaque ligne (= cible `null`) - Footer : - Bouton "Confirmer la migration" désactivé tant qu'au moins un mapping est manquant - Toast au succès : "N tâches migrées, projet sur workflow ''" ## 6. MCP | Tool | Changement | |---|---| | `list-statuses` | Ajout d'un param optionnel `projectId?: int`. Si fourni → renvoie les statuts du workflow du projet. Sinon → renvoie tous les statuts avec `workflowId` et `category` ajoutés. Description mise à jour pour mentionner les workflows. | | `list-workflows` (nouveau) | Liste tous les workflows avec leurs statuts groupés (`{ id, name, isDefault, statuses: [...] }`). | | `create-task` / `update-task` | La validation backend (côté entité Task) rejette automatiquement un `status` n'appartenant pas au workflow du projet. Documenter dans la description du tool. | | `switch-project-workflow` (nouveau, ROLE_ADMIN) | Wrappe l'endpoint `POST /api/projects/{id}/switch-workflow`. Params : `projectId`, `workflowId`, `mapping: { [sourceStatusId]: targetStatusId \| null }`. Renvoie `{ migratedTaskCount }`. Mêmes validations que l'endpoint HTTP. | ## 7. Permissions | Action | Rôle requis | |---|---| | Lire les workflows et leurs statuts | `ROLE_USER` | | Créer / éditer / supprimer un workflow | `ROLE_ADMIN` | | Créer / éditer / supprimer un statut | `ROLE_ADMIN` | | Changer le workflow d'un projet (switch) | `ROLE_ADMIN` | ## 8. Hors scope (YAGNI explicites) - **Workflows en read-only intégrés** (ex : "Scrum officiel" non éditable) — pas besoin pour l'instant - **Transitions autorisées** entre statuts (ex : impossible de passer de "Backlog" directement à "Done") — pas demandé, ajouterait beaucoup de complexité - **Versioning des workflows** (historique des modifs) — pas demandé - **Workflow par groupe de tâches** (TaskGroup avec son propre workflow dans un projet) — pas demandé ## 9. Risques et limites - **Migration M2 (backfill catégories)** : la migration échoue si elle rencontre un label de statut autre que les 5 standards. Si la prod a dérivé entre temps, ajouter le mapping manuellement à la migration avant déploiement. - **`my-tasks` kanban groupé** : avec des projets multi-workflows, l'utilisateur voit une card "In Dev" et une card "En cours" dans la même colonne `in_progress`. Le badge statut sur la card doit rester lisible (taille suffisante, couleur du statut). - **Filtre statut dans `my-tasks` (vue liste)** : aujourd'hui pas de filtre statut côté `my-tasks` (cf. code), donc rien à adapter. Si on en ajoute un plus tard, il faudra qu'il propose les catégories plutôt que les statuts spécifiques. - **Sélection multi-projets dans `TaskBulkActions`** : le bouton "Changer de statut" se désactive ; à valider que le reste du bulk reste utilisable (assignee, priorité, effort, group — eux restent globaux ou per-project comme aujourd'hui). ## 10. Étapes de livraison suggérées 1. Migrations BDD + entité `Workflow` + enum `StatusCategory` + adaptations entités `TaskStatus` et `Project` 2. Validation cross-entity sur `Task` + sérialisation des nouvelles propriétés 3. Endpoint `POST /api/projects/{id}/switch-workflow` + processor 4. Service frontend `workflows` + types DTO 5. UI admin : `AdminWorkflowTab` + `WorkflowDrawer` 6. Adaptation `projects/[id]/index.vue` (kanban filtré par workflow) 7. Adaptation `my-tasks.vue` (kanban groupé par catégorie) 8. `ProjectWorkflowSwitchModal` + intégration dans `ProjectDrawer` 9. Adaptation `TaskBulkActions` et autres écrans transverses 10. MCP : modification `list-statuses` + nouveaux `list-workflows` et `switch-project-workflow` + mise à jour des descriptions 11. Tests : PHPUnit pour le processor + validation cross-entity ; tests fonctionnels du switch (HTTP + MCP) Le découpage exact (tickets, ordre, dépendances) sera fait dans le plan d'implémentation.