[IMP] account: improve perf of sequencing account.move

The peformances were bad when posting a lot of moves at the same time.
This is because a SQL query was executed for each document to retrieve
the last sequence used to build the new one.

By batching the documents to sequence, we can reduce drasticaly the
number of SQL queries done.

X-original-commit: ff26e093c98a5b933620642e242530a8931c1471
This commit is contained in:
wan
2020-09-01 09:11:01 +00:00
parent 10e3a2e82e
commit aaf0b65448
2 changed files with 68 additions and 16 deletions
+64 -13
View File
@@ -6,6 +6,7 @@ from odoo.tools import float_compare, date_utils, email_split, email_re
from odoo.tools.misc import formatLang, format_date, get_lang
from datetime import date, timedelta
from collections import defaultdict
from itertools import zip_longest
from hashlib import sha256
from json import dumps
@@ -1004,20 +1005,70 @@ class AccountMove(models.Model):
domain = [('company_id', '=', m.company_id.id), ('type', '=?', m.invoice_filter_type_domain)]
m.suitable_journal_ids = self.env['account.journal'].search(domain)
@api.depends('posted_before', 'state', 'highest_name')
@api.depends('posted_before', 'state', 'journal_id', 'date')
def _compute_name(self):
for record in self.sorted(lambda m: (m.date, m.ref or '', m.id)):
if record.name and not record.posted_before:
# Never been posted, but had a name set
record.name = '/'
if not record.name or record.name == '/':
if not record.posted_before and not record.highest_name:
# First name of the period for the journal, no name yet
record._set_next_sequence()
elif record.state == 'posted':
# No name yet but has been posted
record._set_next_sequence()
record.name = record.name or '/'
def journal_key(move):
return (move.journal_id, move.journal_id.refund_sequence and move.move_type)
def date_key(move):
return (move.date.year, move.date.month)
grouped = defaultdict( # key: journal_id, move_type
lambda: defaultdict( # key: first adjacent (date.year, date.month)
lambda: {
'records': self.env['account.move'],
'format': False,
'format_values': False,
'reset': False
}
)
)
highest_name = self[0]._get_last_sequence() if self else False
# Group the moves by journal and month
for move in self.sorted(lambda m: (m.date, m.ref or '', m.id)):
if not highest_name and move == self[0] and not move.posted_before:
# 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)
pass
elif (move.name and move.name != '/') or move.state != 'posted':
# Has already a name or is not posted, we don't add to a batch
continue
if not grouped[journal_key(move)][date_key(move)]['records']:
# Compute all the values needed to sequence this whole group
move._set_next_sequence()
format, format_values = move._get_sequence_format_param(move.name)
reset = move._deduce_sequence_number_reset(move.name)
grouped[journal_key(move)][date_key(move)]['format'] = format
grouped[journal_key(move)][date_key(move)]['format_values'] = format_values
grouped[journal_key(move)][date_key(move)]['reset'] = reset
grouped[journal_key(move)][date_key(move)]['records'] += move
# Fusion the groups depending on the sequence reset and the format used because `seq` is
# the same counter for multiple groups that might be spread in multiple months.
final_batches = []
for journal_group in grouped.values():
for date_group in journal_group.values():
if not final_batches or final_batches[-1]['format'] != date_group['format']:
final_batches += [date_group]
elif date_group['reset'] == 'never':
final_batches[-1]['records'] += date_group['records']
elif (
date_group['reset'] == 'year'
and final_batches[-1]['records'][0].date.year == date_group['records'][0].date.year
):
final_batches[-1]['records'] += date_group['records']
else:
final_batches += [date_group]
# Give the name based on previously computed values
for batch in final_batches:
for move in batch['records']:
move.name = batch['format'].format(**batch['format_values'])
batch['format_values']['seq'] += 1
batch['records']._compute_split_sequence()
self.filtered(lambda m: not m.name).name = '/'
@api.depends('journal_id', 'date')
def _compute_highest_name(self):
@@ -434,9 +434,10 @@ class TestAccountMove(AccountTestInvoicingCommon):
init_move.name = sequence_init
next_moves.name = False
next_moves._compute_name()
self.assertEqual(next_move.name, sequence_next)
self.assertEqual(next_move_month.name, sequence_next_month)
self.assertEqual(next_move_year.name, sequence_next_year)
self.assertEqual(
[next_move.name, next_move_month.name, next_move_year.name],
[sequence_next, sequence_next_month, sequence_next_year],
)
def test_journal_next_sequence(self):
prefix = "TEST_ORDER/2016/"