23
23
from dateutil.relativedelta import relativedelta
24
24
from osv import fields, osv
25
25
from tools.translate import _
26
from lxml import etree
27
from tools.misc import flatten
28
from destination_tools import many2many_sorted, many2many_notlazy
27
class analytic_account_activable(osv.osv):
30
class analytic_account(osv.osv):
31
_name = "account.analytic.account"
28
32
_inherit = "account.analytic.account"
34
def _get_active(self, cr, uid, ids, field_name, args, context=None):
36
If date out of date_start/date of given analytic account, then account is inactive.
37
The comparison could be done via a date given in context.
40
cmp_date = datetime.date.today().strftime('%Y-%m-%d')
41
if context.get('date', False):
42
cmp_date = context.get('date')
43
for a in self.browse(cr, uid, ids):
45
if a.date_start > cmp_date:
47
if a.date and a.date <= cmp_date:
51
def _search_filter_active(self, cr, uid, ids, name, args, context=None):
53
UTP-410: Add the search on active/inactive CC
56
cmp_date = datetime.date.today().strftime('%Y-%m-%d')
57
if context.get('date', False):
58
cmp_date = context.get('date')
60
if x[0] == 'filter_active' and x[2] == True:
61
arg.append(('date_start', '<=', cmp_date))
63
arg.append(('date', '>', cmp_date))
64
arg.append(('date', '=', False))
65
elif x[0] == 'filter_active' and x[2] == False:
67
arg.append(('date_start', '>', cmp_date))
68
arg.append(('date', '<=', cmp_date))
71
def _search_closed_by_a_fp(self, cr, uid, ids, name, args, context=None):
73
UTP-423: Do not display analytic accounts linked to a soft/hard closed contract.
75
res = [('id', 'not in', [])]
76
if args and args[0] and len(args[0]) == 3:
78
raise osv.except_osv(_('Error'), _('Operator not supported yet!'))
79
# Search all fp_ids from soft_closed contract
81
FROM account_analytic_account a, financing_contract_contract fcc, financing_contract_funding_pool_line fcfl
82
WHERE fcfl.contract_id = fcc.id
83
AND fcfl.funding_pool_id = a.id
84
AND fcc.state in ('soft_closed', 'hard_closed');"""
86
sql_res = cr.fetchall()
88
aa_ids = self.is_blocked_by_a_contract(cr, uid, [x and x[0] for x in sql_res])
90
if isinstance(aa_ids, (int, long)):
92
res = [('id', 'not in', aa_ids)]
95
def _get_fake(self, cr, uid, ids, *a, **b):
96
return {}.fromkeys(ids, False)
98
def _search_intermission_restricted(self, cr, uid, ids, name, args, context=None):
104
raise osv.except_osv(_('Error'), _('Operator not supported on field intermission_restricted!'))
105
if not isinstance(arg[2], (list, tuple)):
106
raise osv.except_osv(_('Error'), _('Operand not supported on field intermission_restricted!'))
107
if arg[2] and (arg[2][0] or arg[2][1]):
109
intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
110
'analytic_account_project_intermission')[1]
113
if arg[2][2] == 'intermission':
114
newargs.append(('id', '=', intermission))
116
newargs.append(('id', '!=', intermission))
120
'name': fields.char('Name', size=128, required=True, translate=1),
121
'code': fields.char('Code', size=24),
122
'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.'),
31
123
'date_start': fields.date('Active from', required=True),
32
124
'date': fields.date('Inactive from', select=True),
125
'category': fields.selection([('OC','Cost Center'),
126
('FUNDING','Funding Pool'),
129
('DEST', 'Destination')], 'Category', select=1),
130
'cost_center_ids': fields.many2many('account.analytic.account', 'funding_pool_associated_cost_centers', 'funding_pool_id', 'cost_center_id', string='Cost Centers', domain="[('type', '!=', 'view'), ('category', '=', 'OC')]"),
131
'for_fx_gain_loss': fields.boolean(string="For FX gain/loss", help="Is this account for default FX gain/loss?"),
132
'destination_ids': many2many_notlazy('account.account', 'account_destination_link', 'destination_id', 'account_id', 'Accounts'),
133
'tuple_destination_account_ids': many2many_sorted('account.destination.link', 'funding_pool_associated_destinations', 'funding_pool_id', 'tuple_id', "Account/Destination"),
134
'tuple_destination_summary': fields.one2many('account.destination.summary', 'funding_pool_id', 'Destination by accounts'),
135
'filter_active': fields.function(_get_active, fnct_search=_search_filter_active, type="boolean", method=True, store=False, string="Show only active analytic accounts",),
136
'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?"),
137
'intermission_restricted': fields.function(_get_fake, fnct_search=_search_intermission_restricted, type="boolean", method=True, store=False, string="Domain to restrict intermission cc"),
36
'date_start': lambda *a: (datetime.datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d')
141
'date_start': lambda *a: (datetime.datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d'),
142
'for_fx_gain_loss': lambda *a: False,
145
def _check_unicity(self, cr, uid, ids, context=None):
148
for account in self.browse(cr, uid, ids, context=context):
149
bad_ids = self.search(cr, uid, [('category', '=', account.category),('|'),('name', '=ilike', account.name),('code', '=ilike', account.code)])
150
if len(bad_ids) and len(bad_ids) > 1:
154
def _check_gain_loss_account_unicity(self, cr, uid, ids, context=None):
156
Check that no more account is "for_fx_gain_loss" available.
160
search_ids = self.search(cr, uid, [('for_fx_gain_loss', '=', True)])
161
if search_ids and len(search_ids) > 1:
165
def _check_gain_loss_account_type(self, cr, uid, ids, context=None):
167
Check account type for fx_gain_loss_account: should be Normal type and Cost Center category
171
for account in self.browse(cr, uid, ids, context=context):
172
if account.for_fx_gain_loss == True and (account.type != 'normal' or account.category != 'OC'):
176
def _check_default_destination(self, cr, uid, ids, context=None):
177
if isinstance(ids, (int, long)):
181
cr.execute('''select a.code, a.name, d.name from
182
'''+self._table+''' d
183
left join account_account a on a.default_destination_id = d.id
184
left join account_destination_link l on l.destination_id = d.id and l.account_id = a.id
185
where a.default_destination_id is not null and l.destination_id is null and d.id in %s ''', (tuple(ids),)
188
for x in cr.fetchall():
189
error.append(_('"%s" is the default destination for the G/L account "%s %s", you can\'t remove it.')%(x[2], x[0], x[1]))
191
raise osv.except_osv(_('Warning !'), "\n".join(error))
195
(_check_unicity, 'You cannot have the same code or name between analytic accounts in the same category!', ['code', 'name', 'category']),
196
(_check_gain_loss_account_unicity, 'You can only have one account used for FX gain/loss!', ['for_fx_gain_loss']),
197
(_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']),
198
(_check_default_destination, "You can't delete an account which has this destination as default", []),
201
def copy(self, cr, uid, id, default=None, context=None, done_list=[], local=False):
202
account = self.browse(cr, uid, id, context=context)
205
default = default.copy()
206
default['code'] = (account['code'] or '') + '(copy)'
207
default['name'] = (account['name'] or '') + '(copy)'
208
default['tuple_destination_summary'] = []
209
# code is deleted in copy method in addons
210
new_id = super(analytic_account, self).copy(cr, uid, id, default, context=context)
211
self.write(cr, uid, new_id, {'code': '%s(copy)' % (account['code'] or '')})
214
def set_funding_pool_parent(self, cr, uid, vals):
215
if 'category' in vals and \
217
vals['category'] == 'FUNDING' and \
218
vals['code'] != 'FUNDING':
219
# for all accounts except the parent one
220
funding_pool_parent = self.search(cr, uid, [('category', '=', 'FUNDING'), ('parent_id', '=', False)])[0]
221
vals['parent_id'] = funding_pool_parent
39
223
def _check_date(self, vals):
40
224
if 'date' in vals and vals['date'] is not False:
41
225
if vals['date'] <= datetime.date.today().strftime('%Y-%m-%d'):
42
# validate the date (must be > today)
43
raise osv.except_osv(_('Warning !'), _('You cannot set an inactivity date lower than tomorrow!'))
226
# validate the date (must be > today)
227
raise osv.except_osv(_('Warning !'), _('You cannot set an inactivity date lower than tomorrow!'))
44
228
elif 'date_start' in vals and not vals['date_start'] < vals['date']:
45
229
# validate that activation date
46
230
raise osv.except_osv(_('Warning !'), _('Activation date must be lower than inactivation date!'))
48
232
def create(self, cr, uid, vals, context=None):
234
Some verifications before analytic account creation
49
236
self._check_date(vals)
50
return super(analytic_account_activable, self).create(cr, uid, vals, context=context)
237
self.set_funding_pool_parent(cr, uid, vals)
238
return super(analytic_account, self).create(cr, uid, vals, context=context)
52
240
def write(self, cr, uid, ids, vals, context=None):
242
Some verifications before analytic account write
53
244
self._check_date(vals)
54
return super(analytic_account_activable, self).write(cr, uid, ids, vals, context=context)
56
def search(self, cr, uid, args, offset=0, limit=None, order=None,
57
context=None, count=False):
58
if context and 'filter_inactive_accounts' in context and context['filter_inactive_accounts']:
59
args.append(('date_start', '<=', datetime.date.today().strftime('%Y-%m-%d')))
61
args.append(('date', '>', datetime.date.today().strftime('%Y-%m-%d')))
62
args.append(('date', '=', False))
64
return super(analytic_account_activable, self).search(cr, uid, args, offset, limit,
65
order, context=context, count=count)
67
analytic_account_activable()
245
self.set_funding_pool_parent(cr, uid, vals)
246
return super(analytic_account, self).write(cr, uid, ids, vals, context=context)
248
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
250
FIXME: this method do others things that not have been documented. Please complete here what method do.
254
if context and 'search_by_ids' in context and context['search_by_ids']:
260
args.append(('id', 'in', ids))
262
# Tuple Account/Destination search
263
for i, arg in enumerate(args):
264
if arg[0] and arg[0] == 'tuple_destination':
266
destination_ids = self.pool.get('account.destination.link').search(cr, uid, [('account_id', '=', arg[2][0]), ('destination_id', '=', arg[2][1])])
267
for adl in self.pool.get('account.destination.link').read(cr, uid, destination_ids, ['funding_pool_ids']):
268
fp_ids.append(adl.get('funding_pool_ids'))
269
fp_ids = flatten(fp_ids)
270
args[i] = ('id', 'in', fp_ids)
271
res = super(analytic_account, self).search(cr, uid, args, offset, limit, order, context=context, count=count)
274
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
277
view = super(analytic_account, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
279
oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
282
if view_type=='form':
283
tree = etree.fromstring(view['arch'])
284
fields = tree.xpath('/form/field[@name="cost_center_ids"]')
286
field.set('domain', "[('type', '!=', 'view'), ('id', 'child_of', [%s])]" % oc_id)
287
view['arch'] = etree.tostring(tree)
290
def on_change_category(self, cr, uid, id, category):
293
res = {'value': {}, 'domain': {}}
294
parent = self.search(cr, uid, [('category', '=', category), ('parent_id', '=', False)])[0]
295
res['value']['parent_id'] = parent
296
res['domain']['parent_id'] = [('category', '=', category), ('type', '=', 'view')]
299
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
304
if context.get('hide_inactive', False):
305
args.append(('filter_active', '=', True))
306
if context.get('current_model') == 'project.project':
307
cr.execute("select analytic_account_id from project_project")
308
project_ids = [x[0] for x in cr.fetchall()]
309
return self.name_get(cr, uid, project_ids, context=context)
310
account = self.search(cr, uid, ['|', ('code', 'ilike', '%%%s%%' % name), ('name', 'ilike', '%%%s%%' % name)]+args, limit=limit, context=context)
311
return self.name_get(cr, uid, account, context=context)
313
def name_get(self, cr, uid, ids, context={}):
315
Get name for analytic account with analytic account code.
316
Example: For an account OC/Project/Mission, we have something like this:
317
MIS-001 (OC-015/PROJ-859)
322
if isinstance(ids, (int, long)):
326
# Browse all accounts
327
for account in self.browse(cr, uid, ids, context=context):
331
# data.insert(0, acc.code)
332
# acc = acc.parent_id
333
# data = ' / '.join(data[1:-1])
334
# display = "%s" % (account.code)
335
# if len(data) and len(data) > 0:
336
# display = "%s (%s)" % (account.code, data)
337
# res.append((account.id, display))
338
res.append((account.id, account.code))
341
def unlink(self, cr, uid, ids, context=None):
343
Delete some analytic account is forbidden!
348
if isinstance(ids, (int, long)):
350
# Prepare some values
351
analytic_accounts = []
354
oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
357
analytic_accounts.append(oc_id)
358
# Search Funding Pool
360
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_funding_pool')[1]
363
analytic_accounts.append(fp_id)
366
f1_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_1')[1]
369
analytic_accounts.append(f1_id)
372
f2_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_2')[1]
375
analytic_accounts.append(f2_id)
376
# Search MSF Private Fund
378
msf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
381
analytic_accounts.append(msf_id)
382
# Accounts verification
384
if id in analytic_accounts:
385
raise osv.except_osv(_('Error'), _('You cannot delete this Analytic Account!'))
386
return super(analytic_account, self).unlink(cr, uid, ids, context=context)
388
def is_blocked_by_a_contract(self, cr, uid, ids):
390
Return ids (analytic accounts) that are blocked by a contract (just FP1)
393
if isinstance(ids, (int, long)):
395
# Prepare some values
397
for aa in self.browse(cr, uid, ids):
398
# Only check funding pool accounts
399
if aa.category != 'FUNDING':
401
link_ids = self.pool.get('financing.contract.funding.pool.line').search(cr, uid, [('funding_pool_id', '=', aa.id)])
403
for link in self.pool.get('financing.contract.funding.pool.line').browse(cr, uid, link_ids):
405
format_ids.append(link.contract_id.id)
406
contract_ids = self.pool.get('financing.contract.contract').search(cr, uid, [('format_id', 'in', format_ids)])
407
for contract in self.pool.get('financing.contract.contract').browse(cr, uid, contract_ids):
408
if contract.state in ['soft_closed', 'hard_closed']:
69
413
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: