Change the way the uniqueness of the sequence numbers is ensured. Instead of using a `SELECT FOR UPDATE` approach inspired from the `ir.sequence`, or even a true UPDATE, we can use a constraint approach. SELECT FOR UPDATE ================= By doing a FOR UPDATE NO WAIT, we are throwing an exception when another transaction is creating a move using the same sequence as this one, by locking the row that holds the current greatest sequence. Since the row doesn't change, the lock is released and the following UPDATE is allowed. Because of this, a "useless" UPDATE was always done on the previous row to ensure a SerializationFailureError if two concurrent transactions were trying to assign the same number. This means that in a database with a lot of concurrent transactions (typically with an online shop), many transactions were aborted/rollbacked. This approach also has the drawback of having the constraint implemented python side, which is less robust. UNIQUE CONSTRAINT ================= Using a constraint on the database level means that the database knows when some transactions will try to do something similar. It will therefore make the concurrent transactions wait instead of aborting the transaction. It is however harder to have the constraint customized by different modules/localization because it is now done directly in the database and not in python. Changing the constraint means deleting/recreating the index. For now, this only happens for Latin America l10n, so a simple hardcoded approach is implemented. In order to achieve this, the business code is trying to use the sequence incrementally until it gets a number it can use for when multiple transactions are concurrent. closes odoo/odoo#106325 X-original-commit: 4b430f8e30efb15f06982e4673cd73531a2119b3 Related: odoo/enterprise#34297 Signed-off-by: Quentin De Paoli <qdp@odoo.com> Signed-off-by: William André (wan) <wan@odoo.com>
55 lines
2.2 KiB
Python
55 lines
2.2 KiB
Python
from odoo import models
|
|
from odoo.tools import index_exists
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = 'account.move'
|
|
|
|
_sql_constraints = [(
|
|
'unique_name', "", "Another entry with the same name already exists.",
|
|
)]
|
|
|
|
def _auto_init(self):
|
|
super()._auto_init()
|
|
if not index_exists(self.env.cr, 'account_move_unique_name'):
|
|
# Make all values of `name` different (naming them `name (1)`, `name (2)`...) so that we can add the following UNIQUE INDEX
|
|
self.env.cr.execute("""
|
|
WITH duplicated_sequence AS (
|
|
SELECT name, journal_id, state
|
|
FROM account_move
|
|
WHERE state = 'posted'
|
|
AND name != '/'
|
|
GROUP BY journal_id, name, state
|
|
HAVING COUNT(*) > 1
|
|
),
|
|
to_update AS (
|
|
SELECT move.id,
|
|
move.name,
|
|
move.journal_id,
|
|
move.state,
|
|
move.date,
|
|
row_number() OVER(PARTITION BY move.name, move.journal_id ORDER BY move.name, move.journal_id, move.date) AS row_seq
|
|
FROM duplicated_sequence
|
|
JOIN account_move move ON move.name = duplicated_sequence.name
|
|
AND move.journal_id = duplicated_sequence.journal_id
|
|
AND move.state = duplicated_sequence.state
|
|
),
|
|
new_vals AS (
|
|
SELECT id,
|
|
name || ' (' || (row_seq-1)::text || ')' AS name
|
|
FROM to_update
|
|
WHERE row_seq > 1
|
|
)
|
|
UPDATE account_move
|
|
SET name = new_vals.name
|
|
FROM new_vals
|
|
WHERE account_move.id = new_vals.id;
|
|
""")
|
|
self.env.cr.execute("""
|
|
CREATE UNIQUE INDEX account_move_unique_name
|
|
ON account_move(name, journal_id) WHERE (state = 'posted' AND name != '/');
|
|
""")
|
|
|
|
def _check_unique_sequence_number(self):
|
|
return
|