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: