From de78da846d30d9eae036084c210eb86ac4f42a59 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Wed, 13 May 2026 15:13:23 +0200 Subject: [PATCH] Business rules --- modules/purchase_trade/docs/business/INDEX.md | 4 +- .../docs/business/lots-and-quantities.md | 243 +++++++++++++++--- 2 files changed, 210 insertions(+), 37 deletions(-) diff --git a/modules/purchase_trade/docs/business/INDEX.md b/modules/purchase_trade/docs/business/INDEX.md index 04243e3..15617c2 100644 --- a/modules/purchase_trade/docs/business/INDEX.md +++ b/modules/purchase_trade/docs/business/INDEX.md @@ -23,9 +23,7 @@ Statut: `migration partielle` - `BR-PT-CON-001`: texte par defaut de pricing rule. - `BR-PT-CON-002`: delivery period coherent. - `BR-PT-CON-003`: lieux stock propages dans Create Contracts. -- `BR-PT-LOT-001`: lot physique comme pont metier. -- `BR-PT-LOT-002`: solde ouvert `lot.qt`. -- `BR-PT-LOT-003`: remove physical lot. +- `BR-PT-LOT-001`: cycle de vie des lots et des quantites. - `BR-PT-MAT-001`: Create Contracts multi-lots. - `BR-PT-SHP-001`: affectation controller. - `BR-PT-SHP-002`: couts SLA controller. diff --git a/modules/purchase_trade/docs/business/lots-and-quantities.md b/modules/purchase_trade/docs/business/lots-and-quantities.md index 48a18e9..0a95105 100644 --- a/modules/purchase_trade/docs/business/lots-and-quantities.md +++ b/modules/purchase_trade/docs/business/lots-and-quantities.md @@ -1,56 +1,231 @@ # Lots et quantites Statut: `migration partielle` +Derniere verification code: `2026-05-13` -## BR-PT-LOT-001 - Le lot physique est le pont metier +Cette page consolide les anciennes regles `BR-PT-LOT-001`, +`BR-PT-LOT-002` et `BR-PT-LOT-003` autour d'une regle fonctionnelle unique: +le cycle de vie des quantites ouvertes, forecastees et physiques. -Source: `BR-PT-002` +## Regle business consultant -### Regle consultant +### BR-PT-LOT-001 - Cycle de vie des lots et des quantites -Le lot physique est la reference pour relier une quantite executee a son achat, -sa vente, son shipment et ses documents. +Lors de la creation d'une ligne d'achat ou de vente, le systeme cree un lot +virtuel associe a cette ligne. Ce lot virtuel represente la quantite encore +ouverte du contrat. Sa quantite initiale reprend la quantite contractuelle ou +theorique de la ligne. -### Notes developpeur +En parallele, le systeme ajoute une ligne ouverte dans `lot.qt`. Cette ligne +sert de base aux previsions commerciales et logistiques: vente previsionnelle, +matching futur, transport planifie, shipment, etc. `lot.qt` porte donc le +forecast operationnel, tandis que le lot virtuel reste la representation +globale du solde ouvert dans `lot.lot`. -- Champs: `lot.line`, `lot.sale_line`, `lot_shipment_in`, - `lot_shipment_internal`, `lot_shipment_out`. -- Utiliser ce chemin avant de creer un raccourci facture -> shipment. +Exemple: une quantite ouverte `P1` peut etre progressivement subdivisee dans +`lot.qt` pour prevoir plusieurs ventes ou transports: -## BR-PT-LOT-002 - Le solde ouvert suit les lots physiques existants +- `P1S1T1` +- `P1S1T2` +- `P1S2T3` +- `P1S2T4` -Source: `BR-PT-020` +Ces subdivisions ne creent pas plusieurs lots virtuels pour `P1`. Elles +decrivent seulement la repartition previsionnelle de la quantite ouverte. -### Regle consultant +Quand des lots physiques sont ajoutes, ils sont crees depuis une ligne precise +de Lots Management, donc depuis une ligne `lot.qt`. Si l'utilisateur choisit +`P1S1T1`, le lot physique cree consomme ce forecast particulier. La ligne +`lot.qt` choisie est reduite, puis le lot virtuel de la ligne est recalcule +pour representer seulement le reliquat encore ouvert. -Quand la quantite contractuelle change, le systeme recalcule le reliquat ouvert -en tenant compte des lots physiques deja crees. Il ne doit pas ajouter un delta -qui ferait apparaitre deux fois la meme quantite. +La regle immuable de coherence est: -### Notes developpeur +```text +somme des lots physiques de la ligne + lot virtuel de la ligne += quantite contractuelle/theorique de la ligne +``` -- Cibles: `purchase.line.quantity_theorical`, `sale.line.quantity_theorical`. -- Quantite virtuelle cible = - `quantity_theorical - somme(lots physiques convertis dans l'unite ligne)`. -- `lot.qt` libre = - `quantite virtuelle cible - somme(lot.qt deja matches ou shippes)`. -- Bloquer avec `Please unlink or unmatch lot` si le solde devient negatif. -- Les fees de la ligne doivent etre resynchronises apres modification. +Cette coherence doit rester vraie meme si la quantite contractuelle est +ajustee en cours de route. La quantite contractuelle/theorique est la seule +quantite saisie par l'utilisateur sur la ligne. La quantite standard de la +ligne est un compteur: elle reprend la quantite contractuelle tant qu'il n'y a +pas de lot physique, puis elle reflete la somme des lots physiques executes. -## BR-PT-LOT-003 - Remove physical lot restaure le contexte ouvert +Le retrait d'un lot physique est autorise seulement tant que son mouvement +stock n'est pas finalise. Si ce lot etait deja matche ou rattache a un +shipment, l'utilisateur doit confirmer l'action. Le retrait restaure la +quantite dans la ligne `lot.qt` qui portait le contexte forecast, matching ou +transport ayant permis de creer le lot physique. -Source: `BR-PT-022` +### Tolerance et quantites physiques -### Regle consultant +Un lot physique peut avoir une quantite differente de la prevision initiale, +dans les limites de tolerance du contrat. La tolerance doit se lire comme une +tolerance globale sur la ligne ou le contrat, pas comme une tolerance +independante par transport. -Un lot physique cree par erreur peut etre retire tant que son mouvement stock -n'est pas finalise. Si le lot etait deja shippe ou matche, l'utilisateur doit -confirmer car le contexte est sensible. +Si un lot physique consomme plus ou moins que son forecast, cela doit reduire +ou augmenter dynamiquement la tolerance restante pour les prochains lots +physiques de la meme ligne. Aucune action d'ajout, de weighing, de suppression +ou d'ajustement contractuel ne doit permettre de sortir de la coherence globale +des quantites. -### Notes developpeur +## Section developpeur -- Autorise seulement si le `stock.move` lie est encore en `draft`. -- Restaurer la quantite dans `lot.qt` avec le shipment et/ou le lot oppose - d'origine. -- Agreger avec une ligne compatible si elle existe deja. +### Modeles et champs principaux +- Ligne achat: `purchase.line`. +- Ligne vente: `sale.line`. +- Lot: `lot.lot`. +- Forecast / quantite ouverte: `lot.qt`. +- Historique des quantites de lot: `lot.qt.hist`. +- Quantite contractuelle achat: `purchase.line.quantity_theorical` + (`Contractual Qt`). +- Quantite contractuelle vente: `sale.line.quantity_theorical` + (`Th. quantity`). +- Quantite compteur achat/vente: `quantity`. +- Type de lot: `lot.lot.lot_type`, valeurs `virtual` et `physic`. +- 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`. +- Quantite forecast: `lot.qt.lot_quantity`. +- Tolerances contrat/ligne: `tol_min`, `tol_max`, `tol_min_qt`, + `tol_max_qt`, `tol_min_v`, `tol_max_v`. + +### Creation 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 que `quantity != 0`, un lot `virtual` est + cree. +- Le lot virtuel recoit une premiere entree `lot.qt.hist`. +- La creation du `lot.qt` ouvert est declenchee par + `modules/purchase_trade/lot.py`, `Lot.validate`, via `createVirtualPart` + quand aucun `lot.qt` n'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_s` avec `lot_p = None`. + +### Modification de la quantite contractuelle + +Etat du code verifie: + +- Achat: `purchase.py`, `Line.write`. +- Vente: `sale.py`, `SaleLine.write`. +- La modification de `quantity_theorical` recalcule une quantite virtuelle + cible: + +```text +target_quantity = quantity_theorical - somme(lots physiques convertis) +``` + +- Si cette quantite cible devient negative, le code bloque avec + `Please unlink or unmatch lot`. +- Le `lot.qt` libre est ensuite resynchronise en tenant compte des lignes + `lot.qt` deja allouees, c'est-a-dire deja matchees ou rattachees a un + shipment: + +```text +free_quantity = target_quantity - somme(lot.qt deja matches ou shippes) +``` + +- Si `free_quantity` devient negative, le code bloque avec + `Please unlink or unmatch lot`. +- Si le lot virtuel ne porte pas deja la quantite cible, le code appelle + `vlot.set_current_quantity(target_quantity, target_quantity, 1)`. +- Si un `lot.qt` libre existe, sa quantite est remplacee par `free_quantity`. +- Si aucun `lot.qt` libre n'existe et que `free_quantity > 0`, un nouveau + `lot.qt` libre est cree. +- Les fees de la ligne sont resynchronises apres modification. + +Point specifique achat: + +- Si `quantity_theorical` etait vide au moment de l'initialisation, le code + prend comme baseline la quantite courante du lot virtuel pour eviter de + doubler le `lot.qt` ouvert. + +Point specifique vente: + +- Si l'ancienne `quantity_theorical` est vide, `SaleLine.write` ne lance pas + encore cette resynchronisation. + +### Ajout de lots physiques + +- Wizard: `modules/purchase_trade/lot.py`, `lot.add`. +- Methode principale: `LotQt.add_physical_lots`. +- Creation unitaire: `LotQt.add_physical_lot`. +- L'ajout part obligatoirement d'une ligne `lot.qt` issue de Lots Management. +- Le code refuse l'ajout direct depuis un lot physique. +- Le code refuse l'ajout physique cote 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 matchee si presente, shipment, produit, unite, + quantites, premium et chunk key. +- Apres creation, le code reduit la quantite de la ligne `lot.qt` source du + total physique cree et ne laisse pas la ligne descendre sous zero. +- La sauvegarde/validation du lot physique recalcule: + - le lot virtuel de la ligne via `_recompute_virtual_lot`; + - la quantite compteur de la ligne via `_recalc_line_quantity`; + - les mouvements stock lies si necessaire; + - les fees rattaches au lot, a la ligne ou au shipment. + +### Retrait de lots physiques + +- Wizard: `modules/purchase_trade/lot.py`, `lot.remove`. +- Le retrait d'un lot ouvert est interdit. +- Si le lot physique possede un `stock.move`, ce move doit etre en etat + `draft`. +- Si le lot est matche ou shippe, un warning confirmable est affiche. +- Le code supprime d'abord le move draft si present. +- Le code restaure la quantite physique dans `lot.qt` en reutilisant le + contexte: + - shipment d'origine via `lot.lot_shipment_origin`; + - lot virtuel sale via `getVlot_s()` si le lot etait matche; + - lot virtuel purchase via `getVlot_p()` dans `updateVirtualPart`. +- Si une ligne `lot.qt` compatible existe deja, elle est incrementee. +- Sinon une nouvelle ligne `lot.qt` est creee. +- La suppression du lot physique declenche ensuite le recalcul du lot virtuel, + de la quantite compteur de ligne et des fees. + +### Quantite compteur de ligne + +- Methode: `lot.py`, `Lot._recalc_line_quantity`. +- Si la ligne n'a qu'un seul lot, `quantity` reprend la quantite courante de ce + lot. +- Si la ligne a plusieurs lots, `quantity` somme uniquement les lots physiques. +- Cette logique correspond a la regle fonctionnelle: + - sans physique: `quantity` suit le virtuel; + - avec physiques: `quantity` reflete l'execute physique. + +### Tolerance: etat actuel et point a confirmer + +Le code expose deja les tolerances 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 defauts + depuis le contrat source. + +A confirmer / gap potentiel: + +- Dans `LotQt.add_physical_lots` et `LotQt.add_physical_lot`, je ne vois pas + encore de controle complet qui calcule une tolerance restante globale en + tenant compte des lots physiques deja crees. +- La regle consultant ci-dessus de tolerance globale dynamique doit donc etre + consideree comme cible metier a verifier/implementer avant de la marquer + `active`. + +### Tests proches + +- `modules/purchase_trade/tests/test_module.py` + - `test_sale_line_write_updates_virtual_lot_when_theorical_qty_increases` + - `test_sale_line_write_blocks_theorical_qty_decrease_when_no_open_quantity` + - `test_purchase_line_write_syncs_open_lot_qt_with_physical_lots` + - `test_purchase_line_write_syncs_virtual_fee_quantity` + - `test_purchase_line_write_initial_theorical_qty_does_not_double_open_lot`