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

« back to all changes in this revision

Viewing changes to analytic_distribution/analytic_account.py

  • Committer: jf
  • Date: 2014-05-28 13:16:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2187.
  • Revision ID: jfb@tempo-consulting.fr-20140528131631-13qcl8f5h390rmtu
UFTP-244 [FIX] In sync context, do not auto create the link between account.account and account.destination.link for default destination
this link is created by a dedicated sync rule

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
##############################################################################
21
21
 
22
22
import datetime
23
 
from dateutil.relativedelta import relativedelta
24
 
from osv import fields, osv
 
23
from osv import fields
 
24
from osv import osv
25
25
from tools.translate import _
26
 
from lxml import etree
 
26
from destination_tools import many2many_sorted
 
27
from destination_tools import many2many_notlazy
27
28
from tools.misc import flatten
28
 
from destination_tools import many2many_sorted, many2many_notlazy
29
29
 
30
30
class analytic_account(osv.osv):
31
31
    _name = "account.analytic.account"
48
48
                res[a.id] = False
49
49
        return res
50
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
 
51
    def is_blocked_by_a_contract(self, cr, uid, ids):
 
52
        """
 
53
        Return ids (analytic accounts) that are blocked by a contract (just FP1)
 
54
        """
 
55
        # Some verifications
 
56
        if isinstance(ids, (int, long)):
 
57
            ids = [ids]
 
58
        # Prepare some values
 
59
        res = []
 
60
        for aa in self.browse(cr, uid, ids):
 
61
            # Only check funding pool accounts
 
62
            if aa.category != 'FUNDING':
 
63
                continue
 
64
            link_ids = self.pool.get('financing.contract.funding.pool.line').search(cr, uid, [('funding_pool_id', '=', aa.id)])
 
65
            format_ids = []
 
66
            for link in self.pool.get('financing.contract.funding.pool.line').browse(cr, uid, link_ids):
 
67
                if link.contract_id:
 
68
                    format_ids.append(link.contract_id.id)
 
69
            contract_ids = self.pool.get('financing.contract.contract').search(cr, uid, [('format_id', 'in', format_ids)])
 
70
            for contract in self.pool.get('financing.contract.contract').browse(cr, uid, contract_ids):
 
71
                if contract.state in ['soft_closed', 'hard_closed']:
 
72
                    res.append(aa.id)
 
73
        return res
70
74
 
71
75
    def _search_closed_by_a_fp(self, cr, uid, ids, name, args, context=None):
72
76
        """
92
96
                    res = [('id', 'not in', aa_ids)]
93
97
        return res
94
98
 
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
118
 
    
119
99
    _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.'),
123
 
        'date_start': fields.date('Active from', required=True),
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
100
        'destination_ids': many2many_notlazy('account.account', 'account_destination_link', 'destination_id', 'account_id', 'Accounts'),
133
101
        '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
102
        '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"),
138
 
    }
139
 
 
140
 
    _defaults ={
141
 
        'date_start': lambda *a: (datetime.datetime.today() + relativedelta(months=-3)).strftime('%Y-%m-%d'),
142
 
        'for_fx_gain_loss': lambda *a: False,
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
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
 
 
223
 
    def _check_date(self, vals):
224
 
        if 'date' in vals and vals['date'] is not False:
225
 
            if vals['date'] <= datetime.date.today().strftime('%Y-%m-%d'):
226
 
                # validate the date (must be > today)
227
 
                raise osv.except_osv(_('Warning !'), _('You cannot set an inactivity date lower than tomorrow!'))
228
 
            elif 'date_start' in vals and not vals['date_start'] < vals['date']:
229
 
                # validate that activation date 
230
 
                raise osv.except_osv(_('Warning !'), _('Activation date must be lower than inactivation date!'))
231
 
 
232
 
    def create(self, cr, uid, vals, context=None):
233
 
        """
234
 
        Some verifications before analytic account creation
235
 
        """
236
 
        self._check_date(vals)
237
 
        self.set_funding_pool_parent(cr, uid, vals)
238
 
        return super(analytic_account, self).create(cr, uid, vals, context=context)
239
 
 
240
 
    def write(self, cr, uid, ids, vals, context=None):
241
 
        """
242
 
        Some verifications before analytic account write
243
 
        """
244
 
        self._check_date(vals)
245
 
        self.set_funding_pool_parent(cr, uid, vals)
246
 
        return super(analytic_account, self).write(cr, uid, ids, vals, context=context)
 
103
    }
247
104
 
248
105
    def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
249
106
        """
258
115
            for arg in args2:
259
116
                ids.append(arg[1])
260
117
            args.append(('id', 'in', ids))
261
 
        
 
118
        # UF-1713: Active/inactive functionnality was missing.
 
119
        if context and 'filter_inactive' in context and context['filter_inactive']:
 
120
            args.append(('filter_active', '=', context['filter_inactive']))
262
121
        # Tuple Account/Destination search
263
122
        for i, arg in enumerate(args):
264
123
            if arg[0] and arg[0] == 'tuple_destination':
268
127
                    fp_ids.append(adl.get('funding_pool_ids'))
269
128
                fp_ids = flatten(fp_ids)
270
129
                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
 
130
        return super(analytic_account, self).search(cr, uid, args, offset, limit, order, context=context, count=count)
411
131
 
412
132
analytic_account()
413
133
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: