diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index a3ed200883b..5c75b72ae89 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -3133,8 +3133,12 @@ class AccountMoveLine(models.Model): PROTECTED_FIELDS_LOCK_DATE = PROTECTED_FIELDS_TAX_LOCK_DATE + ['account_id', 'journal_id', 'amount_currency', 'currency_id', 'partner_id'] PROTECTED_FIELDS_RECONCILIATION = ('account_id', 'date', 'debit', 'credit', 'amount_currency', 'currency_id') - if ('account_id' in vals) and self.env['account.account'].browse(vals['account_id']).deprecated: + account_to_write = self.env['account.account'].browse(vals['account_id']) if 'account_id' in vals else None + + # Check writing a deprecated account. + if account_to_write and account_to_write.deprecated: raise UserError(_('You cannot use a deprecated account.')) + # when making a reconciliation on an existing liquidity journal item, mark the payment as reconciled for line in self: if line.parent_state == 'posted': @@ -3160,6 +3164,18 @@ class AccountMoveLine(models.Model): if any(field_will_change(line, field_name) for field_name in PROTECTED_FIELDS_RECONCILIATION): line._check_reconciliation() + # Check switching receivable / payable accounts. + if account_to_write: + account_type = line.account_id.user_type_id.type + if line.move_id.is_sale_document(include_receipts=True): + if (account_type == 'receivable' and account_to_write.user_type_id.type != account_type) \ + or (account_type != 'receivable' and account_to_write.user_type_id.type == 'receivable'): + raise UserError(_("You can only set an account having the receivable type on payment terms lines for customer invoice.")) + if line.move_id.is_purchase_document(include_receipts=True): + if (account_type == 'payable' and account_to_write.user_type_id.type != account_type) \ + or (account_type != 'payable' and account_to_write.user_type_id.type == 'payable'): + raise UserError(_("You can only set an account having the payable type on payment terms lines for vendor bill.")) + result = super(AccountMoveLine, self).write(vals) for line in self: diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py index db0444d753a..b37695b2dc4 100644 --- a/addons/account/tests/test_account_move_out_invoice.py +++ b/addons/account/tests/test_account_move_out_invoice.py @@ -3,6 +3,7 @@ from odoo.addons.account.tests.invoice_test_common import InvoiceTestCommon from odoo.tests.common import Form from odoo.tests import tagged from odoo import fields +from odoo.exceptions import UserError from unittest.mock import patch @@ -1154,6 +1155,30 @@ class TestAccountMoveOutInvoiceOnchanges(InvoiceTestCommon): 'currency_id': self.currency_data['currency'].id, }) + def test_out_invoice_write_2(self): + ''' Ensure to not messing the invoice when writing a bad account type. ''' + move = self.env['account.move'].create({ + 'type': 'out_invoice', + 'partner_id': self.partner_a.id, + 'invoice_line_ids': [ + (0, None, self.product_line_vals_1), + ] + }) + + receivable_lines = move.line_ids.filtered(lambda line: line.account_id.user_type_id.type == 'receivable') + not_receivable_lines = move.line_ids - receivable_lines + + # Write a receivable account on a not-receivable line. + with self.assertRaises(UserError), self.cr.savepoint(): + not_receivable_lines.write({'account_id': receivable_lines[0].account_id.copy().id}) + + # Write a not-receivable account on a receivable line. + with self.assertRaises(UserError), self.cr.savepoint(): + receivable_lines.write({'account_id': not_receivable_lines[0].account_id.copy().id}) + + # Write another receivable account on a receivable line. + receivable_lines.write({'account_id': receivable_lines[0].account_id.copy().id}) + def test_out_invoice_post_1(self): ''' Check the invoice_date will be set automatically at the post date. ''' frozen_today = fields.Date.today()