# Formulaire Pesée Pont Bascule (pesee_bascule)

Écran de saisie d'une pesée pont bascule, page complète (pas de surimpression), accessible par l'URL `/pesee_bascule` (pas d'entrée de menu).

**Formulaire seul pour l'instant** : il n'existe pas encore de table de pesée, donc pas de bouton Valider ni d'enregistrement.

## Fichiers

| Fichier | Rôle |
|---|---|
| `routes/web/pesee_bascule.php` | Groupe `/pesee_bascule` : route `index` (`auth:sanctum`, `interne`) |
| `app/Http/Controllers/pesee_bascule_controller.php` | `index()` : affiche le formulaire ; accès refusé (401) si `profil_bl = 0` |
| `resources/views/pesee_bascule/form.blade.php` | Le formulaire (titre « PESÉE PONT BASCULE ») |
| `resources/views/partials/recherche_dans_saisie.blade.php` | Fonction JS `recherche_dans_saisie()` partagée (sortie de la fiche badge) |

## En-tête (au-dessus des onglets)

Une seule ligne, Code BL et Date côte à côte avec la même police (1,5 rem, gras, classe `zone_entete_pesee_bascule`) ; le bouton a la même hauteur que les zones (2,75 rem).

- **Code BL** (`bl_code`) : non modifiable. Vide pour l'instant : le code sera attribué à l'enregistrement de la pesée.
- **Date** (`bl_date_livraison`) : date du jour par défaut, modifiable.
- Bouton **Nouvelle pesée** (`pesee_bascule_bouton_nouvelle_pesee`), style des boutons de commande, sans recharger la page :
  - vide tous les champs (zones, ids cachés des recherches à 0, listes proposées, tares, libellé du badge), revient sur l'onglet Général ;
  - date du jour dans `bl_date_livraison` ;
  - toggle **Sortie granulat** remis à oui (Pesée brut remis à non) ;
  - `bl_code` = code renvoyé par la route `pesee_bascule.nouveau_code` (`pesee_bascule_controller::nouveau_code`) : **dernier BL créé** (plus grand `id_bl`) de la centrale par défaut des paramètres (`t_parametre.centrale_id`) **+ 1**, même longueur avec zéros à gauche (ex. `001884078` → `001884079`). Seuls les codes entièrement numériques comptent (les codes de remplacement `…R` sont écartés). Le plus grand numéro n'est pas utilisé car un BL isolé hors séquence (`005591150`) le fausserait. Code **indicatif** : il n'est pas réservé, deux pesées ouvertes en même temps peuvent recevoir le même code tant que l'enregistrement n'existe pas.

## Onglets

Style « souligné » propre au formulaire (`#les_onglets_pesee_bascule`) : sans cadre, écriture noire semi-grasse, onglet actif **transparent**, écrit en vert et souligné d'un trait vert (`#8cc63f`, le vert des boutons et des toggles), trait vert pâle (`#c6e39f`) au survol. Les en-têtes des parties (`.card-header`) ont un fond de ce même vert, écriture blanche en gras (18/10/2026) ; le toggle Pesée brut, dans l'en-tête Badge / Véhicule / Benne, y a un libellé blanc, un contour blanc et un vert foncé (`#5b8a21`) une fois activé pour rester visible. Le libellé du toggle Sortie granulat (ligne des onglets) reste noir. Règles en `!important` car `public/inc/css/style.css` force les onglets (`.nav-tabs .nav-link.active` : fond vert, texte blanc) en `!important` ; l'identifiant les rend prioritaires sur cet écran seulement.

Deux toggles en CSS (`toggle_pesee_bascule`, Bootstrap 4.1 n'a pas de `custom-switch`) : gris désactivé, vert clair `#8cc63f` activé, couleur des boutons de commande. Chacun envoie un champ caché `0`, remplacé par `1` quand il est activé. Les deux sont alignés à gauche l'un sous l'autre (`bloc_toggle_pesee_bascule` : même largeur 11 rem et même retrait à droite).

- **Sortie granulat** (`pesee_bascule_sortie_granulat`) : en haut à droite, sur la ligne des onglets (visible depuis tous les onglets), **activé par défaut**.
- **Pesée brut** (`pesee_bascule_pesee_brut`) : à droite de l'en-tête de la partie Badge / Véhicule / Benne, désactivé par défaut.

- **Général** : toutes les parties ci-dessous.
- **Supplément** : grille des lignes du BL (produit, zone, services supplémentaires), voir « Onglet Supplément : grille des lignes du BL ».
- **Divers** : partie « Codes » (Code 1 à 3) et partie « Divers » (Divers 1 à 4), côte à côte ; saisies libres de 12 caractères max, comme `bl_code1..3` et `bl_divers1..4` de `t_bl`.

## Parties du formulaire (onglet Général)

1. **Badge / Véhicule / Benne** (une seule partie, zones étroites) :
   - ligne badge : badge, **P.T.C.A** (`pesee_bascule_ptca`, en t, saisie libre : `t_vehicule` n'a pas de colonne PTCA ; placé au-dessus de la tare véhicule), libellé du badge ;
   - ligne véhicule : véhicule + tare (« t », reprise de la fiche véhicule, **modifiable**) + **immatriculation** en saisie libre, directement après la tare (colonne de la tare à la largeur de la zone ; `t_vehicule` n'a pas de colonne immatriculation : `Vehicule_Numero_Vehicule` est l'ID GPS) ;
   - ligne benne : benne + tare (« t », reprise de la fiche benne, **modifiable**) + bouton **En Transit** tout à droite (`pesee_bascule_bouton_en_transit`, style des boutons de commande, pas encore d'action).
2. **Client / Produit / Transport** (zones réduites) : client, chantier (filtré sur le client, remis à zéro au changement de client), produit (**granulats uniquement**, `produit_type = 0`), transporteur, chauffeur, zone. À droite :
   - du client : **Réf. commande client** (`bl_ref_commande`, saisie libre, 55 caractères) ;
   - du chantier : **Modèle d'impression** (`modele_impression_id`), recherche dans la zone de saisie via la nouvelle route `modele_impression.liste_groupe` (`modele_impression_controller::liste_groupe`, paramètre `modele_impression_a_chercher`, recherche sur `modele_impression_code` et `modele_impression_nom`, 20 résultats max).
3. **Poids** (en tonnes, une **tuile** par zone, **côte à côte** (chacune un tiers de la partie, `rangee_poids_pesee_bascule`) : brut, tare, net ; style `tuile_poids_pesee_bascule` : fond vert très pâle `#f4f9ee`, bordure `#dcedc8`, coins arrondis, petit libellé gris en majuscules, valeur en grand (1,5 rem) vert `#8cc63f` suivie de l'unité « t » ; brut et tare soulignés en pointillés pour montrer qu'ils sont saisissables) : poids brut saisi ; tare proposée = tare véhicule + tare benne (**bloquée à 0, en lecture seule, quand le toggle Pesée brut est actif**, même si le véhicule ou la benne ont une tare ; recalculée et déverrouillée au changement du toggle et par Nouvelle pesée), recalculée à chaque choix de badge, véhicule ou benne et à chaque modification à la main d'une de ces deux tares, et modifiable ; poids net = brut − tare, calculé en lecture seule (affiché en rouge s'il est négatif, vide tant que le brut n'est pas saisi).
4. **Commandes**, à droite de la partie Poids : boutons **Tare**, **Brut** et **Valider** carrés (5,5 rem, `bouton_carre_pesee_bascule`) côte à côte et centrés, style propre (`bouton_commande_pesee_bascule` : vert clair `#8cc63f`, coins arrondis, texte blanc gras, largeur fixe). Pas encore d'action : la lecture des poids sur la bascule et l'enregistrement de la pesée restent à brancher.

   **Affichage des boutons** (`maj_boutons_commande()`, appelée à chaque saisie de poids, recalcul de la tare proposée, changement du toggle Pesée brut, au chargement et après Nouvelle pesée ; une valeur compte si elle est > 0). Un bouton masqué garde sa place (classe Bootstrap `invisible`) : les boutons ne bougent jamais.

   **Tare** est visible seulement si Pesée brut est désactivé **et** que ni la tare véhicule ni la tare benne n'est renseignée (une tare connue n'a pas besoin d'être pesée).

   | Situation | Tare | Brut | Valider |
   |---|---|---|---|
   | Pesée brut désactivé, aucune tare | visible | masqué | masqué |
   | Pesée brut désactivé, tare saisie dans la partie Poids, sans tare véhicule ni benne, pas de brut | visible | visible | masqué |
   | Pesée brut désactivé, tare véhicule ou benne renseignée, pas de brut | masqué | visible | masqué |
   | Pesée brut désactivé, tare et brut | visible si aucune tare véhicule ni benne, sinon masqué | visible | visible |
   | Pesée brut activé, pas de brut | masqué | visible | masqué |
   | Pesée brut activé, brut | masqué | visible | visible |

Mise en page resserrée (style propre au formulaire : marges des cartes et des lignes réduites).

Toutes les zones fonctionnent comme dans la fiche commande : saisie, liste proposée à partir de 2 caractères, champ caché `xxx_id` renseigné au choix d'une ligne. Pas de bouton de recherche.

**Choix d'un badge** : préremplit véhicule, benne, client, chantier, produit, transporteur, chauffeur, zone et les tares avec les éléments enregistrés dans le badge. Chaque zone reste modifiable ensuite.

## Recherche de badge

Nouvelle route `badge_bascule.liste_groupe` (`badge_bascule_controller::liste_groupe`, paramètre `badge_bascule_a_chercher`, 20 résultats max, recherche sur code et libellé). Pour chaque badge, elle renvoie aussi les éléments liés sous la forme `{id, texte}` (texte au format « code -> libellé » des autres recherches) et les tares véhicule / benne.

## Fonction JS partagée

`recherche_dans_saisie(saisie, liste, champ_id, url, parametre, libelle, autres_donnees, au_choix)` est maintenant dans `resources/views/partials/recherche_dans_saisie.blade.php`, incluse par `badge_bascule/form.blade.php` et `pesee_bascule/form.blade.php` (`@include('partials.recherche_dans_saisie')`). Comportement inchangé pour la fiche badge.

## À noter

- Les tables `t_benne`, `t_type_bascule` et `t_badge_bascule` ont été créées en **MyISAM** (moteur par défaut du serveur MySQL), qui ne gère pas les transactions ; `t_bl` est en InnoDB.

## Noms des champs et table BL

Comme dans les blades de commande, l'`id` et le `name` d'un champ sont le **nom de la colonne en base** quand elle existe dans `t_bl`.

| Champ du formulaire | Colonne `t_bl` |
|---|---|
| `bl_code` | `bl_code` |
| `bl_ref_commande` | `bl_ref_commande` |
| `modele_impression_id` | `modele_impression_id` |
| `bl_date_livraison` | `bl_date_livraison` |
| `client_id`, `chantier_id`, `vehicule_id`, `chauffeur_id`, `zone_id` | colonnes du même nom |
| `badge_id`, `benne_id`, `transporteur_id` | colonnes ajoutées par la migration `2026_10_16_1500` |
| `bl_brut` (poids brut) | `bl_brut` (tonnes) |
| `bl_tare` (tare) | `bl_tare` (tonnes) |
| `bl_qte_livree` (poids net) | `bl_qte_livree` (quantité livrée, en tonnes) |
| `bl_code1` à `bl_code3` | `bl_code1` à `bl_code3` |
| `bl_divers1` à `bl_divers4` | `bl_divers1` à `bl_divers4` |

Sans colonne dans `t_bl` (noms conservés) : `produit_id`, `pesee_bascule_immatriculation`, `pesee_bascule_ptca`, `pesee_bascule_sortie_granulat`, `pesee_bascule_pesee_brut`.

### Colonnes ajoutées à `t_bl` pour la pesée

Migration `2026_10_16_1500_V905001_NB_t_bl_badge_benne_transporteur.php` (passée en local le 16/10/2026) : `badge_id`, `benne_id`, `transporteur_id` (integer, défaut 0, indexés, index au nom de la colonne comme `modele_impression_id`). Le formulaire utilise ces trois noms : `badge_id` (renommé depuis `badge_bascule_id`), `benne_id`, `transporteur_id`.

Migration `2026_10_16_1600_V905001_NB_t_bl_bl_brut_bl_tare.php` (passée en local le 16/10/2026) : `bl_brut` et `bl_tare`, decimal(10,3), défaut 0, en tonnes (le net est `bl_qte_livree`). Le formulaire utilise ces noms : `bl_brut` (renommé depuis `pesee_bascule_poids_brut`) et `bl_tare` (renommé depuis `pesee_bascule_poids_tare`).

## Enregistrement du BL (bouton Valider)

Route `pesee_bascule.enregistrer` (POST) → `pesee_bascule_controller::enregistrer`. Remplissage des champs du BL repris de `depart_cycle_service::creerBl` (toutes les colonnes obligatoires de `t_bl` renseignées).

- **Droits** : création refusée si `profil_bl` vaut 0, 1 ou 3 (comme `bl_controller::enregistrer`).
- **Contrôles** : client obligatoire ; produit obligatoire et granulat (`produit_type = 0`) ; brut > 0 ; tare obligatoire sauf en pesée brut ; **net = brut − tare recalculé côté serveur** (le net affiché n'est pas repris) et > 0. Message d'erreur sur la zone concernée (zone de recherche visible pour un `xxx_id`, onglet affiché).
- **Code BL** : attribué par le serveur dans la transaction (`prochain_code_bl`, verrou sur le dernier BL de la centrale, code avancé tant qu'il existe déjà). Il peut donc différer du code affiché ; le code attribué est renvoyé et affiché.
- **BL (`t_bl`)** : centrale par défaut des paramètres ; client, chantier, véhicule, chauffeur, zone, badge, benne, transporteur, modèle d'impression ; `bl_date_livraison` = `bl_date_ref` = date saisie ; `bl_brut`, `bl_tare` (0 en pesée brut), net dans `bl_qte_livree` (et `bl_qte_origine`, `bl_qte_acceptee`, `bl_qte_livraison`) ; réf. commande, codes 1-3, divers 1-4 ; adresses client / chantier copiées ; `tva_id` du chantier, sinon du client ; `adresse_facturation_id` du chantier ; heures de fabrication et de livraison = heure de validation ; `bl_sans_production = 0` et `bl_statut = 0` (pas de départ de cycle ni de cycle sans production pour une pesée) ; `bl_manuel = 0` ; `commande_id = 0`.
- **Ligne de BL (`t_ligne_bl`)** : une ligne granulat (type 5) — plus une ligne zone, voir « Ligne de zone (transport) », quantité = poids net, prix et unité du tarif (`article_tarif_service::chercherPrixArticle`, comme la commande ; 0 si aucun prix), facturable, origine du tarif (« p.base », « Chantier »…, comme les autres BL ; « Pesée » au départ, corrigé le 18/10/2026).
- **Traçabilité** : « BL », code, « Création ».
- Après succès : message « BL xxx enregistré ! », puis formulaire remis à zéro (Nouvelle pesée) au bout de 3 s. Bouton désactivé pendant l'envoi.

Non enregistré (pas de colonne dans `t_bl`) : immatriculation, P.T.C.A, tares véhicule / benne de la ligne, toggle Sortie granulat. Aucune ligne zone / transport n'est créée.

Testé dans une transaction annulée (aucune donnée laissée) : BL `001884079` créé avec ses champs et sa ligne granulat (16,70 T), deuxième pesée `001884080`, refus sans client / avec un liant / net ≤ 0, pesée brut enregistrée avec tare 0.

## Type de pesée (étape 1)

Sélecteur à 3 boutons sur la ligne d'en-tête, entre la date et Nouvelle pesée (`#selecteur_type_pesee_bascule`, boutons `bouton_type_pesee_bascule` : type choisi en vert plein `#8cc63f`, les autres en contour vert). Valeur dans le champ caché `bl_type_pesee`, enregistrée dans **`t_bl.bl_type_pesee`** (migration `2026_10_16_1700_V905001_NB_t_bl_bl_type_pesee.php`, tinyint, défaut 0, passée en local) :

| Valeur | Type |
|---|---|
| 0 | BL hors pesée (tous les BL existants) |
| 1 | Manuelle |
| 2 | Mono / Double (**par défaut**, remis par Nouvelle pesée) |
| 3 | Unitaire |

Mono et double sont un seul type : mono si une tare véhicule ou benne est connue (seul le brut est pesé), double sinon (Tare, En Transit, puis Brut).

| | Manuelle | Mono / Double | Unitaire |
|---|---|---|---|
| Poids | brut et tare saisis, net calculé | brut et tare, net calculé | tuiles poids masquées, tuile **Quantité** (`pesee_bascule_quantite`, unité « U ») |
| Bouton Tare | masqué | visible si Pesée brut inactif et aucune tare connue | masqué |
| Bouton Brut | masqué | visible si tare > 0 ou Pesée brut | masqué |
| Bouton Valider | brut > 0 et net > 0 | brut > 0 et (tare > 0 ou Pesée brut) | quantité > 0 |
| En Transit | masqué | visible en double : tare pesée, aucune tare connue, pas de Pesée brut, pas encore de brut | masqué |
| Tares véhicule / benne | affichées | affichées | masquées (`element_poids_pesee_bascule`) |
| Toggle Pesée brut | actif | actif | masqué et désactivé |

Enregistrement (`pesee_bascule_controller::enregistrer`) : `bl_type_pesee` obligatoire (1, 2 ou 3). En **unitaire** : quantité obligatoire et > 0, `bl_brut` = `bl_tare` = 0, quantité dans `bl_qte_livree`, ligne de BL en unité « U ». Manuelle et mono / double : contrôles de poids inchangés.

**Saisie des poids** (`verrouiller_poids()`, appelée au changement de type, du toggle Pesée brut et au recalcul de la tare) : en **Mono / Double**, le brut et la tare de la partie Poids sont **en lecture seule** (ils viennent de la bascule ; la tare peut être remplie automatiquement : tare véhicule + benne, ou tare d'une pesée en transit reprise). En **Manuelle**, ils sont saisissables, sauf la tare en pesée brut (bloquée à 0). Tant que les boutons Tare / Brut ne lisent pas la bascule, une pesée Mono / Double ne peut donc pas être terminée depuis l'écran.

Testé dans une transaction annulée (aucune donnée laissée) : mono / double et manuelle enregistrés avec leur type, unitaire 12 U (brut / tare 0, ligne « U »), refus sans quantité en unitaire, sans type ou avec un type inconnu.

## Produits autorisés en pesée unitaire (étape 2)

En **unitaire**, seuls les **granulats avec une masse volumique différente de 0** (`t_produit.produit_masse_volumique <> 0`) sont autorisés ; les autres types gardent tous les granulats.

- Recherche : `produit.liste_groupe` accepte un filtre facultatif `avec_masse_volumique` (appliqué seulement s'il est envoyé : la commande et le badge ne l'envoient pas). Le formulaire de pesée l'envoie quand le type est Unitaire.
- Passage vers ou depuis l'unitaire : le produit choisi est vidé (il peut ne plus être autorisé).
- Enregistrement : en unitaire, `produit_id` doit exister avec `produit_type = 0` **et** `produit_masse_volumique <> 0` ; message « Le produit doit être un granulat avec une masse volumique différente de 0 ! ». Un produit prérempli par un badge sans masse volumique est donc refusé à la validation en unitaire.

Testé : la recherche « 4 » renvoie 18 granulats, mais un seul avec le filtre (`4/6APPRIN`, seul granulat local avec une masse volumique) ; unitaire refusé avec un granulat sans masse volumique, accepté avec `4/6APPRIN` ; mono / double toujours accepté avec un granulat sans masse volumique. Tests dans une transaction annulée.

## Double pesée et camions en transit (étape 3)

**Table `t_pesee_bascule_transit`** (migration `2026_10_17_1000_V905001_NB_t_pesee_bascule_transit.php`, passée en local, **InnoDB** pour que la création du BL et la suppression de la pesée en transit soient dans la même transaction), modèle `app/Models/pesee_bascule_transit.php` (relations `lier_badge`, `lier_vehicule`, `lier_benne`, `lier_client`, `lier_chantier`, `lier_produit`, `lier_transporteur`, `lier_chauffeur`, `lier_zone`, `lier_modele_impression`). Colonnes : `id_pesee_bascule_transit`, `pesee_bascule_transit_code` (code temporaire `T` + identifiant sur 6 chiffres, ex. `T000012`), `pesee_bascule_transit_date_heure_tare`, les `xxx_id` du formulaire, `pesee_bascule_transit_tare`, `_vehicule_tare`, `_benne_tare`, `_ptca`, `_immatriculation`, `_sortie_granulat`, `_utilisateur`, et `bl_ref_commande`, `bl_code1..3`, `bl_divers1..4` (mêmes noms que le formulaire).

**Déroulement (type Mono / Double)**

1. Premier passage : tare saisie, produit facultatif. En double pesée (pas de Pesée brut, aucune tare véhicule / benne, tare > 0, pas de brut), **Valider** apparaît à côté de Brut et **met le camion en transit** (`pesee_bascule.mettre_en_transit`) au lieu de créer le BL. Il faut un badge, un véhicule ou une immatriculation pour retrouver le camion ; un véhicule ne peut avoir qu'une pesée en transit. Le formulaire repart ensuite sur une nouvelle pesée.
2. Bouton **En Transit**, toujours visible en Mono / Double : ouvre la liste des camions en transit (`pesee_bascule.liste_transit`, fenêtre `#modale_transit_pesee_bascule`) : heure de la tare, code, badge, véhicule (et immatriculation), client, chantier, produit si renseigné, tare.
3. **Reprendre** (`pesee_bascule.charger_transit`) : formulaire vierge puis rempli avec la pesée en transit (zones de recherche, tares, P.T.C.A, immatriculation, Sortie granulat, réf., codes, divers) ; la tare du premier passage est gardée telle quelle ; champ caché `pesee_bascule_transit_id`. Il reste à peser le brut.
4. Brut pesé : **Valider** crée le BL (code BL définitif attribué à ce moment) et **supprime la pesée en transit** dans la même transaction. Valider sans brut sur une pesée reprise la met à jour au lieu d'en créer une seconde.
5. **Supprimer** (croix rouge dans la liste, confirmation) : `pesee_bascule.supprimer_transit` (DELETE). Pas de contrôle de droit pour l'instant.

Les tares de ligne ont maintenant un `name` (`pesee_bascule_vehicule_tare`, `pesee_bascule_benne_tare`) pour être gardées dans la pesée en transit ; elles ne vont pas dans `t_bl`.

Testé dans une transaction annulée (aucune donnée laissée) : mise en transit sans produit, refus d'un véhicule déjà en transit / sans badge, véhicule ni immatriculation / sans tare, liste et reprise (véhicule, client, tare, immatriculation, codes), mise à jour sans doublon, création du BL avec suppression de la pesée en transit, suppression d'une pesée en transit. Le code temporaire était d'abord daté à la seconde : deux mises en transit dans la même seconde avaient le même code, d'où le code sur l'identifiant.

## Ligne de zone (transport)

À la validation (`pesee_bascule_controller::enregistrer`), si une zone est choisie, le BL reçoit une deuxième ligne (`numero_ligne_bl` 2) de type 4 « Zone », `article_id` = la zone, dans la même transaction que le BL. Règles reprises de la mise à jour des BL depuis le chantier (`recalculer_lignes_transport_lignes_taxes_dans_chantier_fonction` : `recalculer_transport`, `recalculer_mode_transport_bl`) :

- **Prix** : `article_tarif_service::chercherPrixArticle(4, …)`, comme la commande. Il donne le prix au tonnage (`zone_prix_m3`) ou au voyage (`zone_prix_m3_voyage`) selon le mode de transport du chantier, avec les tarifs centrale / client / chantier. Origine : celle du tarif (« p.base », « Chantier »…).
- **Mode de transport** : celui que renvoie le service, donc `chantier_mode_transport` (activité béton) ou `chantier_mode_transport_granulat` (activité granulat) selon l'activité de la centrale par défaut (choix confirmé par l'utilisateur le 18/10/2026).
  - au tonnage : quantité = quantité de la ligne granulat (poids net, ou quantité saisie en unitaire), même unité (T, U…) ;
  - au voyage : quantité 1, sans unité.
- **Facturation du transport du chantier** (`chantier_facture_transport` ou `chantier_facture_transport_granulat`, même choix selon l'activité) : 0 = Facturé (ligne normale), 1 = Non facturé (**pas de ligne**), 2 = Non facturé mais affiché (ligne en quantité 0).
- Sans chantier : facturé, au tonnage. Sans zone (`zone_id` = 0) : pas de ligne.

Le transport incomplet (ligne TI, `parametre_article_ti_id`) n'est pas créé : il dépend du seuil TI du véhicule, une notion béton (m³).

Testé dans une transaction annulée (aucune donnée laissée), centrale locale en activité béton, zone `002` (24 / 180) : chantier facturé au voyage → zone 1 à 180 ; facturé au tonnage → 17,50 T à 24 ; non facturé → pas de ligne ; non facturé mais affiché → quantité 0 (tonnage et voyage) ; sans chantier → 17,50 T ; sans zone → pas de ligne ; unitaire au voyage → zone 1.

## Onglet Supplément : grille des lignes du BL

Tableau HTML éditable (jQuery / Bootstrap, pas de bibliothèque de grille ajoutée : le projet n'utilise que DataTables, en lecture), `#grille_supplement_pesee_bascule`. Colonnes : Type, Article, Quantité, Unité, P.U. HT, TVA, Origine, Montant HT, suppression ; total HT en pied.

- **Unité** : unité du tarif (granulat T, « U » en unitaire, unité du service ; zone : celle du produit au tonnage, « voyage » au voyage).
- **TVA** : taux de la TVA de la fiche article (`tva_id` de `t_produit` / `t_zone` / `t_service`, jointure `t_tva`), comme la facture (`facture_ligne_article_service`). Affichage seulement : `t_ligne_bl` n'a pas de colonne TVA.
- **Saisie de la quantité et du prix** : zones de texte normales (`type="text"`, `inputmode="decimal"`, sans flèches + / -), classe `saisie_numerique_supplement` : seuls les chiffres et un séparateur décimal sont acceptés (la virgule devient un point), 3 décimales au plus pour la quantité, 2 pour le prix (`data-decimales`). Pas de signe moins : pas de prix négatif saisi dans la grille. Le serveur contrôle toujours `numeric`.
- **Origine** : origine du tarif renvoyée par `article_tarif_service` (« p.base », « Chantier », « client », « /S f.client », « g.centrale »…), enregistrée dans `ligne_bl_origine`.

- **Ligne produit** (fond vert pâle) : article et quantité non modifiables, quantité = poids net (quantité saisie en unitaire, unité « U »). Seul le prix se modifie (`pesee_bascule_prix_produit`).
- **Ligne zone** : mêmes règles que « Ligne de zone (transport) » (quantité 1 au voyage, unité « voyage » affichée mais enregistrée vide, 0 si non facturé mais affiché). Masquée sans zone ou si le transport n'est pas facturé. Seul le prix se modifie (`pesee_bascule_prix_zone`).
- **Services** : la dernière ligne est toujours vide (« Ajouter un service... ») ; recherche du service dans la cellule (`service.liste_groupe`, services de la centrale par défaut, même fonction `recherche_dans_saisie` que les autres zones). Au choix : quantité 1 par défaut, tarif calculé, nouvelle ligne vide en bas. Quantité et prix modifiables, croix rouge pour supprimer la ligne. Champs envoyés `services[i][service_id|quantite|prix]` ; ceux de la ligne vide sont `disabled`, donc pas envoyés.
- **Tarifs** : nouvelle route `pesee_bascule.tarifs` (GET `client_id`, `chantier_id`, `produit_id`, `zone_id`, `service_ids[]`) → `article_tarif_service::chercherPrixArticle`, comme la commande ; renvoie libellé, prix, unité, origine (et pour la zone `au_voyage`, `facture_transport`). Recalcul au changement de client, chantier, produit ou zone, au choix d'un badge, à la reprise d'une pesée en transit et au choix d'un service. Seule la dernière réponse est appliquée.
- **Prix modifié à la main** : affiché en gras vert, gardé lors d'un recalcul tant que l'article de la ligne ne change pas (changement de produit, de zone ou de service : retour au tarif).
- Quantités produit / zone et montants mis à jour à chaque saisie de poids ou de quantité. Nouvelle pesée vide la grille.

**Enregistrement** (`enregistrer`) : lignes créées dans l'ordre de la grille (produit 1, zone 2, puis services type 1), par une même fonction `$creer_ligne`. Prix = celui de la grille, sinon le tarif ; `ligne_bl_prix_force` = 1 si le prix diffère du tarif arrondi au centime (comme `ligne_devis_prix_force` dans le devis). Origine (`ligne_bl_origine`) : origine du tarif pour toutes les lignes, celle affichée dans la grille. Unité du service : celle du tarif (`unite_code`). Contrôles : `services.*.service_id` existant dans `t_service`, quantité > 0, prix numériques ; l'erreur met le focus sur la cellule (clé `services.0.quantite` → `#services_0_quantite`, zone de saisie visible pour le service).

Le code tarif / facturation de la zone est mis en commun (`tarif_zone`, `quantite_zone`) entre `tarifs` et `enregistrer`.

### Grille gardée avec une pesée en transit

Le BL n'existe pas encore pendant le transit : les lignes de la grille sont gardées dans une nouvelle table **`t_pesee_bascule_transit_ligne`** (InnoDB, migration `2026_10_18_1000_V905001_NB_t_pesee_bascule_transit_ligne`, modèle `pesee_bascule_transit_ligne`, relation `pesee_bascule_transit::lier_lignes`) : `pesee_bascule_transit_id`, `pesee_bascule_transit_ligne_type_article` (5 produit, 4 zone, 1 service), `article_id`, `pesee_bascule_transit_ligne_quantite` (services), `pesee_bascule_transit_ligne_prix` (null = tarif), `pesee_bascule_transit_ligne_numero` (ordre).

- **Mise en transit** (`mettre_en_transit`) : mêmes contrôles que la validation (`regles_supplement` / `messages_supplement`, communs). Les prix sont comparés au tarif (`prix_force`) : produit / zone gardés **seulement si le prix a été modifié** ; services toujours gardés, avec leur prix seulement s'il diffère du tarif. Les lignes sont remplacées à chaque mise en transit, dans la même transaction que la pesée.
- **Reprise** (`charger_transit`) : renvoie `prix_produit`, `prix_zone` (null = tarif) et `services` (`service_id`, texte, quantité, prix). Le formulaire remet les services dans la grille (`reprendre_lignes_supplement`) ; les prix repris remplacent le tarif une fois (data `prix_repris`) et restent marqués « modifié » s'ils diffèrent du tarif du jour. Un prix non modifié suit donc le tarif en vigueur au retour du camion.
- **Validation du BL** et **suppression** de la pesée en transit : les lignes sont supprimées avec elle (pas de clé étrangère, comme le reste du projet).

Testé dans une transaction annulée : mise en transit avec produit au tarif, zone forcée à 20, service 6 au tarif × 2 et service 20 forcé à 42,10 × 1,5 → 3 lignes (pas de ligne produit, prix du service 6 vide) ; reprise renvoyant ces valeurs ; remise en transit sans le service 6 → lignes remplacées ; refus quantité 0 en transit ; validation du BL → pesée et lignes supprimées ; suppression d'une pesée en transit → lignes supprimées. Migration exécutée sur la base locale.

Testé côté serveur dans une transaction annulée : tarifs (produit, zone 002 au tonnage 24, services FORF95RR 430 VO et FORFAIT95 500 UN, origine « /S f.client ») ; BL sans supplément (2 lignes, non forcées) ; prix produit 15,50 et zone 20 modifiés + 2 services (un au tarif, un à 42,10 en 1,5) → 4 lignes, forcé = 1 sauf le service au tarif ; refus quantité 0, service inconnu, prix non numérique. Vue compilée sans erreur. **Pas testé dans le navigateur** (extension Chrome non connectée).

## À faire

Demandé par l'utilisateur (17/10/2026), à traiter dans les prochaines étapes :

- [ ] **Gérer les entrées** : pesées en entrée de site, en plus des sorties. Le toggle « Sortie granulat » (activé par défaut) existe mais n'est ni exploité ni enregistré.
- [x] **Services supplémentaires** : voir « Onglet Supplément : grille des lignes du BL ».
- [x] **Création de la ligne de zone** : voir « Ligne de zone (transport) » ci-dessous.

Également en attente :

- [ ] Lecture de la bascule sur les boutons **Tare** et **Brut** (en Mono / Double, brut et tare sont en lecture seule : la pesée ne peut pas être terminée depuis l'écran tant que ce n'est pas branché).
- [ ] Droits de suppression des pesées en transit.
