# Prompt p19 — Opérations (web)
*Contextes requis : ctx-global.md + ctx-web-nextjs.md + ctx-laravel-bff.md*

---

## Profil & Accès
- **Rôle** : OPE (Responsable Opérations)
- **Nombre d'utilisateurs** : 2
- **Accès** : fournisseurs, réceptions, stock, transport, flotte, maintenance, HSE, contrôle
- **Session** : 8h, OTP à la connexion

---

## Fichiers à créer

```
app/(erp)/operations/page.tsx
app/(erp)/operations/fournisseurs/page.tsx
app/(erp)/operations/fournisseurs/[id]/page.tsx
app/(erp)/operations/receptions/page.tsx
app/(erp)/operations/receptions/[id]/page.tsx
app/(erp)/operations/stock/page.tsx
app/(erp)/operations/transport/page.tsx
app/(erp)/operations/transport/[id]/page.tsx
app/(erp)/operations/flotte/page.tsx
app/(erp)/operations/maintenance/page.tsx
app/(erp)/operations/hse/page.tsx
app/(erp)/operations/controle/page.tsx
components/operations/FournisseurScoring.tsx
components/operations/ReceptionForm.tsx
components/operations/DensiteControle.tsx
components/operations/StockNiveaux.tsx
components/operations/TransportCircuit.tsx
components/operations/FlotteGrid.tsx
components/operations/MaintenanceTicket.tsx
components/operations/HseIncident.tsx
components/operations/RapportJournalier.tsx
hooks/useOperations.ts
hooks/useFlotte.ts
hooks/useMaintenance.ts
```

---

## Spec UX condensée

```
┌─────────────────────────────────────────────────────────────────────┐
│ FLOWUP ERP          Opérations                              [OPE]   │
│ Rapport journalier: ⚠ À soumettre avant 18h00 (16h23)              │
├─────────────────────────────────────────────────────────────────────┤
│ [Fournisseurs][Réceptions][Stock][Transport][Flotte][Maintenance]   │
│ [HSE][Contrôle Rapport]                                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│ TAB: FOURNISSEURS                                                    │
│ ┌──────────────────┬──────────┬────────────────────────┬─────────┐  │
│ │ FOURNISSEUR      │ SCORING  │ DÉTAIL SCORING         │ BCF     │  │
│ ├──────────────────┼──────────┼────────────────────────┼─────────┤  │
│ │ TotalGin SA      │ ████ 87% │ Qualité 40%: 36/40     │ [QR ✓] │  │
│ │                  │          │ Délais  30%: 28/30     │         │  │
│ │                  │          │ Volumes 20%: 16/20     │         │  │
│ │                  │          │ Réactiv.10%:  7/10     │         │  │
│ │ LogiAfric SARL   │ ███  71% │ Qualité 40%: 28/40     │ [QR ✓] │  │
│ └──────────────────┴──────────┴────────────────────────┴─────────┘  │
│                                                                      │
│ TAB: RÉCEPTIONS                                                      │
│ Réception REC-2026-0089 — TotalGin · 12 000 L Gasoil               │
│ ┌────────────────────────────────────────────────────────────────┐  │
│ │ CONTRÔLE DENSITÉ OBLIGATOIRE                                   │  │
│ │ Produit : Gasoil                                               │  │
│ │ Densité relevée : [0.836] g/cm³                                │  │
│ │ Plage autorisée : 0,820 – 0,845 g/cm³  ✓ Conforme            │  │
│ │ Température : [28]°C                                           │  │
│ │ Densité corrigée 15°C : 0.838  ✓                               │  │
│ └────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
```

### Module Fournisseurs — Scoring auto
- **Qualité** (40%) : taux de conformité livraisons (densité, volumes)
- **Délais** (30%) : respect des délais de livraison (OL vs réception réelle)
- **Volumes** (20%) : respect des volumes commandés vs livrés (±0,5%)
- **Réactivité** (10%) : temps de réponse aux demandes urgentes
- Score total /100, actualisé à chaque réception
- **BCF** (Bon de Commande Fournisseur) avec QR d'authenticité

### Module Réceptions — Contrôle densité obligatoire
- Gasoil : densité à 15°C = **0,820 – 0,845 g/cm³**
- Essence SP95/SP98 : densité à 15°C = **0,720 – 0,775 g/cm³**
- Pétrole lampant : densité à 15°C = **0,780 – 0,820 g/cm³**
- Correction température 15°C selon formule ASTM D1250
- Si densité hors plage → blocage de la réception (ne peut pas être validée)
- Rapport d'écart obligatoire si volume reçu ≠ volume attendu >1%

### Module Transport — Circuit 8 étapes QR+GPS
1. Dépôt départ (QR) → 2. Pesée départ (QR) → 3. En route (GPS auto) → 4. Arrivée station (QR) → 5. Pesée arrivée (QR) → 6. Dépotage (QR cuve) → 7. Signature (double) → 8. Retour dépôt (QR)
- Rapport écart >1% obligatoire
- Suivi GPS temps réel sur carte Leaflet

### Module Flotte — 22 camions-citernes
- Fiche camion : immatriculation, capacité, technicien attitré, kilométrage
- **Immobilisation automatique** si maintenance en retard >15 jours
- Statut : DISPONIBLE / EN_MISSION / EN_MAINTENANCE / IMMOBILISÉ

### Module HSE
- Déclaration incident : SMS immédiat au Directeur et RRH dès soumission
- Checklist inspection quotidienne (23 points pour les stations, 15 pour les camions)
- Rapport mensuel HSE avec statistiques incidents

### Module Contrôle — Rapport journalier
- Obligatoire avant 18h00
- Données : volumes vendus par station, écarts stock, incidents, maintenances du jour
- Alertes si non soumis à 17h30 (15min avant délai)

---

## Endpoints BFF

```
GET /api/fournisseurs
  Query : ?page={n}&per_page=20
  Réponse : [{
    id: INT,
    nom: "TotalGin SA",
    type: "carburant"|"lubrifiant"|"equipement",
    scoring: {
      total: "87",           // VARCHAR (0-100)
      qualite: "36",         // VARCHAR (/40)
      delais: "28",          // VARCHAR (/30)
      volumes: "16",         // VARCHAR (/20)
      reactivite: "7"        // VARCHAR (/10)
    },
    nb_livraisons: 47,
    dernier_bcf_url: "..."
  }]

POST /api/receptions
  Body : {
    fournisseur_id: INT,
    produit_id: INT,
    volume_attendu: "12000",     // VARCHAR (litres)
    volume_recu: "11987",        // VARCHAR
    densite_relevee: "0.836",    // VARCHAR (g/cm³)
    temperature_releve: "28",    // VARCHAR (°C)
    densite_corrigee_15c: "0.838", // VARCHAR (calculée par le client via ASTM)
    conforme: true,
    ecart: "13",                 // VARCHAR (litres)
    station_id: INT
  }
  Réponse : {
    id: INT,
    reference: "REC-2026-0089",
    conforme: true,
    ecart_pct: "0.11",          // VARCHAR
    statut: "ACCEPTÉ"|"BLOQUÉ"  // BLOQUÉ si densité hors plage
  }

GET /api/stock/niveaux
  Query : ?station_id={id}&produit_id={id}
  Réponse : [{
    station_id: INT,
    station_nom: "Kaloum",
    produit_nom: "Gasoil",
    stock_theorique: "23400",    // VARCHAR (litres)
    stock_iot: "23847",          // VARCHAR
    ecart: "447",                // VARCHAR
    ecart_pct: "1.91",           // VARCHAR
    alerte: true
  }]

GET /api/transport/ordres
  Query : ?statut=planifie|en_cours|termine&date={YYYY-MM-DD}
  Réponse : [{
    id: INT,
    reference: "OL-2026-0412",
    fournisseur_nom: "TotalGin",
    station_nom: "Kaloum",
    produit: "Gasoil",
    volume: "12000",             // VARCHAR
    chauffeur_nom: "Moussa Camara",
    camion_immat: "RC-1234-A",
    etape_actuelle: 3,
    statut: "en_cours"
  }]

POST /api/maintenance/tickets
  Body : {
    camion_id: INT|null,
    station_id: INT|null,
    type: "préventive"|"curative"|"urgente",
    description: "...",
    priorite: "haute"|"normale"|"basse"
  }
  Réponse : { id: INT, reference: "MNT-2026-0089", statut: "OUVERT" }

GET /api/flotte/camions
  Réponse : [{
    id: INT,
    immatriculation: "RC-1234-A",
    capacite: "25000",           // VARCHAR (litres)
    statut: "disponible"|"en_mission"|"en_maintenance"|"immobilise",
    km_total: "145230",          // VARCHAR
    derniere_maintenance: "2026-04-28",
    prochaine_maintenance: "2026-07-28",
    jours_depuis_maintenance: 15,
    immobilise_auto: false
  }]

POST /api/hse/incidents
  Body : {
    type: "accident"|"deversement"|"incendie"|"blessure"|"autre",
    gravite: "mineur"|"modere"|"grave",
    station_id: INT|null,
    camion_id: INT|null,
    description: "...",
    mesures_immediates: "..."
  }
  Réponse : { id: INT, reference: "HSE-2026-0047",
              sms_envoye: true, destinataires: ["DG", "RRH"] }

POST /api/operations/rapport-journalier
  Body : {
    date: "2026-05-13",
    stations: [{ station_id: INT, volumes_vendus: "...", ecarts: "...", incidents: [] }],
    maintenances_du_jour: [INT],
    commentaire: "..."
  }
  Réponse : { id: INT, soumis_at: "2026-05-13T16:23:00Z" }

GET /api/operations/rapport-journalier/statut
  Query : ?date={YYYY-MM-DD}
  Réponse : { soumis: boolean, soumis_at: "..."|null, alerte_18h: boolean }
```

---

## Règles spécifiques

- **RGOPE-01** : Contrôle densité = gate bloquant. Si `densite_relevee` hors plage pour le produit, le bouton "Valider la réception" est `disabled` et un message rouge indique la non-conformité. Le BFF refusera également avec HTTP 422.
- **RGOPE-02** : Tous les montants, volumes, densités = VARCHAR(255). La correction de densité à 15°C (formule ASTM D1250) est calculée côté client via une fonction pure `corrigerDensite(densite: string, temp: string): string` qui utilise `Decimal.js`.
- **RGOPE-03** : Immobilisation camion automatique : si `jours_depuis_maintenance >= 15`, le camion passe automatiquement en statut "IMMOBILISÉ" (géré côté BFF, cron quotidien). L'UI affiche le statut sans pouvoir le modifier manuellement (seule une nouvelle maintenance peut le débloquer).
- **RGOPE-04** : HSE — incident soumis → SMS envoyé immédiatement (BFF, synchrone). L'UI affiche "SMS envoyé à DG + RRH" dans le résultat de la soumission.
- **RGOPE-05** : Rapport journalier : si non soumis à 17h30, un bandeau orange d'alerte apparaît en haut de toutes les pages OPE ("⚠ Rapport journalier à soumettre avant 18h00"). Après 18h00 sans soumission = bandeau rouge.
- **RGOPE-06** : Le scoring fournisseur est calculé et stocké côté BFF. L'UI `FournisseurScoring` l'affiche uniquement — pas de calcul client.
- **RGOPE-07** : IDs = `number` (INT UNSIGNED). Soft delete = `statut: 3` pour les fournisseurs désactivés et les tickets maintenance clôturés.
- **RGOPE-08** : La carte transport (suivi GPS camions) utilise Leaflet — `dynamic(() => import(...), { ssr: false })`. Polling 30s pour les positions GPS des camions en mission.

---

## Tâche Claude

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

**Contraintes techniques absolues :**
- Palette navy `#1A2157`, blue `#1E5EFF`, cyan `#00C8C0` — Inter + JetBrains Mono
- Montants/volumes/densités = VARCHAR(255) → `string` dans les types TS
- Calcul densité corrigée 15°C via `Decimal.js` (fonction pure)
- IDs = `number` (INT UNSIGNED). Soft delete = `statut: 3`
- Leaflet = dynamic import SSR disabled pour la carte transport
- Blocage form si densité hors plage (validation côté client + côté BFF)

**Génère dans cet ordre :**
1. Types TypeScript (`types/operations.ts`)
2. Utilitaire `utils/densite.ts` (correction ASTM D1250, Decimal.js, plages par produit)
3. Hook `useOperations.ts` (fournisseurs, réceptions, stock, rapport journalier)
4. Hook `useFlotte.ts` (camions, statuts, immobilisation)
5. Hook `useMaintenance.ts` (tickets, création, statut)
6. `DensiteControle.tsx` (saisie + validation temps réel + correction 15°C)
7. `FournisseurScoring.tsx` (barre de score 4 critères)
8. `ReceptionForm.tsx` (avec DensiteControle intégré, gate bloquant)
9. `StockNiveaux.tsx` (tableau théorique vs IoT vs écart)
10. `TransportCircuit.tsx` (8 étapes + carte Leaflet GPS)
11. `FlotteGrid.tsx` (22 camions, statuts, immobilisation auto)
12. `MaintenanceTicket.tsx` (formulaire ticket)
13. `HseIncident.tsx` (formulaire incident + confirmation SMS)
14. `RapportJournalier.tsx` (formulaire + bandeau alerte 17h30/18h)
15. Pages : `page.tsx` (dashboard ops), `fournisseurs/page.tsx`, `fournisseurs/[id]/page.tsx`, `receptions/page.tsx`, `receptions/[id]/page.tsx`, `stock/page.tsx`, `transport/page.tsx`, `transport/[id]/page.tsx`, `flotte/page.tsx`, `maintenance/page.tsx`, `hse/page.tsx`, `controle/page.tsx`
