diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md index 331f947..c416b36 100644 --- a/modules/purchase_trade/docs/business-rules.md +++ b/modules/purchase_trade/docs/business-rules.md @@ -550,20 +550,17 @@ Owner technique: `a completer` - Priorite: - `importante` -### BR-PT-016 - Les fees `% rate` utilisent la BL Date estimee +### BR-PT-016 - Les fees `% rate` utilisent le delta de financement - Intent: aligner le calcul des frais financiers `% rate` entre achat et vente. - Description: - Pour un `fee.fee` en mode `rate`, le calcul ne depend pas du - `payment_term`. - - La date de fin est toujours issue de l'onglet `Estimated date` de la ligne - achat ou vente: - - chercher `trigger = bldate` - - prendre `estimated_date` - - ajouter `fin_int_delta` + `payment_term`, ni de la date du jour, ni de `fee_date`. + - La periode de calcul est directement le champ `fin_int_delta` de la ligne + `Estimated date` avec `trigger = bldate`. - Resultat attendu: - purchase et sale appliquent la meme formule: - `fee_date -> BL Date + financing delta` + `amount = unit_price * quantity * (price / 100) * fin_int_delta / 360` - si aucune Estimated Date `bldate` n'est renseignee, aucun montant `% rate` n'est calcule - Priorite: diff --git a/modules/purchase_trade/fee.py b/modules/purchase_trade/fee.py index 04ade1b..990813b 100755 --- a/modules/purchase_trade/fee.py +++ b/modules/purchase_trade/fee.py @@ -16,12 +16,11 @@ from itertools import chain, groupby from operator import itemgetter import datetime import logging -from collections import defaultdict -from trytond.exceptions import UserWarning, UserError -from trytond.modules.account.exceptions import PeriodNotFoundError -from trytond.modules.purchase_trade.finance_tools import InterestCalculator - -logger = logging.getLogger(__name__) +from collections import defaultdict +from trytond.exceptions import UserWarning, UserError +from trytond.modules.account.exceptions import PeriodNotFoundError + +logger = logging.getLogger(__name__) def filter_state(state): def filter(func): @@ -338,10 +337,9 @@ class Fee(ModelSQL,ModelView): quantity = self.get_quantity() return Decimal(quantity or 0) - def get_amount(self,name=None): - Date = Pool().get('ir.date') - sign = Decimal(1) - if self.price: + def get_amount(self,name=None): + sign = Decimal(1) + if self.price: # if self.p_r: # if self.p_r == 'pay': # sign = -1 @@ -349,54 +347,32 @@ class Fee(ModelSQL,ModelView): return self.price * sign elif self.mode == 'ppack': return round(self.price * self.quantity,2) - elif self.mode == 'rate': - #take period with estimated trigger date - if self.line: - if self.line.estimated_date: - beg_date = self.fee_date if self.fee_date else Date.today() - 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._get_amount_quantity() * sign,2) + elif self.mode == 'rate': + if self.line: + if self.line.estimated_date: + 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.fin_int_delta: + factor = ( + Decimal(self.price) / Decimal(100) + * Decimal(est_line.fin_int_delta) / Decimal(360) + ) + return round(factor * self.line.unit_price * self._get_amount_quantity() * sign,2) if self.sale_line: if self.sale_line.estimated_date: - beg_date = self.fee_date if self.fee_date else Date.today() est_lines = [ dd for dd in self.sale_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_line and est_line.fin_int_delta: + factor = ( + Decimal(self.price) / Decimal(100) + * Decimal(est_line.fin_int_delta) / Decimal(360) ) - logger.info("EST_DATE:%s", est_date) - 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' - ) - logger.info("FACTOR:%s", factor) - return round( - factor * self.sale_line.unit_price - * self._get_amount_quantity() * sign, 2) + return round( + factor * self.sale_line.unit_price + * self._get_amount_quantity() * sign, 2) elif self.mode == 'perqt': if self.shipment_in: diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index e7ca299..1b782ec 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -243,12 +243,12 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual(values[0]['amount'], Decimal('0')) def test_purchase_rate_fee_amount_uses_virtual_lot_quantity(self): - 'purchase rate fee amount falls back to the purchase virtual lot quantity' + 'purchase rate fee amount uses the financing delta as absolute period' Fee = Pool().get('fee.fee') fee = Fee() fee.mode = 'rate' fee.price = Decimal('12') - fee.fee_date = datetime.date(2026, 4, 23) + fee.fee_date = None fee.quantity = None fee.unit = Mock() fee.shipment_in = None @@ -267,19 +267,15 @@ class PurchaseTradeTestCase(ModuleTestCase): ) fee.sale_line = None - with patch('trytond.modules.purchase_trade.fee.Pool') as PoolMock: - PoolMock.return_value.get.return_value = Mock( - today=Mock(return_value=datetime.date(2026, 4, 23))) + self.assertEqual(fee.get_amount(), Decimal('3.33')) - self.assertEqual(fee.get_amount(), Decimal('13.33')) - - def test_sale_rate_fee_amount_uses_bl_date_estimate(self): - 'sale rate fee amount uses BL estimated date plus financing delta' + def test_sale_rate_fee_amount_uses_absolute_financing_delta(self): + 'sale rate fee amount uses the financing delta as absolute period' Fee = Pool().get('fee.fee') fee = Fee() fee.mode = 'rate' fee.price = Decimal('12') - fee.fee_date = datetime.date(2026, 4, 23) + fee.fee_date = None fee.quantity = Decimal('10') fee.unit = Mock() fee.shipment_in = None @@ -296,11 +292,7 @@ class PurchaseTradeTestCase(ModuleTestCase): ], ) - with patch('trytond.modules.purchase_trade.fee.Pool') as PoolMock: - PoolMock.return_value.get.return_value = Mock( - today=Mock(return_value=datetime.date(2026, 4, 23))) - - self.assertEqual(fee.get_amount(), Decimal('13.33')) + self.assertEqual(fee.get_amount(), Decimal('3.33')) def test_create_pnl_price_from_line_keeps_finished_physical_sale_line(self): 'purchase valuation keeps finished sale-side pnl on physical lots' diff --git a/notes/business_rules.md b/notes/business_rules.md index 3f8223d..0a3d759 100644 --- a/notes/business_rules.md +++ b/notes/business_rules.md @@ -118,12 +118,12 @@ elle existe, par exemple: - Le calcul du montant d'un fee en mode `rate` est aligne entre purchase et sale. - Il ne depend pas du `payment_term`. -- La date de fin vient de l'onglet `Estimated date` de la ligne metier: - chercher `trigger = bldate`, prendre `estimated_date`, puis ajouter - `fin_int_delta`. +- Il ne depend pas de la date du jour ni de `fee_date`. +- La periode de financement est directement `fin_int_delta` sur la ligne + `Estimated date` avec `trigger = bldate`. - La formule reste en interet simple ACT/360: `amount = rate_factor * unit_price * quantity`, avec - `rate_factor = (price / 100) * jours / 360`. + `rate_factor = (price / 100) * fin_int_delta / 360`. - Si aucune Estimated Date `bldate` n'est renseignee, le fee `% rate` ne calcule pas de montant.