Pricing default

This commit is contained in:
2026-06-11 14:31:35 +02:00
parent 855dc3a4dd
commit 6ac19a1334
2 changed files with 187 additions and 12 deletions

View File

@@ -863,18 +863,106 @@ class Component(ModelSQL, ModelView):
def set_cur(cls, components, name, value):
cls.write(components, {'fixed_currency': value})
def get_calendar(self):
if self.calendar:
return self.calendar
if self.price_matrix:
return self.price_matrix.calendar
@staticmethod
def _record_id(record):
return getattr(record, 'id', record)
@classmethod
def _matches_optional_field(cls, matrix_line, field_name, target):
def get_calendar(self):
if self.calendar:
return self.calendar
if self.price_matrix:
return self.price_matrix.calendar
@staticmethod
def _record_id(record):
return getattr(record, 'id', record)
@classmethod
def _trigger_values(cls, trigger):
return {
'pricing_period': cls._record_id(
getattr(trigger, 'pricing_period', None)),
'from_p': getattr(trigger, 'from_p', None),
'to_p': getattr(trigger, 'to_p', None),
'average': getattr(trigger, 'average', None),
'last': getattr(trigger, 'last', None),
'application_period': cls._record_id(
getattr(trigger, 'application_period', None)),
'from_a': getattr(trigger, 'from_a', None),
'to_a': getattr(trigger, 'to_a', None),
}
@classmethod
def _copy_trigger_values(cls, component):
return [
cls._trigger_values(trigger)
for trigger in getattr(component, 'triggers', [])]
@classmethod
def _first_component_domain(cls, line=None, sale_line=None, exclude=None):
domain = []
if line:
domain.append(('line', '=', cls._record_id(line)))
elif sale_line:
domain.append(('sale_line', '=', cls._record_id(sale_line)))
else:
return None
if exclude:
domain.append(('id', '!=', cls._record_id(exclude)))
return domain
@classmethod
def _get_first_component(cls, line=None, sale_line=None, exclude=None):
domain = cls._first_component_domain(line, sale_line, exclude)
if not domain:
return None
components = cls.search(domain, order=[('id', 'ASC')], limit=1)
return components[0] if components else None
@classmethod
def _default_trigger_values(cls, line=None, sale_line=None, exclude=None):
component = cls._get_first_component(line, sale_line, exclude)
if not component:
return []
return cls._copy_trigger_values(component)
@classmethod
def default_triggers(cls):
context = Transaction().context
return cls._default_trigger_values(
line=context.get('default_line'),
sale_line=context.get('default_sale_line'))
def _inherit_first_component_triggers(self):
if getattr(self, 'triggers', None):
return
values = self._default_trigger_values(
line=getattr(self, 'line', None),
sale_line=getattr(self, 'sale_line', None),
exclude=getattr(self, 'id', None))
if values:
Trigger = Pool().get('pricing.trigger')
self.triggers = [Trigger(**value) for value in values]
@fields.depends('line', 'sale_line', 'triggers')
def on_change_line(self):
self._inherit_first_component_triggers()
@fields.depends('line', 'sale_line', 'triggers')
def on_change_sale_line(self):
self._inherit_first_component_triggers()
@classmethod
def create(cls, vlist):
vlist = [values.copy() for values in vlist]
for values in vlist:
if values.get('triggers'):
continue
trigger_values = cls._default_trigger_values(
line=values.get('line'),
sale_line=values.get('sale_line'))
if trigger_values:
values['triggers'] = [('create', trigger_values)]
return super(Component, cls).create(vlist)
@classmethod
def _matches_optional_field(cls, matrix_line, field_name, target):
value = getattr(matrix_line, field_name, None)
if not value:
return True, 0

View File

@@ -2653,6 +2653,93 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertIsNone(pricing.price_component)
def test_pricing_component_default_triggers_copy_first_purchase_component(self):
'new purchase components inherit trigger values from the first component'
Component = Pool().get('pricing.component')
pricing_period = Mock(id=21)
application_period = Mock(id=22)
first_component = Mock(triggers=[
Mock(
pricing_period=pricing_period,
from_p=datetime.date(2026, 4, 1),
to_p=datetime.date(2026, 4, 5),
average=True,
last=False,
application_period=application_period,
from_a=datetime.date(2026, 5, 1),
to_a=datetime.date(2026, 5, 5),
),
])
with patch.object(Component, 'search',
return_value=[first_component]) as search:
values = Component._default_trigger_values(line=10)
search.assert_called_once_with(
[('line', '=', 10)], order=[('id', 'ASC')], limit=1)
self.assertEqual(values, [{
'pricing_period': 21,
'from_p': datetime.date(2026, 4, 1),
'to_p': datetime.date(2026, 4, 5),
'average': True,
'last': False,
'application_period': 22,
'from_a': datetime.date(2026, 5, 1),
'to_a': datetime.date(2026, 5, 5),
}])
def test_pricing_component_create_inherits_first_sale_component_triggers(self):
'component create copies triggers from the first sale line component'
Component = Pool().get('pricing.component')
first_component = Mock(triggers=[
Mock(
pricing_period=Mock(id=21),
from_p=datetime.date(2026, 4, 1),
to_p=datetime.date(2026, 4, 5),
average=True,
last=True,
application_period=None,
from_a=None,
to_a=None,
),
])
with patch.object(Component, 'search',
return_value=[first_component]) as search, patch(
'trytond.modules.purchase_trade.pricing.super') as super_mock:
Component.create([{'sale_line': 10, 'price_source_type': 'curve'}])
search.assert_called_once_with(
[('sale_line', '=', 10)], order=[('id', 'ASC')], limit=1)
created_values = super_mock.return_value.create.call_args.args[0][0]
self.assertEqual(created_values['triggers'], [('create', [{
'pricing_period': 21,
'from_p': datetime.date(2026, 4, 1),
'to_p': datetime.date(2026, 4, 5),
'average': True,
'last': True,
'application_period': None,
'from_a': None,
'to_a': None,
}])])
def test_pricing_component_create_keeps_explicit_triggers(self):
'component create does not replace triggers already provided'
Component = Pool().get('pricing.component')
explicit_triggers = [('create', [{'average': False}])]
with patch.object(Component, 'search') as search, patch(
'trytond.modules.purchase_trade.pricing.super') as super_mock:
Component.create([{
'line': 10,
'price_source_type': 'curve',
'triggers': explicit_triggers,
}])
search.assert_not_called()
created_values = super_mock.return_value.create.call_args.args[0][0]
self.assertIs(created_values['triggers'], explicit_triggers)
def test_pricing_component_must_belong_to_pricing_owner(self):
'pricing rows reject components from another purchase or sale line'
Pricing = Pool().get('pricing.pricing')