# Variantes de formule

Documentation de référence sur la gestion des **variantes de formule** (ex. « Été » / « Hiver ») telle qu'implémentée dans le code (version 9.05.000).

> Voir aussi : [cahier_des_charges_variantes_composition.md](cahier_des_charges_variantes_composition.md) (besoin initial) et [variantes_composition_resume.md](variantes_composition_resume.md) (résumé fonctionnel).

---

## 1. Principe

Une formule peut avoir plusieurs jeux de composition selon la saison ou le contexte. Une **variante** est un jeu global (commun à toutes les formules) : chaque ligne de `t_composition` et de `t_formule_detail` appartient à une variante.

Il faut distinguer deux notions :

| Notion | Où | Rôle |
|---|---|---|
| **Variante active** | `t_formule_variant.formule_variant_active = 1` | Une seule à la fois. Sert à la **production** (envoi des formules aux centrales), à l'API de transfert, à l'import Massia, au trans legacy et au PDF de conformité par défaut. |
| **Variante sélectionnée** | Liste « Variante » de l'onglet Composition de la fiche formule | Choisie par l'utilisateur pour **consulter, modifier, calculer et exporter** n'importe quelle variante, même non active. Pré-sélectionnée sur la variante active. |

---

## 2. Modèle de données

### `t_formule_variant` — modèle [formule_variant.php](../app/Models/formule_variant.php)

| Champ | Type | Remarque |
|---|---|---|
| `id_formule_variant` | PK | |
| `formule_variant_code` | string(12) | Unique, non modifiable après création |
| `formule_variant_libelle` | string(50) | |
| `formule_variant_active` | boolean | Une seule variante à 1 |

Méthodes :
- `formule_variant::getActive()` : variante active, ou à défaut la **première** variante en base.
- `$variante->activer()` : passe toutes les autres à 0, celle-ci à 1.
- Relations `lier_compositions()` et `lier_formule_details()`.

### Tables rattachées par `formule_variant_id`

| Table | Relation inverse |
|---|---|
| `t_composition` | `composition::lier_formule_variant()` |
| `t_formule_detail` | `formule_detail::lier_formule_variant()` |
| `t_tracabilite_composition` | `tracabilite_composition::lier_formule_variant()` |

La clé d'une composition devient donc **formule + centrale + variante**. La colonne est un `integer default 0`, **sans clé étrangère en base** : la cascade est gérée en PHP (voir §4.4).

### Migrations (V9.05.000)

| Migration | Effet |
|---|---|
| `2026_07_29_1410_..._t_formule_variant_active` | Ajoute `formule_variant_active`. Si aucune variante active : active la première existante, ou crée `STD - Défaut` active. |
| `2026_07_29_1411_..._t_composition_formule_variant_id` | Ajoute la colonne et rattache les lignes existantes (0/NULL) à la variante active. |
| `2026_07_29_1412_..._t_formule_detail_formule_variant_id` | Idem pour `t_formule_detail`. |
| `2026_07_29_1413_..._t_tracabilite_composition_formule_variant_id` | Idem pour la traçabilité, rattachée en priorité à `STD`. |

Toutes sont rejouables (gardes `hasColumn`, mise à jour uniquement des lignes à 0/NULL).

---

## 3. Écrans

### Liste des variantes — `/formule_variant` (menu « Variante de formule »)
Vue [formule_variant/index.blade.php](../resources/views/formule_variant/index.blade.php). Colonnes : code, libellé, état, suppression.
- Variante active : bouton vert désactivé **« VARIANTE FORMULE ACTIVE »** et cadenas (non supprimable).
- Autres variantes : bouton **« Rendre active »** et croix rouge de suppression.
- Pas de bouton « Ajouter » : une variante se crée uniquement par duplication.

### Fiche variante — vue [formule_variant/form.blade.php](../resources/views/formule_variant/form.blade.php)
- Code (grisé une fois créé) et libellé, message « Cette variante est actuellement ACTIVE » si c'est le cas.
- Boutons : **Valider**, **Créer à partir de cette variante**, **Supprimer**, **Traçabilité**.
- Le bouton de duplication ouvre un bloc « CRÉER UNE VARIANTE À PARTIR DE : … » qui demande un nouveau code et un nouveau libellé.

### Fiche formule, onglet Composition — vue [formule/form.blade.php](../resources/views/formule/form.blade.php)
- Liste **Centrale** + liste **Variante** (pré-sélection : variante active).
- Changer l'une ou l'autre recharge le détail (`formule/chercher_formule_detail`), le tableau de composition et les calculs.
- Toutes les lectures et écritures (ajout, modification, suppression de ligne, détail, calculs, export, PDF de conformité) envoient `formule_variant_id`.

---

## 4. Règles de gestion — [formule_variant_controller.php](../app/Http/Controllers/formule_variant_controller.php)

### 4.1 Droits (`profil_formule`)

| Action | Interdit aux profils |
|---|---|
| Voir la liste | 0 |
| Créer / dupliquer | 0, 1, 3 |
| Modifier le libellé / activer | 0, 1, 2 |
| Supprimer | tous sauf **5** |

### 4.2 Duplication (`POST /formule_variant/dupliquer`)
1. Validation : variante source existante, code unique (12 car. max), libellé (50 car. max).
2. Création de la nouvelle variante, **inactive**.
3. Dans une transaction : copie en masse (`INSERT … SELECT`) de **toutes** les lignes `t_composition` et `t_formule_detail` de la variante source (toutes formules, toutes centrales), avec le nouvel `formule_variant_id` et `created_at/updated_at = NOW()`. Les colonnes sont lues dynamiquement (`Schema::getColumnListing`), donc une nouvelle colonne est copiée sans modifier le code.
4. Traçabilité : `Duplication depuis <code source>`.

### 4.3 Activation (`POST /formule_variant/{id}/activer`)
1. `activer()` : la variante passe à 1, toutes les autres à 0.
2. Traçabilité : `Activation variante [ANCIEN -> NOUVEAU]`.
3. Envoi en file du job [envoyer_formule_vers_production_apres_change_formule_variant_active](../app/Jobs/envoyer_formule_vers_production_apres_change_formule_variant_active.php) : pour chaque formule `formule_definir_production = 1`, il écrit une traçabilité d'envoi `T_formule / Modification` vers ses centrales. Les centrales récupèrent ainsi les compositions de la nouvelle variante active.

> ⚠️ Le job est en file (`ShouldQueue`) : les formules ne partent vers la production que si le worker de queue tourne.

### 4.4 Suppression (`DELETE /formule_variant/{id}/supprimer`)
- Refusée si la variante est **active**.
- Supprime d'abord les lignes `t_composition` et `t_formule_detail` de la variante, puis la variante elle-même.
- Traçabilité standard + traçabilité d'envoi `T_Formule_Variant / Suppression`.
- L'historique `t_tracabilite_composition` est **conservé**.

### 4.5 Enregistrement / API
- `enregistrer()` ne modifie que code et libellé. `formule_variant_active` n'est jamais pris en compte : on change la variante active uniquement par `activer()`, pour que l'envoi vers la production soit lancé.
- `enregistrer_api()` / `supprimer_api()` : même logique, appelés par l'API de transfert (`api_transfert_controller`, fiche `formule_variant`), recherche par `formule_variant_code`.

---

## 5. Quelle variante est utilisée où ?

| Fonctionnalité | Fichier | Variante utilisée |
|---|---|---|
| Fiche formule : composition, détail, ajout/modif/suppression de ligne | `formule_controller` | Sélectionnée (repli : active) |
| Calculs techniques (masse volumique, E/C, dosage, carbone…) | `Traits/composition_calcul_trait` | Sélectionnée (repli : active) |
| PDF de conformité | `Services/formule_conformite_service` | Sélectionnée (repli : active) |
| Modification groupée de formules | `formule_modification_groupee_controller` + version legacy `public/formule_modification_groupee_laravel_default/` | Filtre « Variante » (vide = toutes). Pour un **ajout** sans variante choisie, la cible est l'**id 1** (codé en dur) |
| Copie de formule d'une centrale à l'autre | `formule_copie_controller`, `copier_formule_centrale()` (`public/inc/fonction.inc.php`) | **Toutes** les variantes |
| Checksum de composition | `calculer_checksum_composition()` (`public/inc/fonction.inc.php`) | Active |
| API de transfert : lecture/écriture de la composition d'une formule | `api_transfert_controller`, `formule_controller::integrer_composition_api()` | Active |
| Trans legacy composition | `public/trans/action_trans_formule_composition.php` | Active |
| Import Massia | `Services/Massia/FormuleMassiaImportService` | Active |
| Suivi traçabilité composition | `tracabilite_composition_controller` | Filtre « Variante », colonne code variante |

---

## 6. Points d'attention

- **`getActive()` revient à la première variante** si aucune n'est active : on ne tombe jamais sur une variante vide tant qu'il en existe au moins une.
- **Activation sans variante active** : `activer()` lit `$ancienne_variante->formule_variant_code` sans tester `null` ; une erreur se produirait si aucune variante n'était active (cas normalement impossible après la migration).
- **Pas de clé étrangère** : toute suppression directe en SQL de `t_formule_variant` laisse des compositions orphelines ; passer par l'écran ou l'API.
- **Suppression sans transaction** : compositions, détails et variante sont supprimés en trois requêtes successives.
- **Modification groupée, ajout** : sans variante sélectionnée, les nouvelles lignes vont dans la variante `id = 1`, qui n'est pas forcément la variante active.
- **Duplication volumineuse** : toutes les formules × centrales sont copiées ; le volume de `t_composition` est multiplié par le nombre de variantes.
