Files
tryton/period.py
T
Cédric Krier a6cb28a180 Fix special search clause on period
- browse take a list of ids
- use getattr instead of __getitem__
(transplanted from 85a56e021875def4b1b4e740b83e504877e76075)
2012-11-07 10:59:00 +01:00

281 lines
10 KiB
Python

#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.pyson import Eval
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.const import OPERATORS
__all__ = ['Period', 'ClosePeriod', 'ReOpenPeriod']
_STATES = {
'readonly': Eval('state') == 'close',
}
_DEPENDS = ['state']
class Period(ModelSQL, ModelView):
'Period'
__name__ = 'account.period'
name = fields.Char('Name', required=True)
code = fields.Char('Code')
start_date = fields.Date('Starting Date', required=True, states=_STATES,
depends=_DEPENDS, select=True)
end_date = fields.Date('Ending Date', required=True, states=_STATES,
depends=_DEPENDS, select=True)
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)
post_move_sequence = fields.Many2One('ir.sequence', 'Post Move Sequence',
required=True, domain=[('code', '=', 'account.move')],
context={'code': 'account.move'}, states={
'required': False,
})
type = fields.Selection([
('standard', 'Standard'),
('adjustment', 'Adjustment'),
], 'Type', required=True,
states=_STATES, depends=_DEPENDS, select=True)
company = fields.Function(fields.Many2One('company.company', 'Company',),
'get_company', searcher='search_company')
@classmethod
def __setup__(cls):
super(Period, cls).__setup__()
cls._constraints += [
('check_dates', 'periods_overlaps'),
('check_fiscalyear_dates', 'fiscalyear_dates'),
('check_post_move_sequence', 'check_move_sequence'),
]
cls._order.insert(0, ('start_date', 'ASC'))
cls._error_messages.update({
'no_period_date': 'No period defined for this date!',
'modify_del_period_moves': 'You can not modify/delete ' \
'a period with moves!',
'create_period_closed_fiscalyear': 'You can not create ' \
'a period on a closed fiscal year!',
'open_period_closed_fiscalyear': 'You can not open ' \
'a period from a closed fiscal year!',
'change_post_move_sequence': 'You can not change ' \
'the post move sequence ' \
'if there is already posted moves in the period',
'close_period_non_posted_move': 'You can not close ' \
'a period with non posted moves!',
'periods_overlaps': 'You can not have two overlapping periods!',
'check_move_sequence': 'You must have different ' \
'post move sequences per fiscal year ' \
'and in the same company!',
'fiscalyear_dates': 'The period dates must be in ' \
'the fiscal year dates',
})
@staticmethod
def default_state():
return 'open'
@staticmethod
def default_type():
return 'standard'
def get_company(self, name):
return self.fiscalyear.company.id
@classmethod
def search_company(cls, name, clause):
return [('fiscalyear.%s' % name,) + tuple(clause[1:])]
def check_dates(self):
cursor = Transaction().cursor
if self.type != 'standard':
return True
cursor.execute('SELECT id ' \
'FROM "' + self._table + '" ' \
'WHERE ((start_date <= %s AND end_date >= %s) ' \
'OR (start_date <= %s AND end_date >= %s) ' \
'OR (start_date >= %s AND end_date <= %s)) ' \
'AND fiscalyear = %s ' \
'AND type = \'standard\' ' \
'AND id != %s',
(self.start_date, self.start_date,
self.end_date, self.end_date,
self.start_date, self.end_date,
self.fiscalyear.id, self.id))
return not cursor.fetchone()
def check_fiscalyear_dates(self):
return not (self.start_date < self.fiscalyear.start_date
or self.end_date > self.fiscalyear.end_date)
def check_post_move_sequence(self):
if self.search([
('post_move_sequence', '=', self.post_move_sequence.id),
('fiscalyear', '!=', self.fiscalyear.id),
]):
return False
if (self.post_move_sequence.company
and (self.post_move_sequence.company.id !=
self.fiscalyear.company.id)):
return False
return True
@classmethod
def find(cls, company_id, date=None, exception=True, test_state=True):
'''
Return the period for the company_id
at the date or the current date.
If exception is set the function will raise an exception
if no period is found.
If test_state is true, it will search on non-closed periods
'''
Date = Pool().get('ir.date')
if not date:
date = Date.today()
clause = [
('start_date', '<=', date),
('end_date', '>=', date),
('fiscalyear.company', '=', company_id),
('type', '=', 'standard'),
]
if test_state:
clause.append(('state', '!=', 'close'))
periods = cls.search(clause, order=[('start_date', 'DESC')], limit=1)
if not periods:
if exception:
cls.raise_user_error('no_period_date')
else:
return None
return periods[0].id
@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')
@classmethod
def search(cls, args, offset=0, limit=None, order=None, count=False,
query_string=False):
args = args[:]
def process_args(args):
i = 0
while i < len(args):
# add test for xmlrpc and pyson that doesn't handle tuple
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:
period = cls(args[i][2][0])
args[i] = (args[i][0], args[i][1],
getattr(period, args[i][2][1]))
elif isinstance(args[i], list):
process_args(args[i])
i += 1
process_args(args)
return super(Period, cls).search(args, offset=offset, limit=limit,
order=order, count=count, query_string=query_string)
@classmethod
def create(cls, vals):
FiscalYear = Pool().get('account.fiscalyear')
vals = vals.copy()
if vals.get('fiscalyear'):
fiscalyear = FiscalYear(vals['fiscalyear'])
if fiscalyear.state == 'close':
cls.raise_user_error('create_period_closed_fiscalyear')
if not vals.get('post_move_sequence'):
vals['post_move_sequence'] = fiscalyear.post_move_sequence.id
return super(Period, cls).create(vals)
@classmethod
def write(cls, periods, vals):
Move = Pool().get('account.move')
for key in vals.keys():
if key in ('start_date', 'end_date', 'fiscalyear'):
cls._check(periods)
break
if vals.get('state') == 'open':
for period in periods:
if period.fiscalyear.state == 'close':
cls.raise_user_error('open_period_closed_fiscalyear')
if vals.get('post_move_sequence'):
for period in periods:
if period.post_move_sequence and \
period.post_move_sequence.id != \
vals['post_move_sequence']:
if Move.search([
('period', '=', period.id),
('state', '=', 'posted'),
]):
cls.raise_user_error('change_post_move_sequence')
super(Period, cls).write(periods, vals)
@classmethod
def delete(cls, periods):
cls._check(periods)
super(Period, cls).delete(periods)
@classmethod
def close(cls, periods):
pool = Pool()
JournalPeriod = pool.get('account.journal.period')
Move = pool.get('account.move')
if Move.search([
('period', 'in', [p.id for p in periods]),
('state', '!=', 'posted'),
]):
cls.raise_user_error('close_period_non_posted_move')
#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',
})
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'