Pnl exclude Mark to finished & price on change
This commit is contained in:
@@ -1278,6 +1278,14 @@ class SaleLine(metaclass=PoolMeta):
|
||||
+ Decimal(lot_premium or 0)),
|
||||
4)
|
||||
|
||||
@fields.depends('product', 'quantity', 'unit_price',
|
||||
methods=['_get_context_sale_price'])
|
||||
def compute_unit_price(self):
|
||||
unit_price = super().compute_unit_price()
|
||||
if unit_price is None:
|
||||
unit_price = self.unit_price
|
||||
return unit_price
|
||||
|
||||
@fields.depends('id','unit','quantity','unit_price','price_pricing','price_type','price_components','estimated_date','lots','fees','enable_linked_currency','linked_price','linked_currency','linked_unit')
|
||||
def on_change_with_unit_price(self, name=None):
|
||||
Date = Pool().get('ir.date')
|
||||
|
||||
@@ -145,6 +145,80 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
{'type': 'derivative', 'amount': Decimal('30')},
|
||||
])
|
||||
|
||||
def test_generate_skips_finished_purchase_line(self):
|
||||
'valuation generation keeps deleting old rows but skips finished purchase lines'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
line = Mock(finished=True)
|
||||
|
||||
with patch.object(Valuation, '_delete_existing') as delete_existing, patch.object(
|
||||
Valuation, 'create_pnl_fee_from_line') as create_fees, patch.object(
|
||||
Valuation, 'create_pnl_price_from_line') as create_prices, patch.object(
|
||||
Valuation, 'create_pnl_der_from_line') as create_derivatives, patch(
|
||||
'trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||
Valuation.generate(line)
|
||||
|
||||
delete_existing.assert_called_once_with(line, selected_types=None)
|
||||
create_fees.assert_not_called()
|
||||
create_prices.assert_not_called()
|
||||
create_derivatives.assert_not_called()
|
||||
PoolMock.return_value.get.assert_not_called()
|
||||
|
||||
def test_generate_skips_finished_sale_line(self):
|
||||
'sale valuation generation keeps deleting old rows but skips finished sale lines'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
sale_line = Mock(finished=True)
|
||||
|
||||
with patch.object(Valuation, '_delete_existing_sale_line') as delete_existing, patch.object(
|
||||
Valuation, 'create_pnl_fee_from_sale_line') as create_fees, patch.object(
|
||||
Valuation, 'create_pnl_price_from_sale_line') as create_prices, patch.object(
|
||||
Valuation, 'create_pnl_der_from_sale_line') as create_derivatives, patch(
|
||||
'trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||
Valuation.generate_from_sale_line(sale_line)
|
||||
|
||||
delete_existing.assert_called_once_with(sale_line, selected_types=None)
|
||||
create_fees.assert_not_called()
|
||||
create_prices.assert_not_called()
|
||||
create_derivatives.assert_not_called()
|
||||
PoolMock.return_value.get.assert_not_called()
|
||||
|
||||
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'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
finished_sale_line = Mock(
|
||||
finished=True,
|
||||
price_type='priced',
|
||||
mtm=[],
|
||||
)
|
||||
sale_lot = Mock(sale_line=finished_sale_line)
|
||||
purchase_lot = Mock(
|
||||
sale_line=None,
|
||||
lot_price=Decimal('10'),
|
||||
lot_type='physic',
|
||||
)
|
||||
purchase_lot.get_current_quantity_converted.return_value = Decimal('2')
|
||||
line = Mock(
|
||||
finished=False,
|
||||
lots=[purchase_lot],
|
||||
price_type='priced',
|
||||
mtm=[],
|
||||
purchase=Mock(id=1, currency=Mock(id=1), company=Mock(currency=Mock(id=1)), party=Mock(id=1)),
|
||||
unit=Mock(id=1),
|
||||
product=Mock(id=1),
|
||||
)
|
||||
|
||||
lot_qt_model = Mock()
|
||||
lot_qt_model.search.return_value = [Mock(lot_s=sale_lot)]
|
||||
|
||||
with patch(
|
||||
'trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||
PoolMock.return_value.get.return_value = lot_qt_model
|
||||
|
||||
values = Valuation.create_pnl_price_from_line(line)
|
||||
|
||||
self.assertEqual(len(values), 1)
|
||||
self.assertEqual(values[0]['type'], 'pur. priced')
|
||||
self.assertNotIn('sale_line', values[0])
|
||||
|
||||
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')
|
||||
@@ -171,6 +245,19 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(
|
||||
SaleLine.default_pricing_rule(), 'Default pricing rule')
|
||||
|
||||
def test_sale_line_compute_unit_price_keeps_existing_value_when_base_returns_none(self):
|
||||
'sale line quantity changes keep the manual unit price when no sale price is found'
|
||||
SaleLine = Pool().get('sale.line')
|
||||
line = SaleLine()
|
||||
line.unit_price = Decimal('123.45')
|
||||
|
||||
with patch('trytond.modules.purchase_trade.sale.super') as super_mock:
|
||||
super_mock.return_value.compute_unit_price.return_value = None
|
||||
|
||||
self.assertEqual(
|
||||
line.compute_unit_price(),
|
||||
Decimal('123.45'))
|
||||
|
||||
def test_purchase_line_default_pricing_rule_comes_from_configuration(self):
|
||||
'purchase line pricing_rule defaults to the purchase_trade singleton value'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
|
||||
@@ -76,6 +76,10 @@ class ValuationBase(ModelSQL):
|
||||
}
|
||||
return type_map.get(valuation_type, None)
|
||||
|
||||
@classmethod
|
||||
def _is_finished(cls, record):
|
||||
return bool(getattr(record, 'finished', False))
|
||||
|
||||
@classmethod
|
||||
def _filter_values_by_types(cls, values, selected_types):
|
||||
if selected_types is None:
|
||||
@@ -406,6 +410,8 @@ class ValuationBase(ModelSQL):
|
||||
sl_line = sl.sale_line
|
||||
if not sl_line:
|
||||
continue
|
||||
if cls._is_finished(sl_line):
|
||||
continue
|
||||
|
||||
if sl_line.price_type == 'basis':
|
||||
premium_delta = cls._get_basis_premium_delta(sl_line)
|
||||
@@ -641,7 +647,11 @@ class ValuationBase(ModelSQL):
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
#if line is matched with sale_line we should add the open sale side
|
||||
sale_lines = line.get_matched_lines() or []
|
||||
sale_open_lots = tuple(s.lot_s for s in sale_lines if s.lot_s)
|
||||
sale_open_lots = tuple(
|
||||
s.lot_s for s in sale_lines
|
||||
if s.lot_s and s.lot_s.sale_line
|
||||
and not cls._is_finished(s.lot_s.sale_line)
|
||||
)
|
||||
all_lots = (line.lots or ()) + sale_open_lots
|
||||
for lot in all_lots:
|
||||
fl = FeeLots.search([('lot', '=', lot.id)])
|
||||
@@ -821,6 +831,8 @@ class ValuationBase(ModelSQL):
|
||||
def generate(cls, line, valuation_type='all'):
|
||||
selected_types = cls._get_generate_types(valuation_type)
|
||||
cls._delete_existing(line, selected_types=selected_types)
|
||||
if cls._is_finished(line):
|
||||
return
|
||||
values = []
|
||||
values.extend(cls.create_pnl_fee_from_line(line))
|
||||
values.extend(cls.create_pnl_price_from_line(line))
|
||||
@@ -837,6 +849,8 @@ class ValuationBase(ModelSQL):
|
||||
def generate_from_sale_line(cls, sale_line, valuation_type='all'):
|
||||
selected_types = cls._get_generate_types(valuation_type)
|
||||
cls._delete_existing_sale_line(sale_line, selected_types=selected_types)
|
||||
if cls._is_finished(sale_line):
|
||||
return
|
||||
values = []
|
||||
values.extend(cls.create_pnl_fee_from_sale_line(sale_line))
|
||||
values.extend(cls.create_pnl_price_from_sale_line(sale_line))
|
||||
|
||||
Reference in New Issue
Block a user