[FIX] account: prevent moves from stealing lines

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 <qdp@odoo.com>
Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
william-andre
2023-12-20 15:21:35 +00:00
parent 70abc5d083
commit f0a346a477
2 changed files with 44 additions and 3 deletions
+14 -3
View File
@@ -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(
@@ -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)]})