From fe40560c31ce69eaa80279dfba313846522986cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Snauwaert?= Date: Tue, 14 Feb 2017 10:12:15 +0100 Subject: [PATCH] [FIX] account: fix bank statement reconciliation writeoff creation Use case: Company is in USD, create a bank journal in euro and set euro currency to 0.9 Create invoice for 80 EUR and pay it. Then create a bank statement of 85 EUR in newly created journal and use reconciliation widget to reconcile statement and payment. Put the remaining 5 EUR as writeoff in bank fees. The move created for those 5 EUR wrongly computed the amount_currency of the counterpart, this commit fix this problem. OPW: 706708 PR #15306 --- .../account/models/account_bank_statement.py | 45 ++++++------ addons/account/tests/test_reconciliation.py | 72 +++++++++++++++++++ 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index dbc0575735b..871f4f9ebd7 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -749,7 +749,7 @@ class AccountBankStatementLine(models.Model): } def _prepare_reconciliation_move_line(self, move, amount): - """ Prepare the dict of values to create the move line from a statement line. + """ Prepare the dict of values to balance the move. :param recordset move: the account.move to link the move line :param float amount: the amount of transaction that wasn't already reconciled @@ -757,25 +757,29 @@ class AccountBankStatementLine(models.Model): company_currency = self.journal_id.company_id.currency_id statement_currency = self.journal_id.currency_id or company_currency st_line_currency = self.currency_id or statement_currency - amount_currency = False - if statement_currency != company_currency or st_line_currency != company_currency: - # First get the ratio total mount / amount not already reconciled - if statement_currency == company_currency: - total_amount = self.amount - elif st_line_currency == company_currency: - total_amount = self.amount_currency - else: - total_amount = statement_currency.with_context({'date': self.date}).compute(self.amount, company_currency, round=False) - if float_compare(total_amount, amount, precision_digits=company_currency.rounding) == 0: - ratio = 1.0 - else: - ratio = total_amount / amount - # Then use it to adjust the statement.line field that correspond to the move.line amount_currency - if statement_currency != company_currency: - amount_currency = self.amount * ratio - elif st_line_currency != company_currency: - amount_currency = self.amount_currency * ratio + st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False + # We have several use case here to compure the currency and amount currency of counterpart line to balance the move: + if st_line_currency != company_currency and st_line_currency == statement_currency: + # company in currency A, statement in currency B and transaction in currency B + # counterpart line must have currency B and correct amount is inverse of already existing lines + amount_currency = -sum([x.amount_currency for x in move.line_ids]) + elif st_line_currency != company_currency and statement_currency == company_currency: + # company in currency A, statement in currency A and transaction in currency B + # counterpart line must have currency B and correct amount is inverse of already existing lines + amount_currency = -sum([x.amount_currency for x in move.line_ids]) + elif st_line_currency != company_currency and st_line_currency != statement_currency: + # company in currency A, statement in currency B and transaction in currency C + # counterpart line must have currency B and use rate between B and C to compute correct amount + amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate + elif st_line_currency == company_currency and statement_currency != company_currency: + # company in currency A, statement in currency B and transaction in currency A + # counterpart line must have currency B and amount is computed using the rate between A and B + amount_currency = amount/st_line_currency_rate + + # last case is company in currency A, statement in currency A and transaction in currency A + # and in this case counterpart line does not need any second currency nor amount_currency + return { 'name': self.name, 'date': self.date, @@ -953,8 +957,7 @@ class AccountBankStatementLine(models.Model): (new_aml | counterpart_move_line).reconcile() - # Create the move line for the statement line using the bank statement line as the remaining amount - # This leaves out the amount already reconciled and avoids rounding errors from currency conversion + # Balance the move st_line_amount = -sum([x.balance for x in move.line_ids]) aml_obj.with_context(check_move_validity=False).create(self._prepare_reconciliation_move_line(move, st_line_amount)) diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 9e98495e8a5..d1dc1ac191c 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -406,3 +406,75 @@ class TestReconciliation(AccountingTestCase): self.assertTrue(aml.reconciled, 'The journal item should be totally reconciled') self.assertEquals(aml.amount_residual, 0, 'The journal item should be totally reconciled') self.assertEquals(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled') + + def test_reconcile_bank_statement_with_payment_and_writeoff(self): + # Use case: + # Company is in EUR, create a bill for 80 USD and register payment of 80 USD. + # create a bank statement in USD bank journal with a bank statement line of 85 USD + # Reconcile bank statement with payment and put the remaining 5 USD in bank fees or another account. + + invoice = self.create_invoice(type='out_invoice', invoice_amount=80, currency_id=self.currency_usd_id) + # register payment on invoice + payment = self.env['account.payment'].create({'payment_type': 'inbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait_id, + 'amount': 80, + 'currency_id': self.currency_usd_id, + 'payment_date': time.strftime('%Y') + '-07-15', + 'journal_id': self.bank_journal_usd.id, + }) + payment.post() + payment_move_line = False + bank_move_line = False + for l in payment.move_line_ids: + if l.account_id.id == self.account_rcv.id: + payment_move_line = l + else: + bank_move_line = l + invoice.register_payment(payment_move_line) + + # create bank statement + bank_stmt = self.acc_bank_stmt_model.create({ + 'journal_id': self.bank_journal_usd.id, + 'date': time.strftime('%Y') + '-07-15', + }) + + bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment', + 'statement_id': bank_stmt.id, + 'partner_id': self.partner_agrolait_id, + 'amount': 85, + 'date': time.strftime('%Y') + '-07-15',}) + + #reconcile the statement with invoice and put remaining in another account + bank_stmt_line.process_reconciliation(payment_aml_rec= bank_move_line, new_aml_dicts=[{ + 'account_id': self.diff_income_account.id, + 'debit': 0, + 'credit': 5, + 'name': 'bank fees', + }]) + + # Check that move lines associated to bank_statement are correct + bank_stmt_aml = self.env['account.move.line'].search([('statement_id', '=', bank_stmt.id)]) + bank_stmt_aml |= bank_stmt_aml.mapped('move_id').mapped('line_ids') + self.assertEquals(len(bank_stmt_aml), 4, "The bank statement should have 4 moves lines") + lines = { + self.account_usd.id: [ + {'debit': 3.27, 'credit': 0.0, 'amount_currency': 5, 'currency_id': self.currency_usd_id}, + {'debit': 52.33, 'credit': 0, 'amount_currency': 80, 'currency_id': self.currency_usd_id} + ], + self.diff_income_account.id: {'debit': 0.0, 'credit': 3.27, 'amount_currency': -5, 'currency_id': self.currency_usd_id}, + self.account_rcv.id: {'debit': 0.0, 'credit': 52.33, 'amount_currency': -80, 'currency_id': self.currency_usd_id}, + } + for aml in bank_stmt_aml: + line = lines[aml.account_id.id] + if type(line) == list: + # find correct line inside the list + if line[0]['debit'] == round(aml.debit, 2): + line = line[0] + else: + line = line[1] + self.assertEquals(round(aml.debit, 2), line['debit']) + self.assertEquals(round(aml.credit, 2), line['credit']) + self.assertEquals(round(aml.amount_currency, 2), line['amount_currency']) + self.assertEquals(aml.currency_id.id, line['currency_id']) \ No newline at end of file