1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 MSF, TeMPO Consulting.
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
22
from osv import fields, osv
24
from analytic_distribution.destination_tools import many2many_sorted
25
from account_override import ACCOUNT_RESTRICTED_AREA
28
class financing_contract_format_line(osv.osv):
30
_name = "financing.contract.format.line"
32
def _create_domain(self, header, element_list):
33
domain = "('" + header + "', 'in', ["
34
if len(element_list) > 0:
35
for element in element_list:
36
domain += str(element.id)
42
def _create_account_destination_domain(self, account_destination_list, general_domain):
43
if len(account_destination_list) == 0:
46
('general_account_id', 'in', []),
47
('destination_id', 'in', []),
49
eval(general_domain['cost_center_domain']),
50
eval(general_domain['funding_pool_domain'])]
51
elif len(account_destination_list) == 1:
54
('general_account_id', '=', account_destination_list[0].account_id.id),
55
('destination_id', '=', account_destination_list[0].destination_id.id),
57
eval(general_domain['cost_center_domain']),
58
eval(general_domain['funding_pool_domain'])]
61
+ self._create_account_destination_domain([account_destination_list[0]], general_domain) \
62
+ self._create_account_destination_domain(account_destination_list[1:], general_domain)
64
def _create_account_quadruplet_domain(self, account_quadruplet_list, funding_pool_ids):
65
if len(account_quadruplet_list) == 0:
68
('general_account_id', '=', False),
69
('destination_id', '=', False),
71
('cost_center_id', '=', False),
72
('account_id', '=', False)]
73
elif len(account_quadruplet_list) == 1:
74
if account_quadruplet_list[0].funding_pool_id.id in funding_pool_ids:
77
('general_account_id', '=', account_quadruplet_list[0].account_destination_id.account_id.id),
78
('destination_id', '=', account_quadruplet_list[0].account_destination_id.destination_id.id),
80
('cost_center_id', '=', account_quadruplet_list[0].cost_center_id.id),
81
('account_id', '=', account_quadruplet_list[0].funding_pool_id.id)]
85
('general_account_id', '=', False),
86
('destination_id', '=', False),
88
('cost_center_id', '=', False),
89
('account_id', '=', False)]
92
+ self._create_account_quadruplet_domain([account_quadruplet_list[0]], funding_pool_ids) \
93
+ self._create_account_quadruplet_domain(account_quadruplet_list[1:], funding_pool_ids)
95
def _get_number_of_childs(self, cr, uid, ids, field_name=None, arg=None, context=None):
99
if isinstance(ids, (int, long)):
101
# Prepare some values
103
for line in self.browse(cr, uid, ids, context=context):
104
res[line.id] = line.child_ids and len(line.child_ids) or 0
107
def _get_parent_ids(self, cr, uid, ids, context=None):
109
for line in self.browse(cr, uid, ids, context=context):
111
res.append(line.parent_id.id)
114
def _get_total_costs(self, cr, uid, browse_overhead_line, field_name=None, context=None):
115
# since this is only called from the overhead line, the domain is
116
# "all children, except the overhead lines"
118
total_line_ids = [line.id for line in browse_overhead_line.format_id.actual_line_ids if line.line_type in ['actual', 'consumption']]
120
if field_name and field_name in ('allocated_budget', 'project_budget'):
121
total_costs = self._get_budget_amount(cr, uid, total_line_ids, field_name, context=context)
123
total_costs = self._get_actual_amount(cr, uid, total_line_ids, field_name, context=context)
124
for total_cost in total_costs.values():
128
def _get_account_destination_quadruplets(self, browse_line):
129
account_destination_result = []
130
account_quadruplet_result = []
131
if browse_line.line_type != 'view':
132
if browse_line.is_quadruplet:
133
account_quadruplet_result = [account_quadruplet for account_quadruplet in browse_line.account_quadruplet_ids]
135
account_destination_result = [account_destination for account_destination in browse_line.account_destination_ids]
137
for child_line in browse_line.child_ids:
138
temp = self._get_account_destination_quadruplets(child_line)
139
account_destination_result += temp['account_destination_list']
140
account_quadruplet_result += temp['account_quadruplet_list']
141
return {'account_destination_list': account_destination_result,
142
'account_quadruplet_list': account_quadruplet_result}
144
def _get_general_domain(self, cr, uid, browse_format, domain_type, context=None):
145
# Method to get the domain (allocated or project) of a line
146
date_domain = "[('document_date', '>=', '"
147
date_domain += browse_format.eligibility_from_date
148
date_domain += "'), ('document_date', '<=', '"
149
date_domain += browse_format.eligibility_to_date
152
funding_pool_ids = []
153
if domain_type == 'allocated':
154
funding_pool_ids = [funding_pool_line.funding_pool_id for funding_pool_line in browse_format.funding_pool_ids if funding_pool_line.funded]
156
funding_pool_ids = [funding_pool_line.funding_pool_id for funding_pool_line in browse_format.funding_pool_ids]
157
funding_pool_domain = self._create_domain('account_id', funding_pool_ids)
159
cost_center_domain = self._create_domain('cost_center_id', browse_format.cost_center_ids)
160
return {'date_domain': date_domain,
161
'funding_pool_domain': funding_pool_domain,
162
'cost_center_domain': cost_center_domain,
163
'funding_pool_ids': [x.id for x in funding_pool_ids]}
165
def _get_analytic_domain(self, cr, uid, browse_line, domain_type, context=None):
166
if browse_line.line_type in ('consumption', 'overhead'):
167
# No domain for those
170
# last domain: get only non-corrected lines.
171
non_corrected_domain = [('is_reallocated', '=', False),
172
('is_reversal', '=', False)]
173
format = browse_line.format_id
174
if format.eligibility_from_date and format.eligibility_to_date:
175
general_domain = self._get_general_domain(cr, uid, format, domain_type, context=context)
176
# Account + destination domain
177
account_destination_quadruplet_ids = self._get_account_destination_quadruplets(browse_line)
178
account_destination_domain = self._create_account_destination_domain(account_destination_quadruplet_ids['account_destination_list'], general_domain)
179
account_quadruplet_domain = self._create_account_quadruplet_domain(account_destination_quadruplet_ids['account_quadruplet_list'], general_domain['funding_pool_ids'])
180
# create the final domain
181
date_domain = eval(general_domain['date_domain'])
182
return [date_domain[0], date_domain[1]] + non_corrected_domain + ['|'] + account_destination_domain + account_quadruplet_domain
184
# Dates are not set (since we are probably in a donor).
188
def _is_overhead_present(self, cr, uid, ids, context={}):
189
for line in self.browse(cr, uid, ids, context=context):
190
if line.line_type == 'overhead':
192
elif line.line_type == 'view':
193
if self._is_overhead_present(cr, uid, [x.id for x in line.child_ids], context=context):
197
def _get_actual_line_ids(self, cr, uid, ids, context={}):
199
for line in self.browse(cr, uid, ids, context=context):
200
if line.line_type == 'view':
201
actual_line_ids += self._get_actual_line_ids(cr, uid, [x.id for x in line.child_ids], context=context)
202
elif line.line_type in ['actual', 'consumption']:
203
actual_line_ids.append(line.id)
204
return actual_line_ids
206
def _get_view_amount(self, browse_line, total_costs, retrieved_lines):
207
if browse_line.line_type == 'view':
209
for child_line in browse_line.child_ids:
210
if child_line.id not in retrieved_lines:
211
self._get_view_amount(child_line, total_costs, retrieved_lines)
212
sum += retrieved_lines[child_line.id]
213
retrieved_lines[browse_line.id] = sum
214
elif browse_line.line_type == 'overhead':
215
if browse_line.overhead_type == 'cost_percentage':
216
# percentage of all costs (sum of all 2nd-level lines, except overhead)
217
retrieved_lines[browse_line.id] = round(total_costs * browse_line.overhead_percentage / 100.0)
218
elif browse_line.overhead_type == 'grant_percentage':
219
# percentage of all costs (sum of all 2nd-level lines, except overhead)
220
retrieved_lines[browse_line.id] = round(total_costs * browse_line.overhead_percentage / (100.0 - browse_line.overhead_percentage))
223
def _get_budget_amount(self, cr, uid, ids, field_name=None, arg=None, context=None):
225
Method to compute the allocated budget/amounts, depending on the information in the line
228
# 1st step: get the real list of actual lines to compute
230
overhead = self._is_overhead_present(cr, uid, ids, context=context)
231
if overhead and len(ids) > 0:
232
line_for_format = self.browse(cr, uid, ids[0], context=context)
233
actual_line_ids = [line.id for line in line_for_format.format_id.actual_line_ids if line.line_type in ['actual', 'consumption']]
235
actual_line_ids = self._get_actual_line_ids(cr, uid, ids, context=context)
237
# 2nd step: retrieve values for all actual lines above
238
for line in self.browse(cr, uid, actual_line_ids, context=context):
242
if line.line_type in ('actual', 'consumption'):
243
# Value is set by the user, just return it
244
if field_name == 'allocated_budget':
245
res[line.id] = line.allocated_budget_value
246
elif field_name == 'project_budget':
247
res[line.id] = line.project_budget_value
249
# 3rd step: compute all remaining lines from the retrieved results
250
total_budget_costs = 0.0
252
total_budget_costs = sum(res.values())
253
for line in self.browse(cr, uid, [id for id in ids if id not in actual_line_ids], context=context):
254
if line.id not in res:
255
self._get_view_amount(line, total_budget_costs, res)
259
def _get_actual_amount(self, cr, uid, ids, field_name=None, arg=None, context=None):
261
Method to compute the allocated budget/amounts, depending on the information in the line
264
# 1st step: get the real list of actual lines to compute
266
overhead = self._is_overhead_present(cr, uid, ids, context=context)
267
if overhead and len(ids) > 0:
268
line_for_format = self.browse(cr, uid, ids[0], context=context)
269
actual_line_ids = [line.id for line in line_for_format.format_id.actual_line_ids if line.line_type in ['actual', 'consumption']]
271
actual_line_ids = self._get_actual_line_ids(cr, uid, ids, context=context)
273
# 2nd step: retrieve values for all actual lines above
274
for line in self.browse(cr, uid, actual_line_ids, context=context):
278
if line.line_type == 'consumption':
279
# Value is set by the user, just return it
280
if field_name == 'allocated_real':
281
res[line.id] = line.allocated_real_value
282
elif field_name == 'project_real':
283
res[line.id] = line.project_real_value
284
elif line.line_type == 'actual':
285
# sum of analytic lines, determined by the domain
287
if field_name == 'project_real':
288
analytic_domain = self._get_analytic_domain(cr, uid, line, 'project', context=context)
289
elif field_name == 'allocated_real':
290
analytic_domain = self._get_analytic_domain(cr, uid, line, 'allocated', context=context)
291
# selection of analytic lines
292
if 'reporting_currency' in context:
293
analytic_line_obj = self.pool.get('account.analytic.line')
294
analytic_lines = analytic_line_obj.search(cr, uid, analytic_domain ,context=context)
296
currency_table = None
297
if 'currency_table_id' in context:
298
currency_table = context['currency_table_id']
299
for analytic_line in analytic_line_obj.browse(cr, uid, analytic_lines, context=context):
300
date_context = {'date': analytic_line.document_date,
301
'currency_table_id': currency_table}
302
real_sum += self.pool.get('res.currency').compute(cr,
304
analytic_line.currency_id.id,
305
context['reporting_currency'],
306
analytic_line.amount_currency or 0.0,
308
context=date_context)
309
# Invert the result from the lines (positive for out, negative for in)
311
res[line.id] = real_sum
313
# 3rd step: compute all remaining lines from the retrieved results
314
total_actual_costs = 0.0
316
total_actual_costs = sum(res.values())
317
for line in self.browse(cr, uid, [id for id in ids if id not in actual_line_ids], context=context):
318
if line.id not in res:
319
self._get_view_amount(line, total_actual_costs, res)
325
'name': fields.char('Name', size=64, required=True),
326
'code': fields.char('Code', size=16, required=True),
327
'format_id': fields.many2one('financing.contract.format', 'Format'),
328
'is_quadruplet': fields.boolean('Input CC/FP at line level?'),
329
'account_destination_ids': many2many_sorted('account.destination.link', 'financing_contract_actual_account_destinations', 'actual_line_id', 'account_destination_id', string='Accounts/Destinations', domain=ACCOUNT_RESTRICTED_AREA['contract_reporting_lines']),
330
'parent_id': fields.many2one('financing.contract.format.line', 'Parent line'),
331
'child_ids': fields.one2many('financing.contract.format.line', 'parent_id', 'Child lines'),
332
'line_type': fields.selection([('view','View'),
334
('consumption','Consumption'),
335
('overhead','Overhead')], 'Line type', required=True),
336
'overhead_type': fields.selection([('cost_percentage','Percentage of direct costs'),
337
('grant_percentage','Percentage of grant')], 'Overhead calculation mode'),
338
'allocated_budget_value': fields.float('Budget allocated amount (value)'),
339
'project_budget_value': fields.float('Budget project amount (value)'),
340
'allocated_real_value': fields.float('Real allocated amount (value)'),
341
'project_real_value': fields.float('Real project amount (value)'),
342
'overhead_percentage': fields.float('Overhead percentage'),
344
'allocated_budget': fields.function(_get_budget_amount, method=True, store=False, string="Funded - Budget", type="float", readonly=True),
345
'project_budget': fields.function(_get_budget_amount, method=True, store=False, string="Total project - Budget", type="float", readonly=True),
347
'allocated_real': fields.function(_get_actual_amount, method=True, store=False, string="Funded - Actuals", type="float", readonly=True),
348
'project_real': fields.function(_get_actual_amount, method=True, store=False, string="Total project - Actuals", type="float", readonly=True),
353
'is_quadruplet': False,
354
'line_type': 'actual',
355
'overhead_type': 'cost_percentage',
356
'parent_id': lambda *a: False
361
def create(self, cr, uid, vals, context=None):
364
# if the account is set as view, remove budget and account values
365
if 'line_type' in vals and vals['line_type'] == 'view':
366
vals['allocated_amount'] = 0.0
367
vals['project_amount'] = 0.0
368
vals['account_destination_ids'] = []
369
vals['account_quadruplet_ids'] = []
370
elif 'is_quadruplet' in vals:
371
if vals['is_quadruplet']:
372
# delete account/destinations
373
vals['account_destination_ids'] = []
376
vals['account_quadruplet_ids'] = []
377
return super(financing_contract_format_line, self).create(cr, uid, vals, context=context)
379
def write(self, cr, uid, ids, vals, context=None):
382
if isinstance(ids, (int, long)):
384
# if the account is set as view, remove budget and account values
385
if 'line_type' in vals and vals['line_type'] == 'view':
386
vals['allocated_amount'] = 0.0
387
vals['project_amount'] = 0.0
388
vals['account_destination_ids'] = [(6, 0, [])]
389
vals['account_quadruplet_ids'] = [(6, 0, [])]
390
elif 'is_quadruplet' in vals:
391
if vals['is_quadruplet']:
392
# delete previous account/destinations
393
vals['account_destination_ids'] = [(6, 0, [])]
395
# delete previous quadruplets
396
vals['account_quadruplet_ids'] = [(6, 0, [])]
397
return super(financing_contract_format_line, self).write(cr, uid, ids, vals, context=context)
399
def copy_format_line(self, cr, uid, browse_source_line, destination_format_id, parent_id=None, context=None):
400
if destination_format_id:
402
'name': browse_source_line.name,
403
'code': browse_source_line.code,
404
'format_id': destination_format_id,
405
'parent_id': parent_id,
406
'line_type': browse_source_line.line_type,
407
'account_quadruplet_ids': [(6, 0, [])],
409
account_destination_ids = [account_destination.id for account_destination in browse_source_line.account_destination_ids]
410
format_line_vals['account_destination_ids'] = [(6, 0, account_destination_ids)]
411
parent_line_id = self.pool.get('financing.contract.format.line').create(cr, uid, format_line_vals, context=context)
412
for child_line in browse_source_line.child_ids:
413
self.copy_format_line(cr, uid, child_line, destination_format_id, parent_line_id, context=context)
416
financing_contract_format_line()
418
class financing_contract_format(osv.osv):
420
_name = "financing.contract.format"
421
_inherit = "financing.contract.format"
424
'actual_line_ids': fields.one2many('financing.contract.format.line', 'format_id', 'Actual lines')
427
def copy_format_lines(self, cr, uid, source_id, destination_id, context=None):
428
# remove all old report lines
429
destination_obj = self.browse(cr, uid, destination_id, context=context)
430
for to_remove_line in destination_obj.actual_line_ids:
431
self.pool.get('financing.contract.format.line').unlink(cr, uid, to_remove_line.id, context=context)
432
source_obj = self.browse(cr, uid, source_id, context=context)
433
# Method to copy a format
435
for source_line in source_obj.actual_line_ids:
436
if not source_line.parent_id:
437
self.pool.get('financing.contract.format.line').copy_format_line(cr,
445
financing_contract_format()
446
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: