bug PL qt double * fee rate
This commit is contained in:
@@ -1468,14 +1468,21 @@ class Line(metaclass=PoolMeta):
|
|||||||
lines = sum(args[::2], [])
|
lines = sum(args[::2], [])
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.id in old_values:
|
if line.id in old_values:
|
||||||
old = Decimal(old_values[line.id] or 0)
|
|
||||||
new = Decimal(line.quantity_theorical or 0)
|
new = Decimal(line.quantity_theorical or 0)
|
||||||
delta = new - old
|
virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual']
|
||||||
|
if not virtual_lots:
|
||||||
|
continue
|
||||||
|
vlot = virtual_lots[0]
|
||||||
|
# Initializing quantity_theorical after virtual-lot creation
|
||||||
|
# must not add the line quantity a second time.
|
||||||
|
old = old_values[line.id]
|
||||||
|
baseline = (
|
||||||
|
Decimal(old)
|
||||||
|
if old is not None
|
||||||
|
else Decimal(vlot.get_current_quantity_converted() or 0)
|
||||||
|
)
|
||||||
|
delta = new - baseline
|
||||||
if delta > 0:
|
if delta > 0:
|
||||||
virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual']
|
|
||||||
if not virtual_lots:
|
|
||||||
continue
|
|
||||||
vlot = virtual_lots[0]
|
|
||||||
new_qty = round(Decimal(vlot.get_current_quantity_converted() or 0) + delta, 5)
|
new_qty = round(Decimal(vlot.get_current_quantity_converted() or 0) + delta, 5)
|
||||||
vlot.set_current_quantity(new_qty, new_qty, 1)
|
vlot.set_current_quantity(new_qty, new_qty, 1)
|
||||||
Lot.save([vlot])
|
Lot.save([vlot])
|
||||||
@@ -1498,10 +1505,6 @@ class Line(metaclass=PoolMeta):
|
|||||||
lqt.lot_unit = line.unit
|
lqt.lot_unit = line.unit
|
||||||
LotQt.save([lqt])
|
LotQt.save([lqt])
|
||||||
elif delta < 0:
|
elif delta < 0:
|
||||||
virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual']
|
|
||||||
if not virtual_lots:
|
|
||||||
continue
|
|
||||||
vlot = virtual_lots[0]
|
|
||||||
decrease = abs(delta)
|
decrease = abs(delta)
|
||||||
lqts = LotQt.search([
|
lqts = LotQt.search([
|
||||||
('lot_p', '=', vlot.id),
|
('lot_p', '=', vlot.id),
|
||||||
|
|||||||
@@ -181,6 +181,49 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
create_derivatives.assert_not_called()
|
create_derivatives.assert_not_called()
|
||||||
PoolMock.return_value.get.assert_not_called()
|
PoolMock.return_value.get.assert_not_called()
|
||||||
|
|
||||||
|
def test_create_pnl_fee_from_line_accepts_missing_rate_amount(self):
|
||||||
|
'purchase fee valuation treats an uncomputed rate amount as zero'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
currency = Mock(id=1)
|
||||||
|
unit = Mock(id=2)
|
||||||
|
product = Mock(id=3, name='Financing fees')
|
||||||
|
supplier = Mock(id=4)
|
||||||
|
lot = Mock(id=5, sale_line=None, lot_type='virtual')
|
||||||
|
lot.get_current_quantity_converted.return_value = Decimal('10')
|
||||||
|
fee = Mock(
|
||||||
|
product=product,
|
||||||
|
supplier=supplier,
|
||||||
|
type='budgeted',
|
||||||
|
p_r='pay',
|
||||||
|
mode='rate',
|
||||||
|
price=Decimal('10'),
|
||||||
|
currency=currency,
|
||||||
|
shipment_in=None,
|
||||||
|
sale_line=None,
|
||||||
|
unit=unit,
|
||||||
|
)
|
||||||
|
fee.get_amount.return_value = None
|
||||||
|
line = Mock(
|
||||||
|
id=6,
|
||||||
|
lots=[lot],
|
||||||
|
get_matched_lines=Mock(return_value=[]),
|
||||||
|
purchase=Mock(id=7, currency=currency),
|
||||||
|
unit=unit,
|
||||||
|
)
|
||||||
|
fee_lots = Mock()
|
||||||
|
fee_lots.search.return_value = [Mock(fee=fee)]
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.side_effect = lambda name: {
|
||||||
|
'ir.date': Mock(today=Mock(return_value=datetime.date(2026, 4, 23))),
|
||||||
|
'currency.currency': Mock(),
|
||||||
|
'fee.lots': fee_lots,
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
values = Valuation.create_pnl_fee_from_line(line)
|
||||||
|
|
||||||
|
self.assertEqual(values[0]['amount'], Decimal('0'))
|
||||||
|
|
||||||
def test_create_pnl_price_from_line_ignores_finished_matched_sale_line(self):
|
def test_create_pnl_price_from_line_ignores_finished_matched_sale_line(self):
|
||||||
'purchase valuation does not add sale-side pnl when the matched sale line is finished'
|
'purchase valuation does not add sale-side pnl when the matched sale line is finished'
|
||||||
Valuation = Pool().get('valuation.valuation')
|
Valuation = Pool().get('valuation.valuation')
|
||||||
@@ -693,6 +736,42 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
with self.assertRaises(UserError):
|
with self.assertRaises(UserError):
|
||||||
SaleLine.write([line], {'quantity_theorical': Decimal('8')})
|
SaleLine.write([line], {'quantity_theorical': Decimal('8')})
|
||||||
|
|
||||||
|
def test_purchase_line_write_initial_theorical_qty_does_not_double_open_lot(self):
|
||||||
|
'purchase line write does not re-add quantity when initializing contractual qty'
|
||||||
|
PurchaseLine = Pool().get('purchase.line')
|
||||||
|
line = Mock(id=3, quantity_theorical=None)
|
||||||
|
vlot = Mock(id=101, lot_type='virtual')
|
||||||
|
vlot.get_current_quantity_converted.return_value = Decimal('10')
|
||||||
|
line.lots = [vlot]
|
||||||
|
|
||||||
|
lot_model = Mock()
|
||||||
|
lotqt_model = Mock()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'trytond.modules.purchase_trade.purchase.Pool'
|
||||||
|
) as PoolMock, patch(
|
||||||
|
'trytond.modules.purchase_trade.purchase.super'
|
||||||
|
) as super_mock:
|
||||||
|
PoolMock.return_value.get.side_effect = lambda name: {
|
||||||
|
'lot.lot': lot_model,
|
||||||
|
'lot.qt': lotqt_model,
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
def fake_super_write(*args):
|
||||||
|
for records, values in zip(args[::2], args[1::2]):
|
||||||
|
if 'quantity_theorical' in values:
|
||||||
|
for record in records:
|
||||||
|
record.quantity_theorical = values['quantity_theorical']
|
||||||
|
|
||||||
|
super_mock.return_value.write.side_effect = fake_super_write
|
||||||
|
|
||||||
|
PurchaseLine.write(
|
||||||
|
[line], {'quantity_theorical': Decimal('10')})
|
||||||
|
|
||||||
|
vlot.set_current_quantity.assert_not_called()
|
||||||
|
lot_model.save.assert_not_called()
|
||||||
|
lotqt_model.save.assert_not_called()
|
||||||
|
|
||||||
def test_party_execution_achieved_percent_uses_real_area_statistics(self):
|
def test_party_execution_achieved_percent_uses_real_area_statistics(self):
|
||||||
'party execution achieved percent reflects the controller share in its area'
|
'party execution achieved percent reflects the controller share in its area'
|
||||||
PartyExecution = Pool().get('party.execution')
|
PartyExecution = Pool().get('party.execution')
|
||||||
|
|||||||
@@ -639,6 +639,11 @@ class ValuationBase(ModelSQL):
|
|||||||
result.extend(budgeted_fees)
|
result.extend(budgeted_fees)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fee_amount_or_zero(cls, fee):
|
||||||
|
amount = fee.get_amount() if hasattr(fee, 'get_amount') else fee.amount
|
||||||
|
return Decimal(amount or 0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_pnl_fee_from_line(cls, line):
|
def create_pnl_fee_from_line(cls, line):
|
||||||
fee_lines = []
|
fee_lines = []
|
||||||
@@ -664,7 +669,7 @@ class ValuationBase(ModelSQL):
|
|||||||
qty = round(lot.get_current_quantity_converted(), 5)
|
qty = round(lot.get_current_quantity_converted(), 5)
|
||||||
if sf.mode == 'ppack' or sf.mode == 'rate':
|
if sf.mode == 'ppack' or sf.mode == 'rate':
|
||||||
price = sf.price
|
price = sf.price
|
||||||
amount = sf.amount * sign
|
amount = cls._fee_amount_or_zero(sf) * sign
|
||||||
elif sf.mode == 'lumpsum':
|
elif sf.mode == 'lumpsum':
|
||||||
price = sf.price
|
price = sf.price
|
||||||
amount = sf.price * sign
|
amount = sf.price * sign
|
||||||
@@ -723,7 +728,7 @@ class ValuationBase(ModelSQL):
|
|||||||
qty = round(lot.get_current_quantity_converted(), 5)
|
qty = round(lot.get_current_quantity_converted(), 5)
|
||||||
if sf.mode == 'ppack' or sf.mode == 'rate':
|
if sf.mode == 'ppack' or sf.mode == 'rate':
|
||||||
price = sf.price
|
price = sf.price
|
||||||
amount = sf.amount * sign
|
amount = cls._fee_amount_or_zero(sf) * sign
|
||||||
elif sf.mode == 'lumpsum':
|
elif sf.mode == 'lumpsum':
|
||||||
price = sf.price
|
price = sf.price
|
||||||
amount = sf.price * sign
|
amount = sf.price * sign
|
||||||
|
|||||||
Reference in New Issue
Block a user