# 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.
