[FIX] account: prevent sequence number gap in Accounting Firms mode

When modifying the accounting date of an invoice in Accounting Firms
mode, Odoo always generates a new invoice sequence number. This behavior
can introduce gaps in the sequences.

1. Activate Accounting Firms mode.
2. Create a new Draft Invoice and save it.
3. Create a second Draft Invoice and save it.
4. Modify the accounting date of the first invoice and save it.
5. Observe that the first invoice's invoice sequence number is updated
   to the next sequence, creating a gap.

Recompute the sequence only when the new date falls into a different
fiscal year.

opw-3164537

closes odoo/odoo#121215

X-original-commit: 28c5c37c420eda8f916dc0f031521313ac18ec08
Signed-off-by: Nshimiyimana Serge Séna (sesn) <sesn@odoo.com>
Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
Nshimiyimana Séna
2023-05-12 08:17:01 +02:00
parent 0a159622c2
commit 63d7e39a09
3 changed files with 95 additions and 27 deletions
+7 -10
View File
@@ -731,22 +731,19 @@ class AccountMove(models.Model):
self = self.sorted(lambda m: (m.date, m.ref or '', m.id))
for move in self:
if not move.highest_name and not move.posted_before and move.date and (not move.name or move.name == '/'):
name_not_set = not move.name or move.name == '/'
if not move.highest_name and not move.posted_before and move.date and name_not_set:
# In the form view, we need to compute a default sequence so that the user can edit
# it. We only check the first move as an approximation (enough for new in form view)
move._set_next_sequence()
elif move.quick_edit_mode and not move.posted_before:
# We always suggest the next sequence as the default name of the new move
move._set_next_sequence()
elif (move.name and move.name != '/') or move.state != 'posted':
try:
move._constrains_date_sequence()
# The name matches the date: we don't recompute
except ValidationError:
# Has never been posted and the name doesn't match the date: recompute it
if name_not_set or not move._sequence_matches_date():
move._set_next_sequence()
else:
# The name is not set yet and it is posted
elif not move.posted_before and not move._sequence_matches_date():
# The date changed before posting on first move of period
move._set_next_sequence()
elif (name_not_set and move.state == 'posted'):
move._set_next_sequence()
self.filtered(lambda m: not m.name).name = '/'
+32 -16
View File
@@ -50,6 +50,22 @@ class SequenceMixin(models.AbstractModel):
def _must_check_constrains_date_sequence(self):
return True
def _sequence_matches_date(self):
self.ensure_one()
date = fields.Date.to_date(self[self._sequence_date_field])
sequence = self[self._sequence_field]
if not sequence or not date:
return True
format_values = self._get_sequence_format_param(sequence)[1]
year_match = (
not format_values["year"]
or format_values["year"] == date.year % 10 ** len(str(format_values["year"]))
)
month_match = not format_values['month'] or format_values['month'] == date.month
return year_match and month_match
@api.constrains(lambda self: (self._sequence_field, self._sequence_date_field))
def _constrains_date_sequence(self):
# Make it possible to bypass the constraint to allow edition of already messed up documents.
@@ -63,22 +79,22 @@ class SequenceMixin(models.AbstractModel):
continue
date = fields.Date.to_date(record[record._sequence_date_field])
sequence = record[record._sequence_field]
if sequence and date and date > constraint_date:
format_values = record._get_sequence_format_param(sequence)[1]
if (
format_values['year'] and format_values['year'] != date.year % 10**len(str(format_values['year']))
or format_values['month'] and format_values['month'] != date.month
):
raise ValidationError(_(
"The %(date_field)s (%(date)s) doesn't match the sequence number of the related %(model)s (%(sequence)s)\n"
"You will need to clear the %(model)s's %(sequence_field)s to proceed.\n"
"In doing so, you might want to resequence your entries in order to maintain a continuous date-based sequence.",
date=format_date(self.env, date),
sequence=sequence,
date_field=record._fields[record._sequence_date_field]._description_string(self.env),
sequence_field=record._fields[record._sequence_field]._description_string(self.env),
model=self.env['ir.model']._get(record._name).display_name,
))
if (
sequence
and date
and date > constraint_date
and not record._sequence_matches_date()
):
raise ValidationError(_(
"The %(date_field)s (%(date)s) doesn't match the sequence number of the related %(model)s (%(sequence)s)\n"
"You will need to clear the %(model)s's %(sequence_field)s to proceed.\n"
"In doing so, you might want to resequence your entries in order to maintain a continuous date-based sequence.",
date=format_date(self.env, date),
sequence=sequence,
date_field=record._fields[record._sequence_date_field]._description_string(self.env),
sequence_field=record._fields[record._sequence_field]._description_string(self.env),
model=self.env['ir.model']._get(record._name).display_name,
))
@api.depends(lambda self: [self._sequence_field])
def _compute_split_sequence(self):
+56 -1
View File
@@ -2,7 +2,7 @@
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo.tests.common import Form, TransactionCase
from odoo import fields, api, SUPERUSER_ID
from odoo import fields, api, SUPERUSER_ID, Command
from odoo.exceptions import ValidationError, UserError
from odoo.tools import mute_logger
@@ -66,6 +66,61 @@ class TestSequenceMixin(TestSequenceMixinCommon):
self.test_move.action_post()
self.assertEqual(self.test_move.name, 'MyMISC/2020/0000001')
def test_sequence_change_date_with_quick_edit_mode(self):
"""
Test the sequence update behavior when changing the date of a move in quick edit mode.
The sequence should only be recalculated if a value (year or month) utilized in the sequence is modified.
"""
self.env.company.quick_edit_mode = "out_and_in_invoices"
self.env.company.fiscalyear_last_day = 30
self.env.company.fiscalyear_last_month = '12'
bill = self.env['account.move'].create({
'partner_id': 1,
'move_type': 'in_invoice',
'date': '2016-01-01',
'line_ids': [
Command.create({
'name': 'line',
'account_id': self.company_data['default_account_revenue'].id,
}),
]
})
bill = bill.copy({'date': '2016-01-01'})
bill.copy({'date': '2016-01-01'})
self.assertEqual(bill.name, 'BILL/2016/01/0002')
with Form(bill) as bill_form:
bill_form.date = '2016-01-02'
self.assertEqual(bill_form.name, 'BILL/2016/01/0002')
bill_form.date = '2016-02-02'
self.assertEqual(bill_form.name, 'BILL/2016/02/0001')
bill_form.date = '2017-01-01'
self.assertEqual(bill_form.name, 'BILL/2017/01/0001')
invoice = self.env['account.move'].create({
'partner_id': 1,
'move_type': 'out_invoice',
'date': '2016-01-01',
'line_ids': [
Command.create({
'name': 'line',
'account_id': self.company_data['default_account_revenue'].id,
}),
]
})
invoice = invoice.copy({'date': '2016-01-01'})
invoice.copy({'date': '2016-01-01'})
self.assertEqual(invoice.name, 'INV/2016/00002')
with Form(invoice) as invoice_form:
invoice_form.date = '2016-01-02'
self.assertEqual(invoice_form.name, 'INV/2016/00002')
invoice_form.date = '2016-02-02'
self.assertEqual(invoice_form.name, 'INV/2016/00002')
invoice_form.date = '2017-01-01'
self.assertEqual(invoice_form.name, 'INV/2017/00001')
def test_journal_sequence(self):
self.assertEqual(self.test_move.name, 'MISC/2016/01/0001')
self.test_move.action_post()