12 KiB
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. categoryobligatoire : 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
reviewdans Dev Kanban). La catégorie n'est pas une contrainte, juste un bucket de regroupement. onDelete=RESTRICTsurProject.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_idnullable +task_status.categorynullable UPDATE task_status SET workflow_id = <id Standard>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
- "À faire" →
- Passe les 2 colonnes en
NOT NULL
M3 — add_workflow_to_project
- Ajoute
project.workflow_idnullable UPDATE project SET workflow_id = <id Standard>pour tous les projets existants- Passe en
NOT NULLavec FKON DELETE RESTRICT
4. Backend (Symfony / API Platform)
Entités
App\Entity\Workflow— nouvelle entité, ApiResource avecROLE_ADMINpour Post/Patch/DeleteApp\Enum\StatusCategory— enum PHP avec les 5 valeurs canoniquesApp\Entity\TaskStatus— ajout des propriétésworkflow(ManyToOne) etcategory(StatusCategory)App\Entity\Project— ajout de la propriétéworkflow(ManyToOne, requise)
Sérialisation
- Groupe
workflow:readpour l'API admin task_status:readajouteworkflowetcategoryproject:readembarque 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: { "<sourceStatusId>": <targetStatusId> | null, ... }
}
Security: ROLE_ADMIN
Processor : App\State\SwitchProjectWorkflowProcessor
- Valide qu'il y a une entrée de mapping pour chaque
statusIdactuellement référencé par les tâches du projet (sinon 422 avec liste des sources manquantes) - Valide que chaque target appartient bien au workflow cible (ou est
null) - Transaction unique :
- Pour chaque entrée du mapping :
UPDATE task SET status_id = <target> WHERE project_id = X AND status_id = <source> UPDATE project SET workflow_id = <new>
- Pour chaque entrée du mapping :
- Retourne
{ project, migratedTaskCount }
Validation cross-entity
- Sur
Task(Post/Patch) : sistatusfourni, valider questatus.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 CRUDfrontend/services/dto/workflow.ts— type TSfrontend/components/admin/AdminWorkflowTab.vue— nouvel onglet dans/adminfrontend/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.statusesau lieu destatusService.getAll() - Le kanban a les colonnes du workflow du projet
- Charge
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
:statusesfiltrés par workflow du projet via les pages parentes (déjà la pattern actuelle)
- Reçoit
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 :
MalioSelectdes 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 (
MalioSelectdes 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)
- Une ligne par statut source effectivement utilisé par les tâches du projet (count > 0) + une ligne "Backlog" si des tâches
- 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-taskskanban groupé : avec des projets multi-workflows, l'utilisateur voit une card "In Dev" et une card "En cours" dans la même colonnein_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
- Migrations BDD + entité
Workflow+ enumStatusCategory+ adaptations entitésTaskStatusetProject - Validation cross-entity sur
Task+ sérialisation des nouvelles propriétés - Endpoint
POST /api/projects/{id}/switch-workflow+ processor - Service frontend
workflows+ types DTO - UI admin :
AdminWorkflowTab+WorkflowDrawer - Adaptation
projects/[id]/index.vue(kanban filtré par workflow) - Adaptation
my-tasks.vue(kanban groupé par catégorie) ProjectWorkflowSwitchModal+ intégration dansProjectDrawer- Adaptation
TaskBulkActionset autres écrans transverses - MCP : modification
list-statuses+ nouveaulist-workflows+ mise à jour des descriptions - 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.