- browse take a list of ids - use getattr instead of __getitem__ (transplanted from 85a56e021875def4b1b4e740b83e504877e76075)
281 lines
10 KiB
Python
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'
|