# Analyse des processus impactant le stock — GPHARMA

> Cartographie du modèle de stock et de **tous les processus** qui le modifient, avec les emplacements de code.
> Basé sur : le schéma `DB_GPHARMA_STRUCTURE.sql` + exploration de `app/` et `resources/helpers/`.

---

## 1. Modèle de données — 3 niveaux de granularité

```
STOCKAGENCE_LOTS      produit × agence × ZONE × LOT (n° lot + péremption)   ← le + fin
      │  Σ StockTotal_Lot par zone
STOCK_ZONESTOCKAGE    produit × agence × ZONE
      │  Σ par produit/agence
STOCKAGENCE           produit × agence                                       ← "stock total par produit par agence"
```

- **`STOCKAGENCE_LOTS`** : la maille physique réelle. Un lot = `NumeroLotProduit` + `DatePeremption`, rattaché à une **zone** (`IDZONESTOCKAGE`), un emplacement, une traçabilité (`IDTracabiliteStock` → `TRACABILITESTOCK`).
- **`STOCK_ZONESTOCKAGE`** : agrégat par zone. **`StockZoneStockage` = Σ `STOCKAGENCE_LOTS.StockTotal_Lot`** du triplet (produit, zone, agence).
- **`STOCKAGENCE`** : agrégat par produit × agence (le « stock total par produit par agence »).

> ⚠️ **Zones dynamiques (modèle courant).** Chaque agence configure **ses propres zones** via `ZONESTOCKAGE`. Il n'y a **plus** de découpage fixe « Réserve / Détail » : les colonnes `StockReserve` / `StockDetail` (et les accumulateurs `SommeQte…Reserve` / `…Detail`) sont de l'**héritage** et ne reflètent plus le modèle. La vérité est : **lot → zone (configurable) → agence**, `STOCK_ZONESTOCKAGE` portant le stock du produit **par zone**.

---

## 2. Les niveaux de stock et leur calcul

| Notion | Colonne(s) | Définition |
|---|---|---|
| **Stock total** | `STOCKAGENCE.StockTotal` (= Σ des zones = Σ des lots) ; `StockTotal_Lot` par lot | stock physique réel |
| **Stock théorique** | `StockTotalTheorique`, `…Theo` | physique ± engagements (commandes réservées, transferts en cours) |
| **Stock en transit** | `StockTransit`, `StockTransitVersReserve`, `StockTransitTIZ`, `QteEnCoursTransfertSite/Interne` | parti d'un site/zone, pas encore reçu |
| **Stock indisponible** | `StockIndisponible(Detail/Reserve/Theo)`, `StockLotIndisponible…` | bloqué / suspendu / périmé, non vendable |
| **En cours de route** | `QuantiteCoursRoute`, `QtecoursDeRoute_Lot` | commandé fournisseur, pas encore réceptionné |
| **Disponible** | `StockDetailDisponible`, `StockReserveDisponible` | physique − indisponible |

Les colonnes **`SommeQte…Detail` / `SommeQte…Reserve`** sont des **accumulateurs par processus** (un compteur par type de mouvement) : le stock est reconstruit à partir d'eux (voir §3).

---

## 3. Le « moteur de stock » (architecture centrale)

⚠️ **Aucun trigger BD ne calcule le stock** — seuls existent `tr_avant_operation_*` (audit/horodatage) et `tr_replica_*` (réplication). **Toute la logique est applicative.**

### 3.1 Fonctions PIVOTS — mouvement incrémental (appelées à chaque opération)
| Fonction | Emplacement | Effet |
|---|---|---|
| `PG_ActualiseStock_Lot($IDLot, $Zone, $Qtm)` | [fonction.php:1615](resources/helpers/fonction.php#L1615) | `STOCKAGENCE_LOTS.StockTotal_Lot += Qtm` → `STOCK_ZONESTOCKAGE.StockZoneStockage += Qtm` → `STOCKAGENCE.StockTotal/StockTotalTheorique += Qtm` (et `StockIndisponible` si lot bloqué). **Le point d'entrée du mouvement physique.** |
| `PG_ActualiseStock_LotSansTheorique($IDLot, $Qtm)` | [fonction.php:1694](resources/helpers/fonction.php#L1694) | idem **sans** toucher le théorique (utilisé en facturation). |
| `updateStokcTheorique($idCmde, …, $sens)` | [fonction.php:605](resources/helpers/fonction.php#L605) | `STOCKAGENCE.StockTotalTheorique += Qte × signe` : **réserve (−)** ou **libère (+)** sur commande. |

### 3.2 Fonctions de RECALCUL GLOBAL — reconstruction depuis les mouvements
| Fonction | Emplacement | Effet |
|---|---|---|
| `PG_Actualise_STOCKS($IDAGENCE, $listeIDPRODUIT)` | [fonctionReq.php:6068](resources/helpers/fonctionReq.php#L6068) | recalcule **toutes** les colonnes de `STOCKAGENCE` **et** de `STOCK_ZONESTOCKAGE` à partir des mouvements (réception, correction, retours, transferts, ventes…). |
| `PG_Actualise_STOCKS_LOT($IDAGENCE, $listeIDPRODUIT)` | [fonctionReq.php:7213](resources/helpers/fonctionReq.php#L7213) | recalcule `STOCKAGENCE_LOTS.StockTotal_Lot` par agrégats, puis **reconstruit `STOCK_ZONESTOCKAGE.StockZoneStockage = SUM(StockTotal_Lot)` par zone**. C'est ici que le lien **lot → zone** est matérialisé. |

Déclenchés (en asynchrone) par : [ActualiserStockProduitJob.php:45](app/Jobs/ActualiserStockProduitJob.php#L45), [actualisationStockProduit.php:53](app/Console/Commands/actualisationStockProduit.php#L53), [produitController.php:3353](app/Http/Controllers/produitController.php#L3353), [FusionsDeProduitJob.php:283](app/Jobs/FusionsDeProduitJob.php#L283).

### 3.3 Propagation
```
mouvement → PG_ActualiseStock_Lot (incrémental)      → LOT → ZONE → AGENCE (théorique)
            OU écriture directe (theorique/zone)
réconciliation → PG_Actualise_STOCKS_LOT / _STOCKS   → reconstruit LOT → ZONE → AGENCE depuis les mouvements
```

---

## 4. Cartographie des processus → code

Légende du sens : **➕** entrée · **➖** sortie · **↔** déplacement · **⏳** transit · **🔒** indisponible · **♻** recalcul/reset · **≡** réservation théorique.

| # | Processus | Sens | Où (fichier:ligne) | Colonnes / via |
|---|---|---|---|---|
| 1 | **Réception achat** (BL fournisseur) — crée le lot | ➕ | [PreReceptionEnCoursController.php:6090](app/Http/Controllers/achats/PreReceptionEnCoursController.php#L6090) (`ActualiseStock`) : `:6189` TRACABILITESTOCK, `:6200` insert LOT, `:6234` zone += | crée LOT + zone += ; annulation `:470/483` `StockTotal(_Lot) −=` |
| 2 | **Vente / facturation / expédition** — consomme le lot | ➖ | bon prépa [fonctionReq.php:4065](resources/helpers/fonctionReq.php#L4065) (`facturerBonPrepa`, `PG_ActualiseStock_LotSansTheorique −`) ; comptoir [nouvelleFactureClientController.php:526](app/Http/Controllers/ventes/nouvelleFactureClientController.php#L526)/751/957 | `StockTotal_Lot`, `StockZoneStockage`, `StockTotal` − |
| 3 | **Commande client** — réserve du théorique | ≡ | [commandeClientControllers.php:131](app/Http/Controllers/ventes/commandeClientControllers.php#L131)/887/1187/1576 (`decrement StockTotalTheorique`) ; libération `:2653` (`increment`) ; import CSV [integrerCsvController.php:322](app/Http/Controllers/ventes/integrerCsvController.php#L322) | `StockTotalTheorique` seulement |
| 4 | **Changement de site d'une commande** | ≡ | [listingCommandeClientController.php:1472](app/Http/Controllers/ventes/listingCommandeClientController.php#L1472) (ancien site +), `:1477` (nouveau site −) | `StockTotalTheorique` |
| 5 | **Transfert inter-site — départ/collecte** | ⏳➖ | [fonctionReq.php:339](resources/helpers/fonctionReq.php#L339) (`ActualiserStockCollecte`, `PG_ActualiseStock_Lot −` + `QtecoursDeRoute_Lot +` au site d'arrivée) ; théorique [transfertInterSiteController.php:3743](app/Http/Controllers/stocks/transfertInterSiteController.php#L3743) | LOT/zone départ − ; en route + |
| 6 | **Transfert inter-site — réception/rangement** | ➕ | `ActualiserStockRange(Getraco)` [fonctionReq.php:7891](resources/helpers/fonctionReq.php#L7891)/7835 (`StockTotal_Lot +`, `QtecoursDeRoute_Lot −`, zone +) ; lot dest. `affectationOuCreationLot` [fonctionReq.php:5133](resources/helpers/fonctionReq.php#L5133) | LOT/zone réception + |
| 7 | **Transfert inter-zone** (même agence) | ↔⏳ | [transfertInterZoneController.php:640](app/Http/Controllers/stocks/transfertInterZoneController.php#L640) (`setValidateDepart` : LOT −, zone −, `StockTransitTIZ +`), `:697` (`setValidateReception` : LOT +, zone +) | ne touche pas STOCKAGENCE direct (via recalcul) |
| 8 | **Inventaire** | ♻ | [inventaireStockController.php:2659](app/Http/Controllers/stocks/inventaireStockController.php#L2659) (reset `StockTotal/Theo/Transit=0`), `:2701` (`= StockZoneStockage` compté), `:2724` LOT `= QteComptageRetenu` | fixe le physique au comptage |
| 9 | **Correction de stock / mouvement manuel** | ↕ | [mouvementStocksController.php:457](app/Http/Controllers/stocks/mouvementStocksController.php#L457)/717 (`PG_ActualiseStock_Lot ±`) ; édition lot [produitController.php:3851](app/Http/Controllers/produitController.php#L3851)-3926 | `StockTotal_Lot` ± + `HISTOCORRECTIONSTOCK` |
| 10 | **Retour client** (réintègre) | ➕ | [reclamationClientController.php:1378](app/Http/Controllers/ventes/reclamationClientController.php#L1378) ; [listeDesFacturesClientController.php:2198](app/Http/Controllers/ventes/listeDesFacturesClientController.php#L2198) ; annulation retour [listeRetourClient.php:269](app/Http/Controllers/ventes/listeRetourClient.php#L269) (`StockDetail/Theo −`) | via `PG_ActualiseStock_Lot +` |
| 11 | **Retour fournisseur** | ➖ | [listingBonRetourController.php:352](app/Http/Controllers/achats/listingBonRetourController.php#L352)/596/662 ; [etatMvtRembourseController.php:221](app/Http/Controllers/stocks/etatMvtRembourseController.php#L221) | via `PG_ActualiseStock_Lot −` |
| 12 | **Blocage / péremption** (indisponible) | 🔒 | [blocageParLotController.php:110](app/Http/Controllers/stocks/blocageParLotController.php#L110) (`STOCKAGENCE_LOTS_BLOQUE` + `StockLotIndisponible +`) ; `bloquerOuDebloquerLotDeLaZone` [fonctionReq.php:13091](resources/helpers/fonctionReq.php#L13091) (`LotProduitSuspendu`) ; périmés [etatDesPerimesController.php:552](app/Http/Controllers/stocks/etatDesPerimesController.php#L552) | `StockIndisponible(Theo) +` (n'enlève PAS `StockTotal`) |
| 13 | **Nivellement / recalcul global** | ♻ | `PG_Actualise_STOCKS` [fonctionReq.php:6068](resources/helpers/fonctionReq.php#L6068) + `PG_Actualise_STOCKS_LOT` [:7213](resources/helpers/fonctionReq.php#L7213) | reconstruit toutes les colonnes |
| 14 | **Fusion de produits** (admin) | ♻ | [FusionsDeProduitJob.php:158](app/Jobs/FusionsDeProduitJob.php#L158) (LOTS.IDPRODUIT), `:162` delete zone, puis recalcul | réaffecte lots/zones au produit principal |
| 15 | **Intégration / synchro base GETRACO** (multi-site) | ♻➕ | `correctionStockDesLotsBDGetraco` [fonctionReq.php:10380](resources/helpers/fonctionReq.php#L10380) (reset puis `= DISPO`) ; [traitementDonneeController.php:2373](app/Http/Controllers/adminSystem/traitementDonneeController.php#L2373)/5466 (import) ; `creationLotGETRACO` [fonctionReq.php:7950](resources/helpers/fonctionReq.php#L7950) | fixe `StockTotal(_Lot)` depuis la source |
| 16 | **Création / suppression produit** | ➕/❌ | [produitController.php:1622](app/Http/Controllers/produitController.php#L1622) (init STOCKAGENCE à 0 par agence), `:3315` delete | init/suppression |
| 17 | **Import seuils réappro** (⚠ pas de quantité) | — | [seuilReappro.php:57](app/Imports/seuilReappro.php#L57) | `SeuilMini/MaxiReapproZS` uniquement |

---

## 5. Schéma des flux de stock

```
                          ┌─────────────────────────── RÉAPPRO / ACHAT ───────────────────────────┐
 Fournisseur ── commande ──► (QuantiteCoursRoute) ── réception BL ──► LOT créé (zone d'entrée) ─────┤
                                                        [1]                                          │
                                                                                                     ▼
   Transfert inter-site   ◄── départ/collecte [5] ⏳ transit ⏳ ──► réception/rangement [6] ──► STOCK PHYSIQUE
   (autre agence)                                                                          (LOT → ZONE → AGENCE)
                                                                                                     │
   Transfert inter-zone [7] ↔ (réserve ↔ détail, zone ↔ zone, StockTransitTIZ)                       │
                                                                                                     │
   Blocage/péremption [12] 🔒 → StockIndisponible (reste en StockTotal)                              │
   Inventaire [8] ♻ / Correction [9] ↕ / Nivellement [13] ♻ → recalage                              │
                                                                                                     ▼
                      Commande client [3] ≡ réserve le THÉORIQUE ──► bon de préparation / collecte
                                                                                    │
                                                                                    ▼
                                                          Facturation / Expédition [2] ➖ ──► Client
                                                                                    ▲
                                                       Retour client [10] ➕ ────────┘
   Retour fournisseur [11] ➖ ──► sort du stock
```

---

## 6. Points d'attention / risques

1. **Double maintenance (incrémental + recalcul global).** Chaque mouvement met à jour le stock en direct (`PG_ActualiseStock_Lot`, `increment/decrement`), **et** des jobs reconstruisent tout (`PG_Actualise_STOCKS(_LOT)`). Si un processus oublie un accumulateur/appel, l'incrémental et le recalcul **divergent** → écarts entre les 3 niveaux. Le recalcul global est le filet de sécurité (à conserver/planifier).
2. **Cohérence des 3 niveaux.** `StockZoneStockage` doit = `Σ StockTotal_Lot`, et `STOCKAGENCE.StockTotal` doit = `Σ` des zones/lots. Utile : une **requête de contrôle** périodique (lot vs zone vs agence) pour détecter les écarts.
3. **`StockTotalTheorique` manipulé en direct à beaucoup d'endroits** (commande, changement de site, transfert, réception) via `increment/decrement` **sans reconstruction systématique** → c'est la colonne la plus exposée à la dérive (réservations non libérées, etc.).
4. **Blocage ≠ sortie.** Le blocage/péremption **n'enlève pas** `StockTotal_Lot` : il augmente `StockIndisponible`. Le vendable = `StockTotal − StockIndisponible`. Toute lecture de « stock dispo » doit soustraire l'indisponible (cf. index `idx_opt_stockagencelots_dispo_fefo`).
5. **Performance / amplification d'écriture.** Chaque mouvement écrit LOT + ZONE + AGENCE, et **chaque écriture déclenche le trigger de réplication** → coût multiplié. Les recalculs `PG_Actualise_STOCKS(_LOT)` sont lourds : bien qu'exécutés en Job (`ActualiserStockProduitJob`), à cibler par produit/agence et hors heures de pointe.
6. **Concurrence.** `increment/decrement` sont atomiques (OK), mais les recalculs *read-modify-write* et les corrections manuelles ([produitController.php:3851](app/Http/Controllers/produitController.php#L3851)-3926) peuvent entrer en course avec des ventes simultanées.
7. **Chemins « admin » qui écrivent en dur** (GETRACO, traitementDonnee, fusion) : puissants mais court-circuitent le moteur — à réserver aux opérations de maintenance encadrées.

---

## 7. Recommandations

- **Source de vérité = le recalcul par mouvements** (`PG_Actualise_STOCKS_LOT`) ; l'incrémental n'est qu'une optimisation d'affichage. Documenter ce principe.
- **Job de réconciliation planifié** (déjà présent via `ActualiserStockProduitJob`) + **requête de contrôle d'écarts** entre lot / zone / agence à surveiller.
- **Encadrer `StockTotalTheorique`** : centraliser réservation/libération (idéalement une seule fonction, comme `updateStokcTheorique`) pour éviter les dérives.
- **Perf** : viser des recalculs ciblés (produit+agence), profiter des index créés (`idx_opt_stockagencelots_agence_produit`, `…_dispo_fefo`) ; attention au cumul avec les triggers de réplication.

---

### Annexe — tables du domaine stock
`STOCKAGENCE`, `STOCKAGENCE_LOTS`, `STOCKAGENCE_LOTS_BLOQUE`, `STOCK_ZONESTOCKAGE`, `TRACABILITESTOCK`, `ZONESTOCKAGE`, `HISTOCORRECTIONSTOCK(_LOT)`, `COMPTA_VALEUR_STOCK`, `STOCKAGENCE_DELETED`.
