Bug fee financing interest

This commit is contained in:
2026-04-30 13:51:54 +02:00
parent 03d65c253c
commit 75bd34b673
4 changed files with 42 additions and 77 deletions

View File

@@ -550,20 +550,17 @@ Owner technique: `a completer`
- Priorite: - Priorite:
- `importante` - `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. - Intent: aligner le calcul des frais financiers `% rate` entre achat et vente.
- Description: - Description:
- Pour un `fee.fee` en mode `rate`, le calcul ne depend pas du - Pour un `fee.fee` en mode `rate`, le calcul ne depend pas du
`payment_term`. `payment_term`, ni de la date du jour, ni de `fee_date`.
- La date de fin est toujours issue de l'onglet `Estimated date` de la ligne - La periode de calcul est directement le champ `fin_int_delta` de la ligne
achat ou vente: `Estimated date` avec `trigger = bldate`.
- chercher `trigger = bldate`
- prendre `estimated_date`
- ajouter `fin_int_delta`
- Resultat attendu: - Resultat attendu:
- purchase et sale appliquent la meme formule: - 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` - si aucune Estimated Date `bldate` n'est renseignee, aucun montant `% rate`
n'est calcule n'est calcule
- Priorite: - Priorite:

View File

@@ -16,12 +16,11 @@ from itertools import chain, groupby
from operator import itemgetter from operator import itemgetter
import datetime import datetime
import logging import logging
from collections import defaultdict from collections import defaultdict
from trytond.exceptions import UserWarning, UserError from trytond.exceptions import UserWarning, UserError
from trytond.modules.account.exceptions import PeriodNotFoundError from trytond.modules.account.exceptions import PeriodNotFoundError
from trytond.modules.purchase_trade.finance_tools import InterestCalculator
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
def filter_state(state): def filter_state(state):
def filter(func): def filter(func):
@@ -338,10 +337,9 @@ class Fee(ModelSQL,ModelView):
quantity = self.get_quantity() quantity = self.get_quantity()
return Decimal(quantity or 0) return Decimal(quantity or 0)
def get_amount(self,name=None): def get_amount(self,name=None):
Date = Pool().get('ir.date') sign = Decimal(1)
sign = Decimal(1) if self.price:
if self.price:
# if self.p_r: # if self.p_r:
# if self.p_r == 'pay': # if self.p_r == 'pay':
# sign = -1 # sign = -1
@@ -349,54 +347,32 @@ class Fee(ModelSQL,ModelView):
return self.price * sign return self.price * sign
elif self.mode == 'ppack': elif self.mode == 'ppack':
return round(self.price * self.quantity,2) return round(self.price * self.quantity,2)
elif self.mode == 'rate': elif self.mode == 'rate':
#take period with estimated trigger date if self.line:
if self.line: if self.line.estimated_date:
if self.line.estimated_date: est_lines = [dd for dd in self.line.estimated_date if dd.trigger == 'bldate']
beg_date = self.fee_date if self.fee_date else Date.today() est_line = est_lines[0] if est_lines else None
est_lines = [dd for dd in self.line.estimated_date if dd.trigger == 'bldate'] if est_line and est_line.fin_int_delta:
est_line = est_lines[0] if est_lines else None factor = (
if est_line and est_line.estimated_date: Decimal(self.price) / Decimal(100)
est_date = est_line.estimated_date + datetime.timedelta( * Decimal(est_line.fin_int_delta) / Decimal(360)
days=est_line.fin_int_delta or 0 )
) return round(factor * self.line.unit_price * self._get_amount_quantity() * sign,2)
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)
if self.sale_line: if self.sale_line:
if self.sale_line.estimated_date: if self.sale_line.estimated_date:
beg_date = self.fee_date if self.fee_date else Date.today()
est_lines = [ est_lines = [
dd for dd in self.sale_line.estimated_date dd for dd in self.sale_line.estimated_date
if dd.trigger == 'bldate' if dd.trigger == 'bldate'
] ]
est_line = est_lines[0] if est_lines else None est_line = est_lines[0] if est_lines else None
if est_line and est_line.estimated_date: if est_line and est_line.fin_int_delta:
est_date = est_line.estimated_date + datetime.timedelta( factor = (
days=est_line.fin_int_delta or 0 Decimal(self.price) / Decimal(100)
* Decimal(est_line.fin_int_delta) / Decimal(360)
) )
logger.info("EST_DATE:%s", est_date) return round(
if est_date and beg_date: factor * self.sale_line.unit_price
factor = InterestCalculator.calculate( * self._get_amount_quantity() * sign, 2)
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)
elif self.mode == 'perqt': elif self.mode == 'perqt':
if self.shipment_in: if self.shipment_in:

View File

@@ -243,12 +243,12 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(values[0]['amount'], Decimal('0')) self.assertEqual(values[0]['amount'], Decimal('0'))
def test_purchase_rate_fee_amount_uses_virtual_lot_quantity(self): 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 = Pool().get('fee.fee')
fee = Fee() fee = Fee()
fee.mode = 'rate' fee.mode = 'rate'
fee.price = Decimal('12') fee.price = Decimal('12')
fee.fee_date = datetime.date(2026, 4, 23) fee.fee_date = None
fee.quantity = None fee.quantity = None
fee.unit = Mock() fee.unit = Mock()
fee.shipment_in = None fee.shipment_in = None
@@ -267,19 +267,15 @@ class PurchaseTradeTestCase(ModuleTestCase):
) )
fee.sale_line = None fee.sale_line = None
with patch('trytond.modules.purchase_trade.fee.Pool') as PoolMock: self.assertEqual(fee.get_amount(), Decimal('3.33'))
PoolMock.return_value.get.return_value = Mock(
today=Mock(return_value=datetime.date(2026, 4, 23)))
self.assertEqual(fee.get_amount(), Decimal('13.33')) def test_sale_rate_fee_amount_uses_absolute_financing_delta(self):
'sale rate fee amount uses the financing delta as absolute period'
def test_sale_rate_fee_amount_uses_bl_date_estimate(self):
'sale rate fee amount uses BL estimated date plus financing delta'
Fee = Pool().get('fee.fee') Fee = Pool().get('fee.fee')
fee = Fee() fee = Fee()
fee.mode = 'rate' fee.mode = 'rate'
fee.price = Decimal('12') fee.price = Decimal('12')
fee.fee_date = datetime.date(2026, 4, 23) fee.fee_date = None
fee.quantity = Decimal('10') fee.quantity = Decimal('10')
fee.unit = Mock() fee.unit = Mock()
fee.shipment_in = None fee.shipment_in = None
@@ -296,11 +292,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
], ],
) )
with patch('trytond.modules.purchase_trade.fee.Pool') as PoolMock: self.assertEqual(fee.get_amount(), Decimal('3.33'))
PoolMock.return_value.get.return_value = Mock(
today=Mock(return_value=datetime.date(2026, 4, 23)))
self.assertEqual(fee.get_amount(), Decimal('13.33'))
def test_create_pnl_price_from_line_keeps_finished_physical_sale_line(self): def test_create_pnl_price_from_line_keeps_finished_physical_sale_line(self):
'purchase valuation keeps finished sale-side pnl on physical lots' 'purchase valuation keeps finished sale-side pnl on physical lots'

View File

@@ -118,12 +118,12 @@ elle existe, par exemple:
- Le calcul du montant d'un fee en mode `rate` est aligne entre purchase et - Le calcul du montant d'un fee en mode `rate` est aligne entre purchase et
sale. sale.
- Il ne depend pas du `payment_term`. - Il ne depend pas du `payment_term`.
- La date de fin vient de l'onglet `Estimated date` de la ligne metier: - Il ne depend pas de la date du jour ni de `fee_date`.
chercher `trigger = bldate`, prendre `estimated_date`, puis ajouter - La periode de financement est directement `fin_int_delta` sur la ligne
`fin_int_delta`. `Estimated date` avec `trigger = bldate`.
- La formule reste en interet simple ACT/360: - La formule reste en interet simple ACT/360:
`amount = rate_factor * unit_price * quantity`, avec `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 - Si aucune Estimated Date `bldate` n'est renseignee, le fee `% rate` ne
calcule pas de montant. calcule pas de montant.