From 30dd0e0e273b7d8d840aaff5e4bae0ce0d1ac7a1 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 19 Mar 2024 20:10:13 +0100 Subject: [PATCH] [IMP] calculate opening debit, credit from balance of opening lines only consider posted move lines for opening balance --- .../models/xaf_auditfile_export.py | 34 +++--- .../test_l10n_nl_xaf_auditfile_export.py | 103 +++++++++++++++--- 2 files changed, 106 insertions(+), 31 deletions(-) diff --git a/l10n_nl_xaf_auditfile_export/models/xaf_auditfile_export.py b/l10n_nl_xaf_auditfile_export/models/xaf_auditfile_export.py index dd1e2c0a4..8b11c66d0 100644 --- a/l10n_nl_xaf_auditfile_export/models/xaf_auditfile_export.py +++ b/l10n_nl_xaf_auditfile_export/models/xaf_auditfile_export.py @@ -254,33 +254,31 @@ def get_taxes(self): @api.multi def get_ob_totals(self): """return totals of opening balance""" - self.env.cr.execute( - "select sum(l.credit), sum(l.debit), count(distinct a.id) " - "from account_move_line l, account_account a, " - " account_account_type t " - "where a.user_type_id = t.id " - "and l.account_id = a.id " - "and l.date < %s " - "and l.company_id=%s " - "and t.include_initial_balance = true ", - (self.date_start, self.company_id.id), - ) - row = self.env.cr.fetchall()[0] - return dict( - credit=round(row[0] or 0.0, 2), - debit=round(row[1] or 0.0, 2), - count=row[2] or 0, + result = dict( + credit=0.0, + debit=0.0, + count=0 ) + for line in self.get_ob_lines(): + balance = line['balance'] + if balance > 0: + result['debit'] += balance + else: + result['credit'] -= balance + result['count'] += 1 + return result @api.multi def get_ob_lines(self): """return opening balance entries""" self.env.cr.execute( - "select a.id, a.code, sum(l.balance) " + # pylint: disable=sql-injection + "select a.id, a.code, sum(l.balance) " + "from account_move_line l, account_account a, " - " account_account_type t " + " account_move m, account_account_type t " "where a.user_type_id = t.id " "and a.id = l.account_id and l.date < %s " + "and l.move_id = m.id and m.state = 'posted' " "and l.company_id=%s " "and t.include_initial_balance = true " "group by a.id, a.code", diff --git a/l10n_nl_xaf_auditfile_export/tests/test_l10n_nl_xaf_auditfile_export.py b/l10n_nl_xaf_auditfile_export/tests/test_l10n_nl_xaf_auditfile_export.py index 26a0f800b..9031b2f80 100644 --- a/l10n_nl_xaf_auditfile_export/tests/test_l10n_nl_xaf_auditfile_export.py +++ b/l10n_nl_xaf_auditfile_export/tests/test_l10n_nl_xaf_auditfile_export.py @@ -2,18 +2,18 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import base64 +from datetime import timedelta from lxml import etree from io import BytesIO import os from zipfile import ZipFile + from odoo.tests.common import TransactionCase from odoo.tools import mute_logger -def get_transaction_line_count_from_xml(auditfile): - ''' Helper XML method to parse and return the transaction line count ''' - line_count = 0 +def xaf_xpath(auditfile, query): with ZipFile(BytesIO(base64.b64decode(auditfile)), 'r') as z: contents = z.read(z.filelist[-1]).decode() parser = etree.XMLParser( @@ -23,16 +23,29 @@ def get_transaction_line_count_from_xml(auditfile): remove_blank_text=True ) root = etree.XML(bytes(contents, encoding='utf8'), parser=parser) - # xpath query to select all element nodes in namespace - # Source: https://stackoverflow.com/a/30233635 - query = "descendant-or-self::*[namespace-uri()!='']" - for element in root.xpath(query): - element.tag = etree.QName(element).localname - journals = root.xpath("/auditfile/company/transactions/journal") - for journal in journals: - transactions = journal.xpath("transaction/trLine") - for _ in transactions: - line_count += 1 + for element in root.xpath( + query, namespaces={'a': 'http://www.auditfiles.nl/XAF/3.2'} + ): + yield element + + +def get_transaction_line_count_from_xml(auditfile): + ''' Helper XML method to parse and return the transaction line count ''' + line_count = 0 + # xpath query to select all element nodes in namespace + # Source: https://stackoverflow.com/a/30233635 + query = "descendant-or-self::*[namespace-uri()!='']" + root = None + for element in xaf_xpath(auditfile, query): + element.tag = etree.QName(element).localname + root = root or element.getroottree() + if not root: + return 0 + journals = root.xpath("/auditfile/company/transactions/journal") + for journal in journals: + transactions = journal.xpath("transaction/trLine") + for _ in transactions: + line_count += 1 return line_count @@ -146,3 +159,67 @@ def test_06_include_moves_from_inactive_journals(self): line_count_after = record_after.get_move_line_count() parsed_count_after = get_transaction_line_count_from_xml(record_after.auditfile) self.assertTrue(parsed_line_count == parsed_count_after == line_count_after) + + def test_07_opening_balance(self): + """Test that we calculate the opening balance correctly""" + record = self.env['xaf.auditfile.export'].create({}) + + acc_receivable = self.env['account.account'].search([ + ( + 'user_type_id', '=', + self.env.ref('account.data_account_type_receivable').id + ), + ], limit=1) + acc_payable = self.env['account.account'].search([ + ('user_type_id', '=', self.env.ref('account.data_account_type_payable').id), + ], limit=1) + acc_revenue = self.env['account.account'].search([ + ('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id), + ], limit=1) + journal = self.env['account.journal'].search([], limit=1) + + move_receivable = self.env['account.move'].create({ + 'journal_id': journal.id, + 'date': record.date_start - timedelta(days=1), + 'line_ids': [ + (0, 0, {'account_id': acc_receivable.id, 'credit': 42, 'debit': 0}), + (0, 0, {'account_id': acc_revenue.id, 'credit': 0, 'debit': 42}), + ] + }) + move_payable = self.env['account.move'].create({ + 'journal_id': journal.id, + 'date': record.date_start - timedelta(days=1), + 'line_ids': [ + (0, 0, {'account_id': acc_payable.id, 'credit': 0, 'debit': 4242}), + (0, 0, {'account_id': acc_revenue.id, 'credit': 4242, 'debit': 0}), + ] + }) + + move_receivable.post() + record.button_generate() + + def xaf_val(auditfile, xpath): + return float(''.join(xaf_xpath(auditfile, xpath))) + + total_credit = xaf_val( + record.auditfile, '//a:openingBalance/a:totalCredit/text()') + self.assertEqual(total_credit, 42) + total_debit = xaf_val( + record.auditfile, '//a:openingBalance/a:totalDebit/text()') + self.assertEqual(total_debit, 0) + lines_count = xaf_val( + record.auditfile, '//a:openingBalance/a:linesCount/text()') + self.assertEqual(lines_count, 1) + + move_payable.post() + record = self.env['xaf.auditfile.export'].create({}) + record.button_generate() + total_credit = xaf_val( + record.auditfile, '//a:openingBalance/a:totalCredit/text()') + self.assertEqual(total_credit, 42) + total_debit = xaf_val( + record.auditfile, '//a:openingBalance/a:totalDebit/text()') + self.assertEqual(total_debit, 4242) + lines_count = xaf_val( + record.auditfile, '//a:openingBalance/a:linesCount/text()') + self.assertEqual(lines_count, 2)