[FIX] account: propose values when creating new journal items
The new lines should contain the current balance, and the account of the
last 2 lines if it is the same.
* The computation of the balance was reading on `debit` and `credit`,
leading to a cycle in the compute method. It is useless to try to read
those fields because of `_inverse_debit_credit`
* The computation of the account was simply forgotten during the
refactoring
Also move a constraint from `write` in a more rigorous `api.constrains`,
which revealed a bug for to find the account of payment terms of receipts.
Followup of d8d47f9ff8
task-2956509
closes odoo/odoo#101994
X-original-commit: f04f83b42bb1fbcacbb6bf92a2156ff2be1bf1b1
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
@@ -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),\
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user