Use strict sequence to number account moves

Closes #12603
This commit is contained in:
Cédric Krier
2025-02-11 12:04:55 +01:00
parent 24d89fad16
commit 0fd308172e
15 changed files with 172 additions and 64 deletions
+4 -4
View File
@@ -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'),
+1
View File
@@ -1,3 +1,4 @@
* Use strict sequence to number moves
* Rename post number to number
* Remove number on move and its sequence on journal
+68 -13
View File
@@ -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):
+8 -8
View File
@@ -15,8 +15,8 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_open_period_closed_fiscalyear">
<field name="text">You cannot reopen period "%(period)s" because its fiscal year "%(fiscalyear)s" is closed.</field>
</record>
<record model="ir.message" id="msg_change_period_post_move_sequence">
<field name="text">You cannot change the post move sequence in period "%(period)s" because it has posted moves.</field>
<record model="ir.message" id="msg_change_period_move_sequence">
<field name="text">You cannot change the move sequence in period "%(period)s" because it has posted moves.</field>
</record>
<record model="ir.message" id="msg_close_period_non_posted_moves">
<field name="text">To close period "%(period)s" you must post the moves "%(moves)s".</field>
@@ -24,8 +24,8 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_period_standard_overlap">
<field name="text">The dates of standard period of the same fiscal year can not overlap.</field>
</record>
<record model="ir.message" id="msg_period_post_move_sequence_unique">
<field name="text">A post move sequence can be reused only by periods in the same fiscal year.</field>
<record model="ir.message" id="msg_period_move_sequence_unique">
<field name="text">A move sequence can be reused only by periods in the same fiscal year.</field>
</record>
<record model="ir.message" id="msg_period_fiscalyear_dates">
<field name="text">The dates for period "%(period)s" must be between the dates of its fiscal year "%(fiscalyear)s".</field>
@@ -54,8 +54,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="text">You cannot add/modify/delete tax lines in closed period "%(period)s".</field>
</record>
<record model="ir.message" id="msg_change_fiscalyear_post_move_sequence">
<field name="text">You cannot change the post move sequence on fiscal year "%(fiscalyear)s" because it contains posted moves.</field>
<record model="ir.message" id="msg_change_fiscalyear_move_sequence">
<field name="text">You cannot change the move sequence on fiscal year "%(fiscalyear)s" because it contains posted moves.</field>
</record>
<record model="ir.message" id="msg_no_fiscalyear_date">
<field name="text">To continue, you must create a fiscal year for the date "%(date)s" and company "%(company)s".</field>
@@ -69,8 +69,8 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_open_fiscalyear_earlier">
<field name="text">Open fiscal years can not be before closed fiscal years.</field>
</record>
<record model="ir.message" id="msg_fiscalyear_post_move_sequence_unique">
<field name="text">The post move sequence of fiscal years must be unique.</field>
<record model="ir.message" id="msg_fiscalyear_move_sequence_unique">
<field name="text">The move sequence of fiscal years must be unique.</field>
</record>
<record model="ir.message" id="msg_close_fiscalyear_account_balance_not_zero">
<field name="text">To close the fiscal year, the balance of account "%(account)s" must be zero.</field>
+1 -1
View File
@@ -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():
+69 -15
View File
@@ -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
@@ -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)
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+2 -2
View File
@@ -18,8 +18,8 @@ this repository contains the full copyright notices and license terms. -->
</group>
</page>
<page string="Sequences" id="sequences">
<label name="post_move_sequence"/>
<field name="post_move_sequence"/>
<label name="move_sequence"/>
<field name="move_sequence"/>
</page>
</notebook>
<label name="state" colspan="3"/>
+2 -2
View File
@@ -16,8 +16,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="fiscalyear"/>
<label name="type"/>
<field name="type"/>
<label name="post_move_sequence"/>
<field name="post_move_sequence"/>
<label name="move_sequence"/>
<field name="move_sequence"/>
<newline/>
<label name="state" colspan="3"/>
<field name="state"/>
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='post_move_sequence']" position="after">
<xpath expr="//field[@name='move_sequence']" position="after">
<label name="es_sii_send_invoices"/>
<field name="es_sii_send_invoices"/>
</xpath>
@@ -45,7 +45,7 @@ Create fiscal year::
Set the sequence number::
>>> sequence = fiscalyear.post_move_sequence
>>> sequence = fiscalyear.move_sequence
>>> sequence.number_next = 10
>>> sequence.save()
@@ -61,7 +61,7 @@ Renew fiscal year 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
>>> [int(seq.out_invoice_sequence.number_next)
... for seq in fiscalyear.invoice_sequences]
@@ -73,7 +73,7 @@ Renew fiscal year 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
>>> [int(seq.out_invoice_sequence.number_next)
... for seq in new_fiscalyear.invoice_sequences]
@@ -2,9 +2,7 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath
expr="/form/notebook/page[@id='sequences']/field[@name='post_move_sequence']"
position="after">
<xpath expr="/form/notebook/page[@id='sequences']/field[@name='move_sequence']" position="after">
<field name="invoice_sequences" colspan="4" mode="form,tree"
view_ids="account_invoice.invoice_sequence_view_form,account_invoice.invoice_sequence_view_list_sequence"/>
</xpath>
@@ -2,7 +2,7 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='post_move_sequence']" position="after">
<xpath expr="/form/field[@name='move_sequence']" position="after">
<field name="tax_group_on_cash_basis" colspan="4"/>
</xpath>
</data>