From 0fd308172e3cc0c694c88500c8da9054ba6a0102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 11 Feb 2025 12:04:55 +0100 Subject: [PATCH] Use strict sequence to number account moves Closes #12603 --- demo/account.py | 8 +- modules/account/CHANGELOG | 1 + modules/account/fiscalyear.py | 81 +++++++++++++++--- modules/account/message.xml | 16 ++-- modules/account/move.py | 2 +- modules/account/period.py | 84 +++++++++++++++---- .../tests/scenario_renew_fiscalyear.rst | 10 +-- modules/account/tests/test_module.py | 4 +- modules/account/tests/tools.py | 8 +- modules/account/view/fiscalyear_form.xml | 4 +- modules/account/view/period_form.xml | 4 +- modules/account_es_sii/view/period_form.xml | 2 +- .../tests/scenario_renew_fiscalyear.rst | 6 +- .../account_invoice/view/fiscalyear_form.xml | 4 +- modules/account_tax_cash/view/period_form.xml | 2 +- 15 files changed, 172 insertions(+), 64 deletions(-) diff --git a/demo/account.py b/demo/account.py index c9a50a064b..9fed8b2b7b 100644 --- a/demo/account.py +++ b/demo/account.py @@ -12,7 +12,6 @@ def setup(config, modules, company): AccountTemplate = Model.get('account.account.template') Account = Model.get('account.account') FiscalYear = Model.get('account.fiscalyear') - Sequence = Model.get('ir.sequence') SequenceStrict = Model.get('ir.sequence.strict') SequenceType = Model.get('ir.sequence.type') Party = Model.get('party.party') @@ -63,11 +62,12 @@ def setup(config, modules, company): fiscalyear.start_date = start_date fiscalyear.end_date = start_date + relativedelta(month=12, day=31) fiscalyear.company = company - post_move_sequence = Sequence(name='%s' % start_date.year, + move_sequence = SequenceStrict( + name='%s' % start_date.year, sequence_type=move_sequence_type, company=company) - post_move_sequence.save() - fiscalyear.post_move_sequence = post_move_sequence + move_sequence.save() + fiscalyear.move_sequence = move_sequence invoice_sequence, = fiscalyear.invoice_sequences if 'account_invoice' in modules: for attr, name in (('out_invoice_sequence', 'Invoice'), diff --git a/modules/account/CHANGELOG b/modules/account/CHANGELOG index c386e24657..32c7a4a135 100644 --- a/modules/account/CHANGELOG +++ b/modules/account/CHANGELOG @@ -1,3 +1,4 @@ +* Use strict sequence to number moves * Rename post number to number * Remove number on move and its sequence on journal diff --git a/modules/account/fiscalyear.py b/modules/account/fiscalyear.py index 907da4ece4..2488c358c5 100644 --- a/modules/account/fiscalyear.py +++ b/modules/account/fiscalyear.py @@ -46,13 +46,15 @@ class FiscalYear(Workflow, ModelSQL, ModelView): ('closed', 'Closed'), ('locked', 'Locked'), ], 'State', readonly=True, required=True, sort=False) - post_move_sequence = fields.Many2One( - 'ir.sequence', "Post Move Sequence", required=True, + move_sequence = fields.Many2One( + 'ir.sequence.strict', "Move Sequence", required=True, domain=[ ('sequence_type', '=', Id('account', 'sequence_type_account_move')), ('company', '=', Eval('company', -1)), - ]) + ], + help="Used to generate the move number when posting " + "if the period has no sequence.") company = fields.Many2One( 'company.company', "Company", required=True) icon = fields.Function(fields.Char("Icon"), 'get_icon') @@ -78,8 +80,8 @@ class FiscalYear(Workflow, ModelSQL, ModelView): t.end_date), RangeOverlap), (Case((t.state == 'open', t.id), else_=-1), NotEqual)), 'account.msg_open_fiscalyear_earlier'), - ('post_move_sequence_unique', Unique(t, t.post_move_sequence), - 'account.msg_fiscalyear_post_move_unique'), + ('move_sequence_unique', Unique(t, t.move_sequence), + 'account.msg_fiscalyear_move_unique'), ] cls._order.insert(0, ('start_date', 'DESC')) cls._transitions |= set(( @@ -112,13 +114,66 @@ class FiscalYear(Workflow, ModelSQL, ModelView): @classmethod def __register__(cls, module): + pool = Pool() + Period = pool.get('account.period') + Sequence = pool.get('ir.sequence') + SequenceStrict = pool.get('ir.sequence.strict') + + table_h = cls.__table_handler__(module) cursor = Transaction().connection.cursor() t = cls.__table__() + + migrate_move_sequence = not table_h.column_exist('move_sequence') + super().__register__(module) # Migration from 6.8: rename state close to closed cursor.execute( *t.update([t.state], ['closed'], where=t.state == 'close')) + # Migrationn from 7.4: use strict sequence + if (table_h.column_exist('post_move_sequence') + and migrate_move_sequence): + table_h.not_null_action('post_move_sequence', 'remove') + + period_h = Period.__table_handler__(module) + period = Period.__table__() + old2new = {} + period_migrated = ( + period_h.column_exist('post_move_sequence') + and period_h.column_exist('move_sequence')) + if period_migrated: + cursor.execute(*period.select( + period.post_move_sequence, period.move_sequence)) + old2new.update(cursor) + + cursor.execute(*t.select(t.post_move_sequence, distinct=True)) + for sequence_id, in cursor: + if sequence_id not in old2new: + sequence = Sequence(sequence_id) + new_sequence = SequenceStrict( + name=sequence.name, + sequence_type=sequence.sequence_type, + prefix=sequence.prefix, + suffix=sequence.suffix, + type=sequence.type, + number_next_internal=sequence.number_next_internal, + number_increment=sequence.number_increment, + padding=sequence.padding, + timestamp_rounding=sequence.timestamp_rounding, + timestamp_offset=sequence.timestamp_offset, + last_timestamp=sequence.last_timestamp) + new_sequence.save() + old2new[sequence_id] = new_sequence.id + + for old_id, new_id in old2new.items(): + cursor.execute(*t.update( + [t.move_sequence], [new_id], + where=t.post_move_sequence == old_id)) + + if period_migrated: + table_h.drop_column('post_move_sequence') + period_h.drop_column('post_move_sequence') + @staticmethod def default_state(): return 'open' @@ -160,18 +215,18 @@ class FiscalYear(Workflow, ModelSQL, ModelView): Move = pool.get('account.move') actions = iter(args) for fiscalyears, values in zip(actions, actions): - if values.get('post_move_sequence'): + if values.get('move_sequence'): for fiscalyear in fiscalyears: - if (fiscalyear.post_move_sequence - and fiscalyear.post_move_sequence.id - != values['post_move_sequence']): + if (fiscalyear.move_sequence + and fiscalyear.move_sequence.id + != values['move_sequence']): if Move.search([ ('period.fiscalyear', '=', fiscalyear.id), ('state', '=', 'posted'), ]): raise AccessError( gettext('account.' - 'msg_change_fiscalyear_post_move_sequence', + 'msg_change_fiscalyear_move_sequence', fiscalyear=fiscalyear.rec_name)) super(FiscalYear, cls).write(*args) cls._find_cache.clear() @@ -618,14 +673,14 @@ class RenewFiscalYear(Wizard): def fiscalyear_defaults(self): pool = Pool() - Sequence = pool.get('ir.sequence') + Sequence = pool.get('ir.sequence.strict') defaults = { 'name': self.start.name, 'start_date': self.start.start_date, 'end_date': self.start.end_date, 'periods': [], } - previous_sequence = self.start.previous_fiscalyear.post_move_sequence + previous_sequence = self.start.previous_fiscalyear.move_sequence sequence, = Sequence.copy([previous_sequence], default={ 'name': lambda data: data['name'].replace( @@ -637,7 +692,7 @@ class RenewFiscalYear(Wizard): else: sequence.number_next = previous_sequence.number_next sequence.save() - defaults['post_move_sequence'] = sequence.id + defaults['move_sequence'] = sequence.id return defaults def create_fiscalyear(self): diff --git a/modules/account/message.xml b/modules/account/message.xml index ce54d9a275..fa4c43fef3 100644 --- a/modules/account/message.xml +++ b/modules/account/message.xml @@ -15,8 +15,8 @@ this repository contains the full copyright notices and license terms. --> You cannot reopen period "%(period)s" because its fiscal year "%(fiscalyear)s" is closed. - - You cannot change the post move sequence in period "%(period)s" because it has posted moves. + + You cannot change the move sequence in period "%(period)s" because it has posted moves. To close period "%(period)s" you must post the moves "%(moves)s". @@ -24,8 +24,8 @@ this repository contains the full copyright notices and license terms. --> The dates of standard period of the same fiscal year can not overlap. - - A post move sequence can be reused only by periods in the same fiscal year. + + A move sequence can be reused only by periods in the same fiscal year. The dates for period "%(period)s" must be between the dates of its fiscal year "%(fiscalyear)s". @@ -54,8 +54,8 @@ this repository contains the full copyright notices and license terms. --> You cannot add/modify/delete tax lines in closed period "%(period)s". - - You cannot change the post move sequence on fiscal year "%(fiscalyear)s" because it contains posted moves. + + You cannot change the move sequence on fiscal year "%(fiscalyear)s" because it contains posted moves. To continue, you must create a fiscal year for the date "%(date)s" and company "%(company)s". @@ -69,8 +69,8 @@ this repository contains the full copyright notices and license terms. --> Open fiscal years can not be before closed fiscal years. - - The post move sequence of fiscal years must be unique. + + The move sequence of fiscal years must be unique. To close the fiscal year, the balance of account "%(account)s" must be zero. diff --git a/modules/account/move.py b/modules/account/move.py index be7af0af7e..548d458937 100644 --- a/modules/account/move.py +++ b/modules/account/move.py @@ -512,7 +512,7 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView): move.state = 'posted' if not move.number: move.post_date = today - missing_number[move.period.post_move_sequence_used].append( + missing_number[move.period.move_sequence_used].append( move) for sequence, m_moves in missing_number.items(): diff --git a/modules/account/period.py b/modules/account/period.py index 715d0bbc15..41637eb9c2 100644 --- a/modules/account/period.py +++ b/modules/account/period.py @@ -36,7 +36,8 @@ class Period(Workflow, ModelSQL, ModelView): ('closed', 'Closed'), ('locked', 'Locked'), ], 'State', readonly=True, required=True, sort=False) - post_move_sequence = fields.Many2One('ir.sequence', 'Post Move Sequence', + move_sequence = fields.Many2One( + 'ir.sequence.strict', "Move Sequence", domain=[ ('sequence_type', '=', Id('account', 'sequence_type_account_move')), @@ -44,7 +45,9 @@ class Period(Workflow, ModelSQL, ModelView): ('company', '=', None), ('company', '=', Eval('company', -1)), ], - ]) + ], + help="Used to generate the move number when posting.\n" + "Leave empty to use the fiscal year sequence.") type = fields.Selection([ ('standard', 'Standard'), ('adjustment', 'Adjustment'), @@ -67,11 +70,11 @@ class Period(Workflow, ModelSQL, ModelView): (DateRange(t.start_date, t.end_date, '[]'), RangeOverlap), where=t.type == 'standard'), 'account.msg_period_standard_overlap'), - ('post_move_sequence_unique', + ('move_sequence_unique', Exclude(t, - (t.post_move_sequence, Equal), + (t.move_sequence, Equal), (t.fiscalyear, NotEqual)), - 'account.msg_period_post_move_sequence_unique'), + 'account.msg_period_move_sequence_unique'), ] cls._sql_indexes.add( Index( @@ -102,13 +105,67 @@ class Period(Workflow, ModelSQL, ModelView): @classmethod def __register__(cls, module): + pool = Pool() + FiscalYear = pool.get('account.fiscalyear') + Sequence = pool.get('ir.sequence') + SequenceStrict = pool.get('ir.sequence.strict') + + table_h = cls.__table_handler__(module) cursor = Transaction().connection.cursor() t = cls.__table__() + + migrate_move_sequence = not table_h.column_exist('move_sequence') + super().__register__(module) # Migration from 6.8: rename state close to closed cursor.execute( *t.update([t.state], ['closed'], where=t.state == 'close')) + # Migrationn from 7.4: use strict sequence + if (table_h.column_exist('post_move_sequence') + and migrate_move_sequence): + table_h.not_null_action('post_move_sequence', 'remove') + + fiscalyear_h = FiscalYear.__table_handler__(module) + fiscalyear = FiscalYear.__table__() + old2new = {} + fiscalyear_migrated = ( + fiscalyear_h.column_exist('post_move_sequence') + and fiscalyear_h.column_exist('move_sequence')) + if fiscalyear_migrated: + cursor.execute(*fiscalyear.select( + fiscalyear.post_move_sequence, + fiscalyear.move_sequence)) + old2new.update(cursor) + + cursor.execute(*t.select(t.post_move_sequence, distinct=True)) + for sequence_id, in cursor: + if sequence_id not in old2new: + sequence = Sequence(sequence_id) + new_sequence = SequenceStrict( + name=sequence.name, + sequence_type=sequence.sequence_type, + prefix=sequence.prefix, + suffix=sequence.suffix, + type=sequence.type, + number_next_internal=sequence.number_next_internal, + number_increment=sequence.number_increment, + padding=sequence.padding, + timestamp_rounding=sequence.timestamp_rounding, + timestamp_offset=sequence.timestamp_offset, + last_timestamp=sequence.last_timestamp) + new_sequence.save() + old2new[sequence_id] = new_sequence.id + + for old_id, new_id in old2new.items(): + cursor.execute(*t.update( + [t.move_sequence], [new_id], + where=t.post_move_sequence == old_id)) + + if fiscalyear_migrated: + table_h.drop_column('post_move_sequence') + fiscalyear_h.drop_column('post_move_sequence') + @staticmethod def default_state(): return 'open' @@ -273,9 +330,6 @@ class Period(Workflow, ModelSQL, ModelView): raise AccessError( gettext('account.msg_create_period_closed_fiscalyear', fiscalyear=fiscalyear.rec_name)) - if not vals.get('post_move_sequence'): - vals['post_move_sequence'] = ( - fiscalyear.post_move_sequence.id) periods = super(Period, cls).create(vlist) cls._find_cache.clear() return periods @@ -294,18 +348,18 @@ class Period(Workflow, ModelSQL, ModelView): 'account.msg_open_period_closed_fiscalyear', period=period.rec_name, fiscalyear=period.fiscalyear.rec_name)) - if values.get('post_move_sequence'): + if values.get('move_sequence'): for period in periods: - if (period.post_move_sequence - and period.post_move_sequence.id - != values['post_move_sequence']): + if (period.move_sequence + and period.move_sequence.id + != values['move_sequence']): if Move.search([ ('period', '=', period.id), ('state', '=', 'posted'), ]): raise AccessError( gettext('account' - '.msg_change_period_post_move_sequence', + '.msg_change_period_move_sequence', period=period.rec_name)) args.extend((periods, values)) super(Period, cls).write(*args) @@ -375,5 +429,5 @@ class Period(Workflow, ModelSQL, ModelView): pass @property - def post_move_sequence_used(self): - return self.post_move_sequence or self.fiscalyear.post_move_sequence + def move_sequence_used(self): + return self.move_sequence or self.fiscalyear.move_sequence diff --git a/modules/account/tests/scenario_renew_fiscalyear.rst b/modules/account/tests/scenario_renew_fiscalyear.rst index 9662c559a7..335d6bc4be 100644 --- a/modules/account/tests/scenario_renew_fiscalyear.rst +++ b/modules/account/tests/scenario_renew_fiscalyear.rst @@ -26,7 +26,7 @@ Create fiscal year:: Set the sequence number:: - >>> sequence = fiscalyear.post_move_sequence + >>> sequence = fiscalyear.move_sequence >>> sequence.number_next = 10 >>> sequence.save() @@ -38,7 +38,7 @@ Renew fiscalyear using the wizard:: >>> new_fiscalyear, = renew_fiscalyear.actions[0] >>> len(new_fiscalyear.periods) 12 - >>> int(new_fiscalyear.post_move_sequence.number_next) + >>> int(new_fiscalyear.move_sequence.number_next) 10 Renew fiscalyear resetting sequences:: @@ -47,12 +47,12 @@ Renew fiscalyear resetting sequences:: >>> renew_fiscalyear.form.reset_sequences = True >>> renew_fiscalyear.execute('create_') >>> new_fiscalyear, = renew_fiscalyear.actions[0] - >>> int(new_fiscalyear.post_move_sequence.number_next) + >>> int(new_fiscalyear.move_sequence.number_next) 1 Set the sequence name:: - >>> sequence = new_fiscalyear.post_move_sequence + >>> sequence = new_fiscalyear.move_sequence >>> sequence.name = 'Sequence %s' % new_fiscalyear.name >>> sequence.save() @@ -62,5 +62,5 @@ Renew fiscalyear and test sequence name is updated:: >>> renew_fiscalyear.form.reset_sequences = True >>> renew_fiscalyear.execute('create_') >>> new_fiscalyear, = renew_fiscalyear.actions[0] - >>> assertEqual(new_fiscalyear.post_move_sequence.name, + >>> assertEqual(new_fiscalyear.move_sequence.name, ... 'Sequence %s' % new_fiscalyear.name) diff --git a/modules/account/tests/test_module.py b/modules/account/tests/test_module.py index 4325c6d442..1c815ed100 100644 --- a/modules/account/tests/test_module.py +++ b/modules/account/tests/test_module.py @@ -92,7 +92,7 @@ def create_chart(company, tax=False, chart='account.account_template_root_en'): def get_fiscalyear(company, today=None, start_date=None, end_date=None): pool = Pool() - Sequence = pool.get('ir.sequence') + Sequence = pool.get('ir.sequence.strict') SequenceType = pool.get('ir.sequence.type') FiscalYear = pool.get('account.fiscalyear') @@ -114,7 +114,7 @@ def get_fiscalyear(company, today=None, start_date=None, end_date=None): fiscalyear = FiscalYear(name='%s' % today.year, company=company) fiscalyear.start_date = start_date fiscalyear.end_date = end_date - fiscalyear.post_move_sequence = sequence + fiscalyear.move_sequence = sequence return fiscalyear diff --git a/modules/account/tests/tools.py b/modules/account/tests/tools.py index e50b54856e..512c400120 100644 --- a/modules/account/tests/tools.py +++ b/modules/account/tests/tools.py @@ -15,7 +15,7 @@ __all__ = ['create_fiscalyear', 'create_chart', 'get_accounts', def create_fiscalyear(company=None, today=None, config=None): "Create a fiscal year for the company on today or range" FiscalYear = Model.get('account.fiscalyear', config=config) - Sequence = Model.get('ir.sequence', config=config) + Sequence = Model.get('ir.sequence.strict', config=config) SequenceType = Model.get('ir.sequence.type', config=config) if not company: @@ -39,12 +39,12 @@ def create_fiscalyear(company=None, today=None, config=None): sequence_type, = SequenceType.find( [('name', '=', "Account Move")], limit=1) - post_move_sequence = Sequence( + move_sequence = Sequence( name=str(today.year), sequence_type=sequence_type, company=company) - post_move_sequence.save() - fiscalyear.post_move_sequence = post_move_sequence + move_sequence.save() + fiscalyear.move_sequence = move_sequence return fiscalyear diff --git a/modules/account/view/fiscalyear_form.xml b/modules/account/view/fiscalyear_form.xml index 2238979b75..2444371a66 100644 --- a/modules/account/view/fiscalyear_form.xml +++ b/modules/account/view/fiscalyear_form.xml @@ -18,8 +18,8 @@ this repository contains the full copyright notices and license terms. --> -