diff --git a/account_move_name_sequence/demo/account_journal_demo.xml b/account_move_name_sequence/demo/account_journal_demo.xml
index de295c0c0..fb5f0c854 100644
--- a/account_move_name_sequence/demo/account_journal_demo.xml
+++ b/account_move_name_sequence/demo/account_journal_demo.xml
@@ -4,7 +4,7 @@
Standard Sale Journal Demo
SSJD
sale
- True
+
@@ -12,7 +12,7 @@
Standard Cash Journal Demo
SCJD
cash
- True
+
diff --git a/account_move_name_sequence/demo/ir_sequence_demo.xml b/account_move_name_sequence/demo/ir_sequence_demo.xml
index 4cea3a4ea..26f290072 100644
--- a/account_move_name_sequence/demo/ir_sequence_demo.xml
+++ b/account_move_name_sequence/demo/ir_sequence_demo.xml
@@ -4,8 +4,8 @@
Standard Sale Sequence Demo
SSS_demo/%(range_year)s/
- 1
- 1
+
+
standard
@@ -13,8 +13,8 @@
Standard Cash Sequence Demo
SCS_demo/%(range_year)s/
- 1
- 1
+
+
standard
diff --git a/account_move_name_sequence/models/account_journal.py b/account_move_name_sequence/models/account_journal.py
index b454e45b1..6158ebeca 100644
--- a/account_move_name_sequence/models/account_journal.py
+++ b/account_move_name_sequence/models/account_journal.py
@@ -7,7 +7,7 @@
import logging
-from odoo import api, fields, models
+from odoo import Command, api, fields, models
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
@@ -236,14 +236,12 @@ class AccountJournal(models.Model):
date_from = fields.Date.to_date(f"{year}-1-1")
date_to = fields.Date.to_date(f"{year}-12-31")
seq_vals["date_range_ids"].append(
- (
- 0,
- 0,
+ Command.create(
{
"date_from": date_from,
"date_to": date_to,
"number_next_actual": max_number + 1,
- },
+ }
)
)
return seq_vals
diff --git a/account_move_name_sequence/tests/test_account_move_name_seq.py b/account_move_name_sequence/tests/test_account_move_name_seq.py
index d71c56232..ed6513241 100644
--- a/account_move_name_sequence/tests/test_account_move_name_seq.py
+++ b/account_move_name_sequence/tests/test_account_move_name_seq.py
@@ -9,26 +9,27 @@ from unittest.mock import patch
from freezegun import freeze_time
-from odoo import fields
+from odoo import Command, fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, TransactionCase, tagged
@tagged("post_install", "-at_install")
class TestAccountMoveNameSequence(TransactionCase):
- def setUp(self):
- super().setUp()
- self.company = self.env.ref("base.main_company")
- self.partner = self.env.ref("base.res_partner_3")
- self.misc_journal = self.env["account.journal"].create(
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.company = cls.env.ref("base.main_company")
+ cls.partner = cls.env.ref("base.res_partner_3")
+ cls.misc_journal = cls.env["account.journal"].create(
{
"name": "Test Journal Move name seq",
"code": "ADLM",
"type": "general",
- "company_id": self.company.id,
+ "company_id": cls.company.id,
}
)
- self.sales_seq = self.env["ir.sequence"].create(
+ cls.sales_seq = cls.env["ir.sequence"].create(
{
"name": "TB2C",
"implementation": "no_gap",
@@ -36,48 +37,62 @@ class TestAccountMoveNameSequence(TransactionCase):
"use_date_range": True,
"number_increment": 1,
"padding": 4,
- "company_id": self.company.id,
+ "company_id": cls.company.id,
}
)
- self.sales_journal = self.env["account.journal"].create(
+ cls.sales_journal = cls.env["account.journal"].create(
{
"name": "TB2C",
"code": "TB2C",
"type": "sale",
- "company_id": self.company.id,
+ "company_id": cls.company.id,
"refund_sequence": True,
- "sequence_id": self.sales_seq.id,
+ "sequence_id": cls.sales_seq.id,
}
)
- self.purchase_journal = self.env["account.journal"].create(
+ cls.purchase_journal = cls.env["account.journal"].create(
{
"name": "Test Purchase Journal Move name seq",
"code": "ADLP",
"type": "purchase",
- "company_id": self.company.id,
+ "company_id": cls.company.id,
"refund_sequence": True,
}
)
- self.accounts = self.env["account.account"].search(
- [("company_ids", "=", self.company.id)], limit=2
+ cls.accounts = cls.env["account.account"].search(
+ [("company_ids", "=", cls.company.id)], limit=2
)
- self.account1 = self.accounts[0]
- self.account2 = self.accounts[1]
- self.date = datetime.now()
- self.purchase_journal2 = self.purchase_journal.copy()
+ cls.account1 = cls.accounts[0]
+ cls.account2 = cls.accounts[1]
+ cls.date = datetime.now()
+ cls.purchase_journal2 = cls.purchase_journal.copy()
- self.journals = (
- self.misc_journal
- | self.purchase_journal
- | self.sales_journal
- | self.purchase_journal2
+ cls.journals = (
+ cls.misc_journal
+ | cls.purchase_journal
+ | cls.sales_journal
+ | cls.purchase_journal2
)
# This patch was added to avoid test failures in the CI pipeline caused by the
# `account_journal_restrict_mode` module. It prevents a validation error when
# disabling restrict mode on journals used in the test, allowing moves to be
# set to draft and deleted.
with patch("odoo.models.BaseModel._validate_fields"):
- self.journals.restrict_mode_hash_table = False
+ cls.journals.restrict_mode_hash_table = False
+
+ cls.lines = [
+ Command.create({"account_id": cls.account1.id, "debit": 10}),
+ Command.create({"account_id": cls.account2.id, "credit": 10}),
+ ]
+ cls.invoice_line = [
+ Command.create(
+ {
+ "account_id": cls.account1.id,
+ "price_unit": 42.0,
+ "quantity": 12,
+ },
+ )
+ ]
def test_seq_creation(self):
self.assertTrue(self.misc_journal.sequence_id)
@@ -99,10 +114,7 @@ class TestAccountMoveNameSequence(TransactionCase):
{
"date": self.date,
"journal_id": self.misc_journal.id,
- "line_ids": [
- (0, 0, {"account_id": self.account1.id, "debit": 10}),
- (0, 0, {"account_id": self.account2.id, "credit": 10}),
- ],
+ "line_ids": self.lines,
}
)
self.assertEqual(move.name, "/")
@@ -138,10 +150,7 @@ class TestAccountMoveNameSequence(TransactionCase):
{
"date": "2021-12-31",
"journal_id": self.misc_journal.id,
- "line_ids": [
- (0, 0, {"account_id": self.account1.id, "debit": 10}),
- (0, 0, {"account_id": self.account2.id, "credit": 10}),
- ],
+ "line_ids": self.lines,
}
)
move.action_post()
@@ -151,10 +160,7 @@ class TestAccountMoveNameSequence(TransactionCase):
{
"date": "2022-06-30",
"journal_id": self.misc_journal.id,
- "line_ids": [
- (0, 0, {"account_id": self.account1.id, "debit": 10}),
- (0, 0, {"account_id": self.account2.id, "credit": 10}),
- ],
+ "line_ids": self.lines,
}
)
move.action_post()
@@ -165,15 +171,40 @@ class TestAccountMoveNameSequence(TransactionCase):
{
"date": "2022-07-01",
"journal_id": self.misc_journal.id,
- "line_ids": [
- (0, 0, {"account_id": self.account1.id, "debit": 10}),
- (0, 0, {"account_id": self.account2.id, "credit": 10}),
- ],
+ "line_ids": self.lines,
}
)
move.action_post()
self.assertEqual(move.name, "TEST-2022-07-0001")
+ def test_prefix_move_name_use_move_date_2(self):
+ seq = self.misc_journal.sequence_id
+ seq.prefix = "TEST-%(range_month)s-"
+ with freeze_time("2022-01-01"):
+ move = self.env["account.move"].create(
+ {
+ "date": "2022-06-30",
+ "journal_id": self.misc_journal.id,
+ "line_ids": self.lines,
+ }
+ )
+ move.action_post()
+ self.assertEqual(move.name, "TEST-06-0001")
+
+ def test_prefix_move_name_use_move_date_3(self):
+ seq = self.misc_journal.sequence_id
+ seq.prefix = "TEST-%(range_day)s-"
+ with freeze_time("2022-01-01"):
+ move = self.env["account.move"].create(
+ {
+ "date": "2022-01-01",
+ "journal_id": self.misc_journal.id,
+ "line_ids": self.lines,
+ }
+ )
+ move.action_post()
+ self.assertEqual(move.name, "TEST-01-0001")
+
def test_in_invoice_and_refund(self):
in_invoice = self.env["account.move"].create(
{
@@ -181,25 +212,15 @@ class TestAccountMoveNameSequence(TransactionCase):
"invoice_date": self.date,
"partner_id": self.env.ref("base.res_partner_3").id,
"move_type": "in_invoice",
- "invoice_line_ids": [
- (
- 0,
- 0,
- {
- "account_id": self.account1.id,
- "price_unit": 42.0,
- "quantity": 12,
- },
- ),
- (
- 0,
- 0,
+ "invoice_line_ids": self.invoice_line
+ + [
+ Command.create(
{
"account_id": self.account1.id,
"price_unit": 48.0,
"quantity": 10,
- },
- ),
+ }
+ )
],
}
)
@@ -253,17 +274,7 @@ class TestAccountMoveNameSequence(TransactionCase):
"invoice_date": self.date,
"partner_id": self.env.ref("base.res_partner_3").id,
"move_type": "in_refund",
- "invoice_line_ids": [
- (
- 0,
- 0,
- {
- "account_id": self.account1.id,
- "price_unit": 42.0,
- "quantity": 12,
- },
- )
- ],
+ "invoice_line_ids": self.invoice_line,
}
)
self.assertEqual(in_refund_invoice.name, "/")
@@ -281,10 +292,7 @@ class TestAccountMoveNameSequence(TransactionCase):
{
"date": self.date,
"journal_id": self.misc_journal.id,
- "line_ids": [
- (0, 0, {"account_id": self.account1.id, "debit": 10}),
- (0, 0, {"account_id": self.account2.id, "credit": 10}),
- ],
+ "line_ids": self.lines,
}
)
self.assertEqual(invoice.name, "/")
@@ -310,17 +318,7 @@ class TestAccountMoveNameSequence(TransactionCase):
"invoice_date": self.date,
"partner_id": self.env.ref("base.res_partner_3").id,
"move_type": "in_refund",
- "invoice_line_ids": [
- (
- 0,
- 0,
- {
- "account_id": self.account1.id,
- "price_unit": 42.0,
- "quantity": 12,
- },
- )
- ],
+ "invoice_line_ids": self.invoice_line,
}
)
self.assertEqual(in_refund_invoice.name, "/")
@@ -370,3 +368,21 @@ class TestAccountMoveNameSequence(TransactionCase):
self.assertEqual(invoice.name, "/", "name based on journal instead of sequence")
invoice.action_post()
self.assertIn("TB2CSEQ/", invoice.name, "name was not based on sequence")
+
+ def test_is_end_of_seq_chain(self):
+ self.env.user.groups_id -= self.env.ref("account.group_account_manager")
+ invoice = self.env["account.move"].create(
+ {
+ "date": self.date,
+ "journal_id": self.misc_journal.id,
+ "line_ids": self.lines,
+ }
+ )
+ invoice.action_post()
+ error_msg = (
+ "You cannot delete this entry, as it has already consumed "
+ "a sequence number and is not the last one in the chain. "
+ "You should probably revert it instead."
+ )
+ with self.assertRaisesRegex(UserError, error_msg):
+ invoice._unlink_forbid_parts_of_chain()
diff --git a/account_move_name_sequence/tests/test_sequence_concurrency.py b/account_move_name_sequence/tests/test_sequence_concurrency.py
index 0e7dcea38..327c8f970 100644
--- a/account_move_name_sequence/tests/test_sequence_concurrency.py
+++ b/account_move_name_sequence/tests/test_sequence_concurrency.py
@@ -35,31 +35,64 @@ class ThreadRaiseJoin(threading.Thread):
@tagged("post_install", "-at_install", "test_move_sequence")
class TestSequenceConcurrency(TransactionCase):
- def setUp(self):
- super().setUp()
- self.product = self.env.ref("product.product_delivery_01")
- self.partner = self.env.ref("base.res_partner_12")
- self.partner2 = self.env.ref("base.res_partner_1")
- self.date = fields.Date.to_date("1985-04-14")
- self.journal_sale_std = self.env.ref(
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.product = cls.env.ref("product.product_delivery_01")
+ cls.partner = cls.env.ref("base.res_partner_12")
+ cls.partner2 = cls.env.ref("base.res_partner_1")
+ cls.date = fields.Date.to_date("1985-04-14")
+ cls.journal_sale_std = cls.env.ref(
"account_move_name_sequence.journal_sale_std_demo"
)
- self.journal_cash_std = self.env.ref(
+ cls.journal_cash_std = cls.env.ref(
"account_move_name_sequence.journal_cash_std_demo"
)
- def _new_cr(self):
- return self.env.registry.cursor()
+ cls.cr0 = cls.cursor(cls)
+ cls.env0 = api.Environment(cls.cr0, SUPERUSER_ID, {})
+ cls.cr1 = cls.cursor(cls)
+ cls.env1 = api.Environment(cls.cr1, SUPERUSER_ID, {})
+ cls.cr2 = cls.cursor(cls)
+ cls.env2 = api.Environment(cls.cr2, SUPERUSER_ID, {})
+ for cr in [cls.cr0, cls.cr1, cls.cr2]:
+ # Set a 10-second timeout to avoid waiting too long for release locks
+ cr.execute("SET LOCAL statement_timeout = '10s'")
+ cls.registry.enter_test_mode(cls.cursor(cls))
+ cls.last_existing_move = cls.env["account.move"].search(
+ [], limit=1, order="id desc"
+ )
+
+ @classmethod
+ def _clean_moves_and_payments(cls, last_move):
+ """Delete moves and payments created after finish test."""
+ moves = (
+ cls.env["account.move"]
+ .with_context(force_delete=True)
+ .search([("id", ">=", last_move)])
+ )
+ payments = moves.payment_ids
+ moves_without_payments = moves - payments.move_id
+ if payments:
+ payments.action_draft()
+ payments.unlink()
+ if moves_without_payments:
+ moves_without_payments.filtered(
+ lambda move: move.state != "draft"
+ ).button_draft()
+ moves_without_payments.unlink()
+
+ def _commit_crs(self, *envs):
+ for env in envs:
+ env.cr.commit()
def _create_invoice_form(
self, env, post=True, partner=None, ir_sequence_standard=False
):
- if partner is None:
- # Use another partner to bypass "increase_rank" lock error
- partner = self.partner
ctx = {"default_move_type": "out_invoice"}
with Form(env["account.move"].with_context(**ctx)) as invoice_form:
- invoice_form.partner_id = partner
+ # Use another partner to bypass "increase_rank" lock error
+ invoice_form.partner_id = partner or self.partner
invoice_form.invoice_date = self.date
with invoice_form.invoice_line_ids.new() as line_form:
@@ -80,7 +113,7 @@ class TestSequenceConcurrency(TransactionCase):
invoice.action_post()
return invoice
- def _create_payment_form(self, env, ir_sequence_standard=False):
+ def _create_payment_form(self, env, partner=None, ir_sequence_standard=False):
with Form(
env["account.payment"].with_context(
default_payment_type="inbound",
@@ -88,7 +121,7 @@ class TestSequenceConcurrency(TransactionCase):
default_move_journal_types=("bank", "cash"),
)
) as payment_form:
- payment_form.partner_id = env.ref("base.res_partner_12")
+ payment_form.partner_id = partner or self.partner
payment_form.amount = 100
payment_form.date = self.date
if ir_sequence_standard:
@@ -102,33 +135,10 @@ class TestSequenceConcurrency(TransactionCase):
payment.action_post()
return payment
- def _clean_moves_and_payments(self, move_ids):
- """Delete moves and payments created after finish unittest using
- self.addCleanup(
- self._clean_moves_and_payments,
- self.env,
- (invoices | payments.mapped('move_id')).ids,
- )
- """
- with self._new_cr() as cr:
- env = api.Environment(cr, SUPERUSER_ID, {})
- moves = env["account.move"].with_context(force_delete=True).browse(move_ids)
- payments = moves.payment_ids
- moves_without_payments = moves - payments.move_id
- if payments:
- payments.action_draft()
- payments.unlink()
- if moves_without_payments:
- moves_without_payments.filtered(
- lambda move: move.state != "draft"
- ).button_draft()
- moves_without_payments.unlink()
- env.cr.commit()
-
def _create_invoice_payment(
self, deadlock_timeout, payment_first=False, ir_sequence_standard=False
):
- with self._new_cr() as cr, cr.savepoint():
+ with self.cursor() as cr, cr.savepoint():
env = api.Environment(cr, SUPERUSER_ID, {})
cr_pid = cr.connection.get_backend_pid()
# Avoid waiting for a long time and it needs to be less than deadlock
@@ -159,221 +169,149 @@ class TestSequenceConcurrency(TransactionCase):
def test_sequence_concurrency_10_draft_invoices(self):
"""Creating 2 DRAFT invoices not should raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1, self._new_cr() as cr2:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- env2 = api.Environment(cr2, SUPERUSER_ID, {})
- for cr in [cr0, cr1, cr2]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0)
- self.addCleanup(self._clean_moves_and_payments, invoice.ids)
- env0.cr.commit()
- with env1.cr.savepoint(), env2.cr.savepoint():
- invoice1 = self._create_invoice_form(env1, post=False)
- self.assertEqual(invoice1.state, "draft")
- invoice2 = self._create_invoice_form(env2, post=False)
- self.assertEqual(invoice2.state, "draft")
+ # Create "last move" to lock
+ self._create_invoice_form(self.env0)
+ self.cr0.commit()
+ with self.cr1.savepoint(), self.cr2.savepoint():
+ invoice1 = self._create_invoice_form(self.env1, post=False)
+ self.assertEqual(invoice1.state, "draft")
+ invoice2 = self._create_invoice_form(self.env2, post=False)
+ self.assertEqual(invoice2.state, "draft")
+ self._commit_crs(self.env0, self.env1, self.env2)
def test_sequence_concurrency_20_editing_last_invoice(self):
"""Edit last invoice and create a new invoice
should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- for cr in [cr0, cr1]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0)
-
- self.addCleanup(self._clean_moves_and_payments, invoice.ids)
- env0.cr.commit()
- with env0.cr.savepoint(), env1.cr.savepoint():
- # Edit something in "last move"
- invoice.write({"write_uid": env0.uid})
- env0.flush_all()
- self._create_invoice_form(env1)
+ # Create "last move" to lock
+ invoice = self._create_invoice_form(self.env0)
+ self.cr0.commit()
+ with self.cr0.savepoint(), self.cr1.savepoint():
+ # Edit something in "last move"
+ invoice.write({"write_uid": self.env0.uid})
+ self.env0.flush_all()
+ self._create_invoice_form(self.env1)
+ self._commit_crs(self.env0, self.env1)
def test_sequence_concurrency_30_editing_last_payment(self):
"""Edit last payment and create a new payment
should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- for cr in [cr0, cr1]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- payment = self._create_payment_form(env0)
- payment_move = payment.move_id
- self.addCleanup(self._clean_moves_and_payments, payment_move.ids)
- env0.cr.commit()
- with env0.cr.savepoint(), env1.cr.savepoint():
- # Edit something in "last move"
- payment_move.write({"write_uid": env0.uid})
- env0.flush_all()
- self._create_payment_form(env1)
+ # Create "last move" to lock
+ payment = self._create_payment_form(self.env0)
+ payment_move = payment.move_id
+ self.cr0.commit()
+ with self.cr0.savepoint(), self.cr1.savepoint():
+ # Edit something in "last move"
+ payment_move.write({"write_uid": self.env0.uid})
+ self.env0.flush_all()
+ self._create_payment_form(self.env1)
+ self._commit_crs(self.env0, self.env1)
@tools.mute_logger("odoo.sql_db")
def test_sequence_concurrency_40_reconciling_last_invoice(self):
"""Reconcile last invoice and create a new one
should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- for cr in [cr0, cr1]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0)
- payment = self._create_payment_form(env0)
- payment_move = payment.move_id
- self.addCleanup(
- self._clean_moves_and_payments, invoice.ids + payment_move.ids
- )
- env0.cr.commit()
- lines2reconcile = (
- (payment_move | invoice)
- .mapped("line_ids")
- .filtered(
- lambda line: line.account_id.account_type == "asset_receivable"
- )
- )
- with env0.cr.savepoint(), env1.cr.savepoint():
- # Reconciling "last move"
- # reconcile a payment with many invoices spend a lot so it could
- # lock records too many time
- lines2reconcile.reconcile()
- # Many pieces of code call flush directly
- env0.flush_all()
- self._create_invoice_form(env1)
+ # Create "last move" to lock
+ invoice = self._create_invoice_form(self.env0)
+ payment = self._create_payment_form(self.env0)
+ payment_move = payment.move_id
+ self.cr0.commit()
+ lines2reconcile = (
+ (payment_move | invoice)
+ .mapped("line_ids")
+ .filtered(lambda line: line.account_id.account_type == "asset_receivable")
+ )
+ with self.cr0.savepoint(), self.cr1.savepoint():
+ # Reconciling "last move"
+ # reconcile a payment with many invoices spend a lot so it could
+ # lock records too many time
+ lines2reconcile.reconcile()
+ # Many pieces of code call flush directly
+ self.env0.flush_all()
+ self._create_invoice_form(self.env1)
+ self._commit_crs(self.env0, self.env1)
def test_sequence_concurrency_50_reconciling_last_payment(self):
"""Reconcile last payment and create a new one
should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- for cr in [cr0, cr1]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0)
- payment = self._create_payment_form(env0)
- payment_move = payment.move_id
- self.addCleanup(
- self._clean_moves_and_payments, invoice.ids + payment_move.ids
- )
- env0.cr.commit()
- lines2reconcile = (
- (payment_move | invoice)
- .mapped("line_ids")
- .filtered(
- lambda line: line.account_id.account_type == "asset_receivable"
- )
- )
- with env0.cr.savepoint(), env1.cr.savepoint():
- # Reconciling "last move"
- # reconcile a payment with many invoices spend a lot so it could
- # lock records too many time
- lines2reconcile.reconcile()
- # Many pieces of code call flush directly
- env0.flush_all()
- self._create_payment_form(env1)
+ # Create "last move" to lock
+ invoice = self._create_invoice_form(self.env0)
+ payment = self._create_payment_form(self.env0)
+ payment_move = payment.move_id
+ self.cr0.commit()
+ lines2reconcile = (
+ (payment_move | invoice)
+ .mapped("line_ids")
+ .filtered(lambda line: line.account_id.account_type == "asset_receivable")
+ )
+ with self.cr0.savepoint(), self.cr1.savepoint():
+ # Reconciling "last move"
+ # reconcile a payment with many invoices spend a lot so it could
+ # lock records too many time
+ lines2reconcile.reconcile()
+ # Many pieces of code call flush directly
+ self.env0.flush_all()
+ self._create_payment_form(self.env1)
+ self._commit_crs(self.env0, self.env1)
def test_sequence_concurrency_90_payments(self):
"""Creating concurrent payments should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1, self._new_cr() as cr2:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- env2 = api.Environment(cr2, SUPERUSER_ID, {})
- for cr in [cr0, cr1, cr2]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- payment = self._create_payment_form(env0, ir_sequence_standard=True)
- payment_move_ids = payment.move_id.ids
- self.addCleanup(self._clean_moves_and_payments, payment_move_ids)
- env0.cr.commit()
- with env1.cr.savepoint(), env2.cr.savepoint():
- self._create_payment_form(env1, ir_sequence_standard=True)
- self._create_payment_form(env2, ir_sequence_standard=True)
+ # Create "last move" to lock
+ self._create_payment_form(self.env0, ir_sequence_standard=True)
+ self.cr0.commit()
+ with self.cr1.savepoint(), self.cr2.savepoint():
+ self._create_payment_form(self.env1, ir_sequence_standard=True)
+ self._create_payment_form(self.env2, ir_sequence_standard=True)
+ self._commit_crs(self.env0, self.env1, self.env2)
def test_sequence_concurrency_92_invoices(self):
"""Creating concurrent invoices should not raises errors"""
- with self._new_cr() as cr0, self._new_cr() as cr1, self._new_cr() as cr2:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
- env1 = api.Environment(cr1, SUPERUSER_ID, {})
- env2 = api.Environment(cr2, SUPERUSER_ID, {})
- for cr in [cr0, cr1, cr2]:
- # Set a 10-second timeout to avoid waiting too long for release locks
- cr.execute("SET LOCAL statement_timeout = '10s'")
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0, ir_sequence_standard=True)
- self.addCleanup(self._clean_moves_and_payments, invoice.ids)
- env0.cr.commit()
- with env1.cr.savepoint(), env2.cr.savepoint():
- self._create_invoice_form(env1, ir_sequence_standard=True)
- # Using another partner to bypass "increase_rank" lock error
- self._create_invoice_form(
- env2, partner=self.partner2, ir_sequence_standard=True
- )
+ # Create "last move" to lock
+ self._create_invoice_form(self.env0, ir_sequence_standard=True)
+ self.cr0.commit()
+ with self.cr1.savepoint(), self.cr2.savepoint():
+ self._create_invoice_form(self.env1, ir_sequence_standard=True)
+ # Using another partner to bypass "increase_rank" lock error
+ self._create_invoice_form(
+ self.env2, partner=self.partner2, ir_sequence_standard=True
+ )
+ self._commit_crs(self.env0, self.env1, self.env2)
@tools.mute_logger("odoo.sql_db")
def test_sequence_concurrency_95_pay2inv_inv2pay(self):
"""Creating concurrent payment then invoice and invoice then payment
should not raises errors
It raises deadlock sometimes"""
- with self._new_cr() as cr0:
- env0 = api.Environment(cr0, SUPERUSER_ID, {})
-
- # Create "last move" to lock
- invoice = self._create_invoice_form(env0)
-
- # Create "last move" to lock
- payment = self._create_payment_form(env0)
- payment_move_ids = payment.move_id.ids
- self.addCleanup(
- self._clean_moves_and_payments, invoice.ids + payment_move_ids
- )
- env0.cr.commit()
- env0.cr.execute(
- "SELECT setting FROM pg_settings WHERE name = 'deadlock_timeout'"
- )
- deadlock_timeout = int(env0.cr.fetchone()[0]) # ms
- # You could not have permission to set this parameter
- # psycopg2.errors.InsufficientPrivilege
- self.assertTrue(
- deadlock_timeout,
- "You need to configure PG parameter deadlock_timeout='1s'",
- )
- deadlock_timeout = int(deadlock_timeout / 1000) # s
-
- t_pay_inv = ThreadRaiseJoin(
- target=self._create_invoice_payment,
- args=(deadlock_timeout, True, True),
- name="Thread payment invoice",
- )
- t_inv_pay = ThreadRaiseJoin(
- target=self._create_invoice_payment,
- args=(deadlock_timeout, False, True),
- name="Thread invoice payment",
- )
- self.env.registry.enter_test_mode(self.env.registry.cursor())
- t_pay_inv.start()
- t_inv_pay.start()
- # the thread could raise the error before to wait for it so disable coverage
- self._thread_join(t_pay_inv, deadlock_timeout + 15)
- self._thread_join(t_inv_pay, deadlock_timeout + 15)
+ # Create "last move" to lock
+ self._create_invoice_form(self.env0)
+ # Create "last move" to lock
+ self._create_payment_form(self.env0)
+ self.cr0.commit()
+ self.cr0.execute(
+ "SELECT setting FROM pg_settings WHERE name = 'deadlock_timeout'"
+ )
+ deadlock_timeout = int(self.cr0.fetchone()[0]) # ms
+ # You could not have permission to set this parameter
+ # psycopg2.errors.InsufficientPrivilege
+ self.assertTrue(
+ deadlock_timeout,
+ "You need to configure PG parameter deadlock_timeout='1s'",
+ )
+ deadlock_timeout = int(deadlock_timeout / 1000) # s
+ t_pay_inv = ThreadRaiseJoin(
+ target=self._create_invoice_payment,
+ args=(deadlock_timeout, True, True),
+ name="Thread payment invoice",
+ )
+ t_inv_pay = ThreadRaiseJoin(
+ target=self._create_invoice_payment,
+ args=(deadlock_timeout, False, True),
+ name="Thread invoice payment",
+ )
+ t_pay_inv.start()
+ t_inv_pay.start()
+ # the thread could raise the error before to wait for it so disable coverage
+ self._thread_join(t_pay_inv, deadlock_timeout + 15)
+ self._thread_join(t_inv_pay, deadlock_timeout + 15)
def _thread_join(self, thread_obj, timeout):
try: