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

« back to all changes in this revision

Viewing changes to analytic_distribution/analytic_account.py

  • Committer: Olivier DOSSMANN
  • Date: 2013-05-31 14:22:09 UTC
  • mto: This revision was merged to the branch mainline in revision 1687.
  • Revision ID: od@tempo-consulting.fr-20130531142209-sbcwvzuema11guzz
UF-1991 [FIX] Problem with wizard on "msg" field. Change it to "name".

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
26
29
 
27
 
class analytic_account_activable(osv.osv):
 
30
class analytic_account(osv.osv):
 
31
    _name = "account.analytic.account"
28
32
    _inherit = "account.analytic.account"
 
33
 
 
34
    def _get_active(self, cr, uid, ids, field_name, args, context=None):
 
35
        '''
 
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.
 
38
        '''
 
39
        res = {}
 
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):
 
44
            res[a.id] = True
 
45
            if a.date_start > cmp_date:
 
46
                res[a.id] = False
 
47
            if a.date and a.date <= cmp_date:
 
48
                res[a.id] = False
 
49
        return res
 
50
 
 
51
    def _search_filter_active(self, cr, uid, ids, name, args, context=None):
 
52
        """
 
53
        UTP-410: Add the search on active/inactive CC
 
54
        """
 
55
        arg = []
 
56
        cmp_date = datetime.date.today().strftime('%Y-%m-%d')
 
57
        if context.get('date', False):
 
58
            cmp_date = context.get('date')
 
59
        for x in args:
 
60
            if x[0] == 'filter_active' and x[2] == True:
 
61
                arg.append(('date_start', '<=', cmp_date))
 
62
                arg.append('|')
 
63
                arg.append(('date', '>', cmp_date))
 
64
                arg.append(('date', '=', False))
 
65
            elif x[0] == 'filter_active' and x[2] == False:
 
66
                arg.append('|')
 
67
                arg.append(('date_start', '>', cmp_date))
 
68
                arg.append(('date', '<=', cmp_date))
 
69
        return arg
 
70
 
 
71
    def _search_closed_by_a_fp(self, cr, uid, ids, name, args, context=None):
 
72
        """
 
73
        UTP-423: Do not display analytic accounts linked to a soft/hard closed contract.
 
74
        """
 
75
        res = [('id', 'not in', [])]
 
76
        if args and args[0] and len(args[0]) == 3:
 
77
            if args[0][1] != '=':
 
78
                raise osv.except_osv(_('Error'), _('Operator not supported yet!'))
 
79
            # Search all fp_ids from soft_closed contract
 
80
            sql="""SELECT a.id
 
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');"""
 
85
            cr.execute(sql)
 
86
            sql_res = cr.fetchall()
 
87
            if sql_res:
 
88
                aa_ids = self.is_blocked_by_a_contract(cr, uid, [x and x[0] for x in sql_res])
 
89
                if aa_ids:
 
90
                    if isinstance(aa_ids, (int, long)):
 
91
                        aa_ids = [aa_ids]
 
92
                    res = [('id', 'not in', aa_ids)]
 
93
        return res
 
94
 
 
95
    def _get_fake(self, cr, uid, ids, *a, **b):
 
96
        return {}.fromkeys(ids, False)
 
97
 
 
98
    def _search_intermission_restricted(self, cr, uid, ids, name, args, context=None):
 
99
        if not args:
 
100
            return []
 
101
        newargs = []
 
102
        for arg in args:
 
103
            if arg[1] != '=':
 
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]):
 
108
                try:
 
109
                    intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
 
110
                            'analytic_account_project_intermission')[1]
 
111
                except ValueError:
 
112
                    pass
 
113
                if arg[2][2] == 'intermission':
 
114
                    newargs.append(('id', '=', intermission))
 
115
                else:
 
116
                    newargs.append(('id', '!=', intermission))
 
117
        return newargs
29
118
    
30
119
    _columns = {
 
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'),
 
127
            ('FREE1','Free 1'),
 
128
            ('FREE2','Free 2'),
 
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"),
33
138
    }
34
 
    
 
139
 
35
140
    _defaults ={
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,
37
143
    }
 
144
 
 
145
    def _check_unicity(self, cr, uid, ids, context=None):
 
146
        if not context:
 
147
            context = {}
 
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:
 
151
                return False
 
152
        return True
 
153
 
 
154
    def _check_gain_loss_account_unicity(self, cr, uid, ids, context=None):
 
155
        """
 
156
        Check that no more account is "for_fx_gain_loss" available.
 
157
        """
 
158
        if not context:
 
159
            context = {}
 
160
        search_ids = self.search(cr, uid, [('for_fx_gain_loss', '=', True)])
 
161
        if search_ids and len(search_ids) > 1:
 
162
            return False
 
163
        return True
 
164
 
 
165
    def _check_gain_loss_account_type(self, cr, uid, ids, context=None):
 
166
        """
 
167
        Check account type for fx_gain_loss_account: should be Normal type and Cost Center category
 
168
        """
 
169
        if not context:
 
170
            context = {}
 
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'):
 
173
                return False
 
174
        return True
38
175
    
 
176
    def _check_default_destination(self, cr, uid, ids, context=None):
 
177
        if isinstance(ids, (int, long)):
 
178
            ids = [ids]
 
179
        if not ids:
 
180
            return True
 
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),)
 
186
        )
 
187
        error = []
 
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]))
 
190
        if error:
 
191
            raise osv.except_osv(_('Warning !'), "\n".join(error))
 
192
        return True
 
193
 
 
194
    _constraints = [
 
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", []),
 
199
    ]
 
200
 
 
201
    def copy(self, cr, uid, id, default=None, context=None, done_list=[], local=False):
 
202
        account = self.browse(cr, uid, id, context=context)
 
203
        if not default:
 
204
            default = {}
 
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 '')})
 
212
        return new_id
 
213
 
 
214
    def set_funding_pool_parent(self, cr, uid, vals):
 
215
        if 'category' in vals and \
 
216
           'code' 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
 
222
 
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!'))
47
 
    
 
231
 
48
232
    def create(self, cr, uid, vals, context=None):
 
233
        """
 
234
        Some verifications before analytic account creation
 
235
        """
49
236
        self._check_date(vals)
50
 
        return super(analytic_account_activable, self).create(cr, uid, vals, context=context)
51
 
    
 
237
        self.set_funding_pool_parent(cr, uid, vals)
 
238
        return super(analytic_account, self).create(cr, uid, vals, context=context)
 
239
 
52
240
    def write(self, cr, uid, ids, vals, context=None):
 
241
        """
 
242
        Some verifications before analytic account write
 
243
        """
53
244
        self._check_date(vals)
54
 
        return super(analytic_account_activable, self).write(cr, uid, ids, vals, context=context)
55
 
 
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')))
60
 
            args.append('|')
61
 
            args.append(('date', '>', datetime.date.today().strftime('%Y-%m-%d')))
62
 
            args.append(('date', '=', False))
63
 
            
64
 
        return super(analytic_account_activable, self).search(cr, uid, args, offset, limit,
65
 
                order, context=context, count=count)
66
 
    
67
 
analytic_account_activable()
68
 
 
 
245
        self.set_funding_pool_parent(cr, uid, vals)
 
246
        return super(analytic_account, self).write(cr, uid, ids, vals, context=context)
 
247
 
 
248
    def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
 
249
        """
 
250
        FIXME: this method do others things that not have been documented. Please complete here what method do.
 
251
        """
 
252
        if not context:
 
253
            context = {}
 
254
        if context and 'search_by_ids' in context and context['search_by_ids']:
 
255
            args2 = args[-1][2]
 
256
            del args[-1]
 
257
            ids = []
 
258
            for arg in args2:
 
259
                ids.append(arg[1])
 
260
            args.append(('id', 'in', ids))
 
261
        
 
262
        # Tuple Account/Destination search
 
263
        for i, arg in enumerate(args):
 
264
            if arg[0] and arg[0] == 'tuple_destination':
 
265
                fp_ids = []
 
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)
 
272
        return res
 
273
 
 
274
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
 
275
        if not context:
 
276
            context = {}
 
277
        view = super(analytic_account, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
 
278
        try:
 
279
            oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
 
280
        except ValueError:
 
281
            oc_id = 0
 
282
        if view_type=='form':
 
283
            tree = etree.fromstring(view['arch'])
 
284
            fields = tree.xpath('/form/field[@name="cost_center_ids"]')
 
285
            for field in fields:
 
286
                field.set('domain', "[('type', '!=', 'view'), ('id', 'child_of', [%s])]" % oc_id)
 
287
            view['arch'] = etree.tostring(tree)
 
288
        return view
 
289
 
 
290
    def on_change_category(self, cr, uid, id, category):
 
291
        if not category:
 
292
            return {}
 
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')]
 
297
        return res
 
298
 
 
299
    def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
 
300
        if not args:
 
301
            args=[]
 
302
        if context is None:
 
303
            context={}
 
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)
 
312
 
 
313
    def name_get(self, cr, uid, ids, context={}):
 
314
        """
 
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)
 
318
        """
 
319
        # Some verifications
 
320
        if not context:
 
321
            context = {}
 
322
        if isinstance(ids, (int, long)):
 
323
            ids = [ids]
 
324
        # Prepare some value
 
325
        res = []
 
326
        # Browse all accounts
 
327
        for account in self.browse(cr, uid, ids, context=context):
 
328
#            data = []
 
329
#            acc = account
 
330
#            while acc:
 
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))
 
339
        return res
 
340
 
 
341
    def unlink(self, cr, uid, ids, context=None):
 
342
        """
 
343
        Delete some analytic account is forbidden!
 
344
        """
 
345
        # Some verification
 
346
        if not context:
 
347
            context = {}
 
348
        if isinstance(ids, (int, long)):
 
349
            ids = [ids]
 
350
        # Prepare some values
 
351
        analytic_accounts = []
 
352
        # Search OC CC
 
353
        try:
 
354
            oc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
 
355
        except ValueError:
 
356
            oc_id = 0
 
357
        analytic_accounts.append(oc_id)
 
358
        # Search Funding Pool
 
359
        try:
 
360
            fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_funding_pool')[1]
 
361
        except ValueError:
 
362
            fp_id = 0
 
363
        analytic_accounts.append(fp_id)
 
364
        # Search Free 1
 
365
        try:
 
366
            f1_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_1')[1]
 
367
        except ValueError:
 
368
            f1_id = 0
 
369
        analytic_accounts.append(f1_id)
 
370
        # Search Free 2
 
371
        try:
 
372
            f2_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_free_2')[1]
 
373
        except ValueError:
 
374
            f2_id = 0
 
375
        analytic_accounts.append(f2_id)
 
376
        # Search MSF Private Fund
 
377
        try:
 
378
            msf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
 
379
        except ValueError:
 
380
            msf_id = 0
 
381
        analytic_accounts.append(msf_id)
 
382
        # Accounts verification
 
383
        for id in ids:
 
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)
 
387
 
 
388
    def is_blocked_by_a_contract(self, cr, uid, ids):
 
389
        """
 
390
        Return ids (analytic accounts) that are blocked by a contract (just FP1)
 
391
        """
 
392
        # Some verifications
 
393
        if isinstance(ids, (int, long)):
 
394
            ids = [ids]
 
395
        # Prepare some values
 
396
        res = []
 
397
        for aa in self.browse(cr, uid, ids):
 
398
            # Only check funding pool accounts
 
399
            if aa.category != 'FUNDING':
 
400
                continue
 
401
            link_ids = self.pool.get('financing.contract.funding.pool.line').search(cr, uid, [('funding_pool_id', '=', aa.id)])
 
402
            format_ids = []
 
403
            for link in self.pool.get('financing.contract.funding.pool.line').browse(cr, uid, link_ids):
 
404
                if link.contract_id:
 
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']:
 
409
                    res.append(aa.id)
 
410
        return res
 
411
 
 
412
analytic_account()
69
413
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: