225 lines
13 KiB
Markdown
225 lines
13 KiB
Markdown
# 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 = <id Standard>` 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 = <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 '<nom>'"
|
|
|
|
## 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.
|