~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to financing_contract/contract.py

  • Committer: jf
  • Date: 2012-01-10 20:57:37 UTC
  • mto: (559.2.21 unifield-wm)
  • mto: This revision was merged to the branch mainline in revision 562.
  • Revision ID: jf@ubuntu-20120110205737-gr6hukgf5ggimfup
Data supply

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
19
#
20
20
##############################################################################
 
21
 
 
22
from osv import fields, osv
21
23
import datetime
22
 
from osv import fields, osv
23
 
from tools.translate import _
24
24
 
25
25
class financing_contract_funding_pool_line(osv.osv):
26
26
    # 
27
27
    _name = "financing.contract.funding.pool.line"
28
 
    _description = "Funding pool line"
29
28
    
30
29
    _columns = {
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'),
35
34
    }
36
35
        
39
38
        'total_project': True,
40
39
    }
41
40
    
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)
58
 
        return result
59
 
        
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')
65
 
            for id in ids:
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']
71
 
                
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)
82
 
                
83
 
        return super(financing_contract_funding_pool_line, self).write(cr, uid, ids, vals, context=context)
84
 
    
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)
91
 
                            
92
 
        return super(financing_contract_funding_pool_line, self).unlink(cr, uid, ids, context=context)
93
 
    
94
41
financing_contract_funding_pool_line()
95
42
 
96
43
class financing_contract_contract(osv.osv):
97
44
    
98
45
    _name = "financing.contract.contract"
99
46
    _inherits = {"financing.contract.format": "format_id"}
100
 
    _trace = True
101
47
 
102
48
    def contract_open(self, cr, uid, ids, *args):
103
49
        self.write(cr, uid, ids, {
107
53
        })
108
54
        return True
109
55
 
110
 
    def search_draft_or_temp_posted(self, cr, uid, ids, context=None):
111
 
        """
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.
113
 
        """
114
 
        res = []
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]
118
 
            if fp_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
123
 
                AND absl.id in (
124
 
                    SELECT st.id
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')
129
 
                    ORDER BY st.id
130
 
                ) GROUP BY absl.statement_id"""
131
 
                cr.execute(sql, (tuple(fp_ids),))
132
 
                sql_res = cr.fetchall()
133
 
                if sql_res:
134
 
                    res += [x and x[0] for x in sql_res]
135
 
        return res
136
 
 
137
56
    def contract_soft_closed(self, cr, uid, ids, *args):
138
 
        """
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.
141
 
        """
142
 
        # Search draft/temp posted register lines
143
 
        if isinstance(ids, (long, int)):
144
 
            ids = [ids]
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)
149
 
        if register_ids:
150
 
            msg= ''
151
 
            for i, st in enumerate(self.pool.get('account.bank.statement').browse(cr, uid, register_ids)):
152
 
                if i > 0:
153
 
                    msg += ' - '
154
 
                msg += st.name or ''
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')
167
67
        })
168
68
        return True
169
69
    
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 = []
 
74
        account_ids = []
175
75
        if reporting_type is None:
176
76
            reporting_type = browse_contract.reporting_type
 
77
        # general domain
 
78
        general_domain = format_line_obj._get_general_domain(cr,
 
79
                                                             uid,
 
80
                                                             browse_contract.format_id,
 
81
                                                             reporting_type,
 
82
                                                             context=context)
177
83
        
178
 
        analytic_domain = []
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'])
 
88
                
 
89
        # create the domain
 
90
        analytic_domain = []
 
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],
 
95
                               date_domain[1],
 
96
                               eval(account_domain),
 
97
                               eval(general_domain['funding_pool_domain'])]
 
98
        else: 
 
99
            analytic_domain = [date_domain[0],
 
100
                               date_domain[1],
 
101
                               eval(account_domain),
 
102
                               eval(general_domain['funding_pool_domain']),
 
103
                               eval(general_domain['cost_center_domain'])]
183
104
            
184
105
        return analytic_domain
185
 
 
186
 
    def _get_overhead_amount(self, cr, uid, ids, field_name=None, arg=None, context=None):
187
 
        """
188
 
            Method to compute the overhead amount
189
 
        """
190
 
        res = {}
191
 
        for budget in self.browse(cr, uid, ids, context=context):
192
 
            # default value
193
 
            res[budget.id] = 0.0
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)
198
 
        return res
199
106
    
200
107
    _columns = {
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"),
221
124
    }
222
125
    
223
126
    _defaults = {
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)
226
130
    }
227
131
 
228
 
    def _check_unicity(self, cr, uid, ids, context=None):
 
132
    def _check_unicity(self, cr, uid, ids, context={}):
229
133
        if not context:
230
 
            context = {}
 
134
            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']),
239
143
    ]
240
144
 
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)
243
147
        if not default:
244
148
            default = {}
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)
255
 
        return copy_id
 
152
        return super(financing_contract_contract, self).copy(cr, uid, id, default, context=context)
256
153
    
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={}):
258
155
        res = {}
259
 
        if donor_id:
 
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
263
160
                format_vals = {
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,
268
163
                }
269
 
                res = {'value': format_vals}
270
 
        return res
 
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}
271
166
    
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}
292
 
 
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:
297
 
                warning = {
298
 
                    'title': _('Error'), 
299
 
                    'message': _("The 'Eligibility Date From' should be sooner than the 'Eligibility Date To'.")
300
 
                }
301
 
                return {'warning': warning}
302
 
        return {}
303
 
 
 
187
    
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')
311
195
                                                               context=context)
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),
319
 
 
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),
322
 
 
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
333
212
    
334
 
    def menu_interactive_report(self, cr, uid, ids, context=None):
335
 
        if context is None:
336
 
            context = {}
 
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],
343
 
                        'active_ids': ids,
344
 
                        'display_fp': True})
345
 
        ## INFO: display_fp in context permits to display Funding Pool column and its attached cost center.
 
220
                        'active_ids': ids})
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,
350
225
                                                     uid,
351
226
                                                     vals = {'name': contract.name,
352
 
                                                             'code': contract.code,
353
 
                                                             'line_type': 'view'},
 
227
                                                             'code': contract.code},
354
228
                                                     context=context)
355
229
        
356
230
        # Values to be set
358
232
        project_budget = 0
359
233
        allocated_real = 0
360
234
        project_real = 0
361
 
        project_balance = 0
362
 
        allocated_balance = 0
363
235
        
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)
374
244
        
375
245
        # Refresh contract line with general infos
376
246
        analytic_domain = self.get_contract_domain(cr, uid, contract, context=context)
377
 
 
378
 
        allocated_balance  = allocated_budget - allocated_real
379
 
        project_balance= project_budget - project_real
380
 
 
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,
385
 
 
386
 
                            'allocated_balance': allocated_balance,
387
 
                            'project_balance': project_balance,
388
 
 
389
251
                           'analytic_domain': analytic_domain}
390
252
        reporting_line_obj.write(cr, uid, [contract_line_id], vals=contract_values, context=context)
391
253
        
408
270
               'context': context
409
271
        }
410
272
        
411
 
    def menu_allocated_expense_report(self, cr, uid, ids, context=None):
412
 
        if context is None:
413
 
            context = {}
 
273
    def menu_allocated_expense_report(self, cr, uid, ids, context={}):
414
274
        wiz_obj = self.pool.get('wizard.expense.report')
415
275
        wiz_id = wiz_obj.create(cr, uid, {'reporting_type': 'allocated',
416
276
                                          'filename': 'allocated_expenses.csv',
426
286
                'context': context,
427
287
        }
428
288
        
429
 
    def menu_project_expense_report(self, cr, uid, ids, context=None):
430
 
        if context is None:
431
 
            context = {}
 
289
    def menu_project_expense_report(self, cr, uid, ids, context={}):
432
290
        wiz_obj = self.pool.get('wizard.expense.report')
433
291
        wiz_id = wiz_obj.create(cr, uid, {'reporting_type': 'project',
434
292
                                          'filename': 'project_expenses.csv',
444
302
                'context': context,
445
303
        }
446
304
        
447
 
    def menu_csv_interactive_report(self, cr, uid, ids, context=None):
448
 
        if context is None:
449
 
            context = {}
 
305
    def menu_csv_interactive_report(self, cr, uid, ids, context={}):
450
306
        wiz_obj = self.pool.get('wizard.interactive.report')
451
307
        wiz_id = wiz_obj.create(cr, uid, {'filename': 'interactive_report.csv',
452
308
                                          'contract_id': ids[0]}, context=context)
460
316
                'res_id': [wiz_id],
461
317
                'context': context,
462
318
        }
463
 
        
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
466
 
        if context is None:
467
 
            context = {}
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)
473
 
        return result
474
319
    
475
 
        
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)
482
 
 
483
 
        return super(financing_contract_contract, self).write(cr, uid, ids, vals, context=context)
484
 
 
485
320
financing_contract_contract()
486
321
 
487
322
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: