<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->

# Lots et quantités

Langue : `fr`
Page miroir : [lots-and-quantities.en.md](lots-and-quantities.en.md)
Statut : `migration partielle`
Dernière vérification code : `2026-05-13`

Cette page consolide `BR-PT-LOT-001`, `BR-PT-LOT-002` et `BR-PT-LOT-003`.
Objectif : piloter les règles de quantité depuis une définition métier lisible,
puis les sécuriser par des checks Python et des diagnostics SQL.

## À retenir

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Résumé opérationnel</strong><span>Une ligne trade possède un seul lot virtuel. Le lot virtuel porte le solde ouvert global, <code>lot.qt</code> porte le forecast opérationnel, et les lots physiques consomment ce forecast.</span></div>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Sujet</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle courte</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Quantité saisie</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity_theorical</code> est la quantité métier.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Quantité standard</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> est un compteur technique non éditable.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Avant physique</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> suit <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Après physique</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> reflète l&#x27;exécuté physique.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ligne non finie</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le montant de ligne utilise <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ligne finie</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le montant peut revenir à l&#x27;exécuté physique.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Weight basis</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Achat et vente peuvent lire deux états différents du même lot.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Facturation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Elle choisit ses états dans <code>lot.qt.hist</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Contrôles</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Invariants bloqués en Python et auditables en SQL.</td>
</tr>
</tbody>
</table>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>quantity_theorical
        |
        v
  lot virtuel P1  ---&gt;  lot.qt forecast  ---&gt;  lot physique
        ^                    |
        |                    v
        +------ recalcul après consommation</code></pre>

## Règles consultant

### BR-PT-LOT-001 - Cycle de vie lot virtuel / forecast / physique

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Moment</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Effet métier</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Création de ligne</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Création d&#x27;un lot virtuel unique.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Initialisation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le lot virtuel reprend <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Une ligne ouverte est créée dans <code>lot.qt</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Planification</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> peut être subdivisé par vente, matching, transport ou shipment.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ajout physique</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le lot physique consomme une ligne <code>lot.qt</code> précise.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Après ajout</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> diminue et le lot virtuel est recalculé.</td>
</tr>
</tbody>
</table>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Découpage d&#x27;un solde P1</strong><span><code>P1S1T1</code>, <code>P1S1T2</code>, <code>P1S2T3</code>, <code>P1S2T4</code></span></div>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Point clé</strong><span>Ces découpages ne créent pas plusieurs lots virtuels. Ils décrivent seulement la répartition prévisionnelle du solde ouvert.</span></div>

### BR-PT-LOT-002 - Quantité contractuelle, compteur, ligne finie

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Situation</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Quantité de référence</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Saisie utilisateur</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Aucun lot physique</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity = quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lots physiques présents</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity = somme des lots physiques</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>finished = False</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Montant basé sur <code>quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>finished = True</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Montant basé sur l&#x27;exécuté physique</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Weight basis disponible</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Montant basé sur l&#x27;état <code>wb.qt_type</code> du contrat</td>
</tr>
</tbody>
</table>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Ce que <code>finished</code> ne fait pas</strong><span><code>finished</code> n&#x27;efface pas la quantité contractuelle, ne supprime pas les lots physiques et ne masque pas leur PnL. Il signifie seulement que le reliquat ouvert peut être ignoré pour les calculs d&#x27;exécution.</span></div>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Lecture du même lot physique</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">État possible</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Achat</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">BL via <code>purchase.purchase.wb.qt_type</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Vente</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">LR ou Weight Report via <code>sale.sale.wb.qt_type</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Facturation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Choix indépendant dans <code>lot.qt.hist</code></td>
</tr>
</tbody>
</table>

### BR-PT-LOT-003 - Invariants de quantité

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Invariant</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Formule</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Conservation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>somme(lots physiques) + lot virtuel = quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast ouvert minimal</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>somme(lot.qt non zéro) &gt;= max(lot virtuel, 0)</code></td>
</tr>
</tbody>
</table>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Cas</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel achat</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sommer tous les <code>lot.qt</code> où <code>lot_p = lot virtuel</code>, avec ou sans <code>lot_s</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel vente</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sommer tous les <code>lot.qt</code> où <code>lot_s = lot virtuel</code>, avec ou sans <code>lot_p</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt = 0</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ignoré par les checks ; mémoire possible d&#x27;une prévision vidée.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Surconsommation forecast</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Autorisée après confirmation de tolérance : <code>lot.qt</code> s&#x27;arrête à zéro, mais le lot virtuel absorbe tout le physique.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> supérieur au virtuel</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Accepté si le forecast ouvert restant dépasse le virtuel positif à cause d&#x27;une surconsommation physique.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel négatif</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Autorisé pour compenser l&#x27;écart théorique / exécuté ; forecast attendu = zéro.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> non zéro orphelin</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Interdit si ni <code>lot_p</code> ni <code>lot_s</code> n&#x27;est renseigné.</td>
</tr>
</tbody>
</table>

### BR-PT-LOT-004 - Historique de quantité et weighing

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Élément</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt.hist</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Porte les états de quantité d&#x27;un lot.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Fiche <code>lot.lot</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pas de saisie directe des états.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Modification</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Uniquement via <code>Do weighing</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pas de packing manuel.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Champs packing virtuel</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot_qt</code> et <code>lot_unit</code> non éditables.</td>
</tr>
</tbody>
</table>

### Tolérances

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Point</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Niveau</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">La tolérance est définie au header <code>purchase</code> / <code>sale</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Héritage</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Les lignes héritent de la tolérance header par défaut.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Transport</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pas de tolérance indépendante par transport.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ajout physique</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le contrôle se fait au moment de <code>Add physical lots</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast matché</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Si <code>lot_qt</code> porte <code>lot_p</code> et <code>lot_s</code>, le contrôle porte sur les deux côtés achat et vente.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Dépassement ligne</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Warning confirmable en anglais si la quantité physique projetée dépasse la tolérance courante de la ligne. Le message indique le côté bloquant : <code>purchase</code>, <code>sale</code>, ou les deux si les deux checks déclenchent successivement.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Enveloppe globale</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le dépassement ponctuel d&#x27;une ligne est accepté après confirmation et consomme l&#x27;enveloppe globale du contrat.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Tolérance restante</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Les lignes du contrat sont recalculées avec <code>inherit_tol = False</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Surconsommation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Une ligne qui dépasse la tolérance header garde une tolérance <code>+</code> au moins égale à son dépassement réel.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Autres lignes</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Leur tolérance <code>+</code> est réduite selon l&#x27;enveloppe restante.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sous-consommation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Restitue mécaniquement de la tolérance disponible aux autres lignes lors du recalcul suivant.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matching ouvert</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>Go to matching</code> peut matcher au-delà du solde ouvert strict si la quantité projetée reste dans <code>Qt max</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Jauge ligne</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sur une ligne avec lots physiques, la jauge utilise la somme des physiques ; sans physique, elle utilise la somme des <code>lot.qt</code> liés.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Jauge header</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">La jauge purchase/sale est la moyenne pondérée des jauges de lignes au prorata de <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Jauge matching</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Dans <code>Go to matching</code>, la jauge projette <code>déjà matché + Qt to match</code> contre <code>quantity_theorical</code>, avec bornes <code>-tol_min</code> / <code>tol_max</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Layout jauge ligne</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">En formulaire <code>purchase.line</code> / <code>sale.line</code>, placer la jauge et <code>targeted_qt</code> directement dans la grille principale, sans sous-groupe <code>colspan=&quot;4&quot;</code>, et utiliser <code>xalign=&quot;0&quot;</code> pour éviter un double décalage vers la droite.</td>
</tr>
</tbody>
</table>

## Section développeur

### Champs clés

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Ligne achat : <code>purchase.line</code>
</li>
<li style="margin:0.38rem 0;">Ligne vente : <code>sale.line</code>
</li>
<li style="margin:0.38rem 0;">Lot : <code>lot.lot</code>
</li>
<li style="margin:0.38rem 0;">Forecast : <code>lot.qt</code>
</li>
<li style="margin:0.38rem 0;">Historique : <code>lot.qt.hist</code>
</li>
<li style="margin:0.38rem 0;">Quantité métier achat : <code>purchase.line.quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;">Quantité métier vente : <code>sale.line.quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;">Compteur technique : <code>quantity</code>
</li>
<li style="margin:0.38rem 0;">Ligne finie : <code>purchase.line.finished</code>, <code>sale.line.finished</code>
</li>
<li style="margin:0.38rem 0;">Lot virtuel / physique : <code>lot.lot.lot_type = virtual / physic</code>
</li>
<li style="margin:0.38rem 0;">Lien achat : <code>lot.lot.line</code>
</li>
<li style="margin:0.38rem 0;">Lien vente : <code>lot.lot.sale_line</code>
</li>
<li style="margin:0.38rem 0;">Forecast achat : <code>lot.qt.lot_p</code>
</li>
<li style="margin:0.38rem 0;">Forecast vente : <code>lot.qt.lot_s</code>
</li>
<li style="margin:0.38rem 0;">Quantité forecast : <code>lot.qt.lot_quantity</code>
</li>
<li style="margin:0.38rem 0;">Weight basis : <code>purchase.purchase.wb</code>, <code>sale.sale.wb</code>
</li>
<li style="margin:0.38rem 0;">État Weight basis : <code>purchase.weight.basis.qt_type</code>
</li>
<li style="margin:0.38rem 0;">Packing : <code>lot.lot.lot_qt</code>, <code>lot.lot.lot_unit</code>
</li>
<li style="margin:0.38rem 0;">Tolerances : <code>tol_min</code>, <code>tol_max</code>, <code>tol_min_qt</code>, <code>tol_max_qt</code>, <code>tol_min_v</code>, <code>tol_max_v</code>, <code>tolerance_used</code>, <code>tolerance_min</code>, <code>tolerance_max</code>
</li>
</ul>

### Création ligne / lot virtuel

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Achat : <code>purchase.py</code>, <code>Line.validate</code>
</li>
<li style="margin:0.38rem 0;">Vente : <code>sale.py</code>, <code>SaleLine.validate</code>
</li>
<li style="margin:0.38rem 0;">Si <code>quantity_theorical</code> est saisi et que <code>quantity</code> est vide ou zéro :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> est initialisée depuis <code>quantity_theorical</code> ;
</li>
<li style="margin:0.38rem 0;">seulement si aucun lot physique n&#x27;existe.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Si la ligne est éligible :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">pas <code>created_by_code</code> ;
</li>
<li style="margin:0.38rem 0;">pas encore de lot ;
</li>
<li style="margin:0.38rem 0;">produit non service ;
</li>
<li style="margin:0.38rem 0;"><code>quantity_theorical != 0</code> ;
</li>
<li style="margin:0.38rem 0;">création d&#x27;un lot <code>virtual</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Le lot virtuel reçoit une première entrée <code>lot.qt.hist</code>.
</li>
<li style="margin:0.38rem 0;"><code>Lot.validate</code> crée le <code>lot.qt</code> ouvert via <code>createVirtualPart</code>.
</li>
<li style="margin:0.38rem 0;">Si la ligne est <code>created_by_code</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">elle provient d&#x27;un workflow métier (<code>Create contracts</code>, matching, etc.) ;
</li>
<li style="margin:0.38rem 0;"><code>Lot.validate</code> ne crée pas de <code>lot.qt</code> ouvert automatique ;
</li>
<li style="margin:0.38rem 0;">le workflow rattache ou subdivise son <code>lot.qt</code> source ;
</li>
<li style="margin:0.38rem 0;">le check bloquant s&#x27;applique seulement après stabilisation du workflow.
</li>
</ul>
</li>
</ul>

### Modification de `quantity_theorical`

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Achat : <code>purchase.py</code>, <code>Line.write</code>
</li>
<li style="margin:0.38rem 0;">Vente : <code>sale.py</code>, <code>SaleLine.write</code>
</li>
<li style="margin:0.38rem 0;">Cible lot virtuel :
</li>
</ul>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>target_quantity = quantity_theorical - somme(lots physiques convertis)</code></pre>

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Si <code>target_quantity &lt; 0</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">blocage : <code>Please unlink or unmatch lot</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Cible <code>lot.qt</code> libre :
</li>
</ul>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)</code></pre>

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Si <code>free_quantity &lt; 0</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">blocage : <code>Please unlink or unmatch lot</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Si un <code>lot.qt</code> libre existe :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">sa quantité est remplacée.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Si aucun <code>lot.qt</code> libre n&#x27;existe et <code>free_quantity &gt; 0</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">création d&#x27;un nouveau <code>lot.qt</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Les fees de ligne sont resynchronisés.
</li>
</ul>

### Ajout de lots physiques

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard : <code>lot.add</code>
</li>
<li style="margin:0.38rem 0;">Méthodes :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>LotQt.add_physical_lots</code>
</li>
<li style="margin:0.38rem 0;"><code>LotQt.add_physical_lot</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Source obligatoire : une ligne <code>lot.qt</code>.
</li>
<li style="margin:0.38rem 0;">Ajout direct depuis un lot physique refusé.
</li>
<li style="margin:0.38rem 0;">Ajout physique côté vente par ce wizard refusé : utiliser <code>Apply matching</code>.
</li>
<li style="margin:0.38rem 0;">Le lot physique reprend :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">ligne achat ;
</li>
<li style="margin:0.38rem 0;">vente matchée si présente ;
</li>
<li style="margin:0.38rem 0;">shipment ;
</li>
<li style="margin:0.38rem 0;">produit ;
</li>
<li style="margin:0.38rem 0;">unité ;
</li>
<li style="margin:0.38rem 0;">quantités ;
</li>
<li style="margin:0.38rem 0;">premium ;
</li>
<li style="margin:0.38rem 0;">chunk key.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Après création :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">réduction de la ligne <code>lot.qt</code> source ;
</li>
<li style="margin:0.38rem 0;">pas de quantité <code>lot.qt</code> négative ;
</li>
<li style="margin:0.38rem 0;">recalcul du lot virtuel ;
</li>
<li style="margin:0.38rem 0;">recalcul de <code>quantity</code> ;
</li>
<li style="margin:0.38rem 0;">mise à jour moves et fees si nécessaire.
</li>
</ul>
</li>
</ul>

### Retrait de lots physiques

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard : <code>lot.remove</code>
</li>
<li style="margin:0.38rem 0;">Lot ouvert : retrait interdit.
</li>
<li style="margin:0.38rem 0;">Lot avec <code>stock.move</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">move obligatoire en <code>draft</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Lot matché ou shippé :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">warning confirmable.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Effets :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">suppression du move draft ;
</li>
<li style="margin:0.38rem 0;">restauration de la quantité dans <code>lot.qt</code> ;
</li>
<li style="margin:0.38rem 0;">contexte restauré via shipment, <code>getVlot_p()</code>, <code>getVlot_s()</code> ;
</li>
<li style="margin:0.38rem 0;">recalcul lot virtuel, <code>quantity</code>, fees.
</li>
</ul>
</li>
</ul>

### Weighing / états de quantité

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard : <code>lot.weighing</code>
</li>
<li style="margin:0.38rem 0;">Action UI : <code>Do weighing</code>
</li>
<li style="margin:0.38rem 0;">Écrit ou met à jour <code>lot.qt.hist</code>.
</li>
<li style="margin:0.38rem 0;">Peut mettre à jour <code>lot_state</code>.
</li>
<li style="margin:0.38rem 0;">Synchronise :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">lot ;
</li>
<li style="margin:0.38rem 0;">quantités ouvertes ;
</li>
<li style="margin:0.38rem 0;">fees.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Les vues <code>lot.qt.hist</code> sont consultatives.
</li>
</ul>

### Quantité compteur `quantity`

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Méthode : <code>Lot._recalc_line_quantity</code>
</li>
<li style="margin:0.38rem 0;">Sans physique :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> suit le lot virtuel.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Avec physiques :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> somme uniquement les lots physiques.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;"><code>quantity</code> est readonly côté ligne trade.
</li>
</ul>

### Montant de ligne

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Achat : <code>purchase.line.on_change_with_amount()</code>
</li>
<li style="margin:0.38rem 0;">Vente : <code>sale.line.on_change_with_amount()</code>
</li>
<li style="margin:0.38rem 0;">Helper :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>_get_amount_quantity()</code>
</li>
<li style="margin:0.38rem 0;"><code>_get_weight_basis_quantity()</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Priorités :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>finished = False</code> : <code>quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;"><code>finished = True</code> + Weight basis disponible : somme physique dans cet état
</li>
<li style="margin:0.38rem 0;"><code>finished = True</code> sans Weight basis exploitable : <code>quantity</code>
</li>
<li style="margin:0.38rem 0;">fallback legacy : <code>quantity</code> si <code>quantity_theorical</code> vide
</li>
</ul>
</li>
</ul>

### Garde-fous Python

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Check central :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>lot.lot.assert_lines_quantity_consistency()</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Règle de déclenchement :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">bloquer les états finaux incohérents ;
</li>
<li style="margin:0.38rem 0;">ne pas bloquer les états transitoires internes d&#x27;un workflow ;
</li>
<li style="margin:0.38rem 0;">utiliser <code>Lot.skip_quantity_consistency()</code> uniquement autour d&#x27;une séquence qui rétablit ensuite les invariants ;
</li>
<li style="margin:0.38rem 0;">appeler un check final explicite après la séquence.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Blocage <code>lot.qt</code> orphelin non zéro :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>lot.qt.validate</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Appels après :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">modification <code>quantity_theorical</code> ;
</li>
<li style="margin:0.38rem 0;">création / suppression de lots physiques ;
</li>
<li style="margin:0.38rem 0;">matching / unmatching ;
</li>
<li style="margin:0.38rem 0;">shipping / unshipping ;
</li>
<li style="margin:0.38rem 0;"><code>Create contracts</code> en mode matched ;
</li>
<li style="margin:0.38rem 0;">weighing.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Le cas <code>created_by_code</code> est volontairement exclu du check immédiat <code>Lot.validate</code> :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">le lot virtuel est sauvegardé avant que le <code>lot.qt</code> matched soit rattaché ;
</li>
<li style="margin:0.38rem 0;">l&#x27;invariant est contrôlé par le check final du workflow.
</li>
</ul>
</li>
</ul>

### Diagnostic SQL

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Script :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><a href="sql/quantity_consistency_checks.sql">sql/quantity_consistency_checks.sql</a>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Usage :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">audit des bases de test ;
</li>
<li style="margin:0.38rem 0;">audit des données historiques ;
</li>
<li style="margin:0.38rem 0;">qualification avant correction.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Le script ignore totalement les <code>lot.qt = 0</code>.
</li>
</ul>

## Tests proches

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/tests/test_module.py</code>
</li>
<li style="margin:0.38rem 0;">Couverture existante :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> readonly ;
</li>
<li style="margin:0.38rem 0;">initialisation depuis <code>quantity_theorical</code> ;
</li>
<li style="margin:0.38rem 0;">protection si lots physiques ;
</li>
<li style="margin:0.38rem 0;">amount sur théorique / physique / Weight basis ;
</li>
<li style="margin:0.38rem 0;">resynchronisation des lots virtuels ;
</li>
<li style="margin:0.38rem 0;">blocages quand l&#x27;open ne suffit plus.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Tests à ajouter :
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>lot_hist</code> readonly ;
</li>
<li style="margin:0.38rem 0;"><code>Do weighing</code> crée ou met à jour un état ;
</li>
<li style="margin:0.38rem 0;">lot virtuel sans saisie directe <code>lot_qt</code> / <code>lot_unit</code> ;
</li>
<li style="margin:0.38rem 0;">contrôles SQL rejoués sur jeux de données incohérents.
</li>
</ul>
</li>
</ul>
