18
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
20
##############################################################################
22
from osv import fields, osv
22
from osv import fields, osv
23
from tools.translate import _
25
25
class financing_contract_funding_pool_line(osv.osv):
27
27
_name = "financing.contract.funding.pool.line"
28
_description = "Funding pool line"
31
30
'contract_id': fields.many2one('financing.contract.format', 'Contract', required=True),
32
31
'funding_pool_id': fields.many2one('account.analytic.account', 'Funding pool name', required=True),
33
'funded': fields.boolean('Earmarked'),
32
'funded': fields.boolean('Funded'),
34
33
'total_project': fields.boolean('Total project'),
39
38
'total_project': True,
42
def create(self, cr, uid, vals, context=None):
43
result = super(financing_contract_funding_pool_line, self).create(cr, uid, vals, context=context)
44
# Add the corresponding quadruplets (one for each CC in the format and all A/D)
45
if 'contract_id' in vals and 'funding_pool_id' in vals:
46
# Create quadruplets accordingly
47
quad_obj = self.pool.get('financing.contract.account.quadruplet')
48
format_obj = self.pool.get('financing.contract.format')
49
data = format_obj.get_data_for_quadruplets(cr, vals['contract_id'])
50
# for each funding pool, add all quadruplets
51
for cost_center_id in data['cost_center_ids']:
52
for account_destination_id in data['account_destination_ids']:
53
quad_obj.create(cr, uid,
54
{'format_id': vals['contract_id'],
55
'account_destination_id': account_destination_id,
56
'cost_center_id': cost_center_id,
57
'funding_pool_id': vals['funding_pool_id']}, context=context)
60
def write(self, cr, uid, ids, vals, context=None):
61
if 'funding_pool_id' in vals:
62
# if the funding pool changes, add/remove accordingly
63
quad_obj = self.pool.get('financing.contract.account.quadruplet')
64
format_obj = self.pool.get('financing.contract.format')
66
funding_pool_line = self.browse(cr, uid, id, context=context)
67
format_id = funding_pool_line.contract_id.id
68
data = format_obj.get_data_for_quadruplets(cr, format_id)
69
old_funding_pool_id = funding_pool_line.funding_pool_id.id
70
new_funding_pool_id = vals['funding_pool_id']
72
quads_to_delete = quad_obj.search(cr, uid, [('funding_pool_id', '=', old_funding_pool_id)], context=context)
73
quad_obj.unlink(cr, uid, quads_to_delete, context=context)
74
# add missing quadruplets
75
for cost_center_id in data['cost_center_ids']:
76
for account_destination_id in data['account_destination_ids']:
77
quad_obj.create(cr, uid,
78
{'format_id': format_id,
79
'account_destination_id': account_destination_id,
80
'cost_center_id': cost_center_id,
81
'funding_pool_id': new_funding_pool_id}, context=context)
83
return super(financing_contract_funding_pool_line, self).write(cr, uid, ids, vals, context=context)
85
def unlink(self, cr, uid, ids, context=None):
86
# for unlink, simple: remove all lines for those funding pools
87
quad_obj = self.pool.get('financing.contract.account.quadruplet')
88
for funding_pool_line in self.browse(cr, uid, ids, context=context):
89
quads_to_delete = quad_obj.search(cr, uid, [('funding_pool_id', '=', funding_pool_line.funding_pool_id.id)], context=context)
90
quad_obj.unlink(cr, uid, quads_to_delete, context=context)
92
return super(financing_contract_funding_pool_line, self).unlink(cr, uid, ids, context=context)
94
41
financing_contract_funding_pool_line()
96
43
class financing_contract_contract(osv.osv):
98
45
_name = "financing.contract.contract"
99
46
_inherits = {"financing.contract.format": "format_id"}
102
48
def contract_open(self, cr, uid, ids, *args):
103
49
self.write(cr, uid, ids, {
110
def search_draft_or_temp_posted(self, cr, uid, ids, context=None):
112
Search all draft/temp posted register lines that have an analytic distribution in which funding pool lines have an analytic account set to those given in contract.
115
for c in self.browse(cr, uid, ids):
116
# Search draft posted statement lines
117
fp_ids = [x and x.funding_pool_id and x.funding_pool_id.id for x in c.funding_pool_ids]
119
sql = """SELECT absl.statement_id
120
FROM account_bank_statement_line AS absl, funding_pool_distribution_line AS fp
121
WHERE distribution_id = analytic_distribution_id
122
AND fp.analytic_id in %s
125
FROM account_bank_statement_line st
126
LEFT JOIN account_bank_statement_line_move_rel rel ON rel.move_id = st.id
127
LEFT JOIN account_move am ON am.id = rel.statement_id
128
WHERE (rel.statement_id is null OR am.state != 'posted')
130
) GROUP BY absl.statement_id"""
131
cr.execute(sql, (tuple(fp_ids),))
132
sql_res = cr.fetchall()
134
res += [x and x[0] for x in sql_res]
137
56
def contract_soft_closed(self, cr, uid, ids, *args):
139
If some draft/temp posted register lines that have an analytic distribution in which funding pool lines have an analytic account set to those given in contract, then raise an error.
140
Otherwise set contract as soft closed.
142
# Search draft/temp posted register lines
143
if isinstance(ids, (long, int)):
145
for cont in self.read(cr, uid, ids, ['funding_pool_ids']):
146
if not cont['funding_pool_ids']:
147
raise osv.except_osv(_('Error'), _("This contract can not be soft-closed because it is not linked to any funding pool."))
148
register_ids = self.search_draft_or_temp_posted(cr, uid, ids)
151
for i, st in enumerate(self.pool.get('account.bank.statement').browse(cr, uid, register_ids)):
155
raise osv.except_osv(_('Error'), _("There are still expenses linked to contract's funding pools not hard-posted in registers: %s") % (msg,))
156
# Normal behaviour (change contract ' state)
157
57
self.write(cr, uid, ids, {
158
58
'state': 'soft_closed',
159
59
'soft_closed_date': datetime.date.today().strftime('%Y-%m-%d')
170
def get_contract_domain(self, cr, uid, browse_contract, reporting_type=None, context=None):
70
def get_contract_domain(self, cr, uid, browse_contract, reporting_type=None, context={}):
171
71
# we update the context with the contract reporting type and currency
172
72
format_line_obj = self.pool.get('financing.contract.format.line')
173
73
# Values to be set
174
account_destination_ids = []
175
75
if reporting_type is None:
176
76
reporting_type = browse_contract.reporting_type
78
general_domain = format_line_obj._get_general_domain(cr,
80
browse_contract.format_id,
179
84
# parse parent lines (either value or sum of children's values)
180
85
for line in browse_contract.actual_line_ids:
181
86
if not line.parent_id:
182
account_destination_ids += format_line_obj._get_analytic_domain(cr, uid, line, reporting_type, context=context)
87
account_ids += format_line_obj._get_account_ids(line, general_domain['funding_pool_account_ids'])
91
account_domain = format_line_obj._create_domain('general_account_id', account_ids)
92
date_domain = eval(general_domain['date_domain'])
93
if reporting_type == 'allocated':
94
analytic_domain = [date_domain[0],
97
eval(general_domain['funding_pool_domain'])]
99
analytic_domain = [date_domain[0],
101
eval(account_domain),
102
eval(general_domain['funding_pool_domain']),
103
eval(general_domain['cost_center_domain'])]
184
105
return analytic_domain
186
def _get_overhead_amount(self, cr, uid, ids, field_name=None, arg=None, context=None):
188
Method to compute the overhead amount
191
for budget in self.browse(cr, uid, ids, context=context):
194
if budget.overhead_type == 'cost_percentage':
195
res[budget.id] = round(budget.grant_amount * budget.overhead_percentage / (100.0 + budget.overhead_percentage))
196
elif budget.overhead_type == 'grant_percentage':
197
res[budget.id] = round(budget.grant_amount * budget.overhead_percentage / 100.0)
201
108
'name': fields.char('Financing contract name', size=64, required=True),
202
109
'code': fields.char('Financing contract code', size=16, required=True),
203
'donor_id': fields.many2one('financing.contract.donor', 'Donor', required=True, domain="[('active', '=', True)]"),
110
'donor_id': fields.many2one('financing.contract.donor', 'Donor', required=True),
204
111
'donor_grant_reference': fields.char('Donor grant reference', size=64),
205
112
'hq_grant_reference': fields.char('HQ grant reference', size=64),
206
'grant_amount': fields.float('Grant amount', required=True),
207
'overhead_amount': fields.function(_get_overhead_amount, method=True, store=False, string="Overhead amount", type="float", readonly=True),
113
'grant_amount': fields.float('Grant amount', size=64, required=True),
208
114
'reporting_currency': fields.many2one('res.currency', 'Reporting currency', required=True),
209
115
'notes': fields.text('Notes'),
210
116
'open_date': fields.date('Open date'),
215
121
('soft_closed', 'Soft-closed'),
216
122
('hard_closed', 'Hard-closed')], 'State'),
217
123
'currency_table_id': fields.many2one('res.currency.table', 'Currency Table'),
218
'instance_id': fields.many2one('msf.instance','Proprietary Instance', required=True),
219
# Define for _inherits
220
'format_id': fields.many2one('financing.contract.format', 'Format', ondelete="cascade"),
224
127
'state': 'draft',
225
128
'reporting_currency': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
129
'format_id': lambda self,cr,uid,context: self.pool.get('financing.contract.format').create(cr, uid, {}, context=context)
228
def _check_unicity(self, cr, uid, ids, context=None):
132
def _check_unicity(self, cr, uid, ids, context={}):
231
135
for contract in self.browse(cr, uid, ids, context=context):
232
136
bad_ids = self.search(cr, uid, [('|'),('name', '=ilike', contract.name),('code', '=ilike', contract.code)])
233
137
if len(bad_ids) and len(bad_ids) > 1:
238
142
(_check_unicity, 'You cannot have the same code or name between contracts!', ['code', 'name']),
241
def copy(self, cr, uid, id, default=None, context=None, done_list=[], local=False):
145
def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
242
146
contract = self.browse(cr, uid, id, context=context)
245
149
default = default.copy()
246
150
default['code'] = (contract['code'] or '') + '(copy)'
247
151
default['name'] = (contract['name'] or '') + '(copy)'
248
# Copy lines manually but remove CCs and FPs
249
default['funding_pool_ids'] = []
250
default['cost_center_ids'] = []
251
default['actual_line_ids'] = []
252
copy_id = super(financing_contract_contract, self).copy(cr, uid, id, default, context=context)
253
copy = self.browse(cr, uid, copy_id, context=context)
254
self.pool.get('financing.contract.format').copy_format_lines(cr, uid, contract.format_id.id, copy.format_id.id, context=context)
152
return super(financing_contract_contract, self).copy(cr, uid, id, default, context=context)
257
def onchange_donor_id(self, cr, uid, ids, donor_id, format_id, actual_line_ids, context=None):
154
def onchange_donor_id(self, cr, uid, ids, donor_id, format_id, actual_line_ids, context={}):
156
if donor_id and format_id:
260
157
donor = self.pool.get('financing.contract.donor').browse(cr, uid, donor_id, context=context)
261
158
if donor.format_id:
262
159
source_format = donor.format_id
264
161
'format_name': source_format.format_name,
265
162
'reporting_type': source_format.reporting_type,
266
'overhead_type': source_format.overhead_type,
267
'overhead_percentage': source_format.overhead_percentage,
269
res = {'value': format_vals}
164
self.pool.get('financing.contract.format').copy_format_lines(cr, uid, donor.format_id.id, format_id, context=context)
165
return {'value': format_vals}
272
def onchange_currency_table(self, cr, uid, ids, currency_table_id, reporting_currency_id, context=None):
167
def onchange_currency_table(self, cr, uid, ids, currency_table_id, reporting_currency_id, context={}):
273
168
values = {'reporting_currency': False}
274
169
if reporting_currency_id:
275
170
# it can be a currency from another table
289
184
# Restrain domain to selected table (or None if none selected
290
185
domains = {'reporting_currency': [('currency_table_id', '=', currency_table_id)]}
291
186
return {'value': values, 'domain': domains}
293
def onchange_date(self, cr, uid, ids, eligibility_from_date, eligibility_to_date):
294
""" This function will be called on the change of dates of the financing contract"""
295
if eligibility_from_date and eligibility_to_date:
296
if eligibility_from_date >= eligibility_to_date:
299
'message': _("The 'Eligibility Date From' should be sooner than the 'Eligibility Date To'.")
301
return {'warning': warning}
304
188
def create_reporting_line(self, cr, uid, browse_contract, browse_format_line, parent_report_line_id=None, context=None):
305
189
format_line_obj = self.pool.get('financing.contract.format.line')
306
190
reporting_line_obj = self.pool.get('financing.contract.donor.reporting.line')
312
196
vals = {'name': browse_format_line.name,
313
197
'code': browse_format_line.code,
314
'line_type': browse_format_line.line_type,
315
198
'allocated_budget': round(browse_format_line.allocated_budget),
316
199
'project_budget': round(browse_format_line.project_budget),
317
200
'allocated_real': round(browse_format_line.allocated_real),
318
201
'project_real': round(browse_format_line.project_real),
320
'project_balance': round(browse_format_line.project_budget) - round(browse_format_line.project_real),
321
'allocated_balance': round(browse_format_line.allocated_budget) - round(browse_format_line.allocated_real),
323
202
'analytic_domain': analytic_domain,
324
203
'parent_id': parent_report_line_id}
325
204
reporting_line_id = reporting_line_obj.create(cr,
331
210
self.create_reporting_line(cr, uid, browse_contract, child_line, reporting_line_id, context=context)
332
211
return reporting_line_id
334
def menu_interactive_report(self, cr, uid, ids, context=None):
213
def menu_interactive_report(self, cr, uid, ids, context={}):
337
214
# we update the context with the contract reporting type
338
215
contract = self.browse(cr, uid, ids[0], context=context)
339
216
context.update({'reporting_currency': contract.reporting_currency.id,
340
217
'reporting_type': contract.reporting_type,
341
218
'currency_table_id': contract.currency_table_id.id,
342
219
'active_id': ids[0],
345
## INFO: display_fp in context permits to display Funding Pool column and its attached cost center.
346
221
reporting_line_obj = self.pool.get('financing.contract.donor.reporting.line')
347
222
# Create reporting lines
348
223
# Contract line first (we'll fill it later)
349
224
contract_line_id = reporting_line_obj.create(cr,
351
226
vals = {'name': contract.name,
352
'code': contract.code,
353
'line_type': 'view'},
227
'code': contract.code},
356
230
# Values to be set
358
232
project_budget = 0
359
233
allocated_real = 0
362
allocated_balance = 0
364
236
# create "real" lines
365
237
for line in contract.actual_line_ids:
366
238
if not line.parent_id:
367
# UTP-853: self.create_reporting_line rounds each line
368
# (int value) so we add a round for sums equivalence
369
allocated_budget += round(line.allocated_budget)
370
project_budget += round(line.project_budget)
371
allocated_real += round(line.allocated_real)
372
project_real += round(line.project_real)
239
allocated_budget += line.allocated_budget
240
project_budget += line.project_budget
241
allocated_real += line.allocated_real
242
project_real += line.project_real
373
243
reporting_line_id = self.create_reporting_line(cr, uid, contract, line, contract_line_id, context=context)
375
245
# Refresh contract line with general infos
376
246
analytic_domain = self.get_contract_domain(cr, uid, contract, context=context)
378
allocated_balance = allocated_budget - allocated_real
379
project_balance= project_budget - project_real
381
247
contract_values = {'allocated_budget': allocated_budget,
382
248
'project_budget': project_budget,
383
249
'allocated_real': allocated_real,
384
250
'project_real': project_real,
386
'allocated_balance': allocated_balance,
387
'project_balance': project_balance,
389
251
'analytic_domain': analytic_domain}
390
252
reporting_line_obj.write(cr, uid, [contract_line_id], vals=contract_values, context=context)
460
316
'res_id': [wiz_id],
461
317
'context': context,
464
def create(self, cr, uid, vals, context=None):
465
# Do not copy lines from the Donor on create if coming from the sync server
468
result = super(financing_contract_contract, self).create(cr, uid, vals, context=context)
469
if not context.get('sync_update_execution'):
470
contract = self.browse(cr, uid, result, context=context)
471
if contract.donor_id and contract.donor_id.format_id and contract.format_id:
472
self.pool.get('financing.contract.format').copy_format_lines(cr, uid, contract.donor_id.format_id.id, contract.format_id.id, context=context)
476
def write(self, cr, uid, ids, vals, context=None):
477
if 'donor_id' in vals:
478
donor = self.pool.get('financing.contract.donor').browse(cr, uid, vals['donor_id'], context=context)
479
for contract in self.browse(cr, uid, ids, context=context):
480
if contract.donor_id and contract.format_id and vals['donor_id'] != contract.donor_id.id:
481
self.pool.get('financing.contract.format').copy_format_lines(cr, uid, donor.format_id.id, contract.format_id.id, context=context)
483
return super(financing_contract_contract, self).write(cr, uid, ids, vals, context=context)
485
320
financing_contract_contract()
487
322
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: