diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md new file mode 100644 index 0000000..9540064 --- /dev/null +++ b/modules/purchase_trade/docs/business-rules.md @@ -0,0 +1,113 @@ +# Business Rules - Purchase Trade + +Statut: `draft` +Version: `v0.1` +Derniere mise a jour: `2026-03-25` +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` + +## 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 diff --git a/modules/purchase_trade/fee.py b/modules/purchase_trade/fee.py index 0af73ce..2771185 100755 --- a/modules/purchase_trade/fee.py +++ b/modules/purchase_trade/fee.py @@ -333,20 +333,25 @@ class Fee(ModelSQL,ModelView): elif self.mode == 'rate': #take period with estimated trigger date if self.line: - if self.line.purchase.payment_term: + if self.line.estimated_date: beg_date = self.fee_date if self.fee_date else Date.today() - est_date = self.line.purchase.payment_term.lines[0].get_date(beg_date,self.line) - if est_date and beg_date: - factor = InterestCalculator.calculate( - start_date=beg_date, - end_date=est_date, - rate=self.price/100, - rate_type='annual', - convention='ACT/360', - compounding='simple' + est_lines = [dd for dd in self.line.estimated_date if dd.trigger == 'bldate'] + est_line = est_lines[0] if est_lines else None + if est_line and est_line.estimated_date: + est_date = est_line.estimated_date + datetime.timedelta( + days=est_line.fin_int_delta or 0 ) + if est_date and beg_date: + factor = InterestCalculator.calculate( + start_date=beg_date, + end_date=est_date, + rate=self.price/100, + rate_type='annual', + convention='ACT/360', + compounding='simple' + ) - return round(factor * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2) + return round(factor * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2) if self.sale_line: if self.sale_line.sale.payment_term: beg_date = self.fee_date if self.fee_date else Date.today() diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index a9e8a6e..b4031ea 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -56,6 +56,7 @@ class Estimated(ModelSQL, ModelView): trigger = fields.Selection(TRIGGERS,"Trigger") estimated_date = fields.Date("Estimated date") + fin_int_delta = fields.Integer("Financing interests delta") class MtmScenario(ModelSQL, ModelView): "MtM Scenario" diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 61933b8..dba26a1 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -1225,7 +1225,6 @@ class Line(metaclass=PoolMeta): old_values = {} for records, values in zip(args[::2], args[1::2]): - logger.info("***WRITE***:%s",values) if 'quantity_theorical' in values: for record in records: old_values[record.id] = record.quantity_theorical @@ -1238,7 +1237,6 @@ class Line(metaclass=PoolMeta): old = Decimal(old_values[line.id] or 0) new = Decimal(line.quantity_theorical or 0) delta = new - old - logger.info("***WRITE_DELTA***:%s",delta) if delta > 0: virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual'] if not virtual_lots: diff --git a/modules/purchase_trade/view/estimated_tree.xml b/modules/purchase_trade/view/estimated_tree.xml index 330ad54..1344ff8 100755 --- a/modules/purchase_trade/view/estimated_tree.xml +++ b/modules/purchase_trade/view/estimated_tree.xml @@ -1,4 +1,5 @@ +