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

« back to all changes in this revision

Viewing changes to analytic_distribution/analytic_line.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:
19
19
#
20
20
##############################################################################
21
21
 
22
 
import datetime
23
22
from osv import osv
 
23
from osv import fields
24
24
from tools.translate import _
 
25
from tools.misc import flatten
 
26
from collections import defaultdict
 
27
from time import strftime
 
28
from lxml import etree
25
29
 
26
 
class analytic_line_activable(osv.osv):
 
30
class analytic_line(osv.osv):
 
31
    _name = "account.analytic.line"
27
32
    _inherit = "account.analytic.line"
28
 
    
29
 
    def _check_date(self, cr, uid, vals):
 
33
 
 
34
    def _get_fake_is_fp_compat_with(self, cr, uid, ids, field_name, args, context=None):
 
35
        """
 
36
        Fake method for 'is_fp_compat_with' field
 
37
        """
 
38
        res = {}
 
39
        for id in ids:
 
40
            res[id] = ''
 
41
        return res
 
42
 
 
43
    def _search_is_fp_compat_with(self, cr, uid, obj, name, args, context=None):
 
44
        """
 
45
        Return domain that permit to give all analytic line compatible with a given FP.
 
46
        """
 
47
        if not args:
 
48
            return []
 
49
        res = []
 
50
        # We just support '=' operator
 
51
        for arg in args:
 
52
            if not arg[1]:
 
53
                raise osv.except_osv(_('Warning'), _('Some search args are missing!'))
 
54
            if arg[1] not in ['=',]:
 
55
                raise osv.except_osv(_('Warning'), _('This filter is not implemented yet!'))
 
56
            if not arg[2]:
 
57
                raise osv.except_osv(_('Warning'), _('Some search args are missing!'))
 
58
            analytic_account = self.pool.get('account.analytic.account').browse(cr, uid, arg[2])
 
59
            tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in analytic_account.tuple_destination_account_ids]
 
60
            cost_center_ids = [x and x.id for x in analytic_account.cost_center_ids]
 
61
            for cc in cost_center_ids:
 
62
                for t in tuple_list:
 
63
                    if res:
 
64
                        res = ['|'] + res
 
65
                    res.append('&')
 
66
                    res.append('&')
 
67
                    res.append(('cost_center_id', '=', cc))
 
68
                    res.append(('general_account_id', '=', t[0]))
 
69
                    res.append(('destination_id', '=', t[1]))
 
70
        return res
 
71
 
 
72
    def _journal_type_get(self, cr, uid, context=None):
 
73
        """
 
74
        Get journal types
 
75
        """
 
76
        return self.pool.get('account.analytic.journal').get_journal_type(cr, uid, context)
 
77
 
 
78
    _columns = {
 
79
        'distribution_id': fields.many2one('analytic.distribution', string='Analytic Distribution'),
 
80
        'cost_center_id': fields.many2one('account.analytic.account', string='Cost Center', domain="[('category', '=', 'OC'), ('type', '<>', 'view')]"),
 
81
        'commitment_line_id': fields.many2one('account.commitment.line', string='Commitment Voucher Line', ondelete='cascade'),
 
82
        'from_write_off': fields.boolean(string='From write-off account line?', readonly=True, help="Indicates that this line come from a write-off account line."),
 
83
        'destination_id': fields.many2one('account.analytic.account', string="Destination", domain="[('category', '=', 'DEST'), ('type', '<>', 'view')]"),
 
84
        'is_fp_compat_with': fields.function(_get_fake_is_fp_compat_with, fnct_search=_search_is_fp_compat_with, method=True, type="char", size=254, string="Is compatible with some FP?"),
 
85
        'distrib_line_id': fields.reference('Distribution Line ID', selection=[('funding.pool.distribution.line', 'FP'),('free.1.distribution.line', 'free1'), ('free.2.distribution.line', 'free2')], size=512),
 
86
        'move_state': fields.related('move_id', 'move_id', 'state', type='selection', size=64, relation="account.move.line", selection=[('draft', 'Unposted'), ('posted', 'Posted')], string='Journal Entry state', readonly=True, help="Indicates that this line come from an Unposted Journal Entry."),
 
87
        'journal_type': fields.related('journal_id', 'type', type='selection', selection=_journal_type_get, string="Journal Type", readonly=True, \
 
88
            help="Indicates the Journal Type of the Analytic journal item"),
 
89
    }
 
90
 
 
91
    _defaults = {
 
92
        'from_write_off': lambda *a: False,
 
93
    }
 
94
 
 
95
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
 
96
        """
 
97
        Change account_id field name to "Funding Pool if we come from a funding pool
 
98
        """
 
99
        # Some verifications
 
100
        if not context:
 
101
            context = {}
 
102
        is_funding_pool_view = False
 
103
        if context.get('display_fp', False) and context.get('display_fp') is True:
 
104
            is_funding_pool_view = True
 
105
        view = super(analytic_line, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
 
106
        if view_type in ('tree', 'search') and is_funding_pool_view:
 
107
            tree = etree.fromstring(view['arch'])
 
108
            # Change OC field
 
109
            fields = tree.xpath('/' + view_type + '//field[@name="account_id"]')
 
110
            for field in fields:
 
111
                field.set('string', _("Funding Pool"))
 
112
                field.set('domain', "[('category', '=', 'FUNDING'), ('type', '<>', 'view')]")
 
113
            view['arch'] = etree.tostring(tree)
 
114
        return view
 
115
 
 
116
    def _check_date(self, cr, uid, vals, context=None):
 
117
        """
 
118
        Check if given account_id is active for given date. Except for mass reallocation ('from' = 'mass_reallocation' in context)
 
119
        """
 
120
        if not context:
 
121
            context = {}
 
122
        if not 'account_id' in vals:
 
123
            raise osv.except_osv(_('Error'), _('No account_id found in given values!'))
30
124
        if 'date' in vals and vals['date'] is not False:
31
125
            account_obj = self.pool.get('account.analytic.account')
32
 
            account = account_obj.browse(cr, uid, vals['account_id'])
33
 
            if vals['date'] < account.date_start \
34
 
            or (account.date != False and \
35
 
                vals['date'] >= account.date):
36
 
                raise osv.except_osv(_('Error !'), _('The analytic account selected is not active.'))
 
126
            date = vals['date']
 
127
            account = account_obj.browse(cr, uid, vals['account_id'], context=context)
 
128
            # FIXME: refactoring of next code
 
129
            if date < account.date_start or (account.date != False and date >= account.date):
 
130
                if 'from' not in context or context.get('from') != 'mass_reallocation':
 
131
                    raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (account.name or '',))
 
132
            if 'cost_center_id' in vals:
 
133
                cc = account_obj.browse(cr, uid, vals['cost_center_id'], context=context)
 
134
                if date < cc.date_start or (cc.date != False and date >= cc.date):
 
135
                    if 'from' not in context or context.get('from') != 'mass_reallocation':
 
136
                        raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (cc.name or '',))
 
137
            if 'destination_id' in vals:
 
138
                dest = account_obj.browse(cr, uid, vals['destination_id'], context=context)
 
139
                if date < dest.date_start or (dest.date != False and date >= dest.date):
 
140
                    if 'from' not in context or context.get('from') != 'mass_reallocation':
 
141
                        raise osv.except_osv(_('Error'), _("The analytic account selected '%s' is not active.") % (dest.name or '',))
 
142
        return True
37
143
 
38
144
    def create(self, cr, uid, vals, context=None):
39
 
        self._check_date(cr, uid, vals)
40
 
        return super(analytic_line_activable, self).create(cr, uid, vals, context=context)
41
 
    
 
145
        """
 
146
        Check date for given date and given account_id
 
147
        """
 
148
        # Some verifications
 
149
        if not context:
 
150
            context = {}
 
151
        # Default behaviour
 
152
        res = super(analytic_line, self).create(cr, uid, vals, context=context)
 
153
        # Check date
 
154
        self._check_date(cr, uid, vals, context=context)
 
155
        # Check soft/hard closed contract
 
156
        sql = """SELECT fcc.id
 
157
        FROM financing_contract_funding_pool_line fcfpl, account_analytic_account a, financing_contract_format fcf, financing_contract_contract fcc
 
158
        WHERE fcfpl.funding_pool_id = a.id
 
159
        AND fcfpl.contract_id = fcf.id
 
160
        AND fcc.format_id = fcf.id
 
161
        AND a.id = %s
 
162
        AND fcc.state in ('soft_closed', 'hard_closed');"""
 
163
        cr.execute(sql, tuple([vals.get('account_id')]))
 
164
        sql_res = cr.fetchall()
 
165
        if sql_res:
 
166
            account = self.pool.get('account.analytic.account').browse(cr, uid, vals.get('account_id'))
 
167
            contract = self.pool.get('financing.contract.contract').browse(cr, uid, sql_res[0][0])
 
168
            raise osv.except_osv(_('Warning'), _('Selected Funding Pool analytic account (%s) is blocked by a soft/hard closed contract: %s') % (account and account.code or '', contract and contract.name or ''))
 
169
        return res
 
170
 
42
171
    def write(self, cr, uid, ids, vals, context=None):
43
 
        self._check_date(cr, uid, vals)
44
 
        return super(analytic_line_activable, self).write(cr, uid, ids, vals, context=context)
45
 
 
46
 
 
47
 
analytic_line_activable()
48
 
 
49
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
 
b'\\ No newline at end of file'
 
172
        """
 
173
        Verify date for all given ids with account
 
174
        """
 
175
        if not context:
 
176
            context = {}
 
177
        if isinstance(ids, (int, long)):
 
178
            ids = [ids]
 
179
        for l in self.browse(cr, uid, ids):
 
180
            vals2 = vals.copy()
 
181
            for el in ['account_id', 'cost_center_id', 'destination_id']:
 
182
                if not el in vals:
 
183
                    vals2.update({el: getattr(l, el, False),})
 
184
            self._check_date(cr, uid, vals2, context=context)
 
185
        return super(analytic_line, self).write(cr, uid, ids, vals, context=context)
 
186
 
 
187
    def update_account(self, cr, uid, ids, account_id, date=False, context=None):
 
188
        """
 
189
        Update account on given analytic lines with account_id on given date
 
190
        """
 
191
        # Some verifications
 
192
        if not context:
 
193
            context = {}
 
194
        if isinstance(ids, (int, long)):
 
195
            ids = [ids]
 
196
        if not account_id:
 
197
            return False
 
198
        if not date:
 
199
            date = strftime('%Y-%m-%d')
 
200
        # Prepare some value
 
201
        account = self.pool.get('account.analytic.account').browse(cr, uid, [account_id], context)[0]
 
202
        context.update({'from': 'mass_reallocation'}) # this permits reallocation to be accepted when rewrite analaytic lines
 
203
        # Process lines
 
204
        for aline in self.browse(cr, uid, ids, context=context):
 
205
            if account.category in ['OC', 'DEST']:
 
206
                # Period verification
 
207
                period = aline.move_id and aline.move_id.period_id or False
 
208
                # Prepare some values
 
209
                fieldname = 'cost_center_id'
 
210
                if account.category == 'DEST':
 
211
                    fieldname = 'destination_id'
 
212
                # if period is not closed, so override line.
 
213
                if period and period.state != 'done':
 
214
                    # Update account
 
215
                    self.write(cr, uid, [aline.id], {fieldname: account_id, 'date': date, 
 
216
                        'source_date': aline.source_date or aline.date}, context=context)
 
217
                # else reverse line before recreating them with right values
 
218
                else:
 
219
                    # First reverse line
 
220
                    self.pool.get('account.analytic.line').reverse(cr, uid, [aline.id])
 
221
                    # then create new lines
 
222
                    self.pool.get('account.analytic.line').copy(cr, uid, aline.id, {fieldname: account_id, 'date': date,
 
223
                        'source_date': aline.source_date or aline.date}, context=context)
 
224
                    # finally flag analytic line as reallocated
 
225
                    self.pool.get('account.analytic.line').write(cr, uid, [aline.id], {'is_reallocated': True})
 
226
            else:
 
227
                # Update account
 
228
                self.write(cr, uid, [aline.id], {'account_id': account_id}, context=context)
 
229
        return True
 
230
 
 
231
    def check_analytic_account(self, cr, uid, ids, account_id, context=None):
 
232
        """
 
233
        Analytic distribution validity verification with given account for given ids.
 
234
        Return all valid ids.
 
235
        """
 
236
        # Some verifications
 
237
        if not context:
 
238
            context = {}
 
239
        if isinstance(ids, (int, long)):
 
240
            ids = [ids]
 
241
        # Prepare some value
 
242
        account = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['category', 'date_start', 'date'], context=context)
 
243
        account_type = account and account.get('category', False) or False
 
244
        res = []
 
245
        if not account_type:
 
246
            return res
 
247
        try:
 
248
            msf_private_fund = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 
 
249
            'analytic_account_msf_private_funds')[1]
 
250
        except ValueError:
 
251
            msf_private_fund = 0
 
252
        expired_date_ids = []
 
253
        date_start = account and account.get('date_start', False) or False
 
254
        date_stop = account and account.get('date', False) or False
 
255
        # Date verification for all lines and fetch all necessary elements sorted by analytic distribution
 
256
        for aline in self.browse(cr, uid, ids):
 
257
            # Add line to expired_date if date is not in date_start - date_stop
 
258
            if (date_start and aline.date < date_start) or (date_stop and aline.date > date_stop):
 
259
                expired_date_ids.append(aline.id)
 
260
        # Process regarding account_type
 
261
        if account_type == 'OC':
 
262
            for aline in self.browse(cr, uid, ids):
 
263
                # Verify that:
 
264
                # - the line doesn't have any draft/open contract
 
265
                check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
 
266
                if check_accounts and aline.account_id.id in check_accounts:
 
267
                    continue
 
268
 
 
269
                if aline.account_id and aline.account_id.id == msf_private_fund:
 
270
                    res.append(aline.id)
 
271
                elif aline.account_id and aline.cost_center_id and aline.account_id.cost_center_ids:
 
272
                    if account_id in [x and x.id for x in aline.account_id.cost_center_ids] or aline.account_id.id == msf_private_fund:
 
273
                        res.append(aline.id)
 
274
        elif account_type == 'FUNDING':
 
275
            fp = self.pool.get('account.analytic.account').read(cr, uid, account_id, ['cost_center_ids', 'tuple_destination_account_ids'], context=context)
 
276
            cc_ids = fp and fp.get('cost_center_ids', []) or []
 
277
            tuple_destination_account_ids = fp and fp.get('tuple_destination_account_ids', []) or []
 
278
            tuple_list = [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in self.pool.get('account.destination.link').browse(cr, uid, tuple_destination_account_ids)]
 
279
            # Browse all analytic line to verify them
 
280
            for aline in self.browse(cr, uid, ids):
 
281
                # Verify that:
 
282
                # - the line doesn't have any draft/open contract
 
283
                check_accounts = self.pool.get('account.analytic.account').is_blocked_by_a_contract(cr, uid, [aline.account_id.id])
 
284
                if check_accounts and aline.account_id.id in check_accounts:
 
285
                    continue
 
286
                # No verification if account is MSF Private Fund because of its compatibility with all elements.
 
287
                if account_id == msf_private_fund:
 
288
                    res.append(aline.id)
 
289
                    continue
 
290
                # Verify that:
 
291
                # - the line have a cost_center_id field (we expect it's a line with a funding pool account)
 
292
                # - the cost_center is in compatible cost center from the new funding pool
 
293
                # - the general account is in compatible account/destination tuple
 
294
                # - the destination is in compatible account/destination tuple
 
295
                if aline.cost_center_id and aline.cost_center_id.id in cc_ids and aline.general_account_id and aline.destination_id and (aline.general_account_id.id, aline.destination_id.id) in tuple_list:
 
296
                    res.append(aline.id)
 
297
        else:
 
298
            # Case of FREE1 and FREE2 lines
 
299
            for id in ids:
 
300
                res.append(id)
 
301
        # Delete elements that are in expired_date_ids
 
302
        for id in expired_date_ids:
 
303
            if id in res:
 
304
                res.remove(id)
 
305
        return res
 
306
 
 
307
analytic_line()
 
308
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: