diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 16bb189f3cf..4e85966a85c 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -89,12 +89,12 @@ class AccountMoveLine(models.Model): ) debit = fields.Monetary( string='Debit', - compute='_compute_debit_credit', inverse='_inverse_debit_credit', store=True, + compute='_compute_debit_credit', inverse='_inverse_debit', store=True, precompute=True, currency_field='company_currency_id', ) credit = fields.Monetary( string='Credit', - compute='_compute_debit_credit', inverse='_inverse_debit_credit', store=True, + compute='_compute_debit_credit', inverse='_inverse_credit', store=True, precompute=True, currency_field='company_currency_id', ) balance = fields.Monetary( @@ -545,7 +545,7 @@ class AccountMoveLine(models.Model): for model, id, account_type, account_id in self.env.cr.fetchall() } for line in term_lines: - account_type = 'asset_receivable' if line.move_id.is_sale_document() else 'liability_payable' + account_type = 'asset_receivable' if line.move_id.is_sale_document(include_receipts=True) else 'liability_payable' move = line.move_id account_id = ( accounts.get(('account.move', move.id, None)) @@ -576,19 +576,25 @@ class AccountMoveLine(models.Model): )[0] for line in self: if not line.account_id and line.display_type not in ('line_section', 'line_note'): - line.account_id = line.company_id.account_journal_suspense_account_id + previous_two_accounts = line.move_id.line_ids.filtered( + lambda l: l.account_id and l.display_type == line.display_type + )[-2:].account_id + if len(previous_two_accounts) == 1: + line.account_id = previous_two_accounts + else: + line.account_id = line.company_id.account_journal_suspense_account_id @api.depends('move_id') def _compute_balance(self): for line in self: - if line.display_type not in ('line_section', 'line_note'): - line.balance = ( - line.balance - or line.debit - line.credit - or -sum((line.move_id.line_ids - line).mapped('balance')) - ) - else: + if line.display_type in ('line_section', 'line_note'): line.balance = False + elif not line.move_id.is_invoice(include_receipts=True): + # Only act as a default value when none of balance/debit/credit is specified + # balance is always the written field because of `_sanitize_vals` + line.balance = -sum((line.move_id.line_ids - line).mapped('balance')) + else: + line.balance = 0 @api.depends('balance', 'move_id.is_storno') def _compute_debit_credit(self): @@ -1101,9 +1107,18 @@ class AccountMoveLine(models.Model): if line.currency_id == line.company_id.currency_id and line.balance != line.amount_currency: line.balance = line.amount_currency - @api.onchange('debit', 'credit') - def _inverse_debit_credit(self): + @api.onchange('debit') + def _inverse_debit(self): for line in self: + if line.debit: + line.credit = 0 + line.balance = line.debit - line.credit + + @api.onchange('credit') + def _inverse_credit(self): + for line in self: + if line.credit: + line.debit = 0 line.balance = line.debit - line.credit @api.onchange('analytic_distribution') @@ -1171,6 +1186,17 @@ class AccountMoveLine(models.Model): if line.reconciled: raise UserError(_('Lines from "Off-Balance Sheet" accounts cannot be reconciled')) + @api.constrains('account_id', 'display_type') + def _check_payable_receivable(self): + for line in self: + account_type = line.account_id.account_type + if line.move_id.is_sale_document(include_receipts=True): + if (line.display_type == 'payment_term') ^ (account_type == 'asset_receivable'): + raise UserError(_("Any journal item on a receivable account must have a due date and vice versa.")) + if line.move_id.is_purchase_document(include_receipts=True): + if (line.display_type == 'payment_term') ^ (account_type == 'liability_payable'): + raise UserError(_("Any journal item on a payable account must have a due date and vice versa.")) + def _affect_tax_report(self): self.ensure_one() return self.tax_ids or self.tax_line_id or self.tax_tag_ids.filtered(lambda x: x.applicability == "taxes") @@ -1277,6 +1303,7 @@ class AccountMoveLine(models.Model): def _sanitize_vals(self, vals): if 'debit' in vals or 'credit' in vals: + vals = vals.copy() if 'balance' in vals: vals.pop('debit', None) vals.pop('credit', None) @@ -1329,6 +1356,11 @@ class AccountMoveLine(models.Model): and (not changed('balance') or (line not in before and not line.balance)) ): line.balance = balance + # Since this method is called during the sync, inside of `create`/`write`, these fields + # already have been computed and marked as so. But this method should re-trigger it since + # it changes the dependencies. + self.env.add_to_compute(self._fields['debit'], container['records']) + self.env.add_to_compute(self._fields['credit'], container['records']) @api.model_create_multi def create(self, vals_list): @@ -1349,7 +1381,7 @@ class AccountMoveLine(models.Model): return lines def new(self, values=None, origin=None, ref=None): - record = super().new(values, origin, ref) + record = super().new(self._sanitize_vals(values), origin, ref) if record.move_id.quick_edit_total_amount and record.move_id.quick_edit_mode: record.move_id._check_total_amount(record.move_id.quick_edit_total_amount) return record @@ -1365,7 +1397,7 @@ class AccountMoveLine(models.Model): raise UserError(_('You cannot use a deprecated account.')) line_to_write = self - self._sanitize_vals(vals) + vals = self._sanitize_vals(vals) for line in self: if not any(self.env['account.move']._field_will_change(line, vals, field_name) for field_name in vals): line_to_write -= line @@ -1389,18 +1421,6 @@ class AccountMoveLine(models.Model): if any(self.env['account.move']._field_will_change(line, vals, 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.account_type - if line.move_id.is_sale_document(include_receipts=True): - if (account_type == 'asset_receivable' and account_to_write.account_type != account_type) \ - or (account_type != 'asset_receivable' and account_to_write.account_type == 'asset_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 == 'liability_payable' and account_to_write.account_type != account_type) \ - or (account_type != 'liability_payable' and account_to_write.account_type == 'liability_payable'): - raise UserError(_("You can only set an account having the payable type on payment terms lines for vendor bill.")) - move_container = {'records': self.move_id, 'self': self, 'line_to_write': line_to_write} with self.move_id._check_balanced(move_container),\ self.move_id._sync_dynamic_lines(move_container),\ diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index c24bc6b0fa3..8fc60da83db 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -592,12 +592,13 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): self.category = self.env.ref('product.product_category_1').copy({'name': 'Test category','property_valuation': 'real_time', 'property_cost_method': 'fifo'}) self.account_receiv = self.env['account.account'].create({'name': 'Receivable', 'code': 'RCV00', 'account_type': 'asset_receivable', 'reconcile': True}) account_expense = self.env['account.account'].create({'name': 'Expense', 'code': 'EXP00', 'account_type': 'liability_current', 'reconcile': True}) + account_income = self.env['account.account'].create({'name': 'Income', 'code': 'INC00', 'account_type': 'asset_current', 'reconcile': True}) account_output = self.env['account.account'].create({'name': 'Output', 'code': 'OUT00', 'account_type': 'liability_current', 'reconcile': True}) account_valuation = self.env['account.account'].create({'name': 'Valuation', 'code': 'STV00', 'account_type': 'asset_receivable', 'reconcile': True}) self.partner.property_account_receivable_id = self.account_receiv - self.category.property_account_income_categ_id = self.account_receiv + self.category.property_account_income_categ_id = account_income self.category.property_account_expense_categ_id = account_expense - self.category.property_stock_account_input_categ_id = self.account_receiv + self.category.property_stock_account_input_categ_id = account_income self.category.property_stock_account_output_categ_id = account_output self.category.property_stock_valuation_account_id = account_valuation self.category.property_stock_journal = self.env['account.journal'].create({'name': 'Stock journal', 'type': 'sale', 'code': 'STK00'}) @@ -1800,11 +1801,12 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): self.category = self.env.ref('product.product_category_1').copy({'name': 'Test category', 'property_valuation': 'real_time', 'property_cost_method': 'fifo'}) account_receiv = self.env['account.account'].create({'name': 'Receivable', 'code': 'RCV00', 'account_type': 'asset_receivable', 'reconcile': True}) account_expense = self.env['account.account'].create({'name': 'Expense', 'code': 'EXP00', 'account_type': 'liability_current', 'reconcile': True}) + account_income = self.env['account.account'].create({'name': 'Income', 'code': 'INC00', 'account_type': 'asset_current', 'reconcile': True}) account_output = self.env['account.account'].create({'name': 'Output', 'code': 'OUT00', 'account_type': 'liability_current', 'reconcile': True}) account_valuation = self.env['account.account'].create({'name': 'Valuation', 'code': 'STV00', 'account_type': 'asset_receivable', 'reconcile': True}) self.stock_location = self.company_data['default_warehouse'].lot_stock_id self.partner.property_account_receivable_id = account_receiv - self.category.property_account_income_categ_id = account_receiv + self.category.property_account_income_categ_id = account_income self.category.property_account_expense_categ_id = account_expense self.category.property_stock_account_input_categ_id = account_receiv self.category.property_stock_account_output_categ_id = account_output