From 7754416cd8a6484dd7d449f302909d7ef70ffe0f Mon Sep 17 00:00:00 2001 From: Olivier Colson Date: Tue, 6 Nov 2018 09:40:21 +0000 Subject: [PATCH] [FIX] account: fix "post at bank reconciliation" option of bank journals Before that, with "post at bank reconciliation option" activated on bank journal, the following scenarii did not work as expected: 1. When creating an invoice, making two payments for it, and then matching only one of them with a bank statement => the invoice was marked as 'paid' while it should have staid 'in payment' until the second payment gets a bank statement. 2. When directly reconciling a bank statement with an invoice for its full amount => the invoice staid in "in payment" state, while it should have been "paid" [IMP] account: add tests for the aforementioned cases closes odoo/odoo#28428 --- .../account/models/account_bank_statement.py | 9 ++- .../test_bank_statement_reconciliation.py | 14 +++++ addons/account/tests/test_payment.py | 57 ++++++++++++++----- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 15ed4ecb5a6..46db7c5c8e7 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -550,7 +550,8 @@ class AccountBankStatementLine(models.Model): aml_rec.payment_id.payment_date = self.date aml_rec.move_id.post() # We check the paid status of the invoices reconciled with this payment - aml_rec.payment_id.reconciled_invoice_ids.filtered(lambda x: x.state == 'in_payment').write({'state': 'paid'}) + for invoice in aml_rec.payment_id.reconciled_invoice_ids: + self._check_invoice_state(invoice) # Create move line(s). Either matching an existing journal entry (eg. invoice), in which # case we reconcile the existing and the new move lines together, or being a write-off. @@ -639,6 +640,8 @@ class AccountBankStatementLine(models.Model): (new_aml | counterpart_move_line).reconcile() + self._check_invoice_state(counterpart_move_line.invoice_id) + # Balance the move st_line_amount = -sum([x.balance for x in move.line_ids]) aml_dict = self._prepare_reconciliation_move_line(move, st_line_amount) @@ -658,3 +661,7 @@ class AccountBankStatementLine(models.Model): counterpart_moves.assert_balanced() return counterpart_moves + + def _check_invoice_state(self, invoice): + if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]): + invoice.write({'state': 'paid'}) diff --git a/addons/account/tests/test_bank_statement_reconciliation.py b/addons/account/tests/test_bank_statement_reconciliation.py index 49d5d74d71d..0311f278a4b 100644 --- a/addons/account/tests/test_bank_statement_reconciliation.py +++ b/addons/account/tests/test_bank_statement_reconciliation.py @@ -26,6 +26,19 @@ class TestBankStatementReconciliation(AccountingTestCase): self.assertEqual(prop[0]['id'], rcv_mv_line.id) def test_full_reconcile(self): + self._reconcile_invoice_with_statement(False) + + def test_post_at_bank_rec_full_reconcile(self): + """ Test the full reconciliation of a bank statement directly with an invoice. + """ + self._reconcile_invoice_with_statement(True) + + def _reconcile_invoice_with_statement(self, post_at_bank_rec): + """ Tests the reconciliation of an invoice with a bank statement, using + the provided 'post at bank reconciliation' value for the bank journal + where to generate the statement. + """ + self.bs_model.with_context(journal_type='bank')._default_journal().post_at_bank_reconciliation = post_at_bank_rec rcv_mv_line = self.create_invoice(100) st_line = self.create_statement_line(100) # reconcile @@ -47,6 +60,7 @@ class TestBankStatementReconciliation(AccountingTestCase): self.assertTrue(rcv_mv_line.reconciled) self.assertTrue(counterpart_mv_line.reconciled) self.assertEqual(counterpart_mv_line.matched_credit_ids, rcv_mv_line.matched_debit_ids) + self.assertEqual(rcv_mv_line.invoice_id.state, 'paid', "The related invoice's state should now be 'paid'") def test_reconcile_with_write_off(self): pass diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index 8a4cb43e7e7..f7ffd6fc49f 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -507,14 +507,14 @@ class TestPayment(AccountingTestCase): self.assertTrue(invoice.move_id.line_ids.filtered(lambda l: l.account_id == self.account_receivable)[0].full_reconcile_id) def test_post_at_bank_reconciliation_payment(self): - # Create a new payment in a journal requiring the journal entries to be posted at bank reconciliation + # Create two new payments in a journal requiring the journal entries to be posted at bank reconciliation post_at_bank_rec_journal = bank_journal_euro = self.env['account.journal'].create({ 'name': 'Bank', 'type': 'bank', 'code': 'COUCOU', 'post_at_bank_rec': True, }) - payment = self.payment_model.create({'payment_type': 'inbound', + payment_one = self.payment_model.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, @@ -524,17 +524,48 @@ class TestPayment(AccountingTestCase): 'writeoff_account_id': self.account_receivable.id, 'journal_id': post_at_bank_rec_journal.id, }) - payment.post() + payment_one.post() + payment_two = self.payment_model.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': 11, + 'payment_date': time.strftime('%Y') + '-12-15', + 'payment_difference_handling': 'reconcile', + 'writeoff_account_id': self.account_receivable.id, + 'journal_id': post_at_bank_rec_journal.id, + }) + payment_two.post() - # Check the payment and move state - self.assertEqual(payment.state, 'posted', "Payment shoud be in posted state.") - self.assertEqual(payment.mapped('move_line_ids.move_id.state'), ['draft'], "A posted payment in a bank journal with the 'post at bank reconciliation' option activated should correspond to a draft account.move") + # Check the payments and their move state + self.assertEqual(payment_one.state, 'posted', "Payment one shoud be in posted state.") + self.assertEqual(payment_one.mapped('move_line_ids.move_id.state'), ['draft'], "A posted payment (payment_one) in a bank journal with the 'post at bank reconciliation' option activated should correspond to a draft account.move") + self.assertEqual(payment_two.state, 'posted', "Payment two shoud be in posted state.") + self.assertEqual(payment_two.mapped('move_line_ids.move_id.state'), ['draft'], "A posted payment (payment_two) in a bank journal with the 'post at bank reconciliation' option activated should correspond to a draft account.move") - # Match the payment with a bank statement line - bank_statement = self.reconcile(payment.move_line_ids.filtered(lambda x: x.account_id == post_at_bank_rec_journal.default_debit_account_id), 42) - stmt_line_date = bank_statement.mapped('line_ids.date') + # Reconcile the two payments with an invoice, whose full amount is equal to their sum + invoice = self.create_invoice(amount=53, partner=self.partner_agrolait.id) + (payment_one.move_line_ids + payment_two.move_line_ids + invoice.move_id.line_ids).filtered(lambda x: x.account_id.user_type_id.type == 'receivable').reconcile() - # Check the move has been posted properly - self.assertEqual(payment.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, the payment's account.move should be posted.") - self.assertEqual(payment.mapped('move_line_ids.move_id.date'), stmt_line_date, "After bank reconciliation, the payment's account.move should share the same date as the bank statement.") - self.assertEqual([payment.payment_date], stmt_line_date, "After bank reconciliation, the payment should share the same date as the bank statement.") \ No newline at end of file + self.assertTrue(invoice.reconciled, "Invoice should have been reconciled with the payments") + self.assertEqual(invoice.state, 'in_payment', "Invoice should be in 'in payment' state") + + # Match the first payment with a bank statement line + bank_statement_one = self.reconcile(payment_one.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42) + stmt_line_date_one = bank_statement_one.mapped('line_ids.date') + + self.assertEqual(payment_one.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, payment one's account.move should be posted.") + self.assertEqual(payment_one.mapped('move_line_ids.move_id.date'), stmt_line_date_one, "After bank reconciliation, payment one's account.move should share the same date as the bank statement.") + self.assertEqual([payment_one.payment_date], stmt_line_date_one, "After bank reconciliation, payment one should share the same date as the bank statement.") + self.assertEqual(invoice.state, 'in_payment', "The invoice should still be 'in payment', not all its payments are reconciled with a statement") + + # Match the second payment with a bank statement line + bank_statement_two = self.reconcile(payment_two.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42) + stmt_line_date_two = bank_statement_two.mapped('line_ids.date') + + self.assertEqual(payment_two.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, payment two's account.move should be posted.") + self.assertEqual(payment_two.mapped('move_line_ids.move_id.date'), stmt_line_date_two, "After bank reconciliation, payment two's account.move should share the same date as the bank statement.") + self.assertEqual([payment_two.payment_date], stmt_line_date_two, "After bank reconciliation, payment two should share the same date as the bank statement.") + + # The invoice should now be paid + self.assertEqual(invoice.state, 'paid', "Invoice should be in 'paid' state after having reconciled the two payments with a bank statement")