Files
tradon/modules/purchase_trade/docs/business/lots-and-quantities.md
2026-05-14 11:20:33 +02:00

674 lines
30 KiB
Markdown

<!-- 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</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>somme(lot.qt non zéro) = 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;">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
<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;">Gap à confirmer</strong><span>Le contrôle complet de tolérance restante dans <code>LotQt.add_physical_lots</code> / <code>LotQt.add_physical_lot</code> reste à confirmer.</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;">Point</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle cible</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;">Tolérance globale sur ligne ou contrat.</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;">Surconsommation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Consomme la tolérance 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 de la tolérance restante.</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>
</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>
</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;">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;">weighing.
</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>