From c9980988cc65dc8f962a7bc0ecce6d7de1d72707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 15 Sep 2025 13:18:39 +0200 Subject: [PATCH] Store in the cache all the lines to reconcile when searching for the best reconciliation When the last line is pop from the debit/credit list, the access to the balance may invalidate the cache of other records to fill the cache of the last one. This may become very inefficient on large list of lines. So by setting the record cache size to the size of the list of lines, we ensure that no cache is invalidated during the calculation. Closes #14230 --- modules/account/move.py | 3 +- modules/account/tests/test_module.py | 159 +++++++++++++++++++-------- 2 files changed, 116 insertions(+), 46 deletions(-) diff --git a/modules/account/move.py b/modules/account/move.py index de50e2c3e7..b56b9292a0 100644 --- a/modules/account/move.py +++ b/modules/account/move.py @@ -1834,7 +1834,8 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView): else: return 0 - lines = sorted(lines, key=cls._reconciliation_sort_key) + with Transaction().set_context(_record_cache_size=max(len(lines), 1)): + lines = cls.browse(sorted(lines, key=cls._reconciliation_sort_key)) debit, credit = [], [] remaining = -amount for line in list(lines): diff --git a/modules/account/tests/test_module.py b/modules/account/tests/test_module.py index a6c194e251..64d2e9b66a 100644 --- a/modules/account/tests/test_module.py +++ b/modules/account/tests/test_module.py @@ -769,66 +769,135 @@ class AccountTestCase( def test_find_best_reconciliation(self): "Test find best reconciliation" pool = Pool() + Account = pool.get('account.account') + FiscalYear = pool.get('account.fiscalyear') + Journal = pool.get('account.journal') Line = pool.get('account.move.line') + Move = pool.get('account.move') + Party = pool.get('party.party') - currency = create_currency('USD') + party = Party(name='Party') + party.save() - line1 = Line( - debit=Decimal(10), credit=0, currency=currency, - second_currency=None, account=None, - maturity_date=datetime.date(2024, 1, 1)) - line2 = Line( - debit=0, credit=Decimal(10), currency=currency, - second_currency=None, account=None, - maturity_date=datetime.date(2024, 1, 2)) - line3 = Line( - debit=0, credit=Decimal(5), currency=currency, - second_currency=None, account=None, - maturity_date=datetime.date(2024, 1, 3)) + company = create_company() + with set_company(company): + create_chart(company) + fiscalyear = get_fiscalyear(company) + fiscalyear.save() + FiscalYear.create_period([fiscalyear]) + period = fiscalyear.periods[0] + journal_revenue, = Journal.search([ + ('code', '=', 'REV'), + ]) + receivable, = Account.search([ + ('type.receivable', '=', True), + ('closed', '=', False), + ], limit=1) + currency = company.currency - for (lines, amount, best, remaining) in [ - ([line1], 0, [line1], Decimal('10')), - ([line1], Decimal(10), [line1], Decimal(0)), - ([line1], Decimal(3), [line1], Decimal(7)), - ([line1, line2], 0, [line1, line2], Decimal(0)), - ([line1, line2], Decimal(10), [line1], Decimal(0)), - ([line1, line2], Decimal(-10), [line2], Decimal(0)), - ([line1, line2], Decimal(20), [line1], Decimal(-10)), - ([line1, line2], Decimal(-3), [line1, line2], Decimal(3)), - ([line1, line2], Decimal(-9), [line2], Decimal(-1)), - ([line1, line3], 0, [line1, line3], Decimal(5)), - ([line1, line2, line3], 0, [line1, line2], Decimal(0)), - ]: - with self.subTest(lines=lines, amount=amount): - self.assertEqual( - Line.find_best_reconciliation(lines, currency, amount), - (best, remaining)) + move = Move() + move.period = period + move.journal = journal_revenue + move.date = period.start_date + move.save() + + line1 = Line( + move=move, party=party, + debit=Decimal(10), credit=0, + account=receivable, + maturity_date=datetime.date(2024, 1, 1)) + line2 = Line( + move=move, party=party, + debit=0, credit=Decimal(10), + account=receivable, + maturity_date=datetime.date(2024, 1, 2)) + line3 = Line( + move=move, party=party, + debit=0, credit=Decimal(5), + account=receivable, + maturity_date=datetime.date(2024, 1, 3)) + Line.save([line1, line2, line3]) + + for (lines, amount, best, remaining) in [ + ([line1], 0, [line1], Decimal('10')), + ([line1], Decimal(10), [line1], Decimal(0)), + ([line1], Decimal(3), [line1], Decimal(7)), + ([line1, line2], 0, [line1, line2], Decimal(0)), + ([line1, line2], Decimal(10), [line1], Decimal(0)), + ([line1, line2], Decimal(-10), [line2], Decimal(0)), + ([line1, line2], Decimal(20), [line1], Decimal(-10)), + ([line1, line2], Decimal(-3), [line1, line2], Decimal(3)), + ([line1, line2], Decimal(-9), [line2], Decimal(-1)), + ([line1, line3], 0, [line1, line3], Decimal(5)), + ([line1, line2, line3], 0, [line1, line2], Decimal(0)), + ]: + with self.subTest(lines=lines, amount=amount): + self.assertEqual( + Line.find_best_reconciliation(lines, currency, amount), + (best, remaining)) @with_transaction() def test_find_best_reconciliation_currency(self): "Test find best reconciliation with different currencies" pool = Pool() + Account = pool.get('account.account') + FiscalYear = pool.get('account.fiscalyear') + Journal = pool.get('account.journal') Line = pool.get('account.move.line') + Move = pool.get('account.move') + Party = pool.get('party.party') currency1 = create_currency('USD') currency2 = create_currency('EUR') - line1 = Line( - debit=Decimal(10), credit=0, currency=currency1, - second_currency=None, account=None, - maturity_date=datetime.date(2024, 1, 1)) - line2 = Line( - debit=0, credit=Decimal(20), currency=currency2, - second_currency=currency1, amount_second_currency=Decimal('-10'), - account=None, maturity_date=datetime.date(2024, 1, 1)) - line3 = Line( - debit=0, credit=Decimal(5), currency=None, - second_currency=None, account=None, - maturity_date=datetime.date(2024, 1, 3)) + party = Party(name='Party') + party.save() - self.assertEqual( - Line.find_best_reconciliation([line1, line2, line3], currency1), - ([line1, line2], Decimal(0))) + company = create_company(currency=currency1) + with set_company(company): + create_chart(company) + fiscalyear = get_fiscalyear(company) + fiscalyear.save() + FiscalYear.create_period([fiscalyear]) + period = fiscalyear.periods[0] + journal_revenue, = Journal.search([ + ('code', '=', 'REV'), + ]) + receivable, = Account.search([ + ('type.receivable', '=', True), + ('closed', '=', False), + ], limit=1) + + move = Move() + move.period = period + move.journal = journal_revenue + move.date = period.start_date + move.save() + + line1 = Line( + move=move, party=party, + debit=Decimal(10), credit=0, + second_currency=currency2, + amount_second_currency=Decimal(10), + account=receivable, + maturity_date=datetime.date(2024, 1, 1)) + line2 = Line( + move=move, party=party, + debit=0, credit=Decimal(20), + second_currency=currency2, + amount_second_currency=Decimal('-10'), + account=receivable, maturity_date=datetime.date(2024, 1, 1)) + line3 = Line( + move=move, party=party, + debit=0, credit=Decimal(5), + second_currency=None, account=receivable, + maturity_date=datetime.date(2024, 1, 3)) + Line.save([line1, line2, line3]) + + self.assertEqual( + Line.find_best_reconciliation( + [line1, line2, line3], currency2), + ([line1, line2], Decimal(0))) @with_transaction() def test_tax_compute(self):