# Prompt p18 — Catalogue Produits (web)
*Contextes requis : ctx-global.md + ctx-web-nextjs.md + ctx-laravel-bff.md*

---

## Profil & Accès
- **Rôle** : GPR (Gestionnaire Produits)
- **Nombre d'utilisateurs** : 1
- **Accès** : catalogue complet — 18 produits × 23 stations, statuts, historique, prix
- **Session** : 8h, OTP à la connexion

---

## Fichiers à créer

```
app/(erp)/produits/page.tsx
app/(erp)/produits/[id]/page.tsx
app/(erp)/produits/[id]/historique/page.tsx
app/(erp)/produits/nouveau/page.tsx
app/(erp)/produits/disponibilite/page.tsx
components/produits/ProduitTable.tsx
components/produits/ProduitForm.tsx
components/produits/WorkflowStatuts.tsx
components/produits/MatriceDisponibilite.tsx
components/produits/PrixPlancher.tsx
components/produits/HistoriqueProduit.tsx
components/produits/BrouillonParallele.tsx
hooks/useProduits.ts
```

---

## Spec UX condensée

```
┌─────────────────────────────────────────────────────────────────────┐
│ FLOWUP ERP          Catalogue Produits                      [GPR]   │
├─────────────────────────────────────────────────────────────────────┤
│ [+ Nouveau produit]   [Matrice disponibilité]   Filtres ▾           │
├─────────────────────────────────────────────────────────────────────┤
│ CATALOGUE (18 produits)                                             │
│ ┌──────────────┬──────────────┬──────────┬──────────┬────────────┐  │
│ │ CODE         │ NOM          │ CATÉGORIE│ PRIX/L   │ STATUT     │  │
│ ├──────────────┼──────────────┼──────────┼──────────┼────────────┤  │
│ │ PRD-CAR-001  │ Gasoil       │ Carburant│ 6 000 GNF│ ● ACTIF    │  │
│ │ PRD-CAR-002  │ SP95         │ Carburant│ 6 300 GNF│ ● ACTIF    │  │
│ │ PRD-CAR-003  │ SP98         │ Carburant│ 6 800 GNF│ ● ACTIF    │  │
│ │ PRD-CAR-004  │ Pétrole lamp.│ Carburant│ 5 200 GNF│ ● ACTIF    │  │
│ │ PRD-LUB-001  │ Huile moteur │ Lubrifiant│ 45 000  │ ● ACTIF    │  │
│ │ PRD-LUB-002  │ Graisse    …│ Lubrifiant│ 28 000  │ ○ BROUILLON │  │
│ │ PRD-CAR-005  │ Gasoil v2    │ Carburant│ 6 050 GNF│ ⏳EN_VALID │  │
│ └──────────────┴──────────────┴──────────┴──────────┴────────────┘  │
├─────────────────────────────────────────────────────────────────────┤
│ MATRICE DISPONIBILITÉ (23 stations × 18 produits)                   │
│ ┌───────────────┬──────┬──────┬──────┬──────┬──────┬─────────────┐  │
│ │ STATION       │GAZ001│SP95  │SP98  │PET   │LUB01 │ ...         │  │
│ ├───────────────┼──────┼──────┼──────┼──────┼──────┼─────────────┤  │
│ │ Kaloum        │  ✓   │  ✓   │  ✓   │  ✓   │  ✓   │             │  │
│ │ Ratoma        │  ✓   │  ✓   │  ✗   │  ✓   │  ✓   │             │  │
│ │ Dixinn        │  ✓   │  ✓   │  ✗   │  ✗   │  ✓   │             │  │
│ │ ...           │      │      │      │      │      │             │  │
│ └───────────────┴──────┴──────┴──────┴──────┴──────┴─────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
```

### Workflow statuts
```
BROUILLON → EN_VALIDATION → ACTIF
                          ↘ REFUSÉ → (retour BROUILLON pour correction)
ACTIF → SUSPENDU → ARCHIVÉ (statut 3)
```

### Règles codes produits
- Format : `PRD-[CAT]-[NUM]` (ex: PRD-CAR-001, PRD-LUB-001)
- **[CAT]** : CAR = Carburant, LUB = Lubrifiant, ADD = Additif, DIV = Divers
- **[NUM]** : 3 chiffres, séquentiel par catégorie
- Le code est **IMMUABLE** dès la création — jamais modifiable
- La catégorie est également immuable après création

### Carburants réglementés
- Gasoil, SP95, SP98, Pétrole : prix fixés par la structure officielle mensuelle (p07)
- Prix auto-propagé depuis la structure prix validée → pas de saisie manuelle du prix pompe
- Le GPR peut voir le prix mais pas le modifier manuellement

### Modification produit ACTIF
- Un produit ACTIF ne peut pas être modifié directement
- Toute modification = création d'un brouillon parallèle (l'original reste ACTIF)
- Le brouillon passe par le workflow normal : BROUILLON → EN_VALIDATION → ACTIF
- Dès validation, le brouillon devient ACTIF et l'ancien passe en ARCHIVÉ

### Prix minimum plancher
- Chaque produit a un `prix_plancher` (fixé à la création)
- Le BFF rejette tout prix pompe inférieur au plancher (HTTP 422)
- Le plancher lui-même est modifiable uniquement par le CFO

---

## Endpoints BFF

```
GET /api/produits
  Query : ?statut=actif|brouillon|en_validation|suspendu|archive&categorie={cat}&page={n}
  Réponse : {
    data: [{
      id: INT,
      code: "PRD-CAR-001",          // immuable
      nom: "Gasoil",
      categorie: "Carburant",        // immuable
      prix_unitaire: "6000",         // VARCHAR (GNF/litre)
      prix_plancher: "5500",         // VARCHAR
      statut: 1|2|3|4|5,            // 1=actif 2=brouillon 3=archivé 4=en_validation 5=suspendu
      reglemente: true,              // prix auto depuis structure officielle
      brouillon_en_cours_id: INT|null
    }],
    meta: { total: INT, actifs: INT, en_validation: INT }
  }

GET /api/produits/{id}
  Réponse : {
    id: INT,
    code: "PRD-CAR-001",
    nom: "Gasoil",
    categorie: "Carburant",
    description: "...",
    unite: "litre",
    prix_unitaire: "6000",           // VARCHAR
    prix_plancher: "5500",           // VARCHAR
    statut: 1,
    reglemente: true,
    disponibilite: [{ station_id: INT, station_nom: "Kaloum", disponible: true }],
    brouillon_en_cours: { id: INT, ... }|null
  }

POST /api/produits
  Body : {
    nom: "Nouvelle Huile",
    categorie: "LUB",
    description: "...",
    unite: "litre",
    prix_unitaire: "28000",          // VARCHAR
    prix_plancher: "25000"           // VARCHAR
  }
  Réponse : { id: INT, code: "PRD-LUB-002", statut: 2 }  // BROUILLON

PUT /api/produits/{id}/soumettre
  Body : {}
  Réponse : { id: INT, statut: 4 }  // EN_VALIDATION

PUT /api/produits/{id}/valider
  Body : { decision: "VALIDE"|"REFUSE", commentaire: "..." }
  Réponse : { id: INT, statut: 1|2 }  // 1=ACTIF si validé, 2=BROUILLON si refusé

PUT /api/produits/{id}/suspendre
  Body : { motif: "..." }
  Réponse : { id: INT, statut: 5 }

POST /api/produits/{id}/brouillon-modification
  Body : { nom: "Gasoil v2", prix_unitaire: "6050" }
  Réponse : { id: INT, code: "PRD-CAR-001-draft", statut: 2,
              produit_actif_id: INT }

GET /api/produits/{id}/historique
  Réponse : [{
    id: INT,
    action: "CRÉE"|"SOUMIS"|"VALIDÉ"|"MODIFIÉ"|"SUSPENDU"|"ARCHIVÉ",
    par: "GPR Bah",
    date: "...",
    ancien_prix: "5800"|null,
    nouveau_prix: "6000"|null,
    commentaire: null|"..."
  }]

GET /api/produits/disponibilite
  Réponse : {
    stations: [{ id: INT, nom: "Kaloum" }],   // 23 stations
    produits: [{ id: INT, code: "PRD-CAR-001", nom: "Gasoil" }],  // 18 produits
    matrix: { "1": { "1": true, "2": true, "3": false }, ... }
    // matrix[station_id][produit_id] = disponible
  }

PUT /api/produits/disponibilite
  Body : { station_id: INT, produit_id: INT, disponible: boolean }
  Réponse : { applied: true }
```

---

## Règles spécifiques

- **RGGPR-01** : Le code produit `PRD-[CAT]-[NUM]` est généré par le BFF à la création et est **immuable**. Aucun champ du formulaire ne permet de le saisir ou modifier. Il est affiché en lecture seule avec fond grisé.
- **RGGPR-02** : Pour les carburants réglementés (`reglemente: true`), le champ `prix_unitaire` est en lecture seule dans le formulaire — il est automatiquement mis à jour par la structure de prix (p07).
- **RGGPR-03** : Montants (prix, plancher) = VARCHAR(255). La comparaison `prix_unitaire < prix_plancher` est gérée côté BFF. L'UI affiche uniquement l'erreur HTTP 422.
- **RGGPR-04** : La modification d'un produit ACTIF crée un brouillon parallèle via `POST /api/produits/{id}/brouillon-modification`. L'original reste ACTIF et visible. Le composant `BrouillonParallele` affiche les deux versions côte à côte.
- **RGGPR-05** : La matrice disponibilité (23×18) est scrollable horizontalement. Chaque cellule est une checkbox cliquable — `PUT /api/produits/disponibilite` est appelé immédiatement sur chaque changement (pas de bouton "Enregistrer global").
- **RGGPR-06** : IDs = `number` (INT UNSIGNED). Soft delete = `statut: 3` (ARCHIVÉ). Les produits archivés sont masqués par défaut mais accessibles via filtre.
- **RGGPR-07** : Le workflow de validation est assuré par le DG (même rôle que p07). Le bouton "Soumettre" n'est visible que pour le GPR sur ses propres brouillons.
- **RGGPR-08** : L'historique complet d'un produit (créations, modifications, changements de prix) est accessible sur `[id]/historique`. Il est en lecture seule, ordonné chronologiquement décroissant.

---

## Tâche Claude

Génère le code complet Next.js 15 TypeScript pour le module Catalogue Produits FlowUp ERP.

**Contraintes techniques absolues :**
- Palette navy `#1A2157`, blue `#1E5EFF`, cyan `#00C8C0` — Inter + JetBrains Mono
- Montants = VARCHAR(255) → `string` dans les types TS. Jamais de `parseFloat` pour les prix
- IDs = `number` (INT UNSIGNED). Soft delete = `statut: 3` (ARCHIVÉ)
- Codes produits = `string`, générés par le BFF, champs en lecture seule dans le formulaire
- Matrice 23×18 = scrollable horizontal, checkbox par cellule avec mutation immédiate

**Génère dans cet ordre :**
1. Types TypeScript (`types/produits.ts`)
2. Hook `useProduits.ts` (liste, détail, disponibilité, historique)
3. `WorkflowStatuts.tsx` (timeline visuelle BROUILLON→ACTIF)
4. `PrixPlancher.tsx` (affichage prix + alerte si < plancher)
5. `BrouillonParallele.tsx` (vue côte-à-côte produit ACTIF vs brouillon)
6. `MatriceDisponibilite.tsx` (tableau 23×18, scroll horizontal, checkbox)
7. `HistoriqueProduit.tsx` (timeline chronologique)
8. `ProduitForm.tsx` (code immuable, prix réglementés readonly)
9. `ProduitTable.tsx` (liste avec badges statut, filtres)
10. Pages : `page.tsx`, `[id]/page.tsx`, `[id]/historique/page.tsx`, `nouveau/page.tsx`, `disponibilite/page.tsx`
