Files
Lesstime/docs/superpowers/specs/2026-05-19-project-workflows-design.md
Matthieu 6a942def3f docs(workflows) : spec workflows de statuts par projet
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:59:12 +02:00

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.
  • 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 = <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
  • Passe les 2 colonnes en NOT NULL

M3 — add_workflow_to_project

  • Ajoute project.workflow_id nullable
  • UPDATE project SET workflow_id = <id Standard> 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: { "<sourceStatusId>": <targetStatusId> | 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 = <target> WHERE project_id = X AND status_id = <source>
    • UPDATE project SET workflow_id = <new>
  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.