Business rules
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user