From b1e320e204be99c893df8ef7aec5226b19c6917f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 2 Apr 2017 12:46:28 +0200 Subject: [PATCH] Add definitive lock of period and fiscal year issue6339 review28201002 --- CHANGELOG | 2 + __init__.py | 6 -- doc/index.rst | 9 +-- fiscalyear.py | 87 ++++++++++++------------ fiscalyear.xml | 35 ++++++---- journal.py | 73 ++++++++------------ journal.xml | 54 +++++---------- period.py | 99 +++++++++++++++------------- period.xml | 50 +++++--------- view/fiscalyear_close_start_form.xml | 7 -- view/fiscalyear_form.xml | 8 --- view/fiscalyear_list_close.xml | 12 ++++ view/journal_period_list_close.xml | 10 +++ view/journal_period_tree.xml | 1 - view/period_list_close.xml | 14 ++++ 15 files changed, 224 insertions(+), 243 deletions(-) delete mode 100644 view/fiscalyear_close_start_form.xml create mode 100644 view/fiscalyear_list_close.xml create mode 100644 view/journal_period_list_close.xml create mode 100644 view/period_list_close.xml diff --git a/CHANGELOG b/CHANGELOG index 2484f15187..b89fb846e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add definitive lock of period and fiscal year + Version 4.2.0 - 2016-11-28 * Bug fixes (see mercurial logs for details) * Add test wizard for taxes diff --git a/__init__.py b/__init__.py index 86b3b6b481..73d5853dd4 100644 --- a/__init__.py +++ b/__init__.py @@ -18,7 +18,6 @@ def register(): FiscalYear, FiscalYearLine, BalanceNonDeferralStart, - CloseFiscalYearStart, TypeTemplate, Type, AccountTemplate, @@ -83,14 +82,9 @@ def register(): Pool.register( OpenType, BalanceNonDeferral, - CloseFiscalYear, OpenChartAccount, CreateChart, UpdateChart, - ClosePeriod, - ReOpenPeriod, - CloseJournalPeriod, - ReOpenJournalPeriod, OpenJournal, OpenAccount, ReconcileLines, diff --git a/doc/index.rst b/doc/index.rst index 9b89769f37..602e2ff85a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,15 +8,16 @@ Fiscal Year *********** A fiscal year aggregates a set of periods that are included between -two dates. A Fiscal year can be *Open* or *Closed*. Closing a fiscal -year will close all the corresponding periods. +two dates. A Fiscal year can be *Open*, *Closed* or *Locked*. Closing a fiscal +year will close all the corresponding periods. A *Locked* fiscal can not be +re-open. - Name: The name of the fiscal year. - Code: The code, useful for fast data entry and searching. - Starting and Ending Date: The dates in which the periods should be included. - Company: The company for which the fiscal year is defined. -- State: Can be *Open* or *Closed*. +- State: Can be *Open*, *Closed* or *Locked*. - Periods: The list of periods. - Post Move Sequence: The sequence to use for numbering moves in this fiscal year. @@ -30,7 +31,7 @@ Period ****** A period is mainly defined by a Starting and an Ending date, a Fiscal -Year, a Type and a State (*Open* or *Closed* ). +Year, a Type and a State (*Open*, *Closed* or *Locked*). The type can be *Standard* or *Adjustement*: Periods of type *Standard* on the same fiscal year can not overlap. Period of type diff --git a/fiscalyear.py b/fiscalyear.py index 6eaa033804..4c36989797 100644 --- a/fiscalyear.py +++ b/fiscalyear.py @@ -1,25 +1,23 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from dateutil.relativedelta import relativedelta -from trytond.model import ModelView, ModelSQL, fields -from trytond.wizard import Wizard, StateView, StateTransition, StateAction, \ - Button +from trytond.model import ModelView, ModelSQL, Workflow, fields +from trytond.wizard import Wizard, StateView, StateAction, Button from trytond.tools import datetime_strftime from trytond.pyson import Eval, If, PYSONEncoder from trytond.transaction import Transaction from trytond.pool import Pool __all__ = ['FiscalYear', 'FiscalYearLine', - 'BalanceNonDeferralStart', 'BalanceNonDeferral', - 'CloseFiscalYearStart', 'CloseFiscalYear'] + 'BalanceNonDeferralStart', 'BalanceNonDeferral'] STATES = { - 'readonly': Eval('state') == 'close', + 'readonly': Eval('state') != 'open', } DEPENDS = ['state'] -class FiscalYear(ModelSQL, ModelView): +class FiscalYear(Workflow, ModelSQL, ModelView): 'Fiscal Year' __name__ = 'account.fiscalyear' name = fields.Char('Name', size=None, required=True, depends=DEPENDS) @@ -32,9 +30,10 @@ class FiscalYear(ModelSQL, ModelView): periods = fields.One2Many('account.period', 'fiscalyear', 'Periods', states=STATES, depends=DEPENDS) state = fields.Selection([ - ('open', 'Open'), - ('close', 'Close'), - ], 'State', readonly=True, required=True) + ('open', 'Open'), + ('close', 'Close'), + ('locked', 'Locked'), + ], 'State', readonly=True, required=True) post_move_sequence = fields.Many2One('ir.sequence', 'Post Move Sequence', required=True, domain=[('code', '=', 'account.move'), ['OR', @@ -53,6 +52,7 @@ class FiscalYear(ModelSQL, ModelView): ], select=True) close_lines = fields.Many2Many('account.fiscalyear-account.move.line', 'fiscalyear', 'line', 'Close Lines') + icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __setup__(cls): @@ -73,6 +73,11 @@ class FiscalYear(ModelSQL, ModelView): 'reopen_error': ('You can not reopen fiscal year "%s" until ' 'you reopen all later fiscal years.'), }) + cls._transitions |= set(( + ('open', 'close'), + ('close', 'locked'), + ('close', 'open'), + )) cls._buttons.update({ 'create_period': { 'invisible': ((Eval('state') != 'open') @@ -88,6 +93,9 @@ class FiscalYear(ModelSQL, ModelView): 'reopen': { 'invisible': Eval('state') != 'close', }, + 'lock': { + 'invisible': Eval('state') != 'close', + }, }) @staticmethod @@ -98,6 +106,13 @@ class FiscalYear(ModelSQL, ModelView): def default_company(): return Transaction().context.get('company') + def get_icon(self, name): + return { + 'open': 'tryton-open', + 'close': 'tryton-close', + 'locked': 'tryton-readonly', + }.get(self.state) + @classmethod def validate(cls, years): super(FiscalYear, cls).validate(years) @@ -258,6 +273,7 @@ class FiscalYear(ModelSQL, ModelView): @classmethod @ModelView.button + @Workflow.transition('close') def close(cls, fiscalyears): ''' Close a fiscal year @@ -266,6 +282,12 @@ class FiscalYear(ModelSQL, ModelView): Period = pool.get('account.period') Account = pool.get('account.account') Deferral = pool.get('account.account.deferral') + transaction = Transaction() + database = transaction.database + connection = transaction.connection + + # Lock period to be sure no new period will be created in between. + database.lock(connection, Period._table) deferrals = [] for fiscalyear in fiscalyears: @@ -276,11 +298,6 @@ class FiscalYear(ModelSQL, ModelView): ]): cls.raise_user_error('close_error', (fiscalyear.rec_name,)) - # First close the fiscalyear to be sure - # it will not have new period created between. - cls.write([fiscalyear], { - 'state': 'close', - }) periods = Period.search([ ('fiscalyear', '=', fiscalyear.id), ]) @@ -297,6 +314,7 @@ class FiscalYear(ModelSQL, ModelView): @classmethod @ModelView.button + @Workflow.transition('open') def reopen(cls, fiscalyears): ''' Re-open a fiscal year @@ -306,7 +324,7 @@ class FiscalYear(ModelSQL, ModelView): for fiscalyear in fiscalyears: if cls.search([ ('start_date', '>=', fiscalyear.end_date), - ('state', '=', 'close'), + ('state', '!=', 'open'), ('company', '=', fiscalyear.company.id), ]): cls.raise_user_error('reopen_error') @@ -316,9 +334,16 @@ class FiscalYear(ModelSQL, ModelView): ]) Deferral.delete(deferrals) - cls.write([fiscalyear], { - 'state': 'open', - }) + @classmethod + @ModelView.button + @Workflow.transition('locked') + def lock(cls, fiscalyears): + pool = Pool() + Period = pool.get('account.period') + periods = Period.search([ + ('fiscalyear', 'in', [f.id for f in fiscalyears]), + ]) + Period.lock(periods) class FiscalYearLine(ModelSQL): @@ -453,27 +478,3 @@ class BalanceNonDeferral(Wizard): ('origin', '=', str(self.start.fiscalyear)), ]) return action, {} - - -class CloseFiscalYearStart(ModelView): - 'Close Fiscal Year' - __name__ = 'account.fiscalyear.close.start' - close_fiscalyear = fields.Many2One('account.fiscalyear', - 'Fiscal Year to close', required=True, - domain=[('state', '!=', 'close')]) - - -class CloseFiscalYear(Wizard): - 'Close Fiscal Year' - __name__ = 'account.fiscalyear.close' - start = StateView('account.fiscalyear.close.start', - 'account.fiscalyear_close_start_view_form', [ - Button('Cancel', 'end', 'tryton-cancel'), - Button('Close', 'close', 'tryton-ok', default=True), - ]) - close = StateTransition() - - def transition_close(self): - Fiscalyear = Pool().get('account.fiscalyear') - Fiscalyear.close([self.start.close_fiscalyear]) - return 'end' diff --git a/fiscalyear.xml b/fiscalyear.xml index bba87fdf7a..aa7e305f37 100644 --- a/fiscalyear.xml +++ b/fiscalyear.xml @@ -15,6 +15,12 @@ this repository contains the full copyright notices and license terms. --> tree fiscalyear_tree + + account.fiscalyear + tree + fiscalyear_list_close + + Fiscal Years account.fiscalyear @@ -59,6 +65,22 @@ this repository contains the full copyright notices and license terms. --> + + Close Fiscal Years + account.fiscalyear + + + + + + + + + + - - - account.fiscalyear.close.start - form - fiscalyear_close_start_form - - - - Close Fiscal Year - account.fiscalyear.close - - diff --git a/journal.py b/journal.py index 3f215639a8..82cc639012 100644 --- a/journal.py +++ b/journal.py @@ -5,8 +5,7 @@ from decimal import Decimal from sql import Null from sql.aggregate import Sum -from trytond.model import ModelView, ModelSQL, fields, Unique -from trytond.wizard import Wizard, StateTransition +from trytond.model import ModelView, ModelSQL, Workflow, fields, Unique from trytond import backend from trytond.pyson import Eval, Bool from trytond.transaction import Transaction @@ -18,18 +17,13 @@ from trytond.modules.company.model import ( __all__ = ['JournalType', 'Journal', 'JournalSequence', 'JournalAccount', 'JournalCashContext', - 'JournalPeriod', 'CloseJournalPeriod', 'ReOpenJournalPeriod'] + 'JournalPeriod'] STATES = { 'readonly': Eval('state') == 'close', } DEPENDS = ['state'] -_ICONS = { - 'open': 'tryton-open', - 'close': 'tryton-readonly', -} - class JournalType(ModelSQL, ModelView): 'Journal Type' @@ -312,7 +306,7 @@ class JournalCashContext(ModelView): default_end_date = default_start_date -class JournalPeriod(ModelSQL, ModelView): +class JournalPeriod(Workflow, ModelSQL, ModelView): 'Journal - Period' __name__ = 'account.journal.period' journal = fields.Many2One('account.journal', 'Journal', required=True, @@ -345,6 +339,18 @@ class JournalPeriod(ModelSQL, ModelView): 'journal - period "%(journal_period)s" because period ' '"%(period)s" is closed.'), }) + cls._transitions |= set(( + ('open', 'close'), + ('close', 'open'), + )) + cls._buttons.update({ + 'close': { + 'invisible': Eval('state') != 'open', + }, + 'reopen': { + 'invisible': Eval('state') != 'close', + }, + }) @classmethod def __register__(cls, module_name): @@ -379,7 +385,10 @@ class JournalPeriod(ModelSQL, ModelView): ] def get_icon(self, name): - return _ICONS.get(self.state, '') + return { + 'open': 'tryton-open', + 'close': 'tryton-close', + }.get(self.state) @classmethod def _check(cls, periods): @@ -399,7 +408,7 @@ class JournalPeriod(ModelSQL, ModelView): for vals in vlist: if vals.get('period'): period = Period(vals['period']) - if period.state == 'close': + if period.state != 'open': cls.raise_user_error('create_journal_period', ( period.rec_name,)) return super(JournalPeriod, cls).create(vlist) @@ -413,7 +422,7 @@ class JournalPeriod(ModelSQL, ModelView): cls._check(journal_periods) if values.get('state') == 'open': for journal_period in journal_periods: - if journal_period.period.state == 'close': + if journal_period.period.state != 'open': cls.raise_user_error('open_journal_period', { 'journal_period': journal_period.rec_name, 'period': journal_period.period.rec_name, @@ -426,43 +435,17 @@ class JournalPeriod(ModelSQL, ModelView): super(JournalPeriod, cls).delete(periods) @classmethod + @ModelView.button + @Workflow.transition('close') def close(cls, periods): ''' Close journal - period ''' - cls.write(periods, { - 'state': 'close', - }) + pass @classmethod - def open_(cls, periods): + @ModelView.button + @Workflow.transition('open') + def reopen(cls, periods): "Open journal - period" - cls.write(periods, { - 'state': 'open', - }) - - -class CloseJournalPeriod(Wizard): - 'Close Journal - Period' - __name__ = 'account.journal.period.close' - start_state = 'close' - close = StateTransition() - - def transition_close(self): - JournalPeriod = Pool().get('account.journal.period') - JournalPeriod.close( - JournalPeriod.browse(Transaction().context['active_ids'])) - return 'end' - - -class ReOpenJournalPeriod(Wizard): - 'Re-Open Journal - Period' - __name__ = 'account.journal.period.reopen' - start_state = 'reopen' - reopen = StateTransition() - - def transition_reopen(self): - JournalPeriod = Pool().get('account.journal.period') - JournalPeriod.open_( - JournalPeriod.browse(Transaction().context['active_ids'])) - return 'end' + pass diff --git a/journal.xml b/journal.xml index 47b25dcdff..bb5d2a7650 100644 --- a/journal.xml +++ b/journal.xml @@ -172,6 +172,12 @@ this repository contains the full copyright notices and license terms. --> tree journal_period_tree + + account.journal.period + tree + journal_period_list_close + + Journals - Periods account.journal.period @@ -186,11 +192,12 @@ this repository contains the full copyright notices and license terms. --> + Journals - Periods account.journal.period @@ -201,47 +208,20 @@ this repository contains the full copyright notices and license terms. --> - + Close Journals - Periods - account.journal.period.close - account.journal.period - - - form_action - account.journal.period,-1 - - - - - Re-Open Journals - Periods - account.journal.period.reopen - account.journal.period - - - form_action - account.journal.period,-1 - - - - - Journals - Periods account.journal.period - + - + - - + + - - - - - - + + diff --git a/period.py b/period.py index 918a473bf0..821c4f79a3 100644 --- a/period.py +++ b/period.py @@ -1,22 +1,21 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.model import ModelView, ModelSQL, fields -from trytond.wizard import Wizard, StateTransition +from trytond.model import ModelView, ModelSQL, Workflow, fields from trytond.pyson import Eval from trytond.transaction import Transaction from trytond.pool import Pool from trytond.const import OPERATORS from trytond import backend -__all__ = ['Period', 'ClosePeriod', 'ReOpenPeriod'] +__all__ = ['Period'] _STATES = { - 'readonly': Eval('state') == 'close', + 'readonly': Eval('state') != 'open', } _DEPENDS = ['state'] -class Period(ModelSQL, ModelView): +class Period(Workflow, ModelSQL, ModelView): 'Period' __name__ = 'account.period' name = fields.Char('Name', required=True) @@ -29,9 +28,10 @@ class Period(ModelSQL, ModelView): fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True, states=_STATES, depends=_DEPENDS, select=True) state = fields.Selection([ - ('open', 'Open'), - ('close', 'Close'), - ], 'State', readonly=True, required=True) + ('open', 'Open'), + ('close', 'Close'), + ('locked', 'Locked'), + ], 'State', readonly=True, required=True) post_move_sequence = fields.Many2One('ir.sequence', 'Post Move Sequence', domain=[ ('code', '=', 'account.move'), @@ -48,6 +48,7 @@ class Period(ModelSQL, ModelView): states=_STATES, depends=_DEPENDS, select=True) company = fields.Function(fields.Many2One('company.company', 'Company',), 'on_change_with_company', searcher='search_company') + icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __register__(cls, module_name): @@ -85,6 +86,22 @@ class Period(ModelSQL, ModelView): 'fiscalyear_dates': ('Dates of period "%s" are outside ' 'are outside it\'s fiscal year dates.'), }) + cls._transitions |= set(( + ('open', 'close'), + ('close', 'locked'), + ('close', 'open'), + )) + cls._buttons.update({ + 'close': { + 'invisible': Eval('state') != 'open', + }, + 'reopen': { + 'invisible': Eval('state') != 'close', + }, + 'lock': { + 'invisible': Eval('state') != 'close', + }, + }) @staticmethod def default_state(): @@ -103,6 +120,13 @@ class Period(ModelSQL, ModelView): def search_company(cls, name, clause): return [('fiscalyear.%s' % name,) + tuple(clause[1:])] + def get_icon(self, name): + return { + 'open': 'tryton-open', + 'close': 'tryton-close', + 'locked': 'tryton-readonly', + }.get(self.state) + @classmethod def validate(cls, periods): super(Period, cls).validate(periods) @@ -177,7 +201,7 @@ class Period(ModelSQL, ModelView): ('type', '=', 'standard'), ] if test_state: - clause.append(('state', '!=', 'close')) + clause.append(('state', '=', 'open')) periods = cls.search(clause, order=[('start_date', 'DESC')], limit=1) if not periods: if exception: @@ -237,7 +261,7 @@ class Period(ModelSQL, ModelView): for vals in vlist: if vals.get('fiscalyear'): fiscalyear = FiscalYear(vals['fiscalyear']) - if fiscalyear.state == 'close': + if fiscalyear.state != 'open': cls.raise_user_error('create_period_closed_fiscalyear', (fiscalyear.rec_name,)) if not vals.get('post_move_sequence'): @@ -262,7 +286,7 @@ class Period(ModelSQL, ModelView): break if values.get('state') == 'open': for period in periods: - if period.fiscalyear.state == 'close': + if period.fiscalyear.state != 'open': cls.raise_user_error('open_period_closed_fiscalyear', { 'period': period.rec_name, 'fiscalyear': period.fiscalyear.rec_name, @@ -287,10 +311,18 @@ class Period(ModelSQL, ModelView): super(Period, cls).delete(periods) @classmethod + @ModelView.button + @Workflow.transition('close') def close(cls, periods): pool = Pool() JournalPeriod = pool.get('account.journal.period') Move = pool.get('account.move') + transaction = Transaction() + database = transaction.database + connection = transaction.connection + + # Lock period to be sure no new period will be created in between. + database.lock(connection, JournalPeriod._table) unposted_moves = Move.search([ ('period', 'in', [p.id for p in periods]), @@ -302,47 +334,24 @@ class Period(ModelSQL, ModelView): 'period': unposted_move.period.rec_name, 'move': unposted_move.rec_name, }) - # First close the period to be sure - # it will not have new journal.period created between. - cls.write(periods, { - 'state': 'close', - }) journal_periods = JournalPeriod.search([ ('period', 'in', [p.id for p in periods]), ]) JournalPeriod.close(journal_periods) @classmethod - def open_(cls, periods): - "Open Journal" - cls.write(periods, { - 'state': 'open', - }) + @ModelView.button + @Workflow.transition('open') + def reopen(cls, periods): + "Re-open period" + pass + + @classmethod + @ModelView.button + @Workflow.transition('locked') + def lock(cls, periods): + pass @property def post_move_sequence_used(self): return self.post_move_sequence or self.fiscalyear.post_move_sequence - - -class ClosePeriod(Wizard): - 'Close Period' - __name__ = 'account.period.close' - start_state = 'close' - close = StateTransition() - - def transition_close(self): - Period = Pool().get('account.period') - Period.close(Period.browse(Transaction().context['active_ids'])) - return 'end' - - -class ReOpenPeriod(Wizard): - 'Re-Open Period' - __name__ = 'account.period.reopen' - start_state = 'reopen' - reopen = StateTransition() - - def transition_reopen(self): - Period = Pool().get('account.period') - Period.open_(Period.browse(Transaction().context['active_ids'])) - return 'end' diff --git a/period.xml b/period.xml index 622837ce88..52bcc98d03 100644 --- a/period.xml +++ b/period.xml @@ -13,6 +13,12 @@ this repository contains the full copyright notices and license terms. --> tree period_tree + + account.period + tree + period_list_close + + Periods account.period @@ -50,45 +56,21 @@ this repository contains the full copyright notices and license terms. --> - + Close Periods - account.period.close - account.period - - - form_action - account.period,-1 - - - - Re-Open Periods - account.period.reopen - account.period - - - form_action - account.period,-1 - - - - Periods account.period - + - + - - + + - - - - - - + + diff --git a/view/fiscalyear_close_start_form.xml b/view/fiscalyear_close_start_form.xml deleted file mode 100644 index 34b2f96553..0000000000 --- a/view/fiscalyear_close_start_form.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
-