Aller au contenu

Lots et quantités

Langue : fr Page miroir : 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

Résumé opérationnelUne ligne trade possède un seul lot virtuel. Le lot virtuel porte le solde ouvert global, lot.qt porte le forecast opérationnel, et les lots physiques consomment ce forecast.
Sujet Règle courte
Quantité saisie quantity_theorical est la quantité métier.
Quantité standard quantity est un compteur technique non éditable.
Avant physique quantity suit quantity_theorical.
Après physique quantity reflète l'exécuté physique.
Ligne non finie Le montant de ligne utilise quantity_theorical.
Ligne finie Le montant peut revenir à l'exécuté physique.
Weight basis Achat et vente peuvent lire deux états différents du même lot.
Facturation Elle choisit ses états dans lot.qt.hist.
Contrôles Invariants bloqués en Python et auditables en SQL.
quantity_theorical
        |
        v
  lot virtuel P1  --->  lot.qt forecast  --->  lot physique
        ^                    |
        |                    v
        +------ recalcul après consommation

Règles consultant

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

Moment Effet métier
Création de ligne Création d'un lot virtuel unique.
Initialisation Le lot virtuel reprend quantity_theorical.
Forecast Une ligne ouverte est créée dans lot.qt.
Planification lot.qt peut être subdivisé par vente, matching, transport ou shipment.
Ajout physique Le lot physique consomme une ligne lot.qt précise.
Après ajout lot.qt diminue et le lot virtuel est recalculé.
Découpage d'un solde P1P1S1T1, P1S1T2, P1S2T3, P1S2T4
Point cléCes découpages ne créent pas plusieurs lots virtuels. Ils décrivent seulement la répartition prévisionnelle du solde ouvert.

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

Situation Quantité de référence
Saisie utilisateur quantity_theorical
Aucun lot physique quantity = quantity_theorical
Lots physiques présents quantity = somme des lots physiques
finished = False Montant basé sur quantity_theorical
finished = True Montant basé sur l'exécuté physique
Weight basis disponible Montant basé sur l'état wb.qt_type du contrat
Ce que finished ne fait pasfinished n'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'exécution.
Lecture du même lot physique État possible
Achat BL via purchase.purchase.wb.qt_type
Vente LR ou Weight Report via sale.sale.wb.qt_type
Facturation Choix indépendant dans lot.qt.hist

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

Invariant Formule
Conservation somme(lots physiques) + lot virtuel = quantity_theorical
Forecast ouvert minimal somme(lot.qt non zéro) >= max(lot virtuel, 0)
Cas Règle
Lot virtuel achat Sommer tous les lot.qtlot_p = lot virtuel, avec ou sans lot_s.
Lot virtuel vente Sommer tous les lot.qtlot_s = lot virtuel, avec ou sans lot_p.
lot.qt = 0 Ignoré par les checks ; mémoire possible d'une prévision vidée.
Surconsommation forecast Autorisée après confirmation de tolérance : lot.qt s'arrête à zéro, mais le lot virtuel absorbe tout le physique.
lot.qt supérieur au virtuel Accepté si le forecast ouvert restant dépasse le virtuel positif à cause d'une surconsommation physique.
Lot virtuel négatif Autorisé pour compenser l'écart théorique / exécuté ; forecast attendu = zéro.
lot.qt non zéro orphelin Interdit si ni lot_p ni lot_s n'est renseigné.

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

Élément Règle
lot.qt.hist Porte les états de quantité d'un lot.
Fiche lot.lot Pas de saisie directe des états.
Modification Uniquement via Do weighing.
Lot virtuel Pas de packing manuel.
Champs packing virtuel lot_qt et lot_unit non éditables.

Tolérances

Point Règle
Niveau La tolérance est définie au header purchase / sale.
Héritage Les lignes héritent de la tolérance header par défaut.
Transport Pas de tolérance indépendante par transport.
Ajout physique Le contrôle se fait au moment de Add physical lots.
Forecast matché Si lot_qt porte lot_p et lot_s, le contrôle porte sur les deux côtés achat et vente.
Dépassement ligne 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 : purchase, sale, ou les deux si les deux checks déclenchent successivement.
Enveloppe globale Le dépassement ponctuel d'une ligne est accepté après confirmation et consomme l'enveloppe globale du contrat.
Tolérance restante Les lignes du contrat sont recalculées avec inherit_tol = False.
Surconsommation Une ligne qui dépasse la tolérance header garde une tolérance + au moins égale à son dépassement réel.
Autres lignes Leur tolérance + est réduite selon l'enveloppe restante.
Sous-consommation Restitue mécaniquement de la tolérance disponible aux autres lignes lors du recalcul suivant.
Matching ouvert Go to matching peut matcher au-delà du solde ouvert strict si la quantité projetée reste dans Qt max.
Jauge ligne Sur une ligne avec lots physiques, la jauge utilise la somme des physiques ; sans physique, elle utilise la somme des lot.qt liés.
Jauge header La jauge purchase/sale est la moyenne pondérée des jauges de lignes au prorata de quantity_theorical.
Jauge matching Dans Go to matching, la jauge projette déjà matché + Qt to match contre quantity_theorical, avec bornes -tol_min / tol_max.
Layout jauge ligne En formulaire purchase.line / sale.line, placer la jauge et targeted_qt directement dans la grille principale, sans sous-groupe colspan="4", et utiliser xalign="0" pour éviter un double décalage vers la droite.

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, tolerance_used, tolerance_min, tolerance_max

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.
  • Si la ligne est created_by_code :
    • elle provient d'un workflow métier (Create contracts, matching, etc.) ;
    • Lot.validate ne crée pas de lot.qt ouvert automatique ;
    • le workflow rattache ou subdivise son lot.qt source ;
    • le check bloquant s'applique seulement après stabilisation du workflow.

Modification de quantity_theorical

  • Achat : purchase.py, Line.write
  • Vente : sale.py, SaleLine.write
  • Cible lot virtuel :
target_quantity = quantity_theorical - somme(lots physiques convertis)
  • Si target_quantity < 0 :
    • blocage : Please unlink or unmatch lot.
  • Cible lot.qt libre :
free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)
  • 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()
  • Règle de déclenchement :
    • bloquer les états finaux incohérents ;
    • ne pas bloquer les états transitoires internes d'un workflow ;
    • utiliser Lot.skip_quantity_consistency() uniquement autour d'une séquence qui rétablit ensuite les invariants ;
    • appeler un check final explicite après la séquence.
  • 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 ;
    • Create contracts en mode matched ;
    • weighing.
  • Le cas created_by_code est volontairement exclu du check immédiat Lot.validate :
    • le lot virtuel est sauvegardé avant que le lot.qt matched soit rattaché ;
    • l'invariant est contrôlé par le check final du workflow.

Diagnostic SQL

  • Script :
  • 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.