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

« back to all changes in this revision

Viewing changes to analytic_distribution/account_move_line.py

  • Committer: chloups208
  • Date: 2011-09-07 12:58:12 UTC
  • mto: (307.2.1 unifield-wm)
  • mto: This revision was merged to the branch mainline in revision 311.
  • Revision ID: chloups208@chloups208-laptop-20110907125812-dsb1jt93ae6bgfe2
[UF-390]pro forma invoice + shipment object refactoring + pack family object refactoring

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 MSF, TeMPO consulting
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU Affero General Public License as
9
 
#    published by the Free Software Foundation, either version 3 of the
10
 
#    License, or (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU Affero General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU Affero General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import fields, osv
23
 
import tools
24
 
from tools.translate import _
25
 
 
26
 
class account_move_line(osv.osv):
27
 
    _inherit = 'account.move.line'
28
 
 
29
 
    def _display_analytic_button(self, cr, uid, ids, name, args, context=None):
30
 
        """
31
 
        Return True for all element that correspond to some criteria:
32
 
         - The journal entry state is draft (unposted)
33
 
         - The account is analytic-a-holic
34
 
        """
35
 
        res = {}
36
 
        for ml in self.browse(cr, uid, ids, context=context):
37
 
            res[ml.id] = True
38
 
            # False if account not anlaytic-a-holic
39
 
            if not ml.account_id.is_analytic_addicted:
40
 
                res[ml.id] = False
41
 
        return res
42
 
 
43
 
    def _get_distribution_state(self, cr, uid, ids, name, args, context=None):
44
 
        """
45
 
        Get state of distribution:
46
 
         - if compatible with the move line, then "valid"
47
 
         - if no distribution, take a tour of move distribution, if compatible, then "valid"
48
 
         - if no distribution on move line and move, then "none"
49
 
         - all other case are "invalid"
50
 
        """
51
 
        # Some verifications
52
 
        if not context:
53
 
            context = {}
54
 
        if isinstance(ids, (int, long)):
55
 
            ids = [ids]
56
 
        # Prepare some values
57
 
        res = {}
58
 
        # Browse all given lines
59
 
        for line in self.browse(cr, uid, ids, context=context):
60
 
            res[line.id] = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line.analytic_distribution_id.id, line.move_id and line.move_id.analytic_distribution_id and line.move_id.analytic_distribution_id.id or False, line.account_id.id)
61
 
        return res
62
 
 
63
 
    def _have_analytic_distribution_from_header(self, cr, uid, ids, name, arg, context=None):
64
 
        """
65
 
        If move have an analytic distribution, return False, else return True
66
 
        """
67
 
        # Some verifications
68
 
        if not context:
69
 
            context = {}
70
 
        if isinstance(ids, (int, long)):
71
 
            ids = [ids]
72
 
        res = {}
73
 
        for ml in self.browse(cr, uid, ids, context=context):
74
 
            res[ml.id] = True
75
 
            if ml.analytic_distribution_id:
76
 
                res[ml.id] = False
77
 
        return res
78
 
 
79
 
    def _get_distribution_state_recap(self, cr, uid, ids, name, arg, context=None):
80
 
        """
81
 
        Get a recap from analytic distribution state and if it come from header or not.
82
 
        """
83
 
        if isinstance(ids, (int, long)):
84
 
            ids = [ids]
85
 
        res = {}
86
 
        get_sel = self.pool.get('ir.model.fields').get_browse_selection
87
 
        for ml in self.browse(cr, uid, ids):
88
 
            res[ml.id] = ''
89
 
            from_header = ''
90
 
            if ml.have_analytic_distribution_from_header:
91
 
                from_header = _(' (from header)')
92
 
            d_state = get_sel(cr, uid, ml, 'analytic_distribution_state', context)
93
 
            res[ml.id] = "%s%s" % (d_state, from_header)
94
 
            # Do not show any recap for non analytic-a-holic accounts
95
 
            if ml.account_id and not ml.account_id.is_analytic_addicted:
96
 
                res[ml.id] = ''
97
 
        return res
98
 
 
99
 
    _columns = {
100
 
        'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution'),
101
 
        'display_analytic_button': fields.function(_display_analytic_button, method=True, string='Display analytic button?', type='boolean', readonly=True, 
102
 
            help="This informs system that we can display or not an analytic button", store=False),
103
 
        'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection', 
104
 
            selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')], 
105
 
            string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
106
 
         'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean', 
107
 
            string='Header Distrib.?'),
108
 
        'analytic_distribution_state_recap': fields.function(_get_distribution_state_recap, method=True, type='char', size=30, 
109
 
            string="Distribution", 
110
 
            help="Informs you about analaytic distribution state among 'none', 'valid', 'invalid', from header or not, or no analytic distribution"),
111
 
  }
112
 
 
113
 
    def create_analytic_lines(self, cr, uid, ids, context=None):
114
 
        """
115
 
        Create analytic lines on analytic-a-holic accounts that have an analytical distribution.
116
 
        """
117
 
        # Some verifications
118
 
        if not context:
119
 
            context = {}
120
 
        acc_ana_line_obj = self.pool.get('account.analytic.line')
121
 
        company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
122
 
        for obj_line in self.browse(cr, uid, ids, context=context):
123
 
            # Prepare some values
124
 
            amount = obj_line.debit_currency - obj_line.credit_currency
125
 
            line_distrib_id = obj_line.analytic_distribution_id and obj_line.analytic_distribution_id.id or obj_line.move_id and obj_line.move_id.analytic_distribution_id and obj_line.move_id.analytic_distribution_id.id or False
126
 
            # When you create a journal entry manually, we should not have analytic lines if ONE line is invalid!
127
 
            other_lines_are_ok = True
128
 
            if obj_line.move_id and obj_line.move_id.status and obj_line.move_id.status == 'manu':
129
 
                if obj_line.state != 'valid':
130
 
                    other_lines_are_ok = False
131
 
                for other_line in obj_line.move_id.line_id:
132
 
                    if other_line.state != 'valid':
133
 
                        other_lines_are_ok = False
134
 
            # Check that line have analytic-a-holic account and have a distribution
135
 
            if line_distrib_id and obj_line.account_id.is_analytic_addicted and other_lines_are_ok:
136
 
                ana_state = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line_distrib_id, {}, obj_line.account_id.id)
137
 
                # For manual journal entries, do not raise an error. But delete all analytic distribution linked to other_lines because if one line is invalid, all lines should not create analytic lines
138
 
                if ana_state == 'invalid' and obj_line.move_id.status == 'manu':
139
 
                    ana_line_ids = acc_ana_line_obj.search(cr, uid, [('move_id', 'in', [x.id for x in obj_line.move_id.line_id])])
140
 
                    acc_ana_line_obj.unlink(cr, uid, ana_line_ids)
141
 
                    continue
142
 
                elif ana_state == 'invalid':
143
 
                    raise osv.except_osv(_('Warning'), _('Invalid analytic distribution.'))
144
 
                if not obj_line.journal_id.analytic_journal_id:
145
 
                    raise osv.except_osv(_('Warning'),_("No Analytic Journal! You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name, ))
146
 
                distrib_obj = self.pool.get('analytic.distribution').browse(cr, uid, line_distrib_id, context=context)
147
 
                # create lines
148
 
                for distrib_lines in [distrib_obj.funding_pool_lines, distrib_obj.free_1_lines, distrib_obj.free_2_lines]:
149
 
                    for distrib_line in distrib_lines:
150
 
                        context.update({'date': obj_line.source_date or obj_line.date})
151
 
                        anal_amount = distrib_line.percentage*amount/100
152
 
                        line_vals = {
153
 
                                     'name': obj_line.name,
154
 
                                     'date': obj_line.date,
155
 
                                     'ref': obj_line.ref,
156
 
                                     'journal_id': obj_line.journal_id.analytic_journal_id.id,
157
 
                                     'amount': -1 * self.pool.get('res.currency').compute(cr, uid, obj_line.currency_id.id, company_currency, 
158
 
                                        anal_amount, round=False, context=context),
159
 
                                     'amount_currency': -1 * anal_amount,
160
 
                                     'account_id': distrib_line.analytic_id.id,
161
 
                                     'general_account_id': obj_line.account_id.id,
162
 
                                     'move_id': obj_line.id,
163
 
                                     'distribution_id': distrib_obj.id,
164
 
                                     'user_id': uid,
165
 
                                     'currency_id': obj_line.currency_id.id,
166
 
                                     'distrib_line_id': '%s,%s'%(distrib_line._name, distrib_line.id),
167
 
                                     'document_date': obj_line.document_date,
168
 
                        }
169
 
                        # Update values if we come from a funding pool
170
 
                        if distrib_line._name == 'funding.pool.distribution.line':
171
 
                            destination_id = distrib_line.destination_id and distrib_line.destination_id.id or False
172
 
                            line_vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,
173
 
                                'destination_id': destination_id,})
174
 
                        # Update value if we come from a write-off
175
 
                        if obj_line.is_write_off:
176
 
                            line_vals.update({'from_write_off': True,})
177
 
                        # Add source_date value for account_move_line that are a correction of another account_move_line
178
 
                        if obj_line.corrected_line_id and obj_line.source_date:
179
 
                            line_vals.update({'source_date': obj_line.source_date})
180
 
                        self.pool.get('account.analytic.line').create(cr, uid, line_vals, context=context)
181
 
        return True
182
 
 
183
 
    def unlink(self, cr, uid, ids, context=None, check=True):
184
 
        """
185
 
        Delete analytic lines before unlink move lines.
186
 
        Update Manual Journal Entries.
187
 
        """
188
 
        if not context:
189
 
            context = {}
190
 
        # Search moves
191
 
        move_ids = []
192
 
        for ml in self.browse(cr, uid, ids):
193
 
            if ml.move_id and ml.move_id.state == 'manu':
194
 
                move_ids.append(ml.move_id.id)
195
 
        # Search analytic lines
196
 
        ana_ids = self.pool.get('account.analytic.line').search(cr, uid, [('move_id', 'in', ids)])
197
 
        self.pool.get('account.analytic.line').unlink(cr, uid, ana_ids)
198
 
        # Revalidate move
199
 
        self.pool.get('account.move').validate(cr, uid, move_ids)
200
 
        return super(account_move_line, self).unlink(cr, uid, ids, context=context, check=check)
201
 
 
202
 
    def button_analytic_distribution(self, cr, uid, ids, context=None):
203
 
        """
204
 
        Launch analytic distribution wizard on an move line
205
 
        """
206
 
        # Some verifications
207
 
        if not context:
208
 
            context = {}
209
 
        if isinstance(ids, (int, long)):
210
 
            ids = [ids]
211
 
        if not ids:
212
 
            raise osv.except_osv(_('Error'), _('No journal item given. Please save your line before.'))
213
 
        # Prepare some values
214
 
        ml = self.browse(cr, uid, ids[0], context=context)
215
 
        distrib_id = False
216
 
        amount = ml.debit_currency - ml.credit_currency
217
 
        # Search elements for currency
218
 
        company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
219
 
        currency = ml.currency_id and ml.currency_id.id or company_currency
220
 
        # Get analytic distribution id from this line
221
 
        distrib_id = ml and ml.analytic_distribution_id and ml.analytic_distribution_id.id or False
222
 
        # Prepare values for wizard
223
 
        vals = {
224
 
            'total_amount': amount,
225
 
            'move_line_id': ml.id,
226
 
            'currency_id': currency or False,
227
 
            'state': 'dispatch',
228
 
            'account_id': ml.account_id and ml.account_id.id or False,
229
 
            'posting_date': ml.date,
230
 
            'document_date': ml.document_date,
231
 
        }
232
 
        if distrib_id:
233
 
            vals.update({'distribution_id': distrib_id,})
234
 
        # Create the wizard
235
 
        wiz_obj = self.pool.get('analytic.distribution.wizard')
236
 
        wiz_id = wiz_obj.create(cr, uid, vals, context=context)
237
 
        # Update some context values
238
 
        context.update({
239
 
            'active_id': ids[0],
240
 
            'active_ids': ids,
241
 
        })
242
 
        # Open it!
243
 
        return {
244
 
                'name': _('Analytic distribution'),
245
 
                'type': 'ir.actions.act_window',
246
 
                'res_model': 'analytic.distribution.wizard',
247
 
                'view_type': 'form',
248
 
                'view_mode': 'form',
249
 
                'target': 'new',
250
 
                'res_id': [wiz_id],
251
 
                'context': context,
252
 
        }
253
 
 
254
 
    def _check_employee_analytic_distribution(self, cr, uid, ids, context=None):
255
 
        """
256
 
        Check that analytic distribution could be retrieved from given employee.
257
 
        If not employee, return True.
258
 
        """
259
 
        if not context:
260
 
            context = {}
261
 
        if isinstance(ids, (int, long)):
262
 
            ids = [ids]
263
 
        for l in self.browse(cr, uid, ids):
264
 
            # Next line if this one comes from a non-manual move (journal entry)
265
 
            if l.move_id.status != 'manu':
266
 
                continue
267
 
            # Do not continue if no employee or no cost center (could not be invented)
268
 
            if not l.employee_id or not l.employee_id.cost_center_id:
269
 
                continue
270
 
            if l.account_id and l.account_id.is_analytic_addicted:
271
 
                vals = {'cost_center_id': l.employee_id.cost_center_id.id}
272
 
                if l.employee_id.destination_id:
273
 
                    if l.employee_id.destination_id.id in [x and x.id for x in l.account_id.destination_ids]:
274
 
                        vals.update({'destination_id': l.employee_id.destination_id.id})
275
 
                    else:
276
 
                        vals.update({'destination_id': l.account_id.default_destination_id.id})
277
 
                if l.employee_id.funding_pool_id:
278
 
                    vals.update({'analytic_id': l.employee_id.funding_pool_id.id})
279
 
                    if vals.get('cost_center_id') not in l.employee_id.funding_pool_id.cost_center_ids:
280
 
                        # Fetch default funding pool: MSF Private Fund
281
 
                        try:
282
 
                            msf_fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
283
 
                        except ValueError:
284
 
                            msf_fp_id = 0
285
 
                        vals.update({'analytic_id': msf_fp_id})
286
 
                # Create analytic distribution
287
 
                if 'cost_center_id' in vals and 'analytic_id' in vals and 'destination_id' in vals:
288
 
                    distrib_id = self.pool.get('analytic.distribution').create(cr, uid, {'name': 'check_employee_analytic_distribution'})
289
 
                    vals.update({'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id})
290
 
                    # Create funding pool lines
291
 
                    self.pool.get('funding.pool.distribution.line').create(cr, uid, vals)
292
 
                    # Then cost center lines
293
 
                    vals.update({'analytic_id': vals.get('cost_center_id'),})
294
 
                    self.pool.get('cost.center.distribution.line').create(cr, uid, vals)
295
 
                    # finally free1 and free2
296
 
                    if l.employee_id.free1_id:
297
 
                        self.pool.get('free.1.distribution.line').create(cr, uid, {'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id, 'analytic_id': l.employee_id.free1_id.id})
298
 
                    if l.employee_id.free2_id:
299
 
                        self.pool.get('free.2.distribution.line').create(cr, uid, {'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id, 'analytic_id': l.employee_id.free2_id.id})
300
 
                    if context.get('from_write', False):
301
 
                        return {'analytic_distribution_id': distrib_id,}
302
 
                    # Write analytic distribution on the move line
303
 
                    self.pool.get('account.move.line').write(cr, uid, [l.id], {'analytic_distribution_id': distrib_id}, check=False, update_check=False)
304
 
                else:
305
 
                    return False
306
 
        return True
307
 
 
308
 
    def create(self, cr, uid, vals, context=None, check=True):
309
 
        """
310
 
        Check analytic distribution for employee (if given)
311
 
        """
312
 
        res = super(account_move_line, self).create(cr, uid, vals, context, check)
313
 
        self._check_employee_analytic_distribution(cr, uid, res, context)
314
 
        return res
315
 
 
316
 
    def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
317
 
        """
318
 
        Check line if we come from web (from_web_menu)
319
 
        """
320
 
        if not context:
321
 
            context = {}
322
 
        if isinstance(ids, (int, long)):
323
 
            ids = [ids]
324
 
        if context.get('from_web_menu', False):
325
 
            res = []
326
 
            for ml in self.browse(cr, uid, ids):
327
 
                distrib_state = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, ml.analytic_distribution_id.id, ml.move_id and ml.move_id.analytic_distribution_id and ml.move_id.analytic_distribution_id.id or False, vals.get('account_id') or ml.account_id.id)
328
 
                if distrib_state in ['invalid', 'none']:
329
 
                    vals.update({'state': 'draft'})
330
 
                # Add account_id because of an error with account_activable module for checking date
331
 
                if not 'account_id' in vals and 'date' in vals:
332
 
                    vals.update({'account_id': ml.account_id and ml.account_id.id or False})
333
 
                check = self._check_employee_analytic_distribution(cr, uid, [ml.id], context={'from_write': True})
334
 
                if check and isinstance(check, dict):
335
 
                    vals.update(check)
336
 
                tmp_res = super(account_move_line, self).write(cr, uid, [ml.id], vals, context, False, False)
337
 
                res.append(tmp_res)
338
 
            return res
339
 
        res = super(account_move_line, self).write(cr, uid, ids, vals, context, check, update_check)
340
 
        return res
341
 
 
342
 
    def copy(self, cr, uid, id, default=None, context=None):
343
 
        """
344
 
        Copy analytic_distribution
345
 
        """
346
 
        # Some verifications
347
 
        if not context:
348
 
            context = {}
349
 
        if not default:
350
 
            default = {}
351
 
        # Default method
352
 
        res = super(account_move_line, self).copy(cr, uid, id, default, context)
353
 
        # Update analytic distribution
354
 
        if res:
355
 
            c = self.browse(cr, uid, res, context=context)
356
 
        if res and c.analytic_distribution_id:
357
 
            new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, c.analytic_distribution_id.id, {}, context=context)
358
 
            if new_distrib_id:
359
 
                self.write(cr, uid, [res], {'analytic_distribution_id': new_distrib_id}, context=context)
360
 
        return res
361
 
 
362
 
    def get_analytic_move_lines(self, cr, uid, ids, context=None):
363
 
        """
364
 
        Return FP analytic lines attached to move lines
365
 
        """
366
 
        # Some verifications
367
 
        if not context:
368
 
            context = {}
369
 
        if 'active_ids' in context:
370
 
            ids = context.get('active_ids')
371
 
        if isinstance(ids, (int, long)):
372
 
            ids = [ids]
373
 
        # Search valid ids
374
 
        domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FUNDING')]
375
 
        context.update({'display_fp': True})
376
 
        return {
377
 
            'name': _('Analytic lines (FP) from Journal Items'),
378
 
            'type': 'ir.actions.act_window',
379
 
            'res_model': 'account.analytic.line',
380
 
            'view_type': 'form',
381
 
            'view_mode': 'tree,form',
382
 
            'context': context,
383
 
            'domain': domain,
384
 
            'target': 'current',
385
 
        }
386
 
 
387
 
    def get_analytic_move_free1_lines(self, cr, uid, ids, context=None):
388
 
        """
389
 
        Return FREE1 analytic lines attached to move lines
390
 
        """
391
 
        # Some verifications
392
 
        if not context:
393
 
            context = {}
394
 
        if 'active_ids' in context:
395
 
            ids = context.get('active_ids')
396
 
        if isinstance(ids, (int, long)):
397
 
            ids = [ids]
398
 
        # Search valid ids
399
 
        domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FREE1')]
400
 
        context.update({'display_fp': False, 'categ': 'FREE1'})
401
 
        return {
402
 
            'name': _('Analytic Lines (Free 1) from Journal Items'),
403
 
            'type': 'ir.actions.act_window',
404
 
            'res_model': 'account.analytic.line',
405
 
            'view_type': 'form',
406
 
            'view_mode': 'tree,form',
407
 
            'context': context,
408
 
            'domain': domain,
409
 
            'target': 'current',
410
 
        }
411
 
 
412
 
    def get_analytic_move_free2_lines(self, cr, uid, ids, context=None):
413
 
        """
414
 
        Return FREE2 analytic lines attached to move lines
415
 
        """
416
 
        # Some verifications
417
 
        if not context:
418
 
            context = {}
419
 
        if 'active_ids' in context:
420
 
            ids = context.get('active_ids')
421
 
        if isinstance(ids, (int, long)):
422
 
            ids = [ids]
423
 
        # Search valid ids
424
 
        domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FREE2')]
425
 
        context.update({'display_fp': False, 'categ': 'FREE2'})
426
 
        return {
427
 
            'name': _('Analytic Lines (Free 2) from Journal Items'),
428
 
            'type': 'ir.actions.act_window',
429
 
            'res_model': 'account.analytic.line',
430
 
            'view_type': 'form',
431
 
            'view_mode': 'tree,form',
432
 
            'context': context,
433
 
            'domain': domain,
434
 
            'target': 'current',
435
 
        }
436
 
 
437
 
account_move_line()
438
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: