Files
tryton/period.py
T

350 lines
13 KiB
Python
Raw Normal View History

2014-12-02 12:54:29 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2012-09-24 22:17:29 +02:00
from trytond.model import ModelView, ModelSQL, fields
2012-01-02 17:54:33 +01:00
from trytond.wizard import Wizard, StateTransition
2011-08-21 11:20:29 +02:00
from trytond.pyson import Eval
2010-08-19 22:02:00 +02:00
from trytond.transaction import Transaction
2011-07-03 13:18:40 +02:00
from trytond.pool import Pool
2012-09-24 22:17:29 +02:00
from trytond.const import OPERATORS
2013-09-06 22:33:47 +02:00
from trytond import backend
2012-09-24 22:17:29 +02:00
__all__ = ['Period', 'ClosePeriod', 'ReOpenPeriod']
2008-03-17 18:32:06 +01:00
_STATES = {
2011-08-21 11:20:29 +02:00
'readonly': Eval('state') == 'close',
2008-03-13 23:22:59 +01:00
}
2011-08-04 16:57:14 +02:00
_DEPENDS = ['state']
2008-03-13 23:22:59 +01:00
2009-02-28 16:57:10 +01:00
class Period(ModelSQL, ModelView):
2008-03-13 23:22:59 +01:00
'Period'
2012-09-24 22:17:29 +02:00
__name__ = 'account.period'
2008-09-16 19:43:07 +02:00
name = fields.Char('Name', required=True)
code = fields.Char('Code')
start_date = fields.Date('Starting Date', required=True, states=_STATES,
domain=[('start_date', '<=', Eval('end_date', None))],
depends=_DEPENDS + ['end_date'], select=True)
2008-09-16 19:43:07 +02:00
end_date = fields.Date('Ending Date', required=True, states=_STATES,
domain=[('end_date', '>=', Eval('start_date', None))],
depends=_DEPENDS + ['start_date'], select=True)
2008-03-13 23:22:59 +01:00
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
2012-03-05 10:52:56 +01:00
required=True, states=_STATES, depends=_DEPENDS, select=True)
2008-03-13 23:22:59 +01:00
state = fields.Selection([
('open', 'Open'),
('close', 'Close'),
2008-03-17 18:32:06 +01:00
], 'State', readonly=True, required=True)
2012-04-03 20:06:15 +02:00
post_move_sequence = fields.Many2One('ir.sequence', 'Post Move Sequence',
2014-12-03 16:13:08 +01:00
domain=[
('code', '=', 'account.move'),
['OR',
('company', '=', None),
('company', '=', Eval('company', -1)),
],
],
depends=['company'])
2008-09-16 19:43:07 +02:00
type = fields.Selection([
2011-08-04 16:57:14 +02:00
('standard', 'Standard'),
('adjustment', 'Adjustment'),
], 'Type', required=True,
2012-03-05 10:52:56 +01:00
states=_STATES, depends=_DEPENDS, select=True)
company = fields.Function(fields.Many2One('company.company', 'Company',),
2014-12-03 16:13:08 +01:00
'on_change_with_company', searcher='search_company')
2008-03-13 23:22:59 +01:00
2013-01-22 16:14:24 +01:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:33:47 +02:00
TableHandler = backend.get('TableHandler')
2013-01-22 16:14:24 +01:00
cursor = Transaction().cursor
super(Period, cls).__register__(module_name)
table = TableHandler(cursor, cls, module_name)
# Migration from 2.6: post_move_sequence is no longer required
table.not_null_action('post_move_sequence', 'remove')
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Period, cls).__setup__()
cls._order.insert(0, ('start_date', 'ASC'))
cls._error_messages.update({
'no_period_date': 'No period defined for date "%s".',
'modify_del_period_moves': ('You can not modify/delete '
'period "%s" because it has moves.'),
'create_period_closed_fiscalyear': ('You can not create '
'a period on fiscal year "%s" because it is closed.'),
'open_period_closed_fiscalyear': ('You can not open period '
'"%(period)s" because its fiscal year "%(fiscalyear)s" is '
'closed.'),
'change_post_move_sequence': ('You can not change the post '
'move sequence of period "%s" because there are already '
'posted moves in the period.'),
'close_period_non_posted_move': ('You can not close period '
'"%(period)s" because there are non posted moves '
'"%(move)s" in this period.'),
'periods_overlap': ('"%(first)s" and "%(second)s" periods '
'overlap.'),
'check_move_sequence': ('Period "%(first)s" and "%(second)s" '
'have the same sequence.'),
'fiscalyear_dates': ('Dates of period "%s" are outside '
'are outside it\'s fiscal year dates.'),
})
2012-09-24 22:17:29 +02:00
@staticmethod
def default_state():
2008-03-13 23:22:59 +01:00
return 'open'
2012-09-24 22:17:29 +02:00
@staticmethod
def default_type():
2008-09-16 19:43:07 +02:00
return 'standard'
2014-12-03 16:13:08 +01:00
@fields.depends('fiscalyear')
def on_change_with_company(self, name=None):
if self.fiscalyear:
return self.fiscalyear.company.id
2012-09-24 22:17:29 +02:00
@classmethod
def search_company(cls, name, clause):
return [('fiscalyear.%s' % name,) + tuple(clause[1:])]
@classmethod
def validate(cls, periods):
super(Period, cls).validate(periods)
for period in periods:
period.check_dates()
period.check_fiscalyear_dates()
period.check_post_move_sequence()
2012-09-24 22:17:29 +02:00
def check_dates(self):
2010-08-19 22:02:00 +02:00
cursor = Transaction().cursor
2012-09-24 22:17:29 +02:00
if self.type != 'standard':
return True
2013-09-06 22:33:47 +02:00
table = self.__table__()
cursor.execute(*table.select(table.id,
where=(((table.start_date <= self.start_date)
& (table.end_date >= self.start_date))
| ((table.start_date <= self.end_date)
& (table.end_date >= self.end_date))
| ((table.start_date >= self.start_date)
& (table.end_date <= self.end_date)))
& (table.fiscalyear == self.fiscalyear.id)
& (table.type == 'standard')
& (table.id != self.id)))
period_id = cursor.fetchone()
if period_id:
overlapping_period = self.__class__(period_id[0])
self.raise_user_error('periods_overlap', {
'first': self.rec_name,
'second': overlapping_period.rec_name,
})
2012-09-24 22:17:29 +02:00
def check_fiscalyear_dates(self):
if (self.start_date < self.fiscalyear.start_date
or self.end_date > self.fiscalyear.end_date):
self.raise_user_error('fiscalyear_dates', (self.rec_name,))
2012-09-24 22:17:29 +02:00
def check_post_move_sequence(self):
2013-01-22 16:14:24 +01:00
if not self.post_move_sequence:
return
periods = self.search([
('post_move_sequence', '=', self.post_move_sequence.id),
('fiscalyear', '!=', self.fiscalyear.id),
])
if periods:
self.raise_user_error('check_move_sequence', {
'first': self.rec_name,
'second': periods[0].rec_name,
})
2012-09-24 22:17:29 +02:00
@classmethod
def find(cls, company_id, date=None, exception=True, test_state=True):
2008-03-13 23:22:59 +01:00
'''
Return the period for the company_id
at the date or the current date.
2008-03-13 23:22:59 +01:00
If exception is set the function will raise an exception
2012-06-16 14:26:05 +05:30
if no period is found.
2012-09-24 22:17:29 +02:00
If test_state is true, it will search on non-closed periods
2008-03-13 23:22:59 +01:00
'''
pool = Pool()
Date = pool.get('ir.date')
Lang = pool.get('ir.lang')
2008-10-08 12:17:47 +02:00
2008-03-13 23:22:59 +01:00
if not date:
2012-09-24 22:17:29 +02:00
date = Date.today()
clause = [
2008-03-13 23:22:59 +01:00
('start_date', '<=', date),
('end_date', '>=', date),
('fiscalyear.company', '=', company_id),
2008-09-16 19:43:07 +02:00
('type', '=', 'standard'),
]
if test_state:
clause.append(('state', '!=', 'close'))
2012-09-24 22:17:29 +02:00
periods = cls.search(clause, order=[('start_date', 'DESC')], limit=1)
if not periods:
2008-03-13 23:22:59 +01:00
if exception:
language = Transaction().language
languages = Lang.search([('code', '=', language)])
if not languages:
languages = Lang.search([('code', '=', 'en_US')])
language = languages[0]
formatted = Lang.strftime(date, language.code,
language.date)
cls.raise_user_error('no_period_date', (formatted,))
2008-03-13 23:22:59 +01:00
else:
2012-03-27 10:43:03 +02:00
return None
2012-09-24 22:17:29 +02:00
return periods[0].id
2008-03-13 23:22:59 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def _check(cls, periods):
Move = Pool().get('account.move')
moves = Move.search([
('period', 'in', [p.id for p in periods]),
], limit=1)
if moves:
cls.raise_user_error('modify_del_period_moves', (
moves[0].period.rec_name,))
2012-09-24 22:17:29 +02:00
@classmethod
def search(cls, args, offset=0, limit=None, order=None, count=False,
2013-09-06 22:33:47 +02:00
query=False):
args = args[:]
2012-04-03 20:06:15 +02:00
2008-09-04 18:24:33 +02:00
def process_args(args):
i = 0
while i < len(args):
# add test for xmlrpc and pyson that doesn't handle tuple
2013-02-16 18:54:35 +01:00
if ((isinstance(args[i], tuple)
or (isinstance(args[i], list) and len(args[i]) > 2
and args[i][1] in OPERATORS))
and args[i][0] in ('start_date', 'end_date')
and isinstance(args[i][2], (list, tuple))):
if not args[i][2][0]:
args[i] = ('id', '!=', '0')
else:
2012-11-07 10:59:00 +01:00
period = cls(args[i][2][0])
2012-04-03 20:06:15 +02:00
args[i] = (args[i][0], args[i][1],
2012-11-07 10:59:00 +01:00
getattr(period, args[i][2][1]))
elif isinstance(args[i], list):
process_args(args[i])
2008-09-04 18:24:33 +02:00
i += 1
process_args(args)
2012-09-24 22:17:29 +02:00
return super(Period, cls).search(args, offset=offset, limit=limit,
2013-09-06 22:33:47 +02:00
order=order, count=count, query=query)
2012-09-24 22:17:29 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:17:29 +02:00
FiscalYear = Pool().get('account.fiscalyear')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
if vals.get('fiscalyear'):
fiscalyear = FiscalYear(vals['fiscalyear'])
if fiscalyear.state == 'close':
cls.raise_user_error('create_period_closed_fiscalyear',
(fiscalyear.rec_name,))
2013-01-06 22:18:13 +01:00
if not vals.get('post_move_sequence'):
vals['post_move_sequence'] = (
fiscalyear.post_move_sequence.id)
return super(Period, cls).create(vlist)
2012-09-24 22:17:29 +02:00
@classmethod
2013-12-19 14:44:33 +01:00
def write(cls, *args):
2012-09-24 22:17:29 +02:00
Move = Pool().get('account.move')
2013-12-19 14:44:33 +01:00
actions = iter(args)
args = []
for periods, values in zip(actions, actions):
for key in values.keys():
if key in ('start_date', 'end_date', 'fiscalyear'):
cls._check(periods)
break
if values.get('state') == 'open':
for period in periods:
if period.fiscalyear.state == 'close':
cls.raise_user_error('open_period_closed_fiscalyear', {
'period': period.rec_name,
'fiscalyear': period.fiscalyear.rec_name,
})
if values.get('post_move_sequence'):
for period in periods:
if (period.post_move_sequence
and period.post_move_sequence.id !=
values['post_move_sequence']):
if Move.search([
('period', '=', period.id),
('state', '=', 'posted'),
]):
cls.raise_user_error('change_post_move_sequence',
(period.rec_name,))
args.extend((periods, values))
super(Period, cls).write(*args)
2012-09-24 22:17:29 +02:00
@classmethod
def delete(cls, periods):
cls._check(periods)
super(Period, cls).delete(periods)
2012-09-24 22:17:29 +02:00
@classmethod
def close(cls, periods):
pool = Pool()
JournalPeriod = pool.get('account.journal.period')
Move = pool.get('account.move')
2008-03-31 14:41:54 +02:00
unposted_moves = Move.search([
2013-03-25 18:35:52 +00:00
('period', 'in', [p.id for p in periods]),
('state', '!=', 'posted'),
], limit=1)
if unposted_moves:
unposted_move, = unposted_moves
cls.raise_user_error('close_period_non_posted_move', {
'period': unposted_move.period.rec_name,
2014-12-02 12:54:29 +01:00
'move': unposted_move.rec_name,
})
2014-12-02 12:54:29 +01:00
# First close the period to be sure
# it will not have new journal.period created between.
2012-09-24 22:17:29 +02:00
cls.write(periods, {
'state': 'close',
})
journal_periods = JournalPeriod.search([
('period', 'in', [p.id for p in periods]),
2010-08-19 22:02:00 +02:00
])
2012-09-24 22:17:29 +02:00
JournalPeriod.close(journal_periods)
2012-09-24 22:17:29 +02:00
@classmethod
def open_(cls, periods):
2012-01-02 17:54:33 +01:00
"Open Journal"
2012-09-24 22:17:29 +02:00
cls.write(periods, {
2012-01-02 17:54:33 +01:00
'state': 'open',
})
2013-01-22 16:14:24 +01:00
@property
def post_move_sequence_used(self):
return self.post_move_sequence or self.fiscalyear.post_move_sequence
2014-01-31 14:06:56 +01:00
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('code',) + tuple(clause[1:]),
(cls._rec_name,) + tuple(clause[1:]),
]
class ClosePeriod(Wizard):
'Close Period'
2012-09-24 22:17:29 +02:00
__name__ = 'account.period.close'
2012-01-02 17:54:33 +01:00
start_state = 'close'
close = StateTransition()
2012-09-24 22:17:29 +02:00
def transition_close(self):
Period = Pool().get('account.period')
Period.close(Period.browse(Transaction().context['active_ids']))
2012-01-02 17:54:33 +01:00
return 'end'
class ReOpenPeriod(Wizard):
'Re-Open Period'
2012-09-24 22:17:29 +02:00
__name__ = 'account.period.reopen'
2012-01-02 17:54:33 +01:00
start_state = 'reopen'
reopen = StateTransition()
2012-09-24 22:17:29 +02:00
def transition_reopen(self):
Period = Pool().get('account.period')
Period.open_(Period.browse(Transaction().context['active_ids']))
2012-01-02 17:54:33 +01:00
return 'end'