Files
Inventory_frontend/migration.md
Matthieu cc70fe2b29 feat(permissions) : add role-based UI guards and readonly mode for viewers
- Add usePermissions composable (isAdmin, canEdit, canView)
- Password-protected profile login with modal on profiles page
- Disable all form fields for ROLE_VIEWER across edit/create pages
- Show navigation buttons (Modifier/Consulter) for all roles, hide delete for viewers
- Add readonly prop to ModelTypeForm for category pages
- Disable modal fields (sites, constructeurs) for viewers
- Guard /admin routes in middleware

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 13:36:42 +01:00

230 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan de migration — Réduction de code frontend
> Objectif : réduire ~5 700 LOC sans modifier le fonctionnel.
> Branche : à partir de `refacto/F1-decoupage-mega-composants`
> Statut global : **EN ATTENTE**
---
## Phase 1 — Pages catalogue (3 pages, ~1 200 LOC → ~350 LOC)
### M1.1 · Composant générique `CatalogPage.vue`
- **Motif** : `component-catalog.vue` (348 LOC), `pieces-catalog.vue` (463 LOC) et `product-catalog.vue` (408 LOC) partagent 95 % de structure (recherche, tri, pagination, tableau, suppression, états vides/loading).
- **Différences isolées** : colonnes du tableau, garde de suppression, extraction fournisseur.
- **Plan** :
1. Créer `app/components/common/CatalogPage.vue` acceptant :
- `columns: ColumnDef[]` (nom, clé, slot optionnel)
- `fetchFn: (params) => Promise<PaginatedResult>`
- `deleteFn: (id) => Promise<Result>`
- `deleteGuard?: (item) => string | null` (message bloquant ou null)
- `entityLabel: string`, `createRoute: string`
- Slots nommés pour colonnes custom (`#col-supplier`, etc.)
2. Extraire `supplierDisplayUtils.ts` (pattern `MAX_VISIBLE_SUPPLIERS` dupliqué dans pieces-catalog et product-catalog).
3. Réduire chaque page catalogue à ~80 LOC (config + slots custom).
- **Gain estimé** : ~850 LOC
- **Statut** : `[ ]`
---
## Phase 2 — Composables CRUD génériques (~1 170 LOC → ~400 LOC)
### M2.1 · Factory `useEntityCRUD<T>(config)`
- **Motif** : `usePieces.ts` (240), `useProducts.ts` (305), `useComposants.ts` (231), `useSites.ts` (124) suivent le même pattern CRUD : refs `loading/loaded/error`, `loadItems()` paginé, `create/update/delete` avec mise à jour cache + toast.
- **Différences isolées** : endpoint, normaliseur, enrichissement constructeurs, champs de tri.
- **Plan** :
1. Créer `app/composables/useEntityCRUD.ts` :
```ts
interface EntityCRUDConfig {
endpoint: string
label: string
normalizer?: (item: any) => any
enricher?: (item: any) => Promise<any>
defaultSort?: { field: string; dir: 'asc' | 'desc' }
}
export function useEntityCRUD(config: EntityCRUDConfig)
```
2. Extraire `extractTotal()` dans `apiHelpers.ts` (dupliqué 3×, ~10 LOC chacun).
3. Extraire `buildPaginatedQuery(options)` dans `apiHelpers.ts` (dupliqué 3×, ~15 LOC chacun).
4. Extraire pattern `withResolvedConstructeurs()` dans `useEntityEnricher.ts` (dupliqué 3× dans pieces/products/composants, ~50 LOC chacun).
5. Réduire chaque composable à un appel de factory + méthodes spécifiques.
6. Garder `useMachines.ts` séparé (méthodes spéciales : `reconfigureSkeleton`, `createMachineFromType`).
- **Gain estimé** : ~770 LOC
- **Statut** : `[ ]`
### M2.2 · Helper `withLoadingState()`
- **Motif** : pattern `loading.value = true; try { ... } finally { loading.value = false }` répété 10+ fois dans les composables CRUD.
- **Plan** : créer `app/composables/useLoadingHelper.ts` exportant :
```ts
async function withLoadingState<T>(loading: Ref<boolean>, fn: () => Promise<T>): Promise<T>
```
- **Gain estimé** : ~100 LOC
- **Statut** : `[ ]`
### M2.3 · Fusion `usePersistedValue` + `usePersistedSort`
- **Motif** : même pattern `useCookie()` + `watch()` + JSON parse/stringify.
- **Plan** : fusionner en `usePersistedState<T>(key, fallback, prefix?)`.
- **Gain estimé** : ~30 LOC
- **Statut** : `[ ]`
---
## Phase 3 — Pages edit entités (~2 750 LOC → ~1 200 LOC)
### M3.1 · Composant `HistorySection.vue`
- **Motif** : bloc historique identique (loading/error/empty + itération entries) dans `component/[id]/edit.vue` (L437-503), `pieces/[id]/edit.vue` (L384-450), `product/[id]/edit.vue` (L304-370) — ~67 LOC × 3.
- **Plan** : créer `app/components/common/HistorySection.vue` avec props `entries`, `loading`, `error`.
- **Gain estimé** : ~130 LOC
- **Statut** : `[ ]`
### M3.2 · Composant `DocumentsSection.vue`
- **Motif** : bloc document (upload, liste, preview, download, delete) dupliqué dans les 3 pages edit + `MachineDocumentsCard.vue` + `SiteEditModal.vue` — ~70-180 LOC × 5.
- **Plan** : créer `app/components/common/DocumentsSection.vue` avec props `documents`, `entityId`, `entityType` et events `upload`, `delete`, `preview`.
- **Gain estimé** : ~400 LOC
- **Statut** : `[ ]`
### M3.3 · Composable `useEntityEditForm(config)`
- **Motif** : les 3 pages edit partagent : chargement entité + types + constructeurs, gestion champs custom, normalisation payload, sauvegarde, gestion erreur.
- **Différences** : component a structure display, piece a product selection, product est plus simple.
- **Plan** :
1. Créer `app/composables/useEntityEditForm.ts` gérant le cycle de vie commun (load, save, custom fields sync).
2. Chaque page edit ne garde que ses spécificités.
- **Gain estimé** : ~500 LOC
- **Statut** : `[ ]`
### M3.4 · Réutilisation `customFieldFormUtils.ts` dans `component/create.vue`
- **Motif** : `component/create.vue` (1 266 LOC) réimplémente `resolveFieldName`, `resolveFieldType`, `resolveDefaultValue` déjà dans `customFieldFormUtils.ts`. Aussi 3 fonctions `resolveXxxLabel` quasi-identiques (~18 LOC × 3).
- **Plan** :
1. Remplacer les fonctions locales par les imports de `customFieldFormUtils.ts`.
2. Créer `resolveTypeLabel(entity, typeField, labelField, fallback)` générique.
- **Gain estimé** : ~120 LOC
- **Statut** : `[ ]`
---
## Phase 4 — Décomposition `useMachineDetailData.ts` (1 410 LOC → ~500 LOC)
### M4.1 · Extraire `useMachineDocuments.ts`
- **Motif** : gestion documents (upload, delete, preview, refresh) = ~200 LOC dans le composable monolithique.
- **Gain estimé** : ~150 LOC (après factorisation avec DocumentsSection)
- **Statut** : `[ ]`
### M4.2 · Extraire `useMachineConstructeurs.ts`
- **Motif** : résolution constructeurs avec chaînes de fallback 4 niveaux, `uniqueConstructeurIds`, `resolveConstructeurs` = ~80 LOC.
- **Gain estimé** : ~60 LOC
- **Statut** : `[ ]`
### M4.3 · Fusionner `transformCustomFields` et `transformComponentCustomFields`
- **Motif** : L303-405 et L407-514 — logique quasi-identique de transformation des champs custom, seule la source (machine vs composant) diffère.
- **Plan** : créer `transformEntityCustomFields(entity, fieldSource, config)` paramétrable.
- **Gain estimé** : ~100 LOC
- **Statut** : `[ ]`
### M4.4 · Extraire groupement de requirements
- **Motif** : `componentRequirementGroups`, `pieceRequirementGroups` = computed complexes avec construction de maps et filtres répétitifs.
- **Gain estimé** : ~80 LOC
- **Statut** : `[ ]`
---
## Phase 5 — `StructureNodeEditor.vue` (1 167 LOC → ~600 LOC)
### M5.1 · Composable `useDragDrop.ts`
- **Motif** : 4 handlers drag-drop quasi-identiques (custom fields, pièces, produits, sous-composants) avec chacun `draggingIndex`, `dropTargetIndex`, `reorderClass()`, `handleDragStart/Over/End`.
- **Plan** : créer `useDragDrop<T>(items: Ref<T[]>)` retournant `{ dragging, target, reorderClass, onDragStart, onDragOver, onDragEnd, onDrop }`.
- **Gain estimé** : ~350 LOC
- **Statut** : `[ ]`
### M5.2 · Extraire validation noeud
- **Motif** : `isAssignmentNodeComplete` + logique de validation dispersée.
- **Plan** : déplacer vers `app/shared/utils/structureValidation.ts`.
- **Gain estimé** : ~40 LOC
- **Statut** : `[ ]`
---
## Phase 6 — Micro-duplications restantes (du `micro-dup-report.md`)
### M6.1 · `useControlledModel.ts` (MDUP-004)
- **Motif** : `computed({ get, set })` pour transiter `v-model` entre props et emits — dupliqué dans 6 composants.
- **Gain estimé** : ~60 LOC
- **Statut** : `[ ]`
### M6.2 · `ModalShell.vue` (MDUP-008) + `ModalActions.vue` (MDUP-007)
- **Motif** : squelette de modale DaisyUI (`.modal` + `.modal-box` + titre + footer) dupliqué dans 4+ composants. Pieds de modale « Annuler + Primaire + spinner » dupliqués 5×.
- **Gain estimé** : ~120 LOC
- **Statut** : `[ ]`
### M6.3 · `LoadingButton.vue` (MDUP-010) + `FieldText.vue` (MDUP-009)
- **Motif** : bouton primaire avec spinner (3 occurrences), champ texte simple label+input (5 occurrences).
- **Gain estimé** : ~80 LOC
- **Statut** : `[ ]`
### M6.4 · `createRequirementDefaults` + `useEnsureOptionsLoaded` (MDUP-005, MDUP-006)
- **Motif** : factory de requirement par défaut + `onMounted` identiques dans les sections composant/pièce.
- **Gain estimé** : ~30 LOC
- **Statut** : `[ ]`
---
## Phase 7 — Consolidation custom fields (~1 150 LOC → ~800 LOC)
### M7.1 · Fusionner logique de résolution dans `customFieldUtils.ts`
- **Motif** : `customFieldUtils.ts` (440), `entityCustomFieldLogic.ts` (349), `customFieldFormUtils.ts` (367) contiennent des fonctions de résolution de champs qui se chevauchent (`resolveFieldId`, `resolveFieldName`, génération de clé, déduplication).
- **Plan** : consolider les fonctions dupliquées en gardant la séparation thématique (utils / form / entity) mais en partageant les primitives.
- **Gain estimé** : ~150 LOC
- **Statut** : `[ ]`
---
## Récapitulatif
| Phase | Cible | LOC avant | Gain estimé | Priorité |
|-------|-------|-----------|-------------|----------|
| **P1** | Pages catalogue | ~1 220 | ~850 | Haute |
| **P2** | Composables CRUD | ~1 170 | ~900 | Haute |
| **P3** | Pages edit entités | ~2 750 | ~1 150 | Haute |
| **P4** | useMachineDetailData | ~1 410 | ~390 | Moyenne |
| **P5** | StructureNodeEditor | ~1 167 | ~390 | Moyenne |
| **P6** | Micro-duplications | ~400 | ~290 | Basse |
| **P7** | Custom fields utils | ~1 150 | ~150 | Basse |
| | **Total** | | **~4 120 LOC** | |
### Ordre recommandé
1. **P2** (CRUD generics) — fondation pour P1 et P3
2. **P1** (catalogues) — dépend de P2 pour les fetch functions
3. **P3** (pages edit) — plus gros gain absolu, dépend partiellement de P2
4. **P5** (drag-drop) — indépendant, quick win
5. **P4** (machine detail) — complexe mais fort impact
6. **P6** (micro-dup) — petits gains, faible risque
7. **P7** (custom fields) — délicat, à faire en dernier
### Vérification après chaque phase
```bash
cd Inventory_frontend
npx nuxi typecheck # 0 erreurs
npm run lint:fix # 0 erreurs
npm run build # succès
npx vitest run # 54+ tests pass
```