From c2cb2a874c8f019999471772fdf6d251ce7e642a Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 2 Apr 2026 10:32:36 +0200 Subject: [PATCH] 02.04.26 --- modules/purchase_trade/AGENTS.md | 22 +++++-- modules/purchase_trade/docs/business-rules.md | 58 +++++++++++++++---- .../docs/template-properties.md | 1 + modules/purchase_trade/sale.py | 32 +++++----- modules/purchase_trade/tests/test_module.py | 12 ++++ modules/sale/sale_ict.fodt | 2 +- 6 files changed, 97 insertions(+), 30 deletions(-) diff --git a/modules/purchase_trade/AGENTS.md b/modules/purchase_trade/AGENTS.md index 8a00366..7489422 100644 --- a/modules/purchase_trade/AGENTS.md +++ b/modules/purchase_trade/AGENTS.md @@ -79,15 +79,27 @@ de negoce physique: - `mtm_price` doit afficher le prix brut de valorisation (sans ratio), alors que `mtm` reste le montant calcule selon la logique de strategie - En pricing: - - le `premium` doit impacter le prix total et donc le `amount`, aussi bien - en `priced` qu'en `basis` + - le `unit_price` doit rester un prix de base, hors `premium` + - le `premium` doit impacter le prix total economique et donc le `amount`, + aussi bien en `priced` qu'en `basis` - si `linked currency` est active, le `premium` est saisi dans la devise / - unite liee (ex: `USC/LB`) puis converti vers le `unit_price` de la ligne + unite liee (ex: `USC/LB`) puis converti vers le repere de la ligne pour le + calcul du `amount` - en `basis + linked currency`, le `linked_price` doit representer le prix - basis brut (hors premium) dans la devise liee; le `unit_price` final - ajoute ensuite le premium converti + basis brut (hors premium) dans la devise liee; le `unit_price` reste ce + prix brut converti, et le `premium` converti est ajoute seulement dans + l'`amount` + - si `linked currency` est cochee, `linked_price`, `linked_currency` et + `linked_unit` sont requis + - dans les forms, presenter le bloc prix dans l'ordre: + `price_type` -> linked fields -> `premium` -> `unit_price` -> `amount` - en valuation `basis`, le premium s'applique a chaque composant, pas uniquement a une ligne de resume + - pour une ligne `basis` sans `price_summary`, la valuation fallback doit + utiliser `unit_price + premium` (et pas `unit_price` seul) + - a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun + matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour + alimenter le PnL sale-first ## 5) Conventions de modification diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md index 98e0b69..0c0ea9b 100644 --- a/modules/purchase_trade/docs/business-rules.md +++ b/modules/purchase_trade/docs/business-rules.md @@ -199,12 +199,12 @@ Owner technique: `a completer` - Le `premium` d'une `purchase.line` ou `sale.line` doit impacter le prix total quelle que soit la `price_type`. - Cette regle vaut pour: - - le `unit_price` - les calculs de `amount` - la valuation / PnL - Resultat attendu: - - en `priced`, le prix total = prix de base + premium - - en `basis`, le premium s'ajoute aussi au prix total + - le `unit_price` reste le prix de base, hors premium + - en `priced`, le montant economique = `unit_price + premium` + - en `basis`, le premium s'ajoute aussi au prix total economique - en valuation `basis`, le premium s'applique a chaque composant valorise (ex: meme premium repete sur chaque bloc ICE) - Exemple metier: @@ -221,13 +221,14 @@ Owner technique: `a completer` - Description: - Quand `enable_linked_currency` est coche, le `premium` est saisi dans la devise / unite liee, pas dans la devise / unite native de la ligne. - - Le systeme doit convertir ce premium vers le `unit_price` de la ligne pour - les calculs internes. + - Le systeme doit convertir ce premium vers le repere de la ligne pour les + calculs internes de montant et de valuation. - Resultat attendu: - `premium` est interprete dans le repere `linked_currency` / `linked_unit` - - le `unit_price` converti integre ce premium - - les `amount` et valuations bases sur `unit_price` reflectent donc ce - premium converti + - le `unit_price` ne doit pas absorber ce premium + - les `amount` et valuations doivent refleter ce premium converti + - si `linked currency` est cochee, `linked_price`, `linked_currency` et + `linked_unit` sont obligatoires - Priorite: - `structurante` @@ -238,12 +239,47 @@ Owner technique: `a completer` - Quand une ligne est en `basis` et `linked currency`, le bloc `linked_price` doit etre recalcule automatiquement. - Ce `linked_price` doit representer le prix basis brut, hors premium. - - Le `unit_price` final de la ligne est ensuite obtenu en ajoutant le premium - converti. + - Le `unit_price` de la ligne doit rester ce prix brut converti. + - Le premium converti n'est ajoute qu'au niveau du `amount`. - Resultat attendu: - modification du basis -> mise a jour automatique du `linked_price` - `linked_price` = base market / basis - - `unit_price` = `linked_price` converti + premium converti + - `unit_price` = `linked_price` converti + - `amount` = quantite * (`unit_price` + premium converti) +- Priorite: + - `importante` + +### BR-PT-011 - Une sale line non matchee avec lot virtuel doit generer une valuation sale-first des la validation + +- Intent: ne pas attendre un matching purchase pour afficher le PnL d'une sale + ouverte. +- Description: + - Lors de la validation d'une `sale.line`, le systeme peut creer un lot + `virtual`. + - Si aucun `lot.qt` ne relie ce lot a une `purchase.line`, il faut tout de + meme generer la valuation cote sale. +- Resultat attendu: + - apres creation du lot virtuel, si aucun matching purchase n'existe: + - appeler `Valuation.generate_from_sale_line(line)` + - creer au moins la ligne `sale priced` fallback si la ligne porte un prix + economique via le premium +- Priorite: + - `importante` + +### BR-PT-012 - Fallback valuation basis sans summary: utiliser le prix economique de la ligne + +- Intent: eviter qu'une valuation `basis` ouverte sorte a zero alors que la + ligne a bien une valeur economique via le premium. +- Description: + - Une ligne `basis` peut ne pas avoir encore de `price_summary`. + - Dans ce cas, la valuation fallback ne doit pas prendre `unit_price` seul si + celui-ci est brut et hors premium. +- Resultat attendu: + - le fallback valuation `basis` doit utiliser: + - `unit_price + premium converti` + - cette regle vaut au minimum pour: + - `sale.line` non matchee + - `purchase.line` sans summary - Priorite: - `importante` diff --git a/modules/purchase_trade/docs/template-properties.md b/modules/purchase_trade/docs/template-properties.md index ea020d6..abb272f 100644 --- a/modules/purchase_trade/docs/template-properties.md +++ b/modules/purchase_trade/docs/template-properties.md @@ -239,6 +239,7 @@ Source code: `modules/purchase_trade/invoice.py` Source code: `modules/purchase_trade/sale.py` - `report_terms` +- `report_crop_name` - `report_gross` - `report_net` - `report_qt` diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 6378307..92e53e6 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -352,19 +352,25 @@ class Sale(metaclass=PoolMeta): return '' @property - def report_nb_bale(self): - text_bale = 'NB BALES: ' - nb_bale = 0 - if self.lines: - for line in self.lines: - if line.lots: - nb_bale += sum([l.lot_qt for l in line.lots if l.lot_type == 'physic']) - return text_bale + str(int(nb_bale)) - - @property - def report_deal(self): - if self.lines and self.lines[0].lots and len(self.lines[0].lots)>1: - return self.lines[0].lots[1].line.purchase.number + ' ' + self.number + def report_nb_bale(self): + text_bale = 'NB BALES: ' + nb_bale = 0 + if self.lines: + for line in self.lines: + if line.lots: + nb_bale += sum([l.lot_qt for l in line.lots if l.lot_type == 'physic']) + return text_bale + str(int(nb_bale)) + + @property + def report_crop_name(self): + if self.crop: + return self.crop.name or '' + return '' + + @property + def report_deal(self): + if self.lines and self.lines[0].lots and len(self.lines[0].lots)>1: + return self.lines[0].lots[1].line.purchase.number + ' ' + self.number else: '' diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 9313ef3..f3ffece 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -142,5 +142,17 @@ class PurchaseTradeTestCase(ModuleTestCase): {'type': 'derivative', 'amount': Decimal('30')}, ]) + def test_sale_report_crop_name_handles_missing_crop(self): + 'sale report crop name returns an empty string when crop is missing' + Sale = Pool().get('sale.sale') + + sale = Sale() + sale.crop = None + self.assertEqual(sale.report_crop_name, '') + + sale.crop = Mock(name='crop') + sale.crop.name = 'Main Crop' + self.assertEqual(sale.report_crop_name, 'Main Crop') + del ModuleTestCase diff --git a/modules/sale/sale_ict.fodt b/modules/sale/sale_ict.fodt index 1627c8c..e89e81c 100644 --- a/modules/sale/sale_ict.fodt +++ b/modules/sale/sale_ict.fodt @@ -4069,7 +4069,7 @@ CROP - <sale.crop.name> + <sale.report_crop_name>