20
20
##############################################################################
23
from osv import fields
23
from dateutil.relativedelta import relativedelta
24
from osv import fields, osv
25
25
from tools.translate import _
26
from destination_tools import many2many_sorted
27
from destination_tools import many2many_notlazy
28
from tools.misc import flatten
26
from lxml import etree
30
28
class analytic_account(osv.osv):
31
29
_name = "account.analytic.account"
32
30
_inherit = "account.analytic.account"
35
def copy_data(self, cr, uid, a_id, default=None, context=None):
33
'name': fields.char('Name', size=128, required=True),
34
'code': fields.char('Code', size=24),
35
'type': fields.selection([('view','View'), ('normal','Normal')], 'Type', help='If you select the View Type, it means you won\'t allow to create journal entries using that account.'),
36
'date_start': fields.date('Active from', required=True),
37
'date': fields.date('Inactive from', select=True),
38
'category': fields.selection([('OC','Cost Center'),
39
('FUNDING','Funding Pool'),
41
('FREE2','Free 2')], 'Category', select=1),
42
'cost_center_ids': fields.many2many('account.analytic.account', 'funding_pool_associated_cost_centers', 'funding_pool_id', 'cost_center_id', string='Cost Centers'),
43
'account_ids': fields.many2many('account.account', 'funding_pool_associated_accounts', 'funding_pool_id', 'account_id', string='Accounts'),
44
'for_fx_gain_loss': fields.boolean(string="For FX gain/loss", help="Is this account for default FX gain/loss?"),
48
'date_start': lambda *a: (datetime.datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d'),
49
'for_fx_gain_loss': lambda *a: False,
51
def _check_unicity(self, cr, uid, ids, context=None):
54
for account in self.browse(cr, uid, ids, context=context):
55
bad_ids = self.search(cr, uid, [('category', '=', account.category),('|'),('name', '=ilike', account.name),('code', '=ilike', account.code)])
56
if len(bad_ids) and len(bad_ids) > 1:
60
def _check_gain_loss_account_unicity(self, cr, uid, ids, context=None):
62
Check that no more account is "for_fx_gain_loss" available.
66
search_ids = self.search(cr, uid, [('for_fx_gain_loss', '=', True)])
67
if search_ids and len(search_ids) > 1:
71
def _check_gain_loss_account_type(self, cr, uid, ids, context=None):
73
Check account type for fx_gain_loss_account: should be Normal type and Cost Center category
77
for account in self.browse(cr, uid, ids, context=context):
78
if account.for_fx_gain_loss == True and (account.type != 'normal' or account.category != 'OC'):
83
(_check_unicity, 'You cannot have the same code or name between analytic accounts in the same category!', ['code', 'name', 'category']),
84
(_check_gain_loss_account_unicity, 'You can only have one account used for FX gain/loss!', ['for_fx_gain_loss']),
85
(_check_gain_loss_account_type, 'You have to use a Normal account type and Cost Center category for FX gain/loss!', ['for_fx_gain_loss']),
88
def copy(self, cr, uid, id, default=None, context=None, done_list=[], local=False):
89
account = self.browse(cr, uid, id, context=context)
41
#US-348: Reset some values when duplicating an analytic account
42
default['tuple_destination_account_ids'] = []
43
default['destination_ids'] = []
45
# Copy analytic distribution
46
return super(analytic_account, self).copy_data(cr, uid, a_id, default, context)
48
def _get_active(self, cr, uid, ids, field_name, args, context=None):
50
If date out of date_start/date of given analytic account, then account is inactive.
51
The comparison could be done via a date given in context.
54
cmp_date = datetime.date.today().strftime('%Y-%m-%d')
55
if context.get('date', False):
56
cmp_date = context.get('date')
57
for a in self.browse(cr, uid, ids):
59
if a.date_start > cmp_date:
61
if a.date and a.date <= cmp_date:
65
def is_blocked_by_a_contract(self, cr, uid, ids):
67
Return ids (analytic accounts) that are blocked by a contract (just FP1)
70
if isinstance(ids, (int, long)):
74
for aa in self.browse(cr, uid, ids):
75
# Only check funding pool accounts
76
if aa.category != 'FUNDING':
78
link_ids = self.pool.get('financing.contract.funding.pool.line').search(cr, uid, [('funding_pool_id', '=', aa.id)])
80
for link in self.pool.get('financing.contract.funding.pool.line').browse(cr, uid, link_ids):
82
format_ids.append(link.contract_id.id)
83
contract_ids = self.pool.get('financing.contract.contract').search(cr, uid, [('format_id', 'in', format_ids)])
84
for contract in self.pool.get('financing.contract.contract').browse(cr, uid, contract_ids):
85
if contract.state in ['soft_closed', 'hard_closed']:
89
def _search_closed_by_a_fp(self, cr, uid, ids, name, args, context=None):
91
UTP-423: Do not display analytic accounts linked to a soft/hard closed contract.
93
res = [('id', 'not in', [])]
94
if args and args[0] and len(args[0]) == 3:
96
raise osv.except_osv(_('Error'), _('Operator not supported yet!'))
97
# Search all fp_ids from soft_closed contract
99
FROM account_analytic_account a, financing_contract_contract fcc, financing_contract_funding_pool_line fcfl
100
WHERE fcfl.contract_id = fcc.id
101
AND fcfl.funding_pool_id = a.id
102
AND fcc.state in ('soft_closed', 'hard_closed');"""
104
sql_res = cr.fetchall()
106
aa_ids = self.is_blocked_by_a_contract(cr, uid, [x and x[0] for x in sql_res])
108
if isinstance(aa_ids, (int, long)):
110
res = [('id', 'not in', aa_ids)]
114
'destination_ids': many2many_notlazy('account.account', 'account_destination_link', 'destination_id', 'account_id', 'Accounts'),
115
'tuple_destination_account_ids': many2many_sorted('account.destination.link', 'funding_pool_associated_destinations', 'funding_pool_id', 'tuple_id', "Account/Destination"),
116
'hide_closed_fp': fields.function(_get_active, fnct_search=_search_closed_by_a_fp, type="boolean", method=True, store=False, string="Linked to a soft/hard closed contract?"),
119
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
121
FIXME: this method do others things that not have been documented. Please complete here what method do.
92
default = default.copy()
93
default['code'] = (account['code'] or '') + '(copy)'
94
default['name'] = (account['name'] or '') + '(copy)'
95
return super(analytic_account, self).copy(cr, uid, id, default, context=context)
97
def set_funding_pool_parent(self, cr, uid, vals):
98
if 'category' in vals and \
100
vals['category'] == 'FUNDING' and \
101
vals['code'] != 'FUNDING':
102
# for all accounts except the parent one
103
funding_pool_parent = self.search(cr, uid, [('category', '=', 'FUNDING'), ('parent_id', '=', False)])[0]
104
vals['parent_id'] = funding_pool_parent
106
def _check_date(self, vals):
107
if 'date' in vals and vals['date'] is not False:
108
if vals['date'] <= datetime.date.today().strftime('%Y-%m-%d'):
109
# validate the date (must be > today)
110
raise osv.except_osv(_('Warning !'), _('You cannot set an inactivity date lower than tomorrow!'))
111
elif 'date_start' in vals and not vals['date_start'] < vals['date']:
112
# validate that activation date
113
raise osv.except_osv(_('Warning !'), _('Activation date must be lower than inactivation date!'))
115
def create(self, cr, uid, vals, context=None):
117
Some verifications before analytic account creation
119
self._check_date(vals)
120
self.set_funding_pool_parent(cr, uid, vals)
121
return super(analytic_account, self).create(cr, uid, vals, context=context)
123
def write(self, cr, uid, ids, vals, context=None):
125
Some verifications before analytic account write
127
self._check_date(vals)
128
self.set_funding_pool_parent(cr, uid, vals)
129
return super(analytic_account, self).write(cr, uid, ids, vals, context=context)
131
def search(self, cr, uid, args, offset=0, limit=None, order=None,
132
context=None, count=False):
133
if context and 'filter_inactive_accounts' in context and context['filter_inactive_accounts']:
134
args.append(('date_start', '<=', datetime.date.today().strftime('%Y-%m-%d')))
136
args.append(('date', '>', datetime.date.today().strftime('%Y-%m-%d')))
137
args.append(('date', '=', False))
125
139
if context and 'search_by_ids' in context and context['search_by_ids']:
126
140
args2 = args[-1][2]
129
143
for arg in args2:
130
144
ids.append(arg[1])
131
145
args.append(('id', 'in', ids))
132
# UF-1713: Active/inactive functionnality was missing.
133
if context and 'filter_inactive' in context and context['filter_inactive']:
134
args.append(('filter_active', '=', context['filter_inactive']))
135
# Tuple Account/Destination search
136
for i, arg in enumerate(args):
137
if arg[0] and arg[0] == 'tuple_destination':
139
destination_ids = self.pool.get('account.destination.link').search(cr, uid, [('account_id', '=', arg[2][0]), ('destination_id', '=', arg[2][1])])
140
for adl in self.pool.get('account.destination.link').read(cr, uid, destination_ids, ['funding_pool_ids']):
141
fp_ids.append(adl.get('funding_pool_ids'))
142
fp_ids = flatten(fp_ids)
143
args[i] = ('id', 'in', fp_ids)
144
return super(analytic_account, self).search(cr, uid, args, offset, limit, order, context=context, count=count)
147
return super(analytic_account, self).search(cr, uid, args, offset, limit,
148
order, context=context, count=count)
150
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
153
view = super(analytic_account, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
155
oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
158
if view_type=='form':
159
tree = etree.fromstring(view['arch'])
160
fields = tree.xpath('/form/field[@name="cost_center_ids"]')
162
field.set('domain', "[('type', '!=', 'view'), ('id', 'child_of', [%s])]" % oc_id)
163
view['arch'] = etree.tostring(tree)
166
def on_change_category(self, cr, uid, id, category):
169
res = {'value': {}, 'domain': {}}
170
parent = self.search(cr, uid, [('category', '=', category), ('parent_id', '=', False)])[0]
171
res['value']['parent_id'] = parent
172
res['domain']['parent_id'] = [('category', '=', category), ('type', '=', 'view')]
175
def unlink(self, cr, uid, ids, context=None):
177
Delete the dummy analytic account is forbidden!
182
if isinstance(ids, (int, long)):
184
# Prepare some values
185
analytic_accounts = []
186
# Search dummy CC that have xml_id: analytic_account_project_dummy
188
dummy_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project_dummy')[1]
191
analytic_accounts.append(dummy_id)
194
oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
197
analytic_accounts.append(oc_id)
198
# Search Funding Pool
200
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_funding_pool')[1]
203
analytic_accounts.append(fp_id)
206
f1_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_1')[1]
209
analytic_accounts.append(f1_id)
212
f2_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_2')[1]
215
analytic_accounts.append(f2_id)
216
# Search MSF Private Fund
218
msf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
221
analytic_accounts.append(msf_id)
222
# Accounts verification
224
if id in analytic_accounts:
225
raise osv.except_osv(_('Error'), _('You cannot delete this Analytic Account!'))
226
return super(analytic_account, self).unlink(cr, uid, ids, context=context)
146
228
analytic_account()
147
229
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: