From 16feec36da6d23aaa9ea5bfe1f3faca1b1fe217a Mon Sep 17 00:00:00 2001 From: jbw Date: Wed, 22 Dec 2021 10:14:04 +0000 Subject: [PATCH] [FIX] account: fix change period reconciliation for automatic entry wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When creating automatic entries for multiple moves on different dates with the automatic entry wizard, the wizard would reconcile all the lines on the accrual account of the destination move with the lines on the accrual account of each accrual move. As this would imply reconciling the same lines multiple times, it triggers a reconciliation error. What must be done is, for each accrual move, reconcile it’s lines with only the related lines on the destination move. closes odoo/odoo#83732 Task: 2717578 X-original-commit: 502003ac548d6620374dfbab4fb7be6dee3c1bf5 Signed-off-by: Laurent Smet Signed-off-by: Florian Gilbert Signed-off-by: de Wouters de Bouchout Jean-Benoît (jbw) --- .../tests/test_account_move_out_invoice.py | 134 ++++++++++++++++++ .../wizard/account_automatic_entry_wizard.py | 15 +- 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py index bf3e320ca17..7dd8316c30b 100644 --- a/addons/account/tests/test_account_move_out_invoice.py +++ b/addons/account/tests/test_account_move_out_invoice.py @@ -2813,6 +2813,140 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon): {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, ]) + def test_out_invoice_multi_date_change_period_accrual(self): + dates = ['2017-01-01', '2017-01-01', '2017-02-01'] + values = [] + for date in dates: + values.append({ + 'move_type': 'out_invoice', + 'date': date, + 'partner_id': self.partner_a.id, + 'invoice_date': fields.Date.from_string(date), + 'currency_id': self.currency_data['currency'].id, + 'invoice_payment_term_id': self.pay_terms_a.id, + 'invoice_line_ids': [ + (0, None, { + 'name': self.product_line_vals_1['name'], + 'product_id': self.product_line_vals_1['product_id'], + 'product_uom_id': self.product_line_vals_1['product_uom_id'], + 'quantity': self.product_line_vals_1['quantity'], + 'price_unit': self.product_line_vals_1['price_unit'], + 'tax_ids': self.product_line_vals_1['tax_ids'], + }), + (0, None, { + 'name': self.product_line_vals_2['name'], + 'product_id': self.product_line_vals_2['product_id'], + 'product_uom_id': self.product_line_vals_2['product_uom_id'], + 'quantity': self.product_line_vals_2['quantity'], + 'price_unit': self.product_line_vals_2['price_unit'], + 'tax_ids': self.product_line_vals_2['tax_ids'], + }), + ] + }) + + moves = self.env['account.move'].create(values) + moves.action_post() + + wizard = self.env['account.automatic.entry.wizard'].with_context( + active_model='account.move.line', + active_ids=moves.invoice_line_ids.ids, + ).create({ + 'action': 'change_period', + 'date': '2018-01-01', + 'percentage': 60, + 'journal_id': self.company_data['default_journal_misc'].id, + 'expense_accrual_account': self.env['account.account'].create({ + 'name': 'Accrual Expense Account', + 'code': '234567', + 'user_type_id': self.env.ref('account.data_account_type_expenses').id, + 'reconcile': True, + }).id, + 'revenue_accrual_account': self.env['account.account'].create({ + 'name': 'Accrual Revenue Account', + 'code': '765432', + 'user_type_id': self.env.ref('account.data_account_type_expenses').id, + 'reconcile': True, + }).id, + }) + wizard_res = wizard.do_action() + + for date, move, ref in zip(dates, moves, ['INV/2017/00001', 'INV/2017/00002', 'INV/2017/00003']): + self.assertInvoiceValues(move, [ + { + **self.product_line_vals_1, + 'currency_id': self.currency_data['currency'].id, + 'amount_currency': -1000.0, + 'debit': 0.0, + 'credit': 500.0, + }, + { + **self.product_line_vals_2, + 'currency_id': self.currency_data['currency'].id, + 'amount_currency': -200.0, + 'debit': 0.0, + 'credit': 100.0, + }, + { + **self.tax_line_vals_1, + 'currency_id': self.currency_data['currency'].id, + 'amount_currency': -180.0, + 'debit': 0.0, + 'credit': 90.0, + }, + { + **self.tax_line_vals_2, + 'currency_id': self.currency_data['currency'].id, + 'amount_currency': -30.0, + 'debit': 0.0, + 'credit': 15.0, + }, + { + **self.term_line_vals_1, + 'currency_id': self.currency_data['currency'].id, + 'name': ref, + 'amount_currency': 1410.0, + 'debit': 705.0, + 'credit': 0.0, + 'date_maturity': fields.Date.from_string(date), + }, + ], { + **self.move_vals, + 'currency_id': self.currency_data['currency'].id, + 'date': fields.Date.from_string(date), + 'payment_reference': ref, + }) + + moves = self.env['account.move'].browse(wizard_res['domain'][0][2]) + + accrual_lines = moves.line_ids.sorted('date') + self.assertRecordValues(accrual_lines, [ + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, + {'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + {'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, + {'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True}, + ]) + def test_out_invoice_filter_zero_balance_lines(self): zero_balance_payment_term = self.env['account.payment.term'].create({ 'name': 'zero_balance_payment_term', diff --git a/addons/account/wizard/account_automatic_entry_wizard.py b/addons/account/wizard/account_automatic_entry_wizard.py index e4b4682bdfd..6632c64f1b5 100644 --- a/addons/account/wizard/account_automatic_entry_wizard.py +++ b/addons/account/wizard/account_automatic_entry_wizard.py @@ -321,22 +321,31 @@ class AutomaticEntryWizard(models.TransientModel): created_moves._post() destination_move = created_moves[0] + destination_move_offset = 0 destination_messages = [] + accrual_move_messages = defaultdict(lambda: []) + accrual_move_offsets = defaultdict(int) for move in self.move_line_ids.move_id: amount = sum((self.move_line_ids._origin & move.line_ids).mapped('balance')) accrual_move = created_moves[1:].filtered(lambda m: m.date == move.date) if accrual_account.reconcile: - to_reconcile = (accrual_move + destination_move).mapped('line_ids').filtered(lambda line: line.account_id == accrual_account) - to_reconcile.reconcile() + destination_move_lines = destination_move.mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)[destination_move_offset:destination_move_offset+2] + destination_move_offset += 2 + accrual_move_lines = accrual_move.mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)[accrual_move_offsets[accrual_move]:accrual_move_offsets[accrual_move]+2] + accrual_move_offsets[accrual_move] += 2 + (accrual_move_lines + destination_move_lines).reconcile() move.message_post(body=self._format_strings(_('Adjusting Entries have been created for this invoice:
  • %(link1)s cancelling ' '{percent:f}%% of {amount}
  • %(link0)s postponing it to {new_date}
', link0=self._format_move_link(destination_move), link1=self._format_move_link(accrual_move), ), move, amount)) destination_messages += [self._format_strings(_('Adjusting Entry {link}: {percent:f}% of {amount} recognized from {date}'), move, amount)] - accrual_move.message_post(body=self._format_strings(_('Adjusting Entry for {link}: {percent:f}% of {amount} recognized on {new_date}'), move, amount)) + accrual_move_messages[accrual_move] += [self._format_strings(_('Adjusting Entry for {link}: {percent:f}% of {amount} recognized on {new_date}'), move, amount)] + destination_move.message_post(body='
\n'.join(destination_messages)) + for accrual_move, messages in accrual_move_messages.items(): + accrual_move.message_post(body='
\n'.join(messages)) # open the generated entries action = {