Business rules

This commit is contained in:
2026-05-13 15:13:23 +02:00
parent 50e25138fd
commit de78da846d
2 changed files with 210 additions and 37 deletions

View File

@@ -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.

View File

@@ -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`