From f0a346a477e411fc07f6efda7ab4ed52c77e1f6a Mon Sep 17 00:00:00 2001 From: william-andre Date: Fri, 15 Dec 2023 17:15:11 +0100 Subject: [PATCH] [FIX] account: prevent moves from stealing lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The balance check doesn't work when a line is being moved from one move to another because we never notified the move owning the lines at the start that the lines are being modified. To reproduce using a simple CSV to import: ```csv "line_ids/id","line_ids/product_id" "__export__.account_move_line_9_25fb2fbf","[FURN_7777] Office Chair" ``` To reproduce using a simple server action: ```python self.env['account.move'].create({'line_ids': [(4, 9)]}) ``` We now notify the original move also, triggering everything that needs to be triggered: sync of dynamic lines and the check for the balance. closes odoo/odoo#146906 X-original-commit: 41beb0d4fcc70bebe51ff2c872903486be25dae7 Signed-off-by: Quentin De Paoli Signed-off-by: William André (wan) --- addons/account/models/account_move.py | 17 +++++++++-- .../account/tests/test_account_move_entry.py | 30 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 755f0d1190c..33b2ac9146d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -2401,6 +2401,13 @@ class AccountMove(models.Model): del vals['invoice_line_ids'] return vals + def _stolen_move(self, vals): + for command in vals.get('line_ids', ()): + if command[0] == Command.LINK: + yield self.env['account.move.line'].browse(command[1]).move_id.id + if command[0] == Command.SET: + yield from self.env['account.move.line'].browse(command[2]).move_id.ids + @api.model_create_multi def create(self, vals_list): if any('state' in vals and vals.get('state') == 'posted' for vals in vals_list): @@ -2408,8 +2415,11 @@ class AccountMove(models.Model): container = {'records': self} with self._check_balanced(container): with self._sync_dynamic_lines(container): - moves = super().create([self._sanitize_vals(vals) for vals in vals_list]) - container['records'] = moves + for vals in vals_list: + self._sanitize_vals(vals) + stolen_moves = self.browse(set(move for vals in vals_list for move in self._stolen_move(vals))) + moves = super().create(vals_list) + container['records'] = moves | stolen_moves for move, vals in zip(moves, vals_list): if 'tax_totals' in vals: move.tax_totals = vals['tax_totals'] @@ -2452,7 +2462,8 @@ class AccountMove(models.Model): field = self._fields[fname] if field.compute and not field.readonly: to_protect.append(field) - container = {'records': self} + stolen_moves = self.browse(set(move for move in self._stolen_move(vals))) + container = {'records': self | stolen_moves} with self.env.protecting(to_protect, self), self._check_balanced(container): with self._sync_dynamic_lines(container): res = super(AccountMove, self.with_context( diff --git a/addons/account/tests/test_account_move_entry.py b/addons/account/tests/test_account_move_entry.py index 7034c11b967..fb3b8d6e7ad 100644 --- a/addons/account/tests/test_account_move_entry.py +++ b/addons/account/tests/test_account_move_entry.py @@ -1130,3 +1130,33 @@ class TestAccountMove(AccountTestInvoicingCommon): [("account_id", "=", account.id)], ["balance:sum"], ["account_root_id"] )[0]["balance"] self.assertEqual(balance, 500) + + def test_line_steal(self): + honest_move = self.env['account.move'].create({ + 'line_ids': [ + Command.create({ + 'name': 'receivable', + 'account_id': self.company_data['default_account_receivable'].id, + 'balance': 500.0, + }), + Command.create({ + 'name': 'tax', + 'account_id': self.company_data['default_account_tax_sale'].id, + 'balance': -500.0, + }), + ] + }) + honest_move.action_post() + + with self.assertRaisesRegex(UserError, 'not balanced'), self.env.cr.savepoint(): + self.env['account.move'].create({'line_ids': [Command.set(honest_move.line_ids[0].ids)]}) + + with self.assertRaisesRegex(UserError, 'not balanced'), self.env.cr.savepoint(): + self.env['account.move'].create({'line_ids': [Command.link(honest_move.line_ids[0].id)]}) + + stealer_move = self.env['account.move'].create({}) + with self.assertRaisesRegex(UserError, 'not balanced'), self.env.cr.savepoint(): + stealer_move.write({'line_ids': [Command.set(honest_move.line_ids[0].ids)]}) + + with self.assertRaisesRegex(UserError, 'not balanced'), self.env.cr.savepoint(): + stealer_move.write({'line_ids': [Command.link(honest_move.line_ids[0].id)]})