12 KiB
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 les anciennes règles BR-PT-LOT-001,
BR-PT-LOT-002 et BR-PT-LOT-003 autour d'une règle fonctionnelle unique :
le cycle de vie des quantités ouvertes, forecastées et physiques.
Règle business consultant
BR-PT-LOT-001 - Cycle de vie des lots et des quantités
Lors de la création d'une ligne d'achat ou de vente, le système crée un lot virtuel associé à cette ligne. Ce lot virtuel représente la quantité encore ouverte du contrat. Sa quantité initiale reprend la quantité contractuelle ou théorique de la ligne.
En parallèle, le système ajoute une ligne ouverte dans lot.qt. Cette ligne
sert de base aux prévisions commerciales et logistiques : vente prévisionnelle,
matching futur, transport planifié, shipment, etc. lot.qt porte donc le
forecast opérationnel, tandis que le lot virtuel reste la représentation
globale du solde ouvert dans lot.lot.
Exemple : une quantité ouverte P1 peut être progressivement subdivisée dans
lot.qt pour prévoir plusieurs ventes ou transports :
P1S1T1P1S1T2P1S2T3P1S2T4
Ces subdivisions ne créent pas plusieurs lots virtuels pour P1. Elles
décrivent seulement la répartition prévisionnelle de la quantité ouverte.
Quand des lots physiques sont ajoutés, ils sont créés depuis une ligne précise
de Lots Management, donc depuis une ligne lot.qt. Si l'utilisateur choisit
P1S1T1, le lot physique créé consomme ce forecast particulier. La ligne
lot.qt choisie est réduite, puis le lot virtuel de la ligne est recalculé
pour représenter seulement le reliquat encore ouvert.
La règle immuable de cohérence est :
somme des lots physiques de la ligne + lot virtuel de la ligne
= quantité contractuelle/théorique de la ligne
Cette cohérence doit rester vraie même si la quantité contractuelle est ajustée en cours de route. La quantité contractuelle/théorique est la seule quantité saisie par l'utilisateur sur la ligne. La quantité standard de la ligne est un compteur : elle reprend la quantité contractuelle tant qu'il n'y a pas de lot physique, puis elle reflète la somme des lots physiques exécutés.
Le retrait d'un lot physique est autorisé seulement tant que son mouvement
stock n'est pas finalisé. Si ce lot était déjà matché ou rattaché à un
shipment, l'utilisateur doit confirmer l'action. Le retrait restaure la
quantité dans la ligne lot.qt qui portait le contexte forecast, matching ou
transport ayant permis de créer le lot physique.
L'historique des quantités d'un lot n'est jamais une zone de saisie directe.
Les différents états de quantité d'un lot doivent être modifiés uniquement par
une action métier dédiée, aujourd'hui Do weighing. Cette règle garantit que
les recalculs de quantité ouverte, de lot virtuel, de fees et de mouvements
stock restent synchronisés.
Tolérance et quantités physiques
Un lot physique peut avoir une quantité différente de la prévision initiale, dans les limites de tolérance du contrat. La tolérance doit se lire comme une tolérance globale sur la ligne ou le contrat, pas comme une tolérance indépendante par transport.
Si un lot physique consomme plus ou moins que son forecast, cela doit réduire ou augmenter dynamiquement la tolérance restante pour les prochains lots physiques de la même ligne. Aucune action d'ajout, de weighing, de suppression ou d'ajustement contractuel ne doit permettre de sortir de la cohérence globale des quantités.
Section développeur
Modèles et champs principaux
- Ligne achat :
purchase.line. - Ligne vente :
sale.line. - Lot :
lot.lot. - Forecast / quantité ouverte :
lot.qt. - Historique des quantités de lot :
lot.qt.hist. - Quantité contractuelle achat :
purchase.line.quantity_theorical(Contractual Qt). - Quantité contractuelle vente :
sale.line.quantity_theorical(Th. quantity). - Quantité compteur achat/vente :
quantity. - Type de lot :
lot.lot.lot_type, valeursvirtualetphysic. - Lien achat :
lot.lot.line. - Lien vente :
lot.lot.sale_line. - Liens shipment :
lot_shipment_in,lot_shipment_internal,lot_shipment_out. - Matching forecast achat/vente :
lot.qt.lot_p,lot.qt.lot_s. - Quantité forecast :
lot.qt.lot_quantity. - Tolérances contrat/ligne :
tol_min,tol_max,tol_min_qt,tol_max_qt,tol_min_v,tol_max_v. - Historique des états de quantité :
lot.lot.lot_histverslot.qt.hist. - Champs d'historique :
lot.qt.hist.quantity_type,lot.qt.hist.quantity,lot.qt.hist.gross_quantity. - Champs de packing du lot :
lot.lot.lot_qt,lot.lot.lot_unit.
Création de la ligne et du lot virtuel
- Achat :
modules/purchase_trade/purchase.py,Line.validate. - Vente :
modules/purchase_trade/sale.py,SaleLine.validate. - Si la ligne n'est pas
created_by_code, qu'elle n'a pas encore de lot, que le produit n'est pas un service et quequantity != 0, un lotvirtualest créé. - Le lot virtuel reçoit une première entrée
lot.qt.hist. - La création du
lot.qtouvert est déclenchée parmodules/purchase_trade/lot.py,Lot.validate, viacreateVirtualPartquand aucunlot.qtn'existe encore pour le lot virtuel. - Pour l'achat, le lot virtuel alimente
lot.qt.lot_p. - Pour la vente, le lot virtuel alimente
lot.qt.lot_saveclot_p = None.
Modification de la quantité contractuelle
État du code vérifié :
- Achat :
purchase.py,Line.write. - Vente :
sale.py,SaleLine.write. - La modification de
quantity_theoricalrecalcule une quantité virtuelle cible :
target_quantity = quantity_theorical - somme(lots physiques convertis)
- Si cette quantité cible devient négative, le code bloque avec
Please unlink or unmatch lot. - Le
lot.qtlibre est ensuite resynchronisé en tenant compte des ligneslot.qtdéjà allouées, c'est-à-dire déjà matchées ou rattachées à un shipment :
free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)
- Si
free_quantitydevient négative, le code bloque avecPlease unlink or unmatch lot. - Si le lot virtuel ne porte pas déjà la quantité cible, le code appelle
vlot.set_current_quantity(target_quantity, target_quantity, 1). - Si un
lot.qtlibre existe, sa quantité est remplacée parfree_quantity. - Si aucun
lot.qtlibre n'existe et quefree_quantity > 0, un nouveaulot.qtlibre est créé. - Les fees de la ligne sont resynchronisés après modification.
Point spécifique achat :
- Si
quantity_theoricalétait vide au moment de l'initialisation, le code prend comme baseline la quantité courante du lot virtuel pour éviter de doubler lelot.qtouvert.
Point spécifique vente :
- Si l'ancienne
quantity_theoricalest vide,SaleLine.writene lance pas encore cette resynchronisation.
Ajout de lots physiques
- Wizard :
modules/purchase_trade/lot.py,lot.add. - Méthode principale :
LotQt.add_physical_lots. - Création unitaire :
LotQt.add_physical_lot. - L'ajout part obligatoirement d'une ligne
lot.qtissue de Lots Management. - Le code refuse l'ajout direct depuis un lot physique.
- Le code refuse l'ajout physique côté vente par ce wizard et demande
d'utiliser
Apply matching. - Le nouveau lot physique reprend le contexte de la ligne
lot.qt: ligne achat, vente matchée si présente, shipment, produit, unité, quantités, premium et chunk key. - Après création, le code réduit la quantité de la ligne
lot.qtsource du total physique créé et ne laisse pas la ligne descendre sous zéro. - La sauvegarde/validation du lot physique recalcule :
- le lot virtuel de la ligne via
_recompute_virtual_lot; - la quantité compteur de la ligne via
_recalc_line_quantity; - les mouvements stock liés si nécessaire ;
- les fees rattachés au lot, à la ligne ou au shipment.
- le lot virtuel de la ligne via
Retrait de lots physiques
- Wizard :
modules/purchase_trade/lot.py,lot.remove. - Le retrait d'un lot ouvert est interdit.
- Si le lot physique possède un
stock.move, ce move doit être en étatdraft. - Si le lot est matché ou shippé, un warning confirmable est affiché.
- Le code supprime d'abord le move draft si présent.
- Le code restaure la quantité physique dans
lot.qten réutilisant le contexte :- shipment d'origine via
lot.lot_shipment_origin; - lot virtuel sale via
getVlot_s()si le lot était matché ; - lot virtuel purchase via
getVlot_p()dansupdateVirtualPart.
- shipment d'origine via
- Si une ligne
lot.qtcompatible existe déjà, elle est incrémentée. - Sinon une nouvelle ligne
lot.qtest créée. - La suppression du lot physique déclenche ensuite le recalcul du lot virtuel, de la quantité compteur de ligne et des fees.
Historique de quantité et weighing
- L'historique est porté par
lot.qt.hist. lot.qt.histreprésente les différents états de quantité d'un lot.- Les quantités historiques ne doivent pas être modifiées directement depuis
la fiche
lot.lot. - La vue
lot.lotaffichelot_histen lecture seule. - Les vues
lot.qt.histsont consultatives: pas d'édition directe depuis l'arbre ou le formulaire. - Le chemin fonctionnel de modification est le wizard
lot.weighing, exposé par l'actionDo weighing. - Le wizard écrit ou met à jour l'entrée
lot.qt.histcorrespondant aulot_statechoisi, puis synchronise le lot, les quantités ouvertes et les fees. - Un lot virtuel ne doit pas recevoir de saisie de packing depuis la fiche
lot: les champs
lot_qtetlot_unitsont non éditables pourlot_type = virtual.
Quantité compteur de ligne
- Méthode :
lot.py,Lot._recalc_line_quantity. - Si la ligne n'a qu'un seul lot,
quantityreprend la quantité courante de ce lot. - Si la ligne a plusieurs lots,
quantitysomme uniquement les lots physiques. - Cette logique correspond à la règle fonctionnelle :
- sans physique :
quantitysuit le virtuel ; - avec physiques :
quantityreflète l'exécuté physique.
- sans physique :
Tolérance : état actuel et point à confirmer
Le code expose déjà les tolérances sur contrats et lignes :
- Achat :
purchase.purchase.tol_min,purchase.purchase.tol_max,purchase.line.tol_min,purchase.line.tol_max. - Vente :
sale.sale.tol_min,sale.sale.tol_max,sale.line.tol_min,sale.line.tol_max. - Fonctions d'affichage :
get_tol_min,get_tol_max. - Wizard d'ajout :
lot.add.line.tol_min,lot.add.line.tol_max, avec défauts depuis le contrat source.
À confirmer / gap potentiel :
- Dans
LotQt.add_physical_lotsetLotQt.add_physical_lot, je ne vois pas encore de contrôle complet qui calcule une tolérance restante globale en tenant compte des lots physiques déjà créés. - La règle consultant ci-dessus de tolérance globale dynamique doit donc être
considérée comme cible métier à vérifier/implémenter avant de la marquer
active.
Tests proches
modules/purchase_trade/tests/test_module.pytest_sale_line_write_updates_virtual_lot_when_theorical_qty_increasestest_sale_line_write_blocks_theorical_qty_decrease_when_no_open_quantitytest_purchase_line_write_syncs_open_lot_qt_with_physical_lotstest_purchase_line_write_syncs_virtual_fee_quantitytest_purchase_line_write_initial_theorical_qty_does_not_double_open_lot
À ajouter si cette règle devient sensible côté régression:
- vérifier que
lot_histest en lecture seule sur la fichelot.lot; - vérifier que
Do weighingreste capable de créer ou mettre à jour un état de quantité; - vérifier qu'un lot virtuel ne permet pas la saisie directe de
lot_qtetlot_unit.