20
20
##############################################################################
23
22
from osv import osv
23
from osv import fields
24
24
from tools.translate import _
25
from tools.misc import flatten
26
from collections import defaultdict
27
from time import strftime
28
from lxml import etree
26
class analytic_line_activable(osv.osv):
30
class analytic_line(osv.osv):
31
_name = "account.analytic.line"
27
32
_inherit = "account.analytic.line"
29
def _check_date(self, cr, uid, vals):
34
def _get_fake_is_fp_compat_with(self, cr, uid, ids, field_name, args, context=None):
36
Fake method for 'is_fp_compat_with' field
43
def _search_is_fp_compat_with(self, cr, uid, obj, name, args, context=None):
45
Return domain that permit to give all analytic line compatible with a given FP.
50
# We just support '=' operator
53
raise osv.except_osv(_('Warning'), _('Some search args are missing!'))
54
if arg[1] not in ['=',]:
55
raise osv.except_osv(_('Warning'), _('This filter is not implemented yet!'))
57
raise osv.except_osv(_('Warning'), _('Some search args are missing!'))
58
analytic_account = self.pool.get('account.analytic.account').browse(cr, uid, arg[2])
59
tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in analytic_account.tuple_destination_account_ids]
60
cost_center_ids = [x and x.id for x in analytic_account.cost_center_ids]
61
for cc in cost_center_ids:
67
res.append(('cost_center_id', '=', cc))
68
res.append(('general_account_id', '=', t[0]))
69
res.append(('destination_id', '=', t[1]))
72
def _journal_type_get(self, cr, uid, context=None):
76
return self.pool.get('account.analytic.journal').get_journal_type(cr, uid, context)
79
'distribution_id': fields.many2one('analytic.distribution', string='Analytic Distribution'),
80
'cost_center_id': fields.many2one('account.analytic.account', string='Cost Center', domain="[('category', '=', 'OC'), ('type', '<>', 'view')]"),
81
'commitment_line_id': fields.many2one('account.commitment.line', string='Commitment Voucher Line', ondelete='cascade'),
82
'from_write_off': fields.boolean(string='From write-off account line?', readonly=True, help="Indicates that this line come from a write-off account line."),
83
'destination_id': fields.many2one('account.analytic.account', string="Destination", domain="[('category', '=', 'DEST'), ('type', '<>', 'view')]"),
84
'is_fp_compat_with': fields.function(_get_fake_is_fp_compat_with, fnct_search=_search_is_fp_compat_with, method=True, type="char", size=254, string="Is compatible with some FP?"),
85
'distrib_line_id': fields.reference('Distribution Line ID', selection=[('funding.pool.distribution.line', 'FP'),('free.1.distribution.line', 'free1'), ('free.2.distribution.line', 'free2')], size=512),
86
'move_state': fields.related('move_id', 'move_id', 'state', type='selection', size=64, relation="account.move.line", selection=[('draft', 'Unposted'), ('posted', 'Posted')], string='Journal Entry state', readonly=True, help="Indicates that this line come from an Unposted Journal Entry."),
87
'journal_type': fields.related('journal_id', 'type', type='selection', selection=_journal_type_get, string="Journal Type", readonly=True, \
88
help="Indicates the Journal Type of the Analytic journal item"),
92
'from_write_off': lambda *a: False,
95
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
97
Change account_id field name to "Funding Pool if we come from a funding pool
102
is_funding_pool_view = False
103
if context.get('display_fp', False) and context.get('display_fp') is True:
104
is_funding_pool_view = True
105
view = super(analytic_line, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
106
if view_type in ('tree', 'search') and is_funding_pool_view:
107
tree = etree.fromstring(view['arch'])
109
fields = tree.xpath('/' + view_type + '//field[@name="account_id"]')
111
field.set('string', _("Funding Pool"))
112
field.set('domain', "[('category', '=', 'FUNDING'), ('type', '<>', 'view')]")
113
view['arch'] = etree.tostring(tree)
116
def _check_date(self, cr, uid, vals, context=None):
118
Check if given account_id is active for given date. Except for mass reallocation ('from' = 'mass_reallocation' in context)
122
if not 'account_id' in vals:
123
raise osv.except_osv(_('Error'), _('No account_id found in given values!'))
30
124
if 'date' in vals and vals['date'] is not False:
31
125
account_obj = self.pool.get('account.analytic.account')
32
account = account_obj.browse(cr, uid, vals['account_id'])
33
if vals['date'] < account.date_start \
34
or (account.date != False and \
35
vals['date'] >= account.date):
36
raise osv.except_osv(_('Error !'), _('The analytic account selected is not active.'))
127
account = account_obj.browse(cr, uid, vals['account_id'], context=context)
128
# FIXME: refactoring of next code
129
if date < account.date_start or (account.date != False and date >= account.date):
130
if 'from' not in context or context.get('from') != 'mass_reallocation':
131
raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (account.name or '',))
132
if 'cost_center_id' in vals:
133
cc = account_obj.browse(cr, uid, vals['cost_center_id'], context=context)
134
if date < cc.date_start or (cc.date != False and date >= cc.date):
135
if 'from' not in context or context.get('from') != 'mass_reallocation':
136
raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (cc.name or '',))
137
if 'destination_id' in vals:
138
dest = account_obj.browse(cr, uid, vals['destination_id'], context=context)
139
if date < dest.date_start or (dest.date != False and date >= dest.date):
140
if 'from' not in context or context.get('from') != 'mass_reallocation':
141
raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (dest.name or '',))
38
144
def create(self, cr, uid, vals, context=None):
39
self._check_date(cr, uid, vals)
40
return super(analytic_line_activable, self).create(cr, uid, vals, context=context)
146
Check date for given date and given account_id
152
res = super(analytic_line, self).create(cr, uid, vals, context=context)
154
self._check_date(cr, uid, vals, context=context)
155
# Check soft/hard closed contract
156
sql = """SELECT fcc.id
157
FROM financing_contract_funding_pool_line fcfpl, account_analytic_account a, financing_contract_format fcf, financing_contract_contract fcc
158
WHERE fcfpl.funding_pool_id = a.id
159
AND fcfpl.contract_id = fcf.id
160
AND fcc.format_id = fcf.id
162
AND fcc.state in ('soft_closed', 'hard_closed');"""
163
cr.execute(sql, tuple([vals.get('account_id')]))
164
sql_res = cr.fetchall()
166
account = self.pool.get('account.analytic.account').browse(cr, uid, vals.get('account_id'))
167
contract = self.pool.get('financing.contract.contract').browse(cr, uid, sql_res[0][0])
168
raise osv.except_osv(_('Warning'), _('Selected Funding Pool analytic account (%s) is blocked by a soft/hard closed contract: %s') % (account and account.code or '', contract and contract.name or ''))
42
171
def write(self, cr, uid, ids, vals, context=None):
43
self._check_date(cr, uid, vals)
44
return super(analytic_line_activable, self).write(cr, uid, ids, vals, context=context)
47
analytic_line_activable()
49
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
b'\\ No newline at end of file'
173
Verify date for all given ids with account
177
if isinstance(ids, (int, long)):
179
for l in self.browse(cr, uid, ids):
181
for el in ['account_id', 'cost_center_id', 'destination_id']:
183
vals2.update({el: getattr(l, el, False),})
184
self._check_date(cr, uid, vals2, context=context)
185
return super(analytic_line, self).write(cr, uid, ids, vals, context=context)
187
def update_account(self, cr, uid, ids, account_id, date=False, context=None):
189
Update account on given analytic lines with account_id on given date
194
if isinstance(ids, (int, long)):
199
date = strftime('%Y-%m-%d')
201
account = self.pool.get('account.analytic.account').browse(cr, uid, [account_id], context)[0]
202
context.update({'from': 'mass_reallocation'}) # this permits reallocation to be accepted when rewrite analaytic lines
204
for aline in self.browse(cr, uid, ids, context=context):
205
if account.category in ['OC', 'DEST']:
206
# Period verification
207
period = aline.move_id and aline.move_id.period_id or False
208
# Prepare some values
209
fieldname = 'cost_center_id'
210
if account.category == 'DEST':
211
fieldname = 'destination_id'
212
# if period is not closed, so override line.
213
if period and period.state != 'done':
215
self.write(cr, uid, [aline.id], {fieldname: account_id, 'date': date,
216
'source_date': aline.source_date or aline.date}, context=context)
217
# else reverse line before recreating them with right values
220
self.pool.get('account.analytic.line').reverse(cr, uid, [aline.id])
221
# then create new lines
222
self.pool.get('account.analytic.line').copy(cr, uid, aline.id, {fieldname: account_id, 'date': date,
223
'source_date': aline.source_date or aline.date}, context=context)
224
# finally flag analytic line as reallocated
225
self.pool.get('account.analytic.line').write(cr, uid, [aline.id], {'is_reallocated': True})
228
self.write(cr, uid, [aline.id], {'account_id': account_id}, context=context)
231
def check_analytic_account(self, cr, uid, ids, account_id, context=None):
233
Analytic distribution validity verification with given account for given ids.
234
Return all valid ids.
239
if isinstance(ids, (int, long)):
242
account = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['category', 'date_start', 'date'], context=context)
243
account_type = account and account.get('category', False) or False
248
msf_private_fund = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
249
'analytic_account_msf_private_funds')[1]
252
expired_date_ids = []
253
date_start = account and account.get('date_start', False) or False
254
date_stop = account and account.get('date', False) or False
255
# Date verification for all lines and fetch all necessary elements sorted by analytic distribution
256
for aline in self.browse(cr, uid, ids):
257
# Add line to expired_date if date is not in date_start - date_stop
258
if (date_start and aline.date < date_start) or (date_stop and aline.date > date_stop):
259
expired_date_ids.append(aline.id)
260
# Process regarding account_type
261
if account_type == 'OC':
262
for aline in self.browse(cr, uid, ids):
264
# - the line doesn't have any draft/open contract
265
check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
266
if check_accounts and aline.account_id.id in check_accounts:
269
if aline.account_id and aline.account_id.id == msf_private_fund:
271
elif aline.account_id and aline.cost_center_id and aline.account_id.cost_center_ids:
272
if account_id in [x and x.id for x in aline.account_id.cost_center_ids] or aline.account_id.id == msf_private_fund:
274
elif account_type == 'FUNDING':
275
fp = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['cost_center_ids', 'tuple_destination_account_ids'], context=context)
276
cc_ids = fp and fp.get('cost_center_ids', []) or []
277
tuple_destination_account_ids = fp and fp.get('tuple_destination_account_ids', []) or []
278
tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in self.pool.get('account.destination.link').browse(cr, uid, tuple_destination_account_ids)]
279
# Browse all analytic line to verify them
280
for aline in self.browse(cr, uid, ids):
282
# - the line doesn't have any draft/open contract
283
check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
284
if check_accounts and aline.account_id.id in check_accounts:
286
# No verification if account is MSF Private Fund because of its compatibility with all elements.
287
if account_id == msf_private_fund:
291
# - the line have a cost_center_id field (we expect it's a line with a funding pool account)
292
# - the cost_center is in compatible cost center from the new funding pool
293
# - the general account is in compatible account/destination tuple
294
# - the destination is in compatible account/destination tuple
295
if aline.cost_center_id and aline.cost_center_id.id in cc_ids and aline.general_account_id and aline.destination_id and (aline.general_account_id.id, aline.destination_id.id) in tuple_list:
298
# Case of FREE1 and FREE2 lines
301
# Delete elements that are in expired_date_ids
302
for id in expired_date_ids:
308
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: