From d4b4069deca34afdd30cedcda59e14577ce1f24c Mon Sep 17 00:00:00 2001 From: william-andre Date: Mon, 19 Oct 2020 08:39:03 +0000 Subject: [PATCH] [FIX] account: prevent changing date and journal of move with name We need to ensure the name stays consitent with the journal_id and the date when we try to change one of those fields. X-original-commit: 634aaeeac36d2371743338536466a0bcc9ad6c69 --- addons/account/i18n/account.pot | 16 +++++- .../account/models/account_bank_statement.py | 2 +- addons/account/models/account_move.py | 12 ++++- addons/account/models/sequence_mixin.py | 51 +++++++++++++++---- .../account/tests/test_account_move_entry.py | 17 ++++--- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot index 50776854be5..180f59711be 100644 --- a/addons/account/i18n/account.pot +++ b/addons/account/i18n/account.pot @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-10-29 15:48+0000\n" -"PO-Revision-Date: 2020-10-29 15:48+0000\n" +"PO-Revision-Date: 2020-11-05 16:17+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -11680,6 +11680,12 @@ msgid "" " There is nothing left to process." msgstr "" +#: code:addons/account/models/sequence_mixin.py:0 +#, python-format +msgid "" +"The date (%(date)s) doesn't match the sequence number (%(sequence)s).\n" +"You might want to remove the sequence before proceeding with the change of the date." + #. module: account #: code:addons/account/models/account_payment_term.py:0 #, python-format @@ -13556,6 +13562,14 @@ msgid "" " the journal: %s." msgstr "" +#. module: account +#: code:addons/account/models/account_move.py:0 +#, python-format +msgid "" +"You cannot edit the journal of an account move if it has already a sequence " +"number assigned." +msgstr "" + #. module: account #: code:addons/account/models/account_move.py:0 #, python-format diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 5c3219358da..7c3d66af6e4 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -317,7 +317,7 @@ class AccountBankStatement(models.Model): st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Profit)") st_line_vals['counterpart_account_id'] = stmt.journal_id.profit_account_id.id - + self.env['account.bank.statement.line'].create(st_line_vals) else: balance_end_real = formatLang(self.env, stmt.balance_end_real, currency_obj=stmt.currency_id) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 0aacbbdc5b5..639babf783b 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1063,8 +1063,14 @@ class AccountMove(models.Model): # it. We only check the first move as an approximation (enough for new in form view) pass elif (move.name and move.name != '/') or move.state != 'posted': - # Has already a name or is not posted, we don't add to a batch - continue + try: + if not move.posted_before: + move._constrains_date_sequence() + # Has already a name or is not posted, we don't add to a batch + continue + except ValidationError: + # Has never been posted and the name doesn't match the date: recompute it + pass group = grouped[journal_key(move)][date_key(move)] if not group['records']: # Compute all the values needed to sequence this whole group @@ -1792,6 +1798,8 @@ class AccountMove(models.Model): raise UserError(_('You cannot overwrite the values ensuring the inalterability of the accounting.')) if (move.posted_before and 'journal_id' in vals and move.journal_id.id != vals['journal_id']): raise UserError(_('You cannot edit the journal of an account move if it has been posted once.')) + if (move.name and move.name != '/' and 'journal_id' in vals and move.journal_id.id != vals['journal_id']): + raise UserError(_('You cannot edit the journal of an account move if it has already a sequence number assigned.')) # You can't change the date of a move being inside a locked period. if 'date' in vals and move.date != vals['date']: diff --git a/addons/account/models/sequence_mixin.py b/addons/account/models/sequence_mixin.py index 2cd9739da50..66d56ba6bb9 100644 --- a/addons/account/models/sequence_mixin.py +++ b/addons/account/models/sequence_mixin.py @@ -44,6 +44,28 @@ class SequenceMixin(models.AbstractModel): field=sql.Identifier(self._sequence_field), )) + def __init__(self, pool, cr): + api.constrains(self._sequence_field, self._sequence_date_field)(type(self)._constrains_date_sequence) + return super().__init__(pool, cr) + + def _constrains_date_sequence(self): + for record in self: + date = fields.Date.to_date(record[record._sequence_date_field]) + sequence = record[record._sequence_field] + if sequence and 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 (%(date)s) doesn't match the sequence number (%(sequence)s).\n" + "You might want to remove the sequence before proceeding with the change of the date." + ) % { + 'date': date, + 'sequence': sequence, + }) + @api.depends(lambda self: [self._sequence_field]) def _compute_split_sequence(self): for record in self: @@ -61,25 +83,34 @@ class SequenceMixin(models.AbstractModel): periodicity. Typically, it is the last before the one you want to give a sequence. """ - def _check_grouping(grouping, optional=None, required=None): - sequence_dict = sequence.groupdict() - check = all(key in sequence_dict for key in (optional or [])) and all(sequence_dict.get(key) for key in (required or [])) - check &= 'year' not in (required or []) or 2000 <= int(sequence_dict.get('year', -1)) <= 2100 or len(sequence_dict.get('year', '')) == 2 - check &= 'month' not in (required or []) or 1 <= int(sequence_dict.get('month', -1)) <= 12 - return check + def _check_grouping(grouping, required): + sequence_dict = grouping.groupdict() + if 'seq' not in sequence_dict or any(not sequence_dict.get(key) for key in required): + return False + if 'year' in required and not ( + 2000 <= int(sequence_dict.get('year') or -1) <= 2100 + or len(sequence_dict.get('year') or '') == 2 + ): + return False + if 'month' in required and not 1 <= int(sequence_dict.get('month') or -1) <= 12: + return False + return True if not name: return False sequence = re.match(self._sequence_monthly_regex, name) - if sequence and _check_grouping(sequence, ['seq'], ['year', 'month']): + if sequence and _check_grouping(sequence, ['year', 'month']): return 'month' sequence = re.match(self._sequence_yearly_regex, name) - if sequence and _check_grouping(sequence, ['seq'], ['year']): + if sequence and _check_grouping(sequence, ['year']): return 'year' sequence = re.match(self._sequence_fixed_regex, name) - if sequence and _check_grouping(sequence, ['seq']): + if sequence and _check_grouping(sequence, []): return 'never' - raise ValidationError(_('The sequence regex should at least contain the seq grouping keys. For instance:\n^(?P.*?)(?P\d*)(?P\D*?)$')) + raise ValidationError(_( + 'The sequence regex should at least contain the seq grouping keys. For instance:\n' + '^(?P.*?)(?P\d*)(?P\D*?)$' + )) def _get_last_sequence_domain(self, relaxed=False): """Get the sql domain to retreive the previous sequence number. diff --git a/addons/account/tests/test_account_move_entry.py b/addons/account/tests/test_account_move_entry.py index 2b1b5bd412a..708ac22415a 100644 --- a/addons/account/tests/test_account_move_entry.py +++ b/addons/account/tests/test_account_move_entry.py @@ -379,17 +379,17 @@ class TestAccountMove(AccountTestInvoicingCommon): copy2.journal_id = new_journal self.assertEqual(copy2.name, 'MISC2/2016/01/0001') with Form(copy2) as move_form: # It is editable in the form - move_form.name = 'MyMISC/2099/0001' + move_form.name = 'MyMISC/2016/0001' copy2.action_post() - self.assertEqual(copy2.name, 'MyMISC/2099/0001') + self.assertEqual(copy2.name, 'MyMISC/2016/0001') copy3 = copy2.copy({'date': copy2.date}) self.assertEqual(copy3.name, '/') with self.assertRaises(AssertionError): with Form(copy2) as move_form: # It is not editable in the form - move_form.name = 'MyMISC/2099/0002' + move_form.name = 'MyMISC/2016/0002' copy3.action_post() - self.assertEqual(copy3.name, 'MyMISC/2099/0002') + self.assertEqual(copy3.name, 'MyMISC/2016/0002') copy3.name = 'MISC2/2016/00002' copy4 = copy2.copy({'date': copy2.date}) @@ -412,10 +412,10 @@ class TestAccountMove(AccountTestInvoicingCommon): ('JRNL/2016/00001', 'JRNL/2016/00002', 'JRNL/2016/00003', 'JRNL/2017/00001'), ('1234567', '1234568', '1234569', '1234570'), ('20190910', '20190911', '20190912', '20190913'), - ('2019-0910', '2019-0911', '2019-0912', '2017-0001'), - ('201909-10', '201909-11', '201604-01', '201703-01'), - ('20-10-10', '20-10-11', '16-04-01', '17-03-01'), - ('2010-10', '2010-11', '2010-12', '2017-01'), + ('2016-0910', '2016-0911', '2016-0912', '2017-0001'), + ('201603-10', '201603-11', '201604-01', '201703-01'), + ('16-03-10', '16-03-11', '16-04-01', '17-03-01'), + ('2016-10', '2016-11', '2016-12', '2017-01'), ('JRNL/2016/00001suffix', 'JRNL/2016/00002suffix', 'JRNL/2016/00003suffix', 'JRNL/2017/00001suffix'), ] other_moves = self.env['account.move'].search([('journal_id', '=', self.test_move.journal_id.id)]) - self.test_move @@ -472,6 +472,7 @@ class TestAccountMove(AccountTestInvoicingCommon): def test_journal_override_sequence_regex(self): other_moves = self.env['account.move'].search([('journal_id', '=', self.test_move.journal_id.id)]) - self.test_move other_moves.unlink() # Do not interfere when trying to get the highest name for new periods + self.test_move.date = '2020-01-01' self.test_move.name = '00000876-G 0002/2020' next = self.test_move.copy({'date': self.test_move.date}) next.action_post()