From 472806ef0635c1bb053fb8f4ad990e4f1fa512c6 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 9 Apr 2026 22:38:48 +0200 Subject: [PATCH] pricing manuel --- modules/purchase_trade/pricing.py | 63 ++++++++++++++++++++- modules/purchase_trade/tests/test_module.py | 32 +++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index b426033..9fbff42 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -413,19 +413,22 @@ class Pricing(ModelSQL,ModelView): @classmethod def create(cls, vlist): records = super(Pricing, cls).create(vlist) + cls._sync_manual_last(records) cls._sync_eod_price(records) return records @classmethod def write(cls, *args): super(Pricing, cls).write(*args) - if Transaction().context.get('skip_pricing_eod_sync'): + if (Transaction().context.get('skip_pricing_eod_sync') + or Transaction().context.get('skip_pricing_last_sync')): return records = [] actions = iter(args) for record_set, values in zip(actions, actions): if values: records.extend(record_set) + cls._sync_manual_last(records) cls._sync_eod_price(records) @classmethod @@ -441,6 +444,64 @@ class Pricing(ModelSQL,ModelView): 'eod_price': eod_price, }) + @classmethod + def _get_manual_last_group_domain(cls, record): + component = getattr(record, 'price_component', None) + if getattr(record, 'sale_line', None): + domain = [ + ('sale_line', '=', record.sale_line.id), + ] + domain.append(( + 'price_component', + '=', + component.id if getattr(component, 'id', None) else None, + )) + return domain + if getattr(record, 'line', None): + domain = [ + ('line', '=', record.line.id), + ] + domain.append(( + 'price_component', + '=', + component.id if getattr(component, 'id', None) else None, + )) + return domain + return None + + @classmethod + def _sync_manual_last(cls, records): + if not records: + return + domains = [] + seen = set() + for record in records: + domain = cls._get_manual_last_group_domain(record) + if not domain: + continue + key = tuple(domain) + if key in seen: + continue + seen.add(key) + domains.append(domain) + if not domains: + return + with Transaction().set_context( + skip_pricing_last_sync=True, + skip_pricing_eod_sync=True): + for domain in domains: + pricings = cls.search( + domain, + order=[('pricing_date', 'ASC'), ('id', 'ASC')]) + if not pricings: + continue + last_pricing = pricings[-1] + for pricing in pricings[:-1]: + if pricing.last: + super(Pricing, cls).write([pricing], {'last': False}) + if not last_pricing.last: + super(Pricing, cls).write([last_pricing], {'last': True}) + def get_fixed_price(self): price = Decimal(0) Pricing = Pool().get('pricing.pricing') diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index f2dcae1..268dbda 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -290,6 +290,38 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual( purchase_pricing.get_eod_price_purchase(), Decimal('106.0000')) + def test_pricing_sync_manual_last_uses_greatest_date_per_component_group(self): + 'pricing rows keep one last by line/component, chosen by greatest pricing date' + Pricing = Pool().get('pricing.pricing') + + sale_line = Mock(id=10) + component = Mock(id=33) + first = Mock( + id=1, + price_component=component, + sale_line=sale_line, + line=None, + pricing_date=datetime.date(2026, 4, 10), + last=True, + ) + second = Mock( + id=2, + price_component=component, + sale_line=sale_line, + line=None, + pricing_date=datetime.date(2026, 4, 9), + last=False, + ) + + with patch.object(Pricing, 'search', return_value=[second, first]), patch( + 'trytond.modules.purchase_trade.pricing.super') as super_mock: + Pricing._sync_manual_last([first, second]) + + self.assertEqual(super_mock.return_value.write.call_args_list[0].args[0], [second]) + self.assertEqual(super_mock.return_value.write.call_args_list[0].args[1], {'last': False}) + self.assertEqual(super_mock.return_value.write.call_args_list[1].args[0], [first]) + self.assertEqual(super_mock.return_value.write.call_args_list[1].args[1], {'last': True}) + def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self): 'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories' Sale = Pool().get('sale.sale')