From ddb5d15f53ab59319e11944c63f0fd1ed47b419d Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 28 Jun 2023 11:28:23 +0100 Subject: [PATCH] [FIX] account_journal_general_sequence: fix multicompany Instead of defining the new sequence as a field default, it is now a compute. This is because the sequence depends on the company, but we don't have the `company_id` field until the record is created. Reduced the default number of padded zeroes to 8. This is a product decision. The original implementation didn't make much sense because it allowed the user to set a different sequence per journal, but Odoo already has that kind of sequence. The only purpose of this module is to have a sequence *per company*. To avoid breaking too much, for now, when the journal sequence is the default one, we set it as readonly. Limit the available sequences in the renumbering wizard. Display only those that you have access by your selected context companies. For some reason, Odoo doesn't filter sequences by company automatically. @moduon MT-3076 Co-authored-by: Andrea Cattalani <22261939+anddago78@users.noreply.github.com> --- .../__manifest__.py | 2 +- .../migrations/16.0.2.0.0/post-migration.py | 44 +++++++++++++++ .../models/account_journal.py | 53 ++++++++++++------- .../tests/test_numbering.py | 50 +++++++++++++---- .../views/account_journal.xml | 6 ++- .../wizards/account_move_renumber_wizard.py | 12 ++++- 6 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 account_journal_general_sequence/migrations/16.0.2.0.0/post-migration.py diff --git a/account_journal_general_sequence/__manifest__.py b/account_journal_general_sequence/__manifest__.py index 39bfb62c..6a4b99d8 100644 --- a/account_journal_general_sequence/__manifest__.py +++ b/account_journal_general_sequence/__manifest__.py @@ -3,7 +3,7 @@ { "name": "General sequence in account journals", "summary": "Add configurable sequence to account moves, per journal", - "version": "16.0.1.1.0", + "version": "16.0.2.0.0", "category": "Accounting/Accounting", "website": "https://github.com/OCA/account-financial-tools", "author": "Moduon, Odoo Community Association (OCA)", diff --git a/account_journal_general_sequence/migrations/16.0.2.0.0/post-migration.py b/account_journal_general_sequence/migrations/16.0.2.0.0/post-migration.py new file mode 100644 index 00000000..a17d1baf --- /dev/null +++ b/account_journal_general_sequence/migrations/16.0.2.0.0/post-migration.py @@ -0,0 +1,44 @@ +# Copyright 2023 Moduon Team S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) +from odoo import SUPERUSER_ID, api, fields + + +def migrate(cr, version): + """One journal sequence per company.""" + env = api.Environment(cr, SUPERUSER_ID, {}) + journals = env["account.journal"].search( + [ + ( + "entry_number_sequence_id.code", + "=", + "account_journal_general_sequence.default", + ) + ] + ) + for journal in journals: + if journal.company_id != journal.entry_number_sequence_id.company_id: + new_sequence = env["ir.sequence"].search( + [ + ("code", "=", "account_journal_general_sequence.default"), + ("company_id", "=", journal.company_id.id), + ] + ) or journal.entry_number_sequence_id.copy( + { + "company_id": journal.company_id.id, + "name": "{} ({})".format( + journal.entry_number_sequence_id.name, journal.company_id.name + ), + "number_next_actual": journal.entry_number_sequence_id.number_next_actual, + "date_range_ids": [ + fields.Command.create( + { + "date_from": rng.date_from, + "date_to": rng.date_to, + "number_next_actual": rng.number_next_actual, + } + ) + for rng in journal.entry_number_sequence_id.date_range_ids + ], + } + ) + journal.entry_number_sequence_id = new_sequence diff --git a/account_journal_general_sequence/models/account_journal.py b/account_journal_general_sequence/models/account_journal.py index c36eb153..6a6feb42 100644 --- a/account_journal_general_sequence/models/account_journal.py +++ b/account_journal_general_sequence/models/account_journal.py @@ -2,7 +2,7 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). import logging -from odoo import _, fields, models +from odoo import _, api, fields, models _logger = logging.getLogger(__name__) @@ -13,27 +13,40 @@ class AccountJournal(models.Model): entry_number_sequence_id = fields.Many2one( comodel_name="ir.sequence", string="Account entry number sequence", - default=lambda self: self._default_entry_number_sequence(), + compute="_compute_entry_number_sequence", + domain="[('company_id', '=', company_id)]", + check_company=True, + readonly=False, + store=True, copy=False, help="Sequence used for account entry numbering.", ) + entry_number_sequence_id_name = fields.Char(related="entry_number_sequence_id.code") - def _default_entry_number_sequence(self): + @api.depends("company_id") + def _compute_entry_number_sequence(self): """Get the default sequence for all journals.""" - result = self.env["ir.sequence"].search( - [("code", "=", "account_journal_general_sequence.default")] - ) - if result: - return result - _logger.info("Creating default sequence for account move numbers") - result = self.env["ir.sequence"].create( - { - "name": _("Account entry default numbering"), - "code": "account_journal_general_sequence.default", - "implementation": "no_gap", - "prefix": "%(range_year)s/", - "padding": 10, - "use_date_range": True, - } - ) - return result + for one in self: + sequence = self.env["ir.sequence"].search( + [ + ("code", "=", "account_journal_general_sequence.default"), + ("company_id", "=", one.company_id.id), + ] + ) + if not sequence: + _logger.info("Creating default sequence for account move numbers") + sequence = self.env["ir.sequence"].create( + { + "name": _( + "Account entry default numbering (%s)", + one.company_id.name, + ), + "code": "account_journal_general_sequence.default", + "company_id": one.company_id.id, + "implementation": "no_gap", + "prefix": "%(range_year)s/", + "padding": 8, + "use_date_range": True, + } + ) + one.entry_number_sequence_id = sequence diff --git a/account_journal_general_sequence/tests/test_numbering.py b/account_journal_general_sequence/tests/test_numbering.py index b8c9f86b..8087a499 100644 --- a/account_journal_general_sequence/tests/test_numbering.py +++ b/account_journal_general_sequence/tests/test_numbering.py @@ -2,6 +2,7 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). from freezegun import freeze_time +from odoo.fields import Command from odoo.tests.common import Form, new_test_user, tagged, users from odoo.tools import mute_logger @@ -14,11 +15,18 @@ class RenumberCase(TestAccountReconciliationCommon): @classmethod def setUpClass(cls): super().setUpClass() + companies = cls.company_data["company"] | cls.company_data_2["company"] cls.invoicer = new_test_user( - cls.env, "test_invoicer", "account.group_account_invoice" + cls.env, + "test_invoicer", + "account.group_account_invoice", + company_ids=[Command.set(companies.ids)], ) cls.manager = new_test_user( - cls.env, "test_manager", "account.group_account_manager" + cls.env, + "test_manager", + "account.group_account_manager", + company_ids=[Command.set(companies.ids)], ) @users("test_invoicer") @@ -42,18 +50,22 @@ class RenumberCase(TestAccountReconciliationCommon): next_year_invoice = self._create_invoice( date_invoice="2023-12-31", auto_validate=True ) - next_year_invoice.flush(["entry_number"], next_year_invoice) + next_year_invoice.flush_recordset(["entry_number"]) new_invoice = self._create_invoice( date_invoice="2022-05-10", auto_validate=True ) - new_invoice.flush(["entry_number"], new_invoice) + new_invoice.flush_recordset(["entry_number"]) old_invoice = self._create_invoice( date_invoice="2022-04-30", auto_validate=True ) - old_invoice.flush(["entry_number"], old_invoice) + old_invoice.flush_recordset(["entry_number"]) self.assertLess(new_invoice.entry_number, old_invoice.entry_number) # Fix entry number order with wizard; default values are OK - wiz_f = Form(self.env["account.move.renumber.wizard"]) + wiz_f = Form( + self.env["account.move.renumber.wizard"].with_company( + self.company_data["company"] + ) + ) self.assertEqual(len(wiz_f.available_sequence_ids), 1) wiz = wiz_f.save() wiz.action_renumber() @@ -67,10 +79,10 @@ class RenumberCase(TestAccountReconciliationCommon): wiz_f = Form(self.env["account.move.renumber.wizard"]) wiz = wiz_f.save() wiz.action_renumber() - self.assertEqual(opening_invoice.entry_number, "2022/0000000001") - self.assertEqual(old_invoice.entry_number, "2022/0000000002") - self.assertEqual(new_invoice.entry_number, "2022/0000000003") - self.assertEqual(next_year_invoice.entry_number, "2023/0000000001") + self.assertEqual(opening_invoice.entry_number, "2022/00000001") + self.assertEqual(old_invoice.entry_number, "2022/00000002") + self.assertEqual(new_invoice.entry_number, "2022/00000003") + self.assertEqual(next_year_invoice.entry_number, "2023/00000001") @users("test_invoicer") def test_install_no_entry_number(self): @@ -88,3 +100,21 @@ class RenumberCase(TestAccountReconciliationCommon): invoice.action_post() # Ensure there's no entry number self.assertFalse(invoice.entry_number) + + @users("test_invoicer") + def test_new_company_journal(self): + # Create new companies + cmp1 = self.company_data["company"] + cmp2 = self.company_data_2["company"] + # Create a new invoice for each company + self.env = self.env( + context=dict(self.env.context, allowed_company_ids=cmp1.ids) + ) + invoice1 = self.create_invoice() + self.env = self.env( + context=dict(self.env.context, allowed_company_ids=cmp2.ids) + ) + invoice2 = self.create_invoice() + # Each company has a different sequence, so the entry number should be the same + self.assertEqual(invoice1.entry_number, "2022/00000001") + self.assertEqual(invoice2.entry_number, "2022/00000001") diff --git a/account_journal_general_sequence/views/account_journal.xml b/account_journal_general_sequence/views/account_journal.xml index 13f8e690..d38116b8 100644 --- a/account_journal_general_sequence/views/account_journal.xml +++ b/account_journal_general_sequence/views/account_journal.xml @@ -20,7 +20,11 @@ - + + diff --git a/account_journal_general_sequence/wizards/account_move_renumber_wizard.py b/account_journal_general_sequence/wizards/account_move_renumber_wizard.py index ff261c41..ac9cacdd 100644 --- a/account_journal_general_sequence/wizards/account_move_renumber_wizard.py +++ b/account_journal_general_sequence/wizards/account_move_renumber_wizard.py @@ -41,13 +41,21 @@ class AccountMoveRenumberWizard(models.TransientModel): def _default_entry_number_sequence(self): """Get default sequence if it exists.""" return self.env["ir.sequence"].search( - [("code", "=", "account_journal_general_sequence.default")] + [ + "&", + ("code", "=", "account_journal_general_sequence.default"), + ("company_id", "in", self.env.companies.ids), + ] ) @api.model def _default_available_sequence_ids(self): """Let view display only journal-related sequences.""" - return self.env["account.journal"].search([]).mapped("entry_number_sequence_id") + return ( + self.env["account.journal"] + .search([("company_id", "in", self.env.companies.ids)]) + .mapped("entry_number_sequence_id") + ) def action_renumber(self): """Renumber moves.