1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 MSF, TeMPO Consulting.
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.
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.
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/>.
20
##############################################################################
23
from osv import fields
24
from tools.translate import _
26
class account_invoice(osv.osv):
27
_name = 'account.invoice'
28
_inherit = 'account.invoice'
30
def _check_active_product(self, cr, uid, ids, context=None):
32
Check if the Purchase order contains a line with an inactive products
34
inactive_lines = self.pool.get('account.invoice.line').search(cr, uid, [
35
('product_id.active', '=', False),
36
('invoice_id', 'in', ids),
37
('invoice_id.state', 'not in', ['draft', 'cancel', 'done'])
41
plural = len(inactive_lines) == 1 and _('A product has') or _('Some products have')
42
l_plural = len(inactive_lines) == 1 and _('line') or _('lines')
43
p_plural = len(inactive_lines) == 1 and _('this inactive product') or _('those inactive products')
44
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))
49
(_check_active_product, "You cannot validate this invoice because it contains a line with an inactive product", ['invoice_line', 'state'])
52
def _check_analytic_distribution_state(self, cr, uid, ids, context=None):
54
Check if analytic distribution is valid
58
if isinstance(ids, (int, long)):
60
for inv in self.browse(cr, uid, ids, context=context):
61
for invl in inv.invoice_line:
62
if inv.from_yml_test or invl.from_yml_test:
64
if invl.analytic_distribution_state != 'valid':
65
raise osv.except_osv(_('Error'), _('Analytic distribution is not valid for "%s"') % invl.name)
68
def button_close_direct_invoice(self, cr, uid, ids, context=None):
70
Check analytic distribution before closing pop-up
74
if isinstance(ids, (int, long)):
76
self._check_analytic_distribution_state(cr, uid, ids, context)
77
if context.get('from_register', False):
78
return {'type': 'ir.actions.act_window_close'}
81
def _hook_fields_for_refund(self, cr, uid, *args):
83
Add these fields to result:
84
- analytic_distribution_id
86
res = super(account_invoice, self)._hook_fields_for_refund(cr, uid, args)
87
res.append('analytic_distribution_id')
88
res.append('document_date')
91
def _hook_fields_m2o_for_refund(self, cr, uid, *args):
93
Add these fields to result:
94
- analytic_distribution_id
96
res = super(account_invoice, self)._hook_fields_m2o_for_refund(cr, uid, args)
97
res.append('analytic_distribution_id')
100
def _hook_refund_data(self, cr, uid, data, *args):
102
Copy analytic distribution for refund invoice
106
if 'analytic_distribution_id' in data:
107
if data.get('analytic_distribution_id', False):
108
data['analytic_distribution_id'] = self.pool.get('analytic.distribution').copy(cr, uid, data.get('analytic_distribution_id'), {}) or False
110
data['analytic_distribution_id'] = False
113
def _refund_cleanup_lines(self, cr, uid, lines):
115
Add right analytic distribution values on each lines
117
res = super(account_invoice, self)._refund_cleanup_lines(cr, uid, lines)
120
# Give analytic distribution on line
121
if 'analytic_distribution_id' in el[2]:
122
if el[2].get('analytic_distribution_id', False) and el[2].get('analytic_distribution_id')[0]:
123
distrib_id = el[2].get('analytic_distribution_id')[0]
124
el[2]['analytic_distribution_id'] = self.pool.get('analytic.distribution').copy(cr, uid, distrib_id, {}) or False
127
el[2]['analytic_distribution_id'] = False
128
# Give false analytic lines for 'line' in order not to give an error
129
if 'analytic_line_ids' in el[2]:
130
el[2]['analytic_line_ids'] = False
131
# Give false order_line_id in order not to give an error
132
if 'order_line_id' in el[2]:
133
el[2]['order_line_id'] = el[2].get('order_line_id', False) and el[2]['order_line_id'][0] or False
136
def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None, document_date=None):
138
Reverse lines for given invoice
140
if isinstance(ids, (int, long)):
142
for inv in self.browse(cr, uid, ids):
143
# Check for dates (refund must be done after invoice)
144
if date and date < inv.date_invoice:
145
raise osv.except_osv(_('Error'), _("Posting date for the refund is before the invoice's posting date!"))
146
if document_date and document_date < inv.document_date:
147
raise osv.except_osv(_('Error'), _("Document date for the refund is before the invoice's document date!"))
148
new_ids = super(account_invoice, self).refund(cr, uid, ids, date, period_id, description, journal_id)
151
self.write(cr, uid, new_ids, {'document_date': document_date})
154
def copy(self, cr, uid, id, default=None, context=None):
156
Copy global distribution and give it to new invoice
162
inv = self.browse(cr, uid, [id], context=context)[0]
163
if inv.analytic_distribution_id:
164
new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, inv.analytic_distribution_id.id, {}, context=context)
166
default.update({'analytic_distribution_id': new_distrib_id})
167
return super(account_invoice, self).copy(cr, uid, id, default, context)
169
def action_open_invoice(self, cr, uid, ids, context=None, *args):
171
Add verification on all lines for analytic_distribution_id to be present and valid !
176
if isinstance(ids, (int, long)):
178
# Browse invoice and all invoice lines to detect a non-valid line
179
self._check_analytic_distribution_state(cr, uid, ids)
180
return super(account_invoice, self).action_open_invoice(cr, uid, ids, context, args)
184
class account_invoice_line(osv.osv):
185
_name = 'account.invoice.line'
186
_inherit = 'account.invoice.line'
188
def _get_distribution_state(self, cr, uid, ids, name, args, context=None):
190
Get state of distribution:
191
- if compatible with the invoice line, then "valid"
192
- if no distribution, take a tour of invoice distribution, if compatible, then "valid"
193
- if no distribution on invoice line and invoice, then "none"
194
- all other case are "invalid"
199
if isinstance(ids, (int, long)):
201
# Prepare some values
203
# Browse all given lines
204
for line in self.browse(cr, uid, ids, context=context):
205
if line.from_yml_test:
206
res[line.id] = 'valid'
208
res[line.id] = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line.analytic_distribution_id.id, line.invoice_id.analytic_distribution_id.id, line.account_id.id)
211
def _have_analytic_distribution_from_header(self, cr, uid, ids, name, arg, context=None):
213
If invoice have an analytic distribution, return False, else return True
218
if isinstance(ids, (int, long)):
221
for inv in self.browse(cr, uid, ids, context=context):
223
if inv.analytic_distribution_id:
227
def _get_is_allocatable(self, cr, uid, ids, name, arg, context=None):
229
If expense account, then this account is allocatable.
231
if isinstance(ids, (int, long)):
234
for invl in self.browse(cr, uid, ids):
236
if invl.account_id and invl.account_id.user_type and invl.account_id.user_type.code and invl.account_id.user_type.code != 'expense':
240
def _get_distribution_state_recap(self, cr, uid, ids, name, arg, context=None):
242
Get a recap from analytic distribution state and if it come from header or not.
244
if isinstance(ids, (int, long)):
247
for invl in self.browse(cr, uid, ids):
249
if not invl.is_allocatable:
252
if invl.have_analytic_distribution_from_header:
253
from_header = _(' (from header)')
254
res[invl.id] = '%s%s' % (self.pool.get('ir.model.fields').get_browse_selection(cr, uid, invl, 'analytic_distribution_state', context), from_header)
257
def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
259
Fill the error message if the product of the line is inactive
262
for line in self.browse(cr, uid, ids, context=context):
263
res[line.id] = {'inactive_product': False,
264
'inactive_error': ''}
265
if line.invoice_id and line.invoice_id.state not in ('cancel', 'done') and line.product_id and not line.product_id.active:
267
'inactive_product': True,
268
'inactive_error': _('The product in line is inactive !')
274
'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
275
selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
276
string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
277
'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean',
278
string='Header Distrib.?'),
279
'newline': fields.boolean('New line'),
280
'is_allocatable': fields.function(_get_is_allocatable, method=True, type='boolean', string="Is allocatable?", readonly=True, store=False),
281
'analytic_distribution_state_recap': fields.function(_get_distribution_state_recap, method=True, type='char', size=30,
282
string="Distribution",
283
help="Informs you about analaytic distribution state among 'none', 'valid', 'invalid', from header or not, or no analytic distribution"),
284
'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
285
'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Comment', store=False, multi='inactive'),
289
'newline': lambda *a: True,
290
'have_analytic_distribution_from_header': lambda *a: True,
291
'is_allocatable': lambda *a: True,
292
'analytic_distribution_state_recap': lambda *a: '',
293
'inactive_product': False,
294
'inactive_error': lambda *a: '',
297
def create(self, cr, uid, vals, context=None):
298
vals.update({'newline': False,})
299
return super(account_invoice_line, self).create(cr, uid, vals, context)
301
def copy_data(self, cr, uid, id, default=None, context=None):
303
Copy global distribution and give it to new invoice line
310
# Copy analytic distribution
311
invl = self.browse(cr, uid, [id], context=context)[0]
312
if invl.analytic_distribution_id:
313
new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, invl.analytic_distribution_id.id, {}, context=context)
315
default.update({'analytic_distribution_id': new_distrib_id})
316
return super(account_invoice_line, self).copy_data(cr, uid, id, default, context)
318
account_invoice_line()
319
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: