#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 __future__ import with_statement import datetime import time from dateutil.relativedelta import relativedelta from trytond.model import ModelView, ModelSQL, fields from trytond.wizard import Wizard from trytond.tools import datetime_strftime from trytond.pyson import Equal, Eval, If, In, Get from trytond.transaction import Transaction STATES = { 'readonly': Equal(Eval('state'), 'close'), } DEPENDS = ['state'] class FiscalYear(ModelSQL, ModelView): 'Fiscal Year' _name = 'account.fiscalyear' _description = __doc__ name = fields.Char('Name', size=None, required=True, depends=DEPENDS) code = fields.Char('Code', size=None) start_date = fields.Date('Starting Date', required=True, states=STATES, depends=DEPENDS) end_date = fields.Date('Ending Date', required=True, states=STATES, depends=DEPENDS) periods = fields.One2Many('account.period', 'fiscalyear', 'Periods', states=STATES, depends=DEPENDS) 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'), ['OR', ('company', '=', Eval('company')), ('company', '=', False) ]], context={ 'code': 'account.move', 'company': Eval('company'), }, depends=['company']) company = fields.Many2One('company.company', 'Company', required=True, domain=[ ('id', If(In('company', Eval('context', {})), '=', '!='), Get(Eval('context', {}), 'company', 0)), ]) def __init__(self): super(FiscalYear, self).__init__() self._rpc.update({ 'create_period': True, 'create_period_3': True, 'close': True, 'reopen': True, }) self._constraints += [ ('check_dates', 'fiscalyear_overlaps'), ('check_post_move_sequence', 'different_post_move_sequence'), ] self._order.insert(0, ('start_date', 'ASC')) self._error_messages.update({ 'change_post_move_sequence': 'You can not change ' \ 'the post move sequence', 'no_fiscalyear_date': 'No fiscal year defined for this date!', 'fiscalyear_overlaps': 'You can not have 2 fiscal years that overlaps!', 'different_post_move_sequence': 'You must have different post move sequence per fiscal year!', 'account_balance_not_zero': 'The balance of the account "%s" must be zero!', 'close_error': 'You can not close a fiscal year until ' \ 'there is older fiscal year opened!', 'reopen_error': 'You can not reopen a fiscal year until ' \ 'there is more recent fiscal year closed!', }) def default_state(self): return 'open' def default_company(self): return Transaction().context.get('company') or False def check_dates(self, ids): cursor = Transaction().cursor for fiscalyear in self.browse(ids): 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 company = %s ' \ 'AND id != %s', (fiscalyear.start_date, fiscalyear.start_date, fiscalyear.end_date, fiscalyear.end_date, fiscalyear.start_date, fiscalyear.end_date, fiscalyear.company.id, fiscalyear.id)) if cursor.fetchone(): return False return True def check_post_move_sequence(self, ids): for fiscalyear in self.browse(ids): if self.search([ ('post_move_sequence', '=', fiscalyear.post_move_sequence.id), ('id', '!=', fiscalyear.id), ]): return False return True def write(self, ids, vals): move_obj = self.pool.get('account.move') if vals.get('post_move_sequence'): for fiscalyear in self.browse(ids): if fiscalyear.post_move_sequence and \ fiscalyear.post_move_sequence.id != \ vals['post_move_sequence']: self.raise_user_error('change_post_move_sequence') vals = vals.copy() if 'periods' in vals: operator = ['delete', 'unlink_all', 'unlink', 'create', 'write', 'add', 'set'] vals['periods'].sort( lambda x, y: cmp(operator.index(x[0]), operator.index(y[0]))) return super(FiscalYear, self).write(ids, vals) def delete(self, ids): period_obj = self.pool.get('account.period') period_ids = [] for fiscalyear in self.browse(ids): period_ids.extend([x.id for x in fiscalyear.periods]) period_obj.delete(period_ids) return super(FiscalYear, self).delete(ids) def create_period(self, ids, interval=1): ''' Create periods for the fiscal years with month interval ''' period_obj = self.pool.get('account.period') for fiscalyear in self.browse(ids): period_start_date = fiscalyear.start_date while period_start_date < fiscalyear.end_date: period_end_date = period_start_date + \ relativedelta(months=interval - 1) + \ relativedelta(day=31) if period_end_date > fiscalyear.end_date: period_end_date = fiscalyear.end_date name = datetime_strftime(period_start_date, '%Y-%m') if name != datetime_strftime(period_end_date, '%Y-%m'): name += ' - ' + datetime_strftime(period_end_date, '%Y-%m') period_obj.create({ 'name': name, 'start_date': period_start_date, 'end_date': period_end_date, 'fiscalyear': fiscalyear.id, 'post_move_sequence': fiscalyear.post_move_sequence.id, 'type': 'standard', }) period_start_date = period_end_date + relativedelta(days=1) return True def create_period_3(self, ids): ''' Create periods for the fiscal years with 3 months interval ''' return self.create_period(ids, interval=3) def find(self, company_id, date=None, exception=True): ''' Return the fiscal year for the company_id at the date or the current date. If exception is set the function will raise an exception if any fiscal year is found. ''' date_obj = self.pool.get('ir.date') if not date: date = date_obj.today() ids = self.search([ ('start_date', '<=', date), ('end_date', '>=', date), ('company', '=', company_id), ], order=[('start_date', 'DESC')], limit=1) if not ids: if exception: self.raise_user_error('no_fiscalyear_date') else: return False return ids[0] def _process_account(self, account, fiscalyear): ''' Process account for a fiscal year closed :param account: a BrowseRecord of the account :param fiscalyear: a BrowseRecord of the fiscal year closed ''' currency_obj = self.pool.get('currency.currency') deferral_obj = self.pool.get('account.account.deferral') if account.kind == 'view': return if not account.deferral: if not currency_obj.is_zero(fiscalyear.company.currency, account.balance): self.raise_user_error('account_balance_not_zero', error_args=(account.rec_name,)) else: deferral_obj.create({ 'account': account.id, 'fiscalyear': fiscalyear.id, 'debit': account.debit, 'credit': account.credit, }) def close(self, fiscalyear_id): ''' Close a fiscal year :param fiscalyear_id: the fiscal year id ''' period_obj = self.pool.get('account.period') account_obj = self.pool.get('account.account') if isinstance(fiscalyear_id, list): fiscalyear_id = fiscalyear_id[0] fiscalyear = self.browse(fiscalyear_id) if self.search([ ('end_date', '<=', fiscalyear.start_date), ('state', '=', 'open'), ('company', '=', fiscalyear.company.id), ]): self.raise_user_error('close_error') #First close the fiscalyear to be sure #it will not have new period created between. self.write(fiscalyear_id, { 'state': 'close', }) period_ids = period_obj.search([ ('fiscalyear', '=', fiscalyear_id), ]) period_obj.close(period_ids) with Transaction().set_context(fiscalyear=fiscalyear_id, date=False): account_ids = account_obj.search([ ('company', '=', fiscalyear.company.id), ]) accounts = account_obj.browse(account_ids) for account in accounts: self._process_account(account, fiscalyear) def reopen(self, fiscalyear_id): ''' Re-open a fiscal year :param fiscalyear_id: the fiscal year id ''' deferral_obj = self.pool.get('account.account.deferral') if isinstance(fiscalyear_id, list): fiscalyear_id = fiscalyear_id[0] fiscalyear = self.browse(fiscalyear_id) if self.search([ ('start_date', '>=', fiscalyear.end_date), ('state', '=', 'close'), ('company', '=', fiscalyear.company.id), ]): self.raise_user_error('reopen_error') deferral_ids = deferral_obj.search([ ('fiscalyear', '=', fiscalyear_id), ]) deferral_obj.delete(deferral_ids) self.write(fiscalyear_id, { 'state': 'open', }) FiscalYear() class CloseFiscalYearInit(ModelView): 'Close Fiscal Year Init' _name = 'account.fiscalyear.close_fiscalyear.init' _description = __doc__ close_fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year to close', required=True, domain=[('state', '!=', 'close')]) CloseFiscalYearInit() class CloseFiscalYear(Wizard): 'Close Fiscal Year' _name = 'account.fiscalyear.close_fiscalyear' states = { 'init': { 'result': { 'type': 'form', 'object': 'account.fiscalyear.close_fiscalyear.init', 'state': [ ('end', 'Cancel', 'tryton-cancel'), ('close', 'Close', 'tryton-ok', True), ], }, }, 'close': { 'actions': ['_close'], 'result': { 'type': 'state', 'state': 'end', }, }, } def _close(self, data): fiscalyear_obj = self.pool.get('account.fiscalyear') fiscalyear_obj.close(data['form']['close_fiscalyear']) return {} CloseFiscalYear()