[FIX] account: compute name of multiple moves at the same time

Computing the name of multiple moves at the same time could lead to some
errors because we don't know when the field will be flushed:
1: We can't rely on the context to compute moves, for instance when
   mixing payments and other moves in a bank journal. The context key
   has been removed as it could be useful only when the `payment_id` was
   not set yet, which should only happen in the Form view. But the name
   of payments was not editable before posting a first time(like it is
   for invoices)
   Was failing following test with only `account_reports` installed
   `TestReconciliationReport.test_reconciliation_report_multi_currencies`
2: There was an optimization tho avoid doing `O(n)` queries to find if
   the current move is the first one of the sequence (and needs to be
   precomputed in draft because of that). The algorithm is doing the
   same `SELECT` after anyways[1] or even a second one to relax the
   domain, followed by an `UPDATE` right after. This can't be batched
   and delayed because we rely on the SQL unique constraint to assign
   the numbers.
   Was failing folloing test with only `account_reports` installed
   `TestAgedReceivableReport.test_aged_receivable_sort_lines_by_date`

The reason why it started causing issues on some test databases (only
`account_reports` installed, not on a full runbot build) is still
unknown.

[1]: In order to reduce the number of duplicated queries, we could do it
only once per tuple (journal, date range, is_refund, is_payment), but
getting this tuple is not so easy and reliable without a complex
implementation. It is also not really possible to cache and reuse (i.e
in `highest_number`) because the value changes when other moves are
posted.

Part-of: odoo/odoo#110016
This commit is contained in:
william-andre
2023-02-17 19:30:39 +01:00
parent b08b46f6f2
commit aa041fc75f
2 changed files with 12 additions and 12 deletions
+5 -8
View File
@@ -667,13 +667,12 @@ class AccountMove(models.Model):
domain = [('company_id', '=', company_id), ('type', '=', journal_type)]
m.suitable_journal_ids = self.env['account.journal'].search(domain)
@api.depends('posted_before', 'state', 'journal_id', 'date', 'move_type')
@api.depends('posted_before', 'state', 'journal_id', 'date', 'move_type', 'payment_id')
def _compute_name(self):
self = self.sorted(lambda m: (m.date, m.ref or '', m.id))
highest_name = self[0]._get_last_sequence(lock=False) if self else False
for move in self:
if not highest_name and move == self[0] and not move.posted_before and move.date and (not move.name or move.name == '/'):
if not move.highest_name and not move.posted_before and move.date and (not move.name or move.name == '/'):
# In the form view, we need to compute a default sequence so that the user can edit
# it. We only check the first move as an approximation (enough for new in form view)
move._set_next_sequence()
@@ -2382,7 +2381,6 @@ class AccountMove(models.Model):
return "WHERE FALSE", {}
where_string = "WHERE journal_id = %(journal_id)s AND name != '/'"
param = {'journal_id': self.journal_id.id}
is_payment = self.payment_id or self._context.get('is_payment')
if not relaxed:
domain = [('journal_id', '=', self.journal_id.id), ('id', '!=', self.id or self._origin.id), ('name', 'not in', ('/', '', False))]
@@ -2390,7 +2388,7 @@ class AccountMove(models.Model):
refund_types = ('out_refund', 'in_refund')
domain += [('move_type', 'in' if self.move_type in refund_types else 'not in', refund_types)]
if self.journal_id.payment_sequence:
domain += [('payment_id', '!=' if is_payment else '=', False)]
domain += [('payment_id', '!=' if self.payment_id else '=', False)]
reference_move_name = self.search(domain + [('date', '<=', self.date)], order='date desc', limit=1).name
if not reference_move_name:
reference_move_name = self.search(domain, order='date asc', limit=1).name
@@ -2414,7 +2412,7 @@ class AccountMove(models.Model):
else:
where_string += " AND move_type NOT IN ('out_refund', 'in_refund') "
elif self.journal_id.payment_sequence:
if is_payment:
if self.payment_id:
where_string += " AND payment_id IS NOT NULL "
else:
where_string += " AND payment_id IS NULL "
@@ -2424,14 +2422,13 @@ class AccountMove(models.Model):
def _get_starting_sequence(self):
# EXTENDS account sequence.mixin
self.ensure_one()
is_payment = self.payment_id or self._context.get('is_payment')
if self.journal_id.type in ['sale', 'bank', 'cash']:
starting_sequence = "%s/%04d/00000" % (self.journal_id.code, self.date.year)
else:
starting_sequence = "%s/%04d/%02d/0000" % (self.journal_id.code, self.date.year, self.date.month)
if self.journal_id.refund_sequence and self.move_type in ('out_refund', 'in_refund'):
starting_sequence = "R" + starting_sequence
if self.journal_id.payment_sequence and is_payment:
if self.journal_id.payment_sequence and self.payment_id:
starting_sequence = "P" + starting_sequence
return starting_sequence
+7 -4
View File
@@ -670,8 +670,9 @@ class AccountPayment(models.Model):
# -------------------------------------------------------------------------
def new(self, values=None, origin=None, ref=None):
payment = super(AccountPayment, self.with_context(is_payment=True)).new(values, origin, ref)
payment = super().new(values, origin, ref)
if not payment.journal_id: # might not be computed because declared by inheritance
payment.move_id.payment_id = payment
payment.move_id._compute_journal_id()
return payment
@@ -688,9 +689,11 @@ class AccountPayment(models.Model):
# Force the move_type to avoid inconsistency with residual 'default_move_type' inside the context.
vals['move_type'] = 'entry'
payments = super(AccountPayment, self.with_context(is_payment=True))\
.create(vals_list)\
.with_context(is_payment=False)
payments = super().create([{
'name': False,
'journal_id': self.move_id.with_context(is_payment=True)._search_default_journal().id,
**vals,
} for vals in vals_list])
for i, pay in enumerate(payments):
write_off_line_vals = write_off_line_vals_list[i]