From 3e5ca320c8e107e099610c47e54ccedb4e544900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com> Date: Fri, 14 Oct 2022 15:07:41 +0900 Subject: [PATCH] accounting: round in grouping when no section currency is set Grouping feature checks that the sum of all selected lines == 0, which is often not the case as the values are float. For that, our approach is to round the values with the precision of the accounting currency, since these precisions are usually small (typically 0, 2 or 3), we don't have problems with rounding. Using the section currency is not just a workaround for rounding, it's also correct because we don't consider more precise amounts in accounting transaction lines. The problem with this approach was for the case where no accounting currency is set on the section organisation, in that case we did not round and this sometimes led to "grouping is impossible" errors that are hard to find for users. At this level it's better to use a default rounding precision that would make it possible to use the grouping feature even when section currency is not set. --- ...AccountingTransaction_guessGroupedLines.py | 8 ++- .../test.erp5.testAccounting.py | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_guessGroupedLines.py b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_guessGroupedLines.py index 79ab581e87..6b937253d6 100644 --- a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_guessGroupedLines.py +++ b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_guessGroupedLines.py @@ -75,12 +75,14 @@ changed_line_list = [] for (node, section, mirror_section, _), line_info_list in lines_per_node.items(): if node is None: continue - total_price = sum([l['total_price'] for l in line_info_list]) - # get the currency rounding for this section + # get the currency rounding for this section, with a fallback that something that would + # allow grouping in case precision is not defined. + currency_precision = 5 if section: default_currency = portal.restrictedTraverse(section).getPriceCurrencyValue() if default_currency is not None: - total_price = round(total_price, default_currency.getQuantityPrecision()) + currency_precision = default_currency.getQuantityPrecision() + total_price = round(sum([l['total_price'] for l in line_info_list]), currency_precision) if total_price == 0 or allow_grouping_with_different_quantity: # we should include mirror node in the id_group, but this would reset # id generators and generate grouping references that were already used. diff --git a/bt5/erp5_accounting/TestTemplateItem/portal_components/test.erp5.testAccounting.py b/bt5/erp5_accounting/TestTemplateItem/portal_components/test.erp5.testAccounting.py index b4d118c439..b47593c96f 100644 --- a/bt5/erp5_accounting/TestTemplateItem/portal_components/test.erp5.testAccounting.py +++ b/bt5/erp5_accounting/TestTemplateItem/portal_components/test.erp5.testAccounting.py @@ -3786,6 +3786,61 @@ class TestTransactions(AccountingTestCase): self.tic() self.assertFalse(payment.line_with_grouping_reference.getGroupingReference()) + def test_grouping_reference_rounding(self): + """Reproduction of a bug that grouping was not possible because of rounding error + + >>> 0.1 + 0.2 - 0.3 == 0 + False + """ + invoice = self._makeOne( + title='Invoice', + destination_section_value=self.organisation_module.client_1, + lines=(dict(source_value=self.account_module.goods_purchase, + source_debit=0.3), + dict(source_value=self.account_module.receivable, + source_credit=0.1, + id='line_1'), + dict(source_value=self.account_module.receivable, + source_credit=0.2, + id='line_2'))) + payment = self._makeOne( + title='Invoice Payment', + portal_type='Payment Transaction', + source_payment_value=self.section.newContent( + portal_type='Bank Account'), + destination_section_value=self.organisation_module.client_1, + lines=(dict(source_value=self.account_module.receivable, + id='line_3', + source_debit=0.3), + dict(source_value=self.account_module.bank, + source_credit=0.3,))) + self.tic() + grouped = invoice.AccountingTransaction_guessGroupedLines( + accounting_transaction_line_uid_list=( + invoice.line_1.getUid(), + invoice.line_2.getUid(), + payment.line_3.getUid(), + )) + self.assertEqual( + sorted(grouped), + sorted([ + invoice.line_1.getRelativeUrl(), + invoice.line_2.getRelativeUrl(), + payment.line_3.getRelativeUrl(), + ])) + self.tic() + + def test_grouping_reference_rounding_without_accounting_currency_on_section(self): + accounting_currency = self.section.getPriceCurrency() + self.assertTrue(accounting_currency) + self.section.setPriceCurrency(None) + try: + self.test_grouping_reference_rounding() + finally: + self.abort() + self.section.setPriceCurrency(accounting_currency) + self.tic() + def test_automatically_setting_grouping_reference(self): invoice = self._makeOne( title='First Invoice', -- 2.30.9