From 88a4916662f90db8cc17f47cc079ceef2ef1a26c Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 19 May 2026 17:55:19 +0200 Subject: [PATCH] docs(workflows) : spec workflows de statuts par projet Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-19-project-workflows-design.md | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-19-project-workflows-design.md diff --git a/docs/superpowers/specs/2026-05-19-project-workflows-design.md b/docs/superpowers/specs/2026-05-19-project-workflows-design.md new file mode 100644 index 0000000..34fffbe --- /dev/null +++ b/docs/superpowers/specs/2026-05-19-project-workflows-design.md @@ -0,0 +1,209 @@ +# Workflows de statuts par projet (Kanban custom) + +**Date** : 2026-05-19 +**Branche** : `feat/project-workflows` +**Statut** : design validé, en attente de plan d'implémentation + +## 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 : + - "À faire" → `todo` + - "En cours" → `in_progress` + - "Bloqué" → `blocked` + - "En attente de validation" → `review` + - "Terminé" → `done` + - Tout autre statut existant non-mappable → `in_progress` (fallback neutre) + log warning +- 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` (V2 — non bloquant) | Permet de piloter le switch via MCP. Reporté à une itération ultérieure si pas critique. | + +## 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é +- **MCP `switch-project-workflow`** — peut être ajouté en V2 si le besoin se manifeste + +## 9. Risques et limites + +- **Migration M2 (backfill catégories)** : si un déploiement intermédiaire a créé des statuts non prévus, ils tombent sur le fallback `in_progress`. Vérifier l'état de la prod avant migration et ajuster le SQL si besoin. +- **`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` + nouveau `list-workflows` + mise à jour des descriptions +11. Tests : PHPUnit pour le processor + validation cross-entity ; tests fonctionnels du switch + +Le découpage exact (tickets, ordre, dépendances) sera fait dans le plan d'implémentation.