[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:
@@ -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 = '/'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user