Files
tradon/modules/purchase_trade/docs/business/lots-and-quantities.md
2026-05-14 10:55:47 +02:00

468 lines
21 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
- Ligne achat : `purchase.line`
- Ligne vente : `sale.line`
- Lot : `lot.lot`
- Forecast : `lot.qt`
- Historique : `lot.qt.hist`
- Quantité métier achat : `purchase.line.quantity_theorical`
- Quantité métier vente : `sale.line.quantity_theorical`
- Compteur technique : `quantity`
- Ligne finie : `purchase.line.finished`, `sale.line.finished`
- Lot virtuel / physique : `lot.lot.lot_type = virtual / physic`
- Lien achat : `lot.lot.line`
- Lien vente : `lot.lot.sale_line`
- Forecast achat : `lot.qt.lot_p`
- Forecast vente : `lot.qt.lot_s`
- Quantité forecast : `lot.qt.lot_quantity`
- Weight basis : `purchase.purchase.wb`, `sale.sale.wb`
- État Weight basis : `purchase.weight.basis.qt_type`
- Packing : `lot.lot.lot_qt`, `lot.lot.lot_unit`
- Tolerances : `tol_min`, `tol_max`, `tol_min_qt`, `tol_max_qt`,
`tol_min_v`, `tol_max_v`
### Création ligne / lot virtuel
- Achat : `purchase.py`, `Line.validate`
- Vente : `sale.py`, `SaleLine.validate`
- Si `quantity_theorical` est saisi et que `quantity` est vide ou zéro :
- `quantity` est initialisée depuis `quantity_theorical` ;
- seulement si aucun lot physique n'existe.
- Si la ligne est éligible :
- pas `created_by_code` ;
- pas encore de lot ;
- produit non service ;
- `quantity_theorical != 0` ;
- création d'un lot `virtual`.
- Le lot virtuel reçoit une première entrée `lot.qt.hist`.
- `Lot.validate` crée le `lot.qt` ouvert via `createVirtualPart`.
### Modification de `quantity_theorical`
- Achat : `purchase.py`, `Line.write`
- Vente : `sale.py`, `SaleLine.write`
- Cible lot virtuel :
<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>
- Si `target_quantity < 0` :
- blocage : `Please unlink or unmatch lot`.
- Cible `lot.qt` libre :
<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>
- Si `free_quantity < 0` :
- blocage : `Please unlink or unmatch lot`.
- Si un `lot.qt` libre existe :
- sa quantité est remplacée.
- Si aucun `lot.qt` libre n'existe et `free_quantity > 0` :
- création d'un nouveau `lot.qt`.
- Les fees de ligne sont resynchronisés.
### Ajout de lots physiques
- Wizard : `lot.add`
- Méthodes :
- `LotQt.add_physical_lots`
- `LotQt.add_physical_lot`
- Source obligatoire : une ligne `lot.qt`.
- Ajout direct depuis un lot physique refusé.
- Ajout physique côté vente par ce wizard refusé : utiliser `Apply matching`.
- Le lot physique reprend :
- ligne achat ;
- vente matchée si présente ;
- shipment ;
- produit ;
- unité ;
- quantités ;
- premium ;
- chunk key.
- Après création :
- réduction de la ligne `lot.qt` source ;
- pas de quantité `lot.qt` négative ;
- recalcul du lot virtuel ;
- recalcul de `quantity` ;
- mise à jour moves et fees si nécessaire.
### Retrait de lots physiques
- Wizard : `lot.remove`
- Lot ouvert : retrait interdit.
- Lot avec `stock.move` :
- move obligatoire en `draft`.
- Lot matché ou shippé :
- warning confirmable.
- Effets :
- suppression du move draft ;
- restauration de la quantité dans `lot.qt` ;
- contexte restauré via shipment, `getVlot_p()`, `getVlot_s()` ;
- recalcul lot virtuel, `quantity`, fees.
### Weighing / états de quantité
- Wizard : `lot.weighing`
- Action UI : `Do weighing`
- Écrit ou met à jour `lot.qt.hist`.
- Peut mettre à jour `lot_state`.
- Synchronise :
- lot ;
- quantités ouvertes ;
- fees.
- Les vues `lot.qt.hist` sont consultatives.
### Quantité compteur `quantity`
- Méthode : `Lot._recalc_line_quantity`
- Sans physique :
- `quantity` suit le lot virtuel.
- Avec physiques :
- `quantity` somme uniquement les lots physiques.
- `quantity` est readonly côté ligne trade.
### Montant de ligne
- Achat : `purchase.line.on_change_with_amount()`
- Vente : `sale.line.on_change_with_amount()`
- Helper :
- `_get_amount_quantity()`
- `_get_weight_basis_quantity()`
- Priorités :
- `finished = False` : `quantity_theorical`
- `finished = True` + Weight basis disponible : somme physique dans cet état
- `finished = True` sans Weight basis exploitable : `quantity`
- fallback legacy : `quantity` si `quantity_theorical` vide
### Garde-fous Python
- Check central :
- `lot.lot.assert_lines_quantity_consistency()`
- Blocage `lot.qt` orphelin non zéro :
- `lot.qt.validate`
- Appels après :
- modification `quantity_theorical` ;
- création / suppression de lots physiques ;
- matching / unmatching ;
- shipping / unshipping ;
- weighing.
### Diagnostic SQL
- Script :
- [sql/quantity_consistency_checks.sql](sql/quantity_consistency_checks.sql)
- Usage :
- audit des bases de test ;
- audit des données historiques ;
- qualification avant correction.
- Le script ignore totalement les `lot.qt = 0`.
## Tests proches
- `modules/purchase_trade/tests/test_module.py`
- Couverture existante :
- `quantity` readonly ;
- initialisation depuis `quantity_theorical` ;
- protection si lots physiques ;
- amount sur théorique / physique / Weight basis ;
- resynchronisation des lots virtuels ;
- blocages quand l'open ne suffit plus.
- Tests à ajouter :
- `lot_hist` readonly ;
- `Do weighing` crée ou met à jour un état ;
- lot virtuel sans saisie directe `lot_qt` / `lot_unit` ;
- contrôles SQL rejoués sur jeux de données incohérents.