~unifield-team/unifield-wm/us-671-homere

« back to all changes in this revision

Viewing changes to analytic_distribution/invoice.py

[UF-43] fix added noupdate to demo data

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 osv, fields
23
 
from tools.translate import _
24
 
 
25
 
class account_invoice(osv.osv):
26
 
    _name = 'account.invoice'
27
 
    _inherit = 'account.invoice'
28
 
 
29
 
    def _check_active_product(self, cr, uid, ids, context=None):
30
 
        '''
31
 
        Check if the Purchase order contains a line with an inactive products
32
 
        '''
33
 
        inactive_lines = self.pool.get('account.invoice.line').search(cr, uid, [
34
 
            ('product_id.active', '=', False),
35
 
            ('invoice_id', 'in', ids),
36
 
            ('invoice_id.state', 'not in', ['draft', 'cancel', 'done'])
37
 
        ], context=context)
38
 
 
39
 
        if inactive_lines:
40
 
            plural = len(inactive_lines) == 1 and _('A product has') or _('Some products have')
41
 
            l_plural = len(inactive_lines) == 1 and _('line') or _('lines')
42
 
            p_plural = len(inactive_lines) == 1 and _('this inactive product') or _('those inactive products')
43
 
            raise osv.except_osv(_('Error'), _('%s been inactivated. If you want to validate this document you have to remove/correct the %s containing %s (see red %s of the document)') % (plural, l_plural, p_plural, l_plural))
44
 
            return False
45
 
        return True
46
 
 
47
 
    _constraints = [
48
 
        (_check_active_product, "You cannot validate this invoice because it contains a line with an inactive product", ['invoice_line', 'state'])
49
 
    ]
50
 
 
51
 
    _columns = {
52
 
        'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution', select="1"), # select: optimisation purpose
53
 
    }
54
 
 
55
 
    def _check_analytic_distribution_state(self, cr, uid, ids, context=None):
56
 
        """
57
 
        Check if analytic distribution is valid
58
 
        """
59
 
        if not context:
60
 
            context = {}
61
 
        if isinstance(ids, (int, long)):
62
 
            ids = [ids]
63
 
        for inv in self.browse(cr, uid, ids, context=context):
64
 
            for invl in inv.invoice_line:
65
 
                if invl.analytic_distribution_state != 'valid':
66
 
                    raise osv.except_osv(_('Error'), _('Analytic distribution is not valid for "%s"') % invl.name)
67
 
        return True
68
 
 
69
 
    def _hook_fields_for_refund(self, cr, uid, *args):
70
 
        """
71
 
        Add these fields to result:
72
 
         - analytic_distribution_id
73
 
        """
74
 
        res = super(account_invoice, self)._hook_fields_for_refund(cr, uid, args)
75
 
        res.append('analytic_distribution_id')
76
 
        res.append('document_date')
77
 
        return res
78
 
 
79
 
    def _hook_fields_m2o_for_refund(self, cr, uid, *args):
80
 
        """
81
 
        Add these fields to result:
82
 
         - analytic_distribution_id
83
 
        """
84
 
        res = super(account_invoice, self)._hook_fields_m2o_for_refund(cr, uid, args)
85
 
        res.append('analytic_distribution_id')
86
 
        return res
87
 
 
88
 
    def _hook_refund_data(self, cr, uid, data, *args):
89
 
        """
90
 
        Copy analytic distribution for refund invoice
91
 
        """
92
 
        if not data:
93
 
            return False
94
 
        if 'analytic_distribution_id' in data:
95
 
            if data.get('analytic_distribution_id', False):
96
 
                data['analytic_distribution_id'] = self.pool.get('analytic.distribution').copy(cr, uid, data.get('analytic_distribution_id'), {}) or False
97
 
            else:
98
 
                data['analytic_distribution_id'] = False
99
 
        return data
100
 
 
101
 
    def _refund_cleanup_lines(self, cr, uid, lines):
102
 
        """
103
 
        Add right analytic distribution values on each lines
104
 
        """
105
 
        res = super(account_invoice, self)._refund_cleanup_lines(cr, uid, lines)
106
 
        for el in res:
107
 
            if el[2]:
108
 
                # Give analytic distribution on line
109
 
                if 'analytic_distribution_id' in el[2]:
110
 
                    if el[2].get('analytic_distribution_id', False) and el[2].get('analytic_distribution_id')[0]:
111
 
                        distrib_id = el[2].get('analytic_distribution_id')[0]
112
 
                        el[2]['analytic_distribution_id'] = self.pool.get('analytic.distribution').copy(cr, uid, distrib_id, {}) or False
113
 
                    else:
114
 
                        # default value
115
 
                        el[2]['analytic_distribution_id'] = False
116
 
                # Give false analytic lines for 'line' in order not to give an error
117
 
                if 'analytic_line_ids' in el[2]:
118
 
                    el[2]['analytic_line_ids'] = False
119
 
                # Give false for (because not needed):
120
 
                # - order_line_id
121
 
                # - sale_order_line_id
122
 
                for field in ['order_line_id', 'sale_order_line_id']:
123
 
                    if field in el[2]:
124
 
                        el[2][field] = el[2].get(field, False) and el[2][field][0] or False
125
 
        return res
126
 
 
127
 
    def copy(self, cr, uid, inv_id, default=None, context=None):
128
 
        """
129
 
        Copy global distribution and give it to new invoice
130
 
        """
131
 
        if not context:
132
 
            context = {}
133
 
        if not default:
134
 
            default = {}
135
 
        inv = self.browse(cr, uid, [inv_id], context=context)[0]
136
 
        if inv.analytic_distribution_id:
137
 
            new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, inv.analytic_distribution_id.id, {}, context=context)
138
 
            if new_distrib_id:
139
 
                default.update({'analytic_distribution_id': new_distrib_id})
140
 
        return super(account_invoice, self).copy(cr, uid, inv_id, default, context)
141
 
 
142
 
    def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None, document_date=None):
143
 
        """
144
 
        Reverse lines for given invoice
145
 
        """
146
 
        if isinstance(ids, (int, long)):
147
 
            ids = [ids]
148
 
        for inv in self.browse(cr, uid, ids):
149
 
            # Check for dates (refund must be done after invoice)
150
 
            if date and date < inv.date_invoice:
151
 
                raise osv.except_osv(_('Error'), _("Posting date for the refund is before the invoice's posting date!"))
152
 
            if document_date and document_date < inv.document_date:
153
 
                raise osv.except_osv(_('Error'), _("Document date for the refund is before the invoice's document date!"))
154
 
        new_ids = super(account_invoice, self).refund(cr, uid, ids, date, period_id, description, journal_id)
155
 
        # add document date
156
 
        if document_date:
157
 
            self.write(cr, uid, new_ids, {'document_date': document_date})
158
 
        return new_ids
159
 
 
160
 
    def line_get_convert(self, cr, uid, x, part, date, context=None):
161
 
        res = super(account_invoice, self).line_get_convert(cr, uid, x, part, date, context=context)
162
 
        res['analytic_distribution_id'] = x.get('analytic_distribution_id', False)
163
 
        return res
164
 
 
165
 
    def button_analytic_distribution(self, cr, uid, ids, context=None):
166
 
        """
167
 
        Launch analytic distribution wizard on an invoice
168
 
        """
169
 
        # Some verifications
170
 
        if not context:
171
 
            context = {}
172
 
        if isinstance(ids, (int, long)):
173
 
            ids = [ids]
174
 
        # Prepare some values
175
 
        invoice = self.browse(cr, uid, ids[0], context=context)
176
 
        amount = 0.0
177
 
        # Search elements for currency
178
 
        company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
179
 
        currency = invoice.currency_id and invoice.currency_id.id or company_currency
180
 
        for line in invoice.invoice_line:
181
 
            amount += line.price_subtotal
182
 
        # Get analytic_distribution_id
183
 
        distrib_id = invoice.analytic_distribution_id and invoice.analytic_distribution_id.id
184
 
        # Prepare values for wizard
185
 
        vals = {
186
 
            'total_amount': amount,
187
 
            'invoice_id': invoice.id,
188
 
            'currency_id': currency or False,
189
 
            'state': 'dispatch',
190
 
            'posting_date': invoice.date_invoice,
191
 
            'document_date': invoice.document_date,
192
 
        }
193
 
        if distrib_id:
194
 
            vals.update({'distribution_id': distrib_id,})
195
 
        # Create the wizard
196
 
        wiz_obj = self.pool.get('analytic.distribution.wizard')
197
 
        wiz_id = wiz_obj.create(cr, uid, vals, context=context)
198
 
        # Update some context values
199
 
        context.update({
200
 
            'active_id': ids[0],
201
 
            'active_ids': ids,
202
 
        })
203
 
        # Open it!
204
 
        return {
205
 
                'name': _('Global analytic distribution'),
206
 
                'type': 'ir.actions.act_window',
207
 
                'res_model': 'analytic.distribution.wizard',
208
 
                'view_type': 'form',
209
 
                'view_mode': 'form',
210
 
                'target': 'new',
211
 
                'res_id': [wiz_id],
212
 
                'context': context,
213
 
        }
214
 
 
215
 
    def button_reset_distribution(self, cr, uid, ids, context=None):
216
 
        """
217
 
        Reset analytic distribution on all invoice lines.
218
 
        To do this, just delete the analytic_distribution id link on each invoice line.
219
 
        """
220
 
        if context is None:
221
 
            context = {}
222
 
        if isinstance(ids, (int, long)):
223
 
            ids = [ids]
224
 
        # Prepare some values
225
 
        invl_obj = self.pool.get(self._name + '.line') # PAY ATTENTION to wizard.account.invoice.line
226
 
        # Search invoice lines
227
 
        to_reset = invl_obj.search(cr, uid, [('invoice_id', 'in', ids)])
228
 
        invl_obj.write(cr, uid, to_reset, {'analytic_distribution_id': False})
229
 
        return True
230
 
 
231
 
account_invoice()
232
 
 
233
 
class account_invoice_line(osv.osv):
234
 
    _name = 'account.invoice.line'
235
 
    _inherit = 'account.invoice.line'
236
 
 
237
 
    def _get_distribution_state(self, cr, uid, ids, name, args, context=None):
238
 
        """
239
 
        Get state of distribution:
240
 
         - if compatible with the invoice line, then "valid"
241
 
         - if no distribution, take a tour of invoice distribution, if compatible, then "valid"
242
 
         - if no distribution on invoice line and invoice, then "none"
243
 
         - all other case are "invalid"
244
 
        """
245
 
        # Some verifications
246
 
        if not context:
247
 
            context = {}
248
 
        if isinstance(ids, (int, long)):
249
 
            ids = [ids]
250
 
        # Prepare some values
251
 
        res = {}
252
 
        # Browse all given lines
253
 
        for line in self.browse(cr, uid, ids, context=context):
254
 
            if line.from_yml_test:
255
 
                res[line.id] = 'valid'
256
 
            else:
257
 
                # UF-2115: test for elements
258
 
                line_distribution_id = False
259
 
                invoice_distribution_id = False
260
 
                line_account_id = False
261
 
                if line.analytic_distribution_id:
262
 
                    line_distribution_id = line.analytic_distribution_id.id
263
 
                if line.invoice_id and line.invoice_id.analytic_distribution_id:
264
 
                    invoice_distribution_id = line.invoice_id.analytic_distribution_id.id
265
 
                if line.account_id:
266
 
                    line_account_id = line.account_id.id
267
 
                res[line.id] = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line_distribution_id, invoice_distribution_id, line_account_id)
268
 
        return res
269
 
 
270
 
    def _have_analytic_distribution_from_header(self, cr, uid, ids, name, arg, context=None):
271
 
        """
272
 
        If invoice have an analytic distribution, return False, else return True
273
 
        """
274
 
        # Some verifications
275
 
        if not context:
276
 
            context = {}
277
 
        if isinstance(ids, (int, long)):
278
 
            ids = [ids]
279
 
        res = {}
280
 
        for inv in self.browse(cr, uid, ids, context=context):
281
 
            res[inv.id] = True
282
 
            if inv.analytic_distribution_id:
283
 
                res[inv.id] = False
284
 
        return res
285
 
 
286
 
    def _get_is_allocatable(self, cr, uid, ids, name, arg, context=None):
287
 
        """
288
 
        If analytic-a-holic account, then this account is allocatable.
289
 
        """
290
 
        if isinstance(ids, (int, long)):
291
 
            ids = [ids]
292
 
        res = {}
293
 
        for invl in self.browse(cr, uid, ids):
294
 
            res[invl.id] = True
295
 
            if invl.account_id and not invl.account_id.is_analytic_addicted:
296
 
                res[invl.id] = False
297
 
        return res
298
 
 
299
 
    def _get_distribution_state_recap(self, cr, uid, ids, name, arg, context=None):
300
 
        """
301
 
        Get a recap from analytic distribution state and if it come from header or not.
302
 
        """
303
 
        if isinstance(ids, (int, long)):
304
 
            ids = [ids]
305
 
        res = {}
306
 
        for invl in self.browse(cr, uid, ids):
307
 
            res[invl.id] = ''
308
 
            if not invl.is_allocatable:
309
 
                continue
310
 
            from_header = ''
311
 
            if invl.have_analytic_distribution_from_header:
312
 
                from_header = _(' (from header)')
313
 
            res[invl.id] = '%s%s' % (self.pool.get('ir.model.fields').get_browse_selection(cr, uid, invl, 'analytic_distribution_state', context), from_header)
314
 
        return res
315
 
 
316
 
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
317
 
        '''
318
 
        Fill the error message if the product of the line is inactive
319
 
        '''
320
 
        res = {}
321
 
        for line in self.browse(cr, uid, ids, context=context):
322
 
            res[line.id] = {'inactive_product': False,
323
 
                            'inactive_error': ''}
324
 
            if line.invoice_id and line.invoice_id.state not in ('cancel', 'done') and line.product_id and not line.product_id.active:
325
 
                res[line.id] = {
326
 
                    'inactive_product': True,
327
 
                    'inactive_error': _('The product in line is inactive !')
328
 
                }
329
 
        return res
330
 
 
331
 
    def _get_analytic_lines(self, cr, uid, ids, field_name, arg, context=None):
332
 
        """
333
 
        """
334
 
        # Checks
335
 
        if context is None:
336
 
            context = {}
337
 
        # Prepare some values
338
 
        res = {}
339
 
        for invl in self.browse(cr, uid, ids):
340
 
            res[invl.id] = []
341
 
            for ml in (invl.move_lines or []):
342
 
                if ml.analytic_lines:
343
 
                    res[invl.id] = self.pool.get('account.analytic.line').get_corrections_history(cr, uid, [x.id for x in ml.analytic_lines])
344
 
        return res
345
 
 
346
 
    _columns = {
347
 
        'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution', select="1"), # select is for optimisation purposes
348
 
        'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
349
 
            selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
350
 
            string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
351
 
        'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean',
352
 
            string='Header Distrib.?'),
353
 
        'newline': fields.boolean('New line'),
354
 
        'is_allocatable': fields.function(_get_is_allocatable, method=True, type='boolean', string="Is allocatable?", readonly=True, store=False),
355
 
        'analytic_distribution_state_recap': fields.function(_get_distribution_state_recap, method=True, type='char', size=30,
356
 
            string="Distribution",
357
 
            help="Informs you about analaytic distribution state among 'none', 'valid', 'invalid', from header or not, or no analytic distribution"),
358
 
        'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
359
 
        'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Comment', store=False, multi='inactive'),
360
 
        'analytic_lines': fields.function(_get_analytic_lines, method=True, type='one2many', relation='account.analytic.line', store=False, string='Analytic lines', help='Give all analytic lines linked to this invoice line. With correction ones.'),
361
 
    }
362
 
 
363
 
    _defaults = {
364
 
        'newline': lambda *a: True,
365
 
        'have_analytic_distribution_from_header': lambda *a: True,
366
 
        'is_allocatable': lambda *a: True,
367
 
        'analytic_distribution_state_recap': lambda *a: '',
368
 
        'inactive_product': False,
369
 
        'inactive_error': lambda *a: '',
370
 
    }
371
 
 
372
 
    def create(self, cr, uid, vals, context=None):
373
 
        """
374
 
        Set newline field to False.
375
 
        """
376
 
        vals.update({'newline': False,})
377
 
        return super(account_invoice_line, self).create(cr, uid, vals, context)
378
 
 
379
 
    def copy_data(self, cr, uid, l_id, default=None, context=None):
380
 
        """
381
 
        Copy global distribution and give it to new invoice line
382
 
        """
383
 
        # Some verifications
384
 
        if not context:
385
 
            context = {}
386
 
        if not default:
387
 
            default = {}
388
 
        # Copy analytic distribution
389
 
        invl = self.browse(cr, uid, [l_id], context=context)[0]
390
 
        if invl.analytic_distribution_id:
391
 
            new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, invl.analytic_distribution_id.id, {}, context=context)
392
 
            if new_distrib_id:
393
 
                default.update({'analytic_distribution_id': new_distrib_id})
394
 
        return super(account_invoice_line, self).copy_data(cr, uid, l_id, default, context)
395
 
 
396
 
    def move_line_get_item(self, cr, uid, line, context=None):
397
 
        """
398
 
        Give right analytic distribution when creating move lines
399
 
        """
400
 
        # Some verifications
401
 
        if not context:
402
 
            context = {}
403
 
        # Default result
404
 
        res = super(account_invoice_line, self).move_line_get_item(cr, uid, line, context=context)
405
 
        # Update result by copying analytic distribution from invoice line
406
 
        ana_obj = self.pool.get('analytic.distribution')
407
 
        if line.analytic_distribution_id:
408
 
            new_distrib_id = ana_obj.copy(cr, uid, line.analytic_distribution_id.id, {}, context=context)
409
 
            if new_distrib_id:
410
 
                res['analytic_distribution_id'] = new_distrib_id
411
 
        # If no distribution on invoice line, take those from invoice and copy it!
412
 
        elif line.invoice_id and line.invoice_id.analytic_distribution_id:
413
 
            new_distrib_id = ana_obj.copy(cr, uid, line.invoice_id.analytic_distribution_id.id, {}, context=context)
414
 
            if new_distrib_id:
415
 
                res['analytic_distribution_id'] = new_distrib_id
416
 
        return res
417
 
 
418
 
    def button_analytic_distribution(self, cr, uid, ids, context=None):
419
 
        """
420
 
        Launch analytic distribution wizard on an invoice line
421
 
        """
422
 
        # Some verifications
423
 
        if not context:
424
 
            context = {}
425
 
        if isinstance(ids, (int, long)):
426
 
            ids = [ids]
427
 
        if not ids:
428
 
            raise osv.except_osv(_('Error'), _('No invoice line given. Please save your invoice line before.'))
429
 
        # Prepare some values
430
 
        invoice_line = self.browse(cr, uid, ids[0], context=context)
431
 
        negative_inv = False
432
 
        amount = invoice_line.price_subtotal or 0.0
433
 
        # Search elements for currency
434
 
        company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
435
 
        currency = invoice_line.invoice_id.currency_id and invoice_line.invoice_id.currency_id.id or company_currency
436
 
        # Change amount sign if necessary
437
 
        if invoice_line.invoice_id.type in ['out_invoice', 'in_refund']:
438
 
            negative_inv = True
439
 
        if negative_inv:
440
 
            amount = -1 * amount
441
 
        # Get analytic distribution id from this line
442
 
        distrib_id = invoice_line and invoice_line.analytic_distribution_id and invoice_line.analytic_distribution_id.id or False
443
 
        # Prepare values for wizard
444
 
        vals = {
445
 
            'total_amount': amount,
446
 
            'invoice_line_id': invoice_line.id,
447
 
            'currency_id': currency or False,
448
 
            'state': 'dispatch',
449
 
            'account_id': invoice_line.account_id and invoice_line.account_id.id or False,
450
 
            'posting_date': invoice_line.invoice_id.date_invoice,
451
 
            'document_date': invoice_line.invoice_id.document_date,
452
 
        }
453
 
        if distrib_id:
454
 
            vals.update({'distribution_id': distrib_id,})
455
 
        # Create the wizard
456
 
        wiz_obj = self.pool.get('analytic.distribution.wizard')
457
 
        wiz_id = wiz_obj.create(cr, uid, vals, context=context)
458
 
        # Update some context values
459
 
        context.update({
460
 
            'active_id': ids[0],
461
 
            'active_ids': ids,
462
 
        })
463
 
        # Open it!
464
 
        return {
465
 
                'name': _('Analytic distribution'),
466
 
                'type': 'ir.actions.act_window',
467
 
                'res_model': 'analytic.distribution.wizard',
468
 
                'view_type': 'form',
469
 
                'view_mode': 'form',
470
 
                'target': 'new',
471
 
                'res_id': [wiz_id],
472
 
                'context': context,
473
 
        }
474
 
 
475
 
account_invoice_line()
476
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: