From b3c33ed31452a9a10cde839b4989da077314a2f6 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sat, 6 Jun 2026 11:03:53 +0200 Subject: [PATCH] Valuation cron --- modules/purchase_trade/cron.py | 9 ++++-- modules/purchase_trade/cron.xml | 20 ++++++++----- modules/purchase_trade/tests/test_module.py | 32 +++++++++++++++++++++ modules/purchase_trade/valuation.py | 23 +++++++++++++++ 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/modules/purchase_trade/cron.py b/modules/purchase_trade/cron.py index f9b2fe8..6b4f022 100755 --- a/modules/purchase_trade/cron.py +++ b/modules/purchase_trade/cron.py @@ -12,9 +12,12 @@ class Cron(metaclass=PoolMeta): @classmethod def __setup__(cls): - super().__setup__() - cls.method.selection.append( - ('forex.cron|update_forex', "Update Forex Prices")) + super().__setup__() + cls.method.selection.append( + ('forex.cron|update_forex', "Update Forex Prices")) + cls.method.selection.append( + ('valuation.valuation|update_daily_snapshot', + "Update Valuation Snapshot")) class PriceCron(ModelSQL, ModelView): "Price Cron" diff --git a/modules/purchase_trade/cron.xml b/modules/purchase_trade/cron.xml index 48d7ffb..32aff6c 100755 --- a/modules/purchase_trade/cron.xml +++ b/modules/purchase_trade/cron.xml @@ -36,10 +36,16 @@ --> - - forex.cron|update_forex - - days - - - + + forex.cron|update_forex + + days + + + + valuation.valuation|update_daily_snapshot + + days + + + diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 8de7bdd..78225f7 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -722,6 +722,38 @@ class PurchaseTradeTestCase(ModuleTestCase): {'type': 'derivative'}, ]) + def test_update_daily_snapshot_generates_all_purchases_and_unmatched_sales(self): + 'daily valuation cron snapshots purchases and unmatched sale lines' + Valuation = Pool().get('valuation.valuation') + PurchaseLine = Mock() + SaleLine = Mock() + purchase_lines = [Mock(id=1), Mock(id=2)] + unmatched_sale_line = Mock(id=3) + unmatched_sale_line.get_matched_lines.return_value = [] + matched_sale_line = Mock(id=4) + matched_sale_line.get_matched_lines.return_value = [ + Mock(lot_p=Mock(line=Mock(id=5)))] + PurchaseLine.search.return_value = purchase_lines + SaleLine.search.return_value = [unmatched_sale_line, matched_sale_line] + + with patch( + 'trytond.modules.purchase_trade.valuation.Pool' + ) as PoolMock, patch.object( + Valuation, 'generate') as generate, patch.object( + Valuation, 'generate_from_sale_line' + ) as generate_from_sale_line: + PoolMock.return_value.get.side_effect = lambda name: { + 'purchase.line': PurchaseLine, + 'sale.line': SaleLine, + }[name] + + Valuation.update_daily_snapshot() + + generate.assert_any_call(purchase_lines[0]) + generate.assert_any_call(purchase_lines[1]) + self.assertEqual(generate.call_count, 2) + generate_from_sale_line.assert_called_once_with(unmatched_sale_line) + 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') diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py index 2e31631..23304cd 100644 --- a/modules/purchase_trade/valuation.py +++ b/modules/purchase_trade/valuation.py @@ -1225,6 +1225,29 @@ class Valuation(ValuationBase, ModelView): "Valuation" __name__ = 'valuation.valuation' + @classmethod + def update_daily_snapshot(cls): + PurchaseLine = Pool().get('purchase.line') + SaleLine = Pool().get('sale.line') + + purchase_lines = PurchaseLine.search([]) + sale_lines = [ + sale_line for sale_line in SaleLine.search([]) + if cls._sale_line_is_unmatched(sale_line) + ] + + for line in purchase_lines: + cls.generate(line) + + for sale_line in sale_lines: + cls.generate_from_sale_line(sale_line) + + logger.info( + "Updated valuation snapshot for %s purchase line(s) and " + "%s unmatched sale line(s)", + len(purchase_lines), + len(sale_lines)) + @classmethod def get_totals(cls): cursor = Transaction().connection.cursor()