2
0

[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>
This commit is contained in:
Jairo Llopis 2023-06-28 11:28:23 +01:00
parent 69d5ffe23f
commit ddb5d15f53
No known key found for this signature in database
GPG Key ID: E47E3BE44B940490
6 changed files with 133 additions and 34 deletions

View File

@ -3,7 +3,7 @@
{ {
"name": "General sequence in account journals", "name": "General sequence in account journals",
"summary": "Add configurable sequence to account moves, per journal", "summary": "Add configurable sequence to account moves, per journal",
"version": "16.0.1.1.0", "version": "16.0.2.0.0",
"category": "Accounting/Accounting", "category": "Accounting/Accounting",
"website": "https://github.com/OCA/account-financial-tools", "website": "https://github.com/OCA/account-financial-tools",
"author": "Moduon, Odoo Community Association (OCA)", "author": "Moduon, Odoo Community Association (OCA)",

View File

@ -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

View File

@ -2,7 +2,7 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
import logging import logging
from odoo import _, fields, models from odoo import _, api, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -13,27 +13,40 @@ class AccountJournal(models.Model):
entry_number_sequence_id = fields.Many2one( entry_number_sequence_id = fields.Many2one(
comodel_name="ir.sequence", comodel_name="ir.sequence",
string="Account entry number 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, copy=False,
help="Sequence used for account entry numbering.", 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.""" """Get the default sequence for all journals."""
result = self.env["ir.sequence"].search( for one in self:
[("code", "=", "account_journal_general_sequence.default")] sequence = self.env["ir.sequence"].search(
) [
if result: ("code", "=", "account_journal_general_sequence.default"),
return result ("company_id", "=", one.company_id.id),
_logger.info("Creating default sequence for account move numbers") ]
result = self.env["ir.sequence"].create( )
{ if not sequence:
"name": _("Account entry default numbering"), _logger.info("Creating default sequence for account move numbers")
"code": "account_journal_general_sequence.default", sequence = self.env["ir.sequence"].create(
"implementation": "no_gap", {
"prefix": "%(range_year)s/", "name": _(
"padding": 10, "Account entry default numbering (%s)",
"use_date_range": True, one.company_id.name,
} ),
) "code": "account_journal_general_sequence.default",
return result "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

View File

@ -2,6 +2,7 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from freezegun import freeze_time from freezegun import freeze_time
from odoo.fields import Command
from odoo.tests.common import Form, new_test_user, tagged, users from odoo.tests.common import Form, new_test_user, tagged, users
from odoo.tools import mute_logger from odoo.tools import mute_logger
@ -14,11 +15,18 @@ class RenumberCase(TestAccountReconciliationCommon):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
companies = cls.company_data["company"] | cls.company_data_2["company"]
cls.invoicer = new_test_user( 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.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") @users("test_invoicer")
@ -42,18 +50,22 @@ class RenumberCase(TestAccountReconciliationCommon):
next_year_invoice = self._create_invoice( next_year_invoice = self._create_invoice(
date_invoice="2023-12-31", auto_validate=True 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( new_invoice = self._create_invoice(
date_invoice="2022-05-10", auto_validate=True 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( old_invoice = self._create_invoice(
date_invoice="2022-04-30", auto_validate=True 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) self.assertLess(new_invoice.entry_number, old_invoice.entry_number)
# Fix entry number order with wizard; default values are OK # 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) self.assertEqual(len(wiz_f.available_sequence_ids), 1)
wiz = wiz_f.save() wiz = wiz_f.save()
wiz.action_renumber() wiz.action_renumber()
@ -67,10 +79,10 @@ class RenumberCase(TestAccountReconciliationCommon):
wiz_f = Form(self.env["account.move.renumber.wizard"]) wiz_f = Form(self.env["account.move.renumber.wizard"])
wiz = wiz_f.save() wiz = wiz_f.save()
wiz.action_renumber() wiz.action_renumber()
self.assertEqual(opening_invoice.entry_number, "2022/0000000001") self.assertEqual(opening_invoice.entry_number, "2022/00000001")
self.assertEqual(old_invoice.entry_number, "2022/0000000002") self.assertEqual(old_invoice.entry_number, "2022/00000002")
self.assertEqual(new_invoice.entry_number, "2022/0000000003") self.assertEqual(new_invoice.entry_number, "2022/00000003")
self.assertEqual(next_year_invoice.entry_number, "2023/0000000001") self.assertEqual(next_year_invoice.entry_number, "2023/00000001")
@users("test_invoicer") @users("test_invoicer")
def test_install_no_entry_number(self): def test_install_no_entry_number(self):
@ -88,3 +100,21 @@ class RenumberCase(TestAccountReconciliationCommon):
invoice.action_post() invoice.action_post()
# Ensure there's no entry number # Ensure there's no entry number
self.assertFalse(invoice.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")

View File

@ -20,7 +20,11 @@
<field name="inherit_id" ref="account.view_account_journal_form" /> <field name="inherit_id" ref="account.view_account_journal_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="refund_sequence" position="before"> <field name="refund_sequence" position="before">
<field name="entry_number_sequence_id" /> <field name="entry_number_sequence_id_name" invisible="1" />
<field
name="entry_number_sequence_id"
attrs="{'readonly':[('entry_number_sequence_id_name','=','account_journal_general_sequence.default')]}"
/>
</field> </field>
</field> </field>
</record> </record>

View File

@ -41,13 +41,21 @@ class AccountMoveRenumberWizard(models.TransientModel):
def _default_entry_number_sequence(self): def _default_entry_number_sequence(self):
"""Get default sequence if it exists.""" """Get default sequence if it exists."""
return self.env["ir.sequence"].search( 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 @api.model
def _default_available_sequence_ids(self): def _default_available_sequence_ids(self):
"""Let view display only journal-related sequences.""" """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): def action_renumber(self):
"""Renumber moves. """Renumber moves.