# Business Rules - Purchase Trade Statut: `draft` Version: `v0.2` Derniere mise a jour: `2026-03-27` Owner metier: `a completer` Owner technique: `a completer` ## 1) Scope - Domaine: `purchase_trade` - Hors scope: - Modules impactes: - `purchase_trade` - `lot` ## 2) Glossaire - `Purchase Line`: ligne d'achat. - `quantity_theorical`: quantite theorique contractuelle de la ligne. - `Virtual Lot`: lot unique de type `virtual` rattache a une `purchase.line`. - `lot.qt`: table des quantites ouvertes, matchées ou shippées par lot. - `lot.qt ouvert`: enregistrement `lot.qt` avec `lot_p = virtual lot`, `lot_s = None` et sans shipment. ## 3) Regles metier ### BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat - Intent: conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`. - Description: - Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur. - La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`. - Conditions d'entree: - Une `purchase.line` existe deja. - Son champ `quantity_theorical` est modifie via `write`. - Un lot `virtual` est rattache a la ligne. - Resultat attendu: - Si `delta > 0`: - augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist` - augmenter le `lot.qt` ouvert existant - si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta - Si `delta < 0`: - diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante - diminuer la quantite courante du lot `virtual` du meme delta - si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot` - Definition du `lot.qt` ouvert: - `lot_p = virtual lot` - `lot_s = None` - `lot_shipment_in = None` - `lot_shipment_internal = None` - `lot_shipment_out = None` - Exceptions: - si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien - Priorite: - `bloquante` - Source: - `Decision metier documentee dans les commentaires de purchase_trade.purchase.Line.write` ### BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment - Intent: disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente. - Description: - Le lot physique (`lot_type = physic`) porte simultanement le lien vers: - la `purchase.line` via `lot.line` - la `sale.line` via `lot.sale_line` - le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out` - Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite. - Resultat attendu: - depuis une facture d'achat: - remonter a la `purchase.line` - puis au lot physique de la ligne - puis au shipment et aux donnees logistiques associees - depuis une facture de vente: - remonter a la `sale.line` - puis au lot physique matchant qui porte aussi la `purchase.line` - puis au shipment et aux donnees logistiques associees - Cas d'usage typiques: - recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location` - retrouver une facture provisoire liee au lot - retrouver des fees rattaches au shipment - Priorite: - `structurante` ### BR-PT-003 - Le freight amount des templates facture vient du fee de shipment - Intent: afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique. - Description: - Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme. - Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture. - Regle de navigation: - retrouver le lot physique pertinent depuis la facture - retrouver son shipment - chercher le `fee.fee` avec: - `shipment_in = shipment.id` - `product.name = 'Maritime freight'` - utiliser `fee.get_amount()` comme montant de fret - Portee: - s'applique aussi bien aux factures d'achat qu'aux factures de vente - cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line` - Priorite: - `importante` ## 4) Exemples concrets ### Exemple E1 - Augmentation simple - Donnees: - `ancienne quantity_theorical = 100` - `nouvelle quantity_theorical = 120` - `lot.qt ouvert = 40` - Attendu: - lot `virtual` augmente de `20` - `lot.qt ouvert` passe de `40` a `60` ### Exemple E2 - Augmentation sans lot.qt ouvert - Donnees: - `ancienne quantity_theorical = 100` - `nouvelle quantity_theorical = 110` - aucun `lot.qt` ouvert - Attendu: - lot `virtual` augmente de `10` - creation d'un `lot.qt` ouvert a `10` ### Exemple E3 - Diminution possible - Donnees: - `ancienne quantity_theorical = 100` - `nouvelle quantity_theorical = 90` - `lot.qt ouvert = 25` - Attendu: - lot `virtual` diminue de `10` - `lot.qt ouvert` passe de `25` a `15` ### Exemple E4 - Diminution impossible - Donnees: - `ancienne quantity_theorical = 100` - `nouvelle quantity_theorical = 80` - `lot.qt ouvert = 5` - Attendu: - blocage avec `Please unlink or unmatch lot` ## 5) Impact code attendu - Fichiers Python concernes: - `modules/purchase_trade/purchase.py` - `modules/purchase_trade/lot.py` ## 6) Strategie de tests Pour cette regle, couvrir au minimum: - augmentation avec `lot.qt` ouvert existant - augmentation sans `lot.qt` ouvert - diminution possible - diminution impossible avec erreur