[MERGE] : Merge with lp:~openerp-dev/openobject-addons/trunk-dev-addons2
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 import time
23 from operator import itemgetter
24 import decimal_precision as dp
25
26 import netsvc
27 from osv import fields, osv, orm
28 import pooler
29 from tools import config
30 from tools.translate import _
31
32 class account_invoice(osv.osv):
33     def _amount_all(self, cr, uid, ids, name, args, context=None):
34         res = {}
35         for invoice in self.browse(cr, uid, ids, context=context):
36             res[invoice.id] = {
37                 'amount_untaxed': 0.0,
38                 'amount_tax': 0.0,
39                 'amount_total': 0.0
40             }
41             for line in invoice.invoice_line:
42                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
43             for line in invoice.tax_line:
44                 res[invoice.id]['amount_tax'] += line.amount
45             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
46         return res
47
48     def _get_journal(self, cr, uid, context=None):
49         if context is None:
50             context = {}
51         type_inv = context.get('type', 'out_invoice')
52         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
53         company_id = context.get('company_id', user.company_id.id)
54         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
55         refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
56         journal_obj = self.pool.get('account.journal')
57         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
58                                             ('company_id', '=', company_id),
59                                             ('refund_journal', '=', refund_journal.get(type_inv, False))],
60                                                 limit=1)
61         if res:
62             return res[0]
63         else:
64             return False
65
66     def _get_currency(self, cr, uid, context=None):
67         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
68         if user.company_id:
69             return user.company_id.currency_id.id
70         else:
71             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
72
73     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
74         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
75         tt = type2journal.get(type_inv, 'sale')
76         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
77         if not result:
78             raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
79         return result[0]
80
81     def _get_type(self, cr, uid, context=None):
82         if context is None:
83             context = {}
84         return context.get('type', 'out_invoice')
85
86     def _reconciled(self, cr, uid, ids, name, args, context=None):
87         res = {}
88         for id in ids:
89             res[id] = self.test_paid(cr, uid, [id])
90         return res
91
92     def _get_reference_type(self, cr, uid, context=None):
93         return [('none', _('Free Reference'))]
94
95     def _amount_residual(self, cr, uid, ids, name, args, context=None):
96         res = {}
97         cur_obj = self.pool.get('res.currency')
98         data_inv = self.browse(cr, uid, ids)
99         if context is None:
100             context = {}
101
102         for inv in data_inv:
103             debit = credit = 0.0
104             context.update({'date':inv.date_invoice})
105             context_unreconciled=context.copy()
106             for lines in inv.move_lines:
107                 debit_tmp = lines.debit
108                 credit_tmp = lines.credit
109                 # If currency conversion needed
110                 if inv.company_id.currency_id.id <> inv.currency_id.id:
111                     # If invoice paid, compute currency amount according to invoice date
112                     # otherwise, take the line date
113                     if not inv.reconciled:
114                         context.update({'date':lines.date})
115                     context_unreconciled.update({'date':lines.date})
116                     # If amount currency setted, compute for debit and credit in company currency
117                     if lines.amount_currency < 0:
118                         credit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False, context=context_unreconciled))
119                     elif lines.amount_currency > 0:
120                         debit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False, context=context_unreconciled))
121                     # Then, recomput into invoice currency to avoid rounding trouble !
122                     debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False, context=context)
123                     credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False, context=context)
124                 else:
125                     debit+=debit_tmp
126                     credit+=credit_tmp
127
128             if not inv.amount_total:
129                 result = 0.0
130             elif inv.type in ('out_invoice','in_refund'):
131                 amount = credit-debit
132                 result = inv.amount_total - amount
133             else:
134                 amount = debit-credit
135                 result = inv.amount_total - amount
136             # Use is_zero function to avoid rounding trouble => should be fixed into ORM
137             res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id, result) and result or 0.0
138
139         return res
140
141     def _get_lines(self, cr, uid, ids, name, arg, context=None):
142         res = {}
143         for id in ids:
144             move_lines = self.move_line_id_payment_get(cr, uid, [id])
145             if not move_lines:
146                 res[id] = []
147                 continue
148             res[id] = []
149             data_lines = self.pool.get('account.move.line').browse(cr, uid, move_lines)
150             partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
151             for line in data_lines:
152                 ids_line = []
153                 if line.reconcile_id:
154                     ids_line = line.reconcile_id.line_id
155                 elif line.reconcile_partial_id:
156                     ids_line = line.reconcile_partial_id.line_partial_ids
157                 l = map(lambda x: x.id, ids_line)
158                 partial_ids.append(line.id)
159                 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
160         return res
161
162     def _get_invoice_line(self, cr, uid, ids, context=None):
163         result = {}
164         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
165             result[line.invoice_id.id] = True
166         return result.keys()
167
168     def _get_invoice_tax(self, cr, uid, ids, context=None):
169         result = {}
170         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
171             result[tax.invoice_id.id] = True
172         return result.keys()
173
174     def _compute_lines(self, cr, uid, ids, name, args, context=None):
175         result = {}
176         for invoice in self.browse(cr, uid, ids, context):
177             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
178             src = []
179             lines = []
180             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
181                 temp_lines = []#Added temp list to avoid duplicate records
182                 if m.reconcile_id:
183                     temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
184                 elif m.reconcile_partial_id:
185                     temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
186                 lines += [x for x in temp_lines if x not in lines]
187                 src.append(m.id)
188
189             lines = filter(lambda x: x not in src, lines)
190             result[invoice.id] = lines
191         return result
192
193     def _get_invoice_from_line(self, cr, uid, ids, context=None):
194         move = {}
195         for line in self.pool.get('account.move.line').browse(cr, uid, ids):
196             if line.reconcile_partial_id:
197                 for line2 in line.reconcile_partial_id.line_partial_ids:
198                     move[line2.move_id.id] = True
199             if line.reconcile_id:
200                 for line2 in line.reconcile_id.line_id:
201                     move[line2.move_id.id] = True
202         invoice_ids = []
203         if move:
204             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
205         return invoice_ids
206
207     def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
208         move = {}
209         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
210             for line in r.line_partial_ids:
211                 move[line.move_id.id] = True
212             for line in r.line_id:
213                 move[line.move_id.id] = True
214
215         invoice_ids = []
216         if move:
217             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
218         return invoice_ids
219
220     _name = "account.invoice"
221     _description = 'Invoice'
222     _order = "id desc"
223
224     _columns = {
225         'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
226         'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
227         'type': fields.selection([
228             ('out_invoice','Customer Invoice'),
229             ('in_invoice','Supplier Invoice'),
230             ('out_refund','Customer Refund'),
231             ('in_refund','Supplier Refund'),
232             ],'Type', readonly=True, select=True, change_default=True),
233
234         'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
235         'internal_number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
236         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
237         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
238             required=True, readonly=True, states={'draft':[('readonly',False)]}),
239         'comment': fields.text('Additional Information'),
240
241         'state': fields.selection([
242             ('draft','Draft'),
243             ('proforma','Pro-forma'),
244             ('proforma2','Pro-forma'),
245             ('open','Open'),
246             ('paid','Paid'),
247             ('cancel','Cancelled')
248             ],'State', select=True, readonly=True,
249             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
250             \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
251             \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
252             \n* The \'Paid\' state is set automatically when invoice is paid.\
253             \n* The \'Cancelled\' state is used when user cancel invoice.'),
254         'date_invoice': fields.date('Date Invoiced', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, help="Keep empty to use the current date"),
255         'date_due': fields.date('Due Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]},
256             help="If you use payment terms, the due date will be computed automatically at the generation "\
257                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."),
258         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
259         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
260         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
261         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
262             help="If you use payment terms, the due date will be computed automatically at the generation "\
263                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
264                 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
265         'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
266
267         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
268         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
269         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
270
271         'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, help="Link to the automatically generated Journal Items."),
272         'amount_untaxed': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Untaxed',
273             store={
274                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
275                 'account.invoice.tax': (_get_invoice_tax, None, 20),
276                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
277             },
278             multi='all'),
279         'amount_tax': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Tax',
280             store={
281                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
282                 'account.invoice.tax': (_get_invoice_tax, None, 20),
283                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
284             },
285             multi='all'),
286         'amount_total': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Total',
287             store={
288                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
289                 'account.invoice.tax': (_get_invoice_tax, None, 20),
290                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
291             },
292             multi='all'),
293         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294         'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
295         'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
296         'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
297         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
298             store={
299                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
300                 'account.move.line': (_get_invoice_from_line, None, 50),
301                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
302             }, help="The Journal Entry of the invoice have been totally reconciled with one or several Journal Entries of payment."),
303         'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
304             help='Bank Account Number, Company bank account if Invoice is customer or supplier refund, otherwise Partner bank account number.', readonly=True, states={'draft':[('readonly',False)]}),
305         'move_lines':fields.function(_get_lines , method=True, type='many2many', relation='account.move.line', string='Entry Lines'),
306         'residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual',
307             store={
308                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
309                 'account.invoice.tax': (_get_invoice_tax, None, 50),
310                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
311                 'account.move.line': (_get_invoice_from_line, None, 50),
312                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
313             },
314             help="Remaining amount due."),
315         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
316         'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
317         'user_id': fields.many2one('res.users', 'Salesman', readonly=True, states={'draft':[('readonly',False)]}),
318         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
319     }
320     _defaults = {
321         'type': _get_type,
322         'state': 'draft',
323         'journal_id': _get_journal,
324         'currency_id': _get_currency,
325         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
326         'reference_type': 'none',
327         'check_total': 0.0,
328         'internal_number': False,
329         'user_id': lambda s, cr, u, c: u,
330     }
331
332     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
333         if context.get('active_model','') in ['res.partner']:
334             partner = self.pool.get(context['active_model']).read(cr,uid,context['active_ids'],['supplier','customer'])[0]
335             if not view_type:
336                 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.tree')])[0]
337                 view_type = 'tree'
338             if view_type == 'form':
339                 if partner['supplier'] and not partner['customer']:
340                     view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.supplier.form')])[0]
341                 else:
342                     view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.form')])[0]
343         return super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
344
345     def create(self, cr, uid, vals, context=None):
346         try:
347             res = super(account_invoice, self).create(cr, uid, vals, context)
348             for inv_id, name in self.name_get(cr, uid, [res], context=context):
349                 message = _('Invoice ') + " '" + name + "' "+ _("is waiting for validation.")
350                 self.log(cr, uid, inv_id, message)
351             return res
352         except Exception, e:
353             if '"journal_id" viol' in e.args[0]:
354                 raise orm.except_orm(_('Configuration Error!'),
355                      _('There is no Accounting Journal of type Sale/Purchase defined!'))
356             else:
357                 raise orm.except_orm(_('UnknownError'), str(e))
358
359     def confirm_paid(self, cr, uid, ids, context=None):
360         self.write(cr, uid, ids, {'state':'paid'}, context=context)
361         return True
362
363     def unlink(self, cr, uid, ids, context=None):
364         invoices = self.read(cr, uid, ids, ['state'])
365         unlink_ids = []
366         for t in invoices:
367             if t['state'] in ('draft', 'cancel'):
368                 unlink_ids.append(t['id'])
369             else:
370                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
371         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
372         return True
373
374     def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
375             date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
376         invoice_addr_id = False
377         contact_addr_id = False
378         partner_payment_term = False
379         acc_id = False
380         bank_id = False
381         fiscal_position = False
382
383         opt = [('uid', str(uid))]
384         if partner_id:
385
386             opt.insert(0, ('id', partner_id))
387             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
388             contact_addr_id = res['contact']
389             invoice_addr_id = res['invoice']
390             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
391             if company_id:
392                 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
393                     property_obj = self.pool.get('ir.property')
394                     rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
395                     pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
396                     if not rec_pro_id:
397                         rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
398                     if not pay_pro_id:
399                         pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
400                     rec_line_data = property_obj.read(cr,uid,rec_pro_id,['name','value','res_id'])
401                     pay_line_data = property_obj.read(cr,uid,pay_pro_id,['name','value','res_id'])
402                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
403                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
404                     if not rec_res_id and not pay_res_id:
405                         raise osv.except_osv(_('Configuration Error !'),
406                             _('Can not find account chart for this company, Please Create account.'))
407                     account_obj = self.pool.get('account.account')
408                     rec_obj_acc = account_obj.browse(cr, uid, [rec_res_id])
409                     pay_obj_acc = account_obj.browse(cr, uid, [pay_res_id])
410                     p.property_account_receivable = rec_obj_acc[0]
411                     p.property_account_payable = pay_obj_acc[0]
412
413             if type in ('out_invoice', 'out_refund'):
414                 acc_id = p.property_account_receivable.id
415             else:
416                 acc_id = p.property_account_payable.id
417             fiscal_position = p.property_account_position and p.property_account_position.id or False
418             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
419             if p.bank_ids:
420                 bank_id = p.bank_ids[0].id
421
422         result = {'value': {
423             'address_contact_id': contact_addr_id,
424             'address_invoice_id': invoice_addr_id,
425             'account_id': acc_id,
426             'payment_term': partner_payment_term,
427             'fiscal_position': fiscal_position
428             }
429         }
430
431         if type in ('in_invoice', 'in_refund'):
432             result['value']['partner_bank_id'] = bank_id
433
434         if payment_term != partner_payment_term:
435             if partner_payment_term:
436                 to_update = self.onchange_payment_term_date_invoice(
437                     cr, uid, ids, partner_payment_term, date_invoice)
438                 result['value'].update(to_update['value'])
439             else:
440                 result['value']['date_due'] = False
441
442         if partner_bank_id != bank_id:
443             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
444             result['value'].update(to_update['value'])
445         return result
446
447     def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
448         if curr_id and company_id:
449             currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
450             if currency.company_id.id != company_id:
451                 raise osv.except_osv(_('Configuration Error !'),
452                         _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
453         return {}
454
455     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
456         if not payment_term_id:
457             return {}
458         res={}
459         pt_obj= self.pool.get('account.payment.term')
460         if not date_invoice :
461             date_invoice = time.strftime('%Y-%m-%d')
462
463         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
464
465         if pterm_list:
466             pterm_list = [line[0] for line in pterm_list]
467             pterm_list.sort()
468             res= {'value':{'date_due': pterm_list[-1]}}
469         else:
470              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
471
472         return res
473
474     def onchange_invoice_line(self, cr, uid, ids, lines):
475         return {}
476
477     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id=False):
478         return {'value': {}}
479
480     def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
481         val = {}
482         dom = {}
483         obj_journal = self.pool.get('account.journal')
484         if company_id and part_id and type:
485             acc_id = False
486             partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
487             if partner_obj.property_account_payable and partner_obj.property_account_receivable:
488                 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
489                     property_obj = self.pool.get('ir.property')
490                     rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
491                     pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
492                     if not rec_pro_id:
493                         rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
494                     if not pay_pro_id:
495                         pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
496                     rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value','res_id'])
497                     pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value','res_id'])
498                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
499                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
500                     if not rec_res_id and not pay_res_id:
501                         raise osv.except_osv(_('Configuration Error !'),
502                             _('Can not find account chart for this company, Please Create account.'))
503                     if type in ('out_invoice', 'out_refund'):
504                         acc_id = rec_res_id
505                     else:
506                         acc_id = pay_res_id
507                     val= {'account_id': acc_id}
508             account_obj = self.pool.get('account.account')
509             if ids:
510                 if company_id:
511                     inv_obj = self.browse(cr,uid,ids)
512                     for line in inv_obj[0].invoice_line:
513                         if line.account_id:
514                             if line.account_id.company_id.id != company_id:
515                                 result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
516                                 if not result_id:
517                                     raise osv.except_osv(_('Configuration Error !'),
518                                         _('Can not find account chart for this company in invoice line account, Please Create account.'))
519                                 r_id = self.pool.get('account.invoice.line').write(cr, uid, [line.id], {'account_id': result_id[0]})
520             else:
521                 if invoice_line:
522                     for inv_line in invoice_line:
523                         obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
524                         if obj_l.company_id.id != company_id:
525                             raise osv.except_osv(_('Configuration Error !'),
526                                 _('invoice line account company is not match with invoice company.'))
527                         else:
528                             continue
529         if company_id and type:
530             if type in ('out_invoice', 'out_refund'):
531                 journal_type = 'sale'
532             else:
533                 journal_type = 'purchase'
534             journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
535             if journal_ids:
536                 val['journal_id'] = journal_ids[0]
537             else:
538                 raise osv.except_osv(_('Configuration Error !'), _('Can\'t find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Financial Accounting\Accounts\Journals.' % (journal_type)))
539             dom = {'journal_id':  [('id', 'in', journal_ids)]}
540         else:
541             journal_ids = obj_journal.search(cr, uid, [])
542
543         if currency_id and company_id:
544             currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
545             if currency.company_id and currency.company_id.id != company_id:
546                 val['currency_id'] = False
547             else:
548                 val['currency_id'] = currency.id
549
550         if company_id:
551             company = self.pool.get('res.company').browse(cr, uid, company_id)
552             if company.currency_id.company_id and company.currency_id.company_id.id != company_id:
553                 val['currency_id'] = False
554             else:
555                 val['currency_id'] = company.currency_id.id
556
557         return {'value' : val, 'domain': dom }
558
559     # go from canceled state to draft state
560     def action_cancel_draft(self, cr, uid, ids, *args):
561         self.write(cr, uid, ids, {'state':'draft'})
562         wf_service = netsvc.LocalService("workflow")
563         for inv_id in ids:
564             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
565         return True
566
567     def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
568         """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
569         Hook method to be overridden in additional modules to verify and possibly alter the
570         move lines to be created by an invoice, for special cases.
571         :param invoice_browse: browsable record of the invoice that is generating the move lines
572         :param move_lines: list of dictionaries with the account.move.lines (as for create())
573         :return: the (possibly updated) final move_lines to create for this invoice
574         """
575         return move_lines
576
577     # Workflow stuff
578     #################
579
580     # return the ids of the move lines which has the same account than the invoice
581     # whose id is in ids
582     def move_line_id_payment_get(self, cr, uid, ids, *args):
583         res = []
584         if not ids: return res
585         cr.execute('SELECT l.id '\
586                    'FROM account_move_line l '\
587                    'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
588                    'WHERE i.id IN %s '\
589                    'AND l.account_id=i.account_id',
590                    (tuple(ids),))
591         res = map(itemgetter(0), cr.fetchall())
592         return res
593
594     def copy(self, cr, uid, id, default={}, context=None):
595         default.update({
596             'state':'draft',
597             'number':False,
598             'move_id':False,
599             'move_name':False,
600             'internal_number': False,
601         })
602         if 'date_invoice' not in default:
603             default.update({
604                 'date_invoice':False
605             })
606         if 'date_due' not in default:
607             default.update({
608                 'date_due':False
609             })
610         return super(account_invoice, self).copy(cr, uid, id, default, context)
611
612     def test_paid(self, cr, uid, ids, *args):
613         res = self.move_line_id_payment_get(cr, uid, ids)
614         if not res:
615             return False
616         ok = True
617         for id in res:
618             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
619             ok = ok and  bool(cr.fetchone()[0])
620         return ok
621
622     def button_reset_taxes(self, cr, uid, ids, context=None):
623         if not context:
624             context = {}
625         ait_obj = self.pool.get('account.invoice.tax')
626         for id in ids:
627             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
628             partner = self.browse(cr, uid, id, context=context).partner_id
629             if partner.lang:
630                 context.update({'lang': partner.lang})
631             for taxe in ait_obj.compute(cr, uid, id, context=context).values():
632                 ait_obj.create(cr, uid, taxe)
633          # Update the stored value (fields.function), so we write to trigger recompute
634         self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
635         return True
636
637     def button_compute(self, cr, uid, ids, context=None, set_total=False):
638         self.button_reset_taxes(cr, uid, ids, context)
639         for inv in self.browse(cr, uid, ids):
640             if set_total:
641                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
642         return True
643
644     def _convert_ref(self, cr, uid, ref):
645         return (ref or '').replace('/','')
646
647     def _get_analytic_lines(self, cr, uid, id):
648         inv = self.browse(cr, uid, [id])[0]
649         cur_obj = self.pool.get('res.currency')
650
651         company_currency = inv.company_id.currency_id.id
652         if inv.type in ('out_invoice', 'in_refund'):
653             sign = 1
654         else:
655             sign = -1
656
657         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
658         for il in iml:
659             if il['account_analytic_id']:
660                 if inv.type in ('in_invoice', 'in_refund'):
661                     ref = inv.reference
662                 else:
663                     ref = self._convert_ref(cr, uid, inv.number)
664                 il['analytic_lines'] = [(0,0, {
665                     'name': il['name'],
666                     'date': inv['date_invoice'],
667                     'account_id': il['account_analytic_id'],
668                     'unit_amount': il['quantity'],
669                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
670                     'product_id': il['product_id'],
671                     'product_uom_id': il['uos_id'],
672                     'general_account_id': il['account_id'],
673                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
674                     'ref': ref,
675                 })]
676         return iml
677
678     def action_date_assign(self, cr, uid, ids, *args):
679         for inv in self.browse(cr, uid, ids):
680             res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
681             if res and res['value']:
682                 self.write(cr, uid, [inv.id], res['value'])
683         return True
684
685     def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
686         """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
687         Hook method to be overridden in additional modules to verify and possibly alter the
688         move lines to be created by an invoice, for special cases.
689         :param invoice_browse: browsable record of the invoice that is generating the move lines
690         :param move_lines: list of dictionaries with the account.move.lines (as for create())
691         :return: the (possibly updated) final move_lines to create for this invoice
692         """
693         return move_lines
694
695     def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
696         if not inv.tax_line:
697             for tax in compute_taxes.values():
698                 ait_obj.create(cr, uid, tax)
699         else:
700             tax_key = []
701             for tax in inv.tax_line:
702                 if tax.manual:
703                     continue
704                 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
705                 tax_key.append(key)
706                 if not key in compute_taxes:
707                     raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
708                 base = compute_taxes[key]['base']
709                 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
710                     raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
711             for key in compute_taxes:
712                 if not key in tax_key:
713                     raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
714
715     def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
716         total = 0
717         total_currency = 0
718         cur_obj = self.pool.get('res.currency')
719         for i in invoice_move_lines:
720             if inv.currency_id.id != company_currency:
721                 i['currency_id'] = inv.currency_id.id
722                 i['amount_currency'] = i['price']
723                 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
724                         company_currency, i['price'],
725                         context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
726             else:
727                 i['amount_currency'] = False
728                 i['currency_id'] = False
729             i['ref'] = ref
730             if inv.type in ('out_invoice','in_refund'):
731                 total += i['price']
732                 total_currency += i['amount_currency'] or i['price']
733                 i['price'] = - i['price']
734             else:
735                 total -= i['price']
736                 total_currency -= i['amount_currency'] or i['price']
737         return total, total_currency, invoice_move_lines
738
739     def inv_line_characteristic_hashcode(self, invoice, invoice_line):
740         """Overridable hashcode generation for invoice lines. Lines having the same hashcode
741         will be grouped together if the journal has the 'group line' option. Of course a module
742         can add fields to invoice lines that would need to be tested too before merging lines
743         or not."""
744         return "%s-%s-%s-%s-%s"%(
745             invoice_line['account_id'],
746             invoice_line.get('tax_code_id',"False"),
747             invoice_line.get('product_id',"False"),
748             invoice_line.get('analytic_account_id',"False"),
749             invoice_line.get('date_maturity',"False"))
750
751     def group_lines(self, cr, uid, iml, line, inv):
752         """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
753         if inv.journal_id.group_invoice_lines:
754             line2 = {}
755             for x, y, l in line:
756                 tmp = self.inv_line_characteristic_hashcode(inv, l)
757
758                 if tmp in line2:
759                     am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
760                     line2[tmp]['debit'] = (am > 0) and am or 0.0
761                     line2[tmp]['credit'] = (am < 0) and -am or 0.0
762                     line2[tmp]['tax_amount'] += l['tax_amount']
763                     line2[tmp]['analytic_lines'] += l['analytic_lines']
764                 else:
765                     line2[tmp] = l
766             line = []
767             for key, val in line2.items():
768                 line.append((0,0,val))
769
770         return line
771
772     def action_move_create(self, cr, uid, ids, *args):
773         """Creates invoice related analytics and financial move lines"""
774         ait_obj = self.pool.get('account.invoice.tax')
775         cur_obj = self.pool.get('res.currency')
776         context = {}
777         for inv in self.browse(cr, uid, ids):
778             if not inv.journal_id.sequence_id:
779                 raise osv.except_osv(_('Error !'), _('Please define sequence on invoice journal'))
780             if not inv.invoice_line:
781                 raise osv.except_osv(_('No Invoice Lines !'), _('Please create some invoice lines.'))
782             if inv.move_id:
783                 continue
784
785             if not inv.date_invoice:
786                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
787             company_currency = inv.company_id.currency_id.id
788             # create the analytical lines
789             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
790             # one move line per invoice line
791             iml = self._get_analytic_lines(cr, uid, inv.id)
792             # check if taxes are all computed
793
794             context.update({'lang': inv.partner_id.lang})
795             compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
796             self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
797
798             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
799                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
800
801             # one move line per tax line
802             iml += ait_obj.move_line_get(cr, uid, inv.id)
803
804             entry_type=''
805             if inv.type in ('in_invoice', 'in_refund'):
806                 ref = inv.reference
807                 entry_type = 'journal_pur_voucher'
808                 if inv.type == 'in_refund':
809                     entry_type = 'cont_voucher'
810             else:
811                 ref = self._convert_ref(cr, uid, inv.number)
812                 entry_type = 'journal_sale_vou'
813                 if inv.type == 'out_refund':
814                     entry_type = 'cont_voucher'
815
816             diff_currency_p = inv.currency_id.id <> company_currency
817             # create one move line for the total and possibly adjust the other lines amount
818             total = 0
819             total_currency = 0
820             total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
821             acc_id = inv.account_id.id
822
823             name = inv['name'] or '/'
824             totlines = False
825             if inv.payment_term:
826                 totlines = self.pool.get('account.payment.term').compute(cr,
827                         uid, inv.payment_term.id, total, inv.date_invoice or False)
828             if totlines:
829                 res_amount_currency = total_currency
830                 i = 0
831                 for t in totlines:
832                     if inv.currency_id.id != company_currency:
833                         amount_currency = cur_obj.compute(cr, uid,
834                                 company_currency, inv.currency_id.id, t[1])
835                     else:
836                         amount_currency = False
837
838                     # last line add the diff
839                     res_amount_currency -= amount_currency or 0
840                     i += 1
841                     if i == len(totlines):
842                         amount_currency += res_amount_currency
843
844                     iml.append({
845                         'type': 'dest',
846                         'name': name,
847                         'price': t[1],
848                         'account_id': acc_id,
849                         'date_maturity': t[0],
850                         'amount_currency': diff_currency_p \
851                                 and  amount_currency or False,
852                         'currency_id': diff_currency_p \
853                                 and inv.currency_id.id or False,
854                         'ref': ref,
855                     })
856             else:
857                 iml.append({
858                     'type': 'dest',
859                     'name': name,
860                     'price': total,
861                     'account_id': acc_id,
862                     'date_maturity' : inv.date_due or False,
863                     'amount_currency': diff_currency_p \
864                             and total_currency or False,
865                     'currency_id': diff_currency_p \
866                             and inv.currency_id.id or False,
867                     'ref': ref
868             })
869
870             date = inv.date_invoice or time.strftime('%Y-%m-%d')
871             part = inv.partner_id.id
872
873             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
874
875             line = self.group_lines(cr, uid, iml, line, inv)
876
877             journal_id = inv.journal_id.id
878             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
879             if journal.centralisation:
880                 raise osv.except_osv(_('UserError'),
881                         _('Cannot create invoice move on centralised journal'))
882
883             line = self.finalize_invoice_move_lines(cr, uid, inv, line)
884
885             move = {
886                 'ref': inv.reference and inv.reference or inv.name,
887                 'line_id': line,
888                 'journal_id': journal_id,
889                 'date': date,
890                 'type': entry_type,
891                 'narration':inv.comment
892             }
893             period_id=inv.period_id and inv.period_id.id or False
894             if not period_id:
895                 period_ids= self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or time.strftime('%Y-%m-%d')),('date_stop','>=',inv.date_invoice or time.strftime('%Y-%m-%d'))])
896                 if len(period_ids):
897                     period_id=period_ids[0]
898             if period_id:
899                 move['period_id'] = period_id
900                 for i in line:
901                     i[2]['period_id'] = period_id
902
903             move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
904             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
905             # make the invoice point to that move
906             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
907             # Pass invoice in context in method post: used if you want to get the same
908             # account move reference when creating the same invoice after a cancelled one:
909             self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
910         self._log_event(cr, uid, ids)
911
912         return True
913
914     def line_get_convert(self, cr, uid, x, part, date, context=None):
915         return {
916             'date_maturity': x.get('date_maturity', False),
917             'partner_id':part,
918             'name':x['name'][:64],
919             'date': date,
920             'debit':x['price']>0 and x['price'],
921             'credit':x['price']<0 and -x['price'],
922             'account_id':x['account_id'],
923             'analytic_lines':x.get('analytic_lines', []),
924             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
925             'currency_id':x.get('currency_id', False),
926             'tax_code_id': x.get('tax_code_id', False),
927             'tax_amount': x.get('tax_amount', False),
928             'ref':x.get('ref',False),
929             'quantity':x.get('quantity',1.00),
930             'product_id':x.get('product_id', False),
931             'product_uom_id':x.get('uos_id',False),
932             'analytic_account_id':x.get('account_analytic_id',False),
933         }
934
935     def action_number(self, cr, uid, ids, *args):
936         #TODO: not correct fix but required a frech values before reading it.
937         self.write(cr, uid, ids, {})
938
939         for obj_inv in self.browse(cr, uid, ids):
940             id = obj_inv.id
941             invtype = obj_inv.type
942             number = obj_inv.number
943             move_id = obj_inv.move_id and obj_inv.move_id.id or False
944             reference = obj_inv.reference or ''
945
946             self.write(cr, uid, ids, {'internal_number':number})
947
948             if invtype in ('in_invoice', 'in_refund'):
949                 ref = reference
950             else:
951                 ref = self._convert_ref(cr, uid, number)
952
953             cr.execute('UPDATE account_move SET ref=%s ' \
954                     'WHERE id=%s AND (ref is null OR ref = \'\')',
955                     (ref, move_id))
956             cr.execute('UPDATE account_move_line SET ref=%s ' \
957                     'WHERE move_id=%s AND (ref is null OR ref = \'\')',
958                     (ref, move_id))
959             cr.execute('UPDATE account_analytic_line SET ref=%s ' \
960                     'FROM account_move_line ' \
961                     'WHERE account_move_line.move_id = %s ' \
962                         'AND account_analytic_line.move_id = account_move_line.id',
963                         (ref, move_id))
964
965             for inv_id, name in self.name_get(cr, uid, [id]):
966                 message = _('Invoice ') + " '" + name + "' "+ _("is validated.")
967                 self.log(cr, uid, inv_id, message)
968
969         return True
970
971     def action_cancel(self, cr, uid, ids, *args):
972         account_move_obj = self.pool.get('account.move')
973         invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
974         for i in invoices:
975             if i['move_id']:
976                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
977                 # delete the move this invoice was pointing to
978                 # Note that the corresponding move_lines and move_reconciles
979                 # will be automatically deleted too
980                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
981             if i['payment_ids']:
982                 account_move_line_obj = self.pool.get('account.move.line')
983                 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
984                 for move_line in pay_ids:
985                     if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
986                         raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
987
988         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
989         self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
990         return True
991
992     ###################
993
994     def list_distinct_taxes(self, cr, uid, ids):
995         invoices = self.browse(cr, uid, ids)
996         taxes = {}
997         for inv in invoices:
998             for tax in inv.tax_line:
999                 if not tax['name'] in taxes:
1000                     taxes[tax['name']] = {'name': tax['name']}
1001         return taxes.values()
1002
1003     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
1004         #TODO: implement messages system
1005         return True
1006
1007     def name_get(self, cr, uid, ids, context=None):
1008         if not len(ids):
1009             return []
1010         types = {
1011                 'out_invoice': 'CI: ',
1012                 'in_invoice': 'SI: ',
1013                 'out_refund': 'OR: ',
1014                 'in_refund': 'SR: ',
1015                 }
1016         return [(r['id'], types[r['type']]+(r['number'] or '')+' '+(r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
1017
1018     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1019         if not args:
1020             args=[]
1021         if context is None:
1022             context={}
1023         ids = []
1024         if name:
1025             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
1026         if not ids:
1027             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
1028         return self.name_get(cr, user, ids, context)
1029
1030     def _refund_cleanup_lines(self, cr, uid, lines):
1031         for line in lines:
1032             del line['id']
1033             del line['invoice_id']
1034             for field in ('company_id', 'partner_id', 'account_id', 'product_id',
1035                           'uos_id', 'account_analytic_id', 'tax_code_id', 'base_code_id'):
1036                 line[field] = line.get(field, False) and line[field][0]
1037             if 'invoice_line_tax_id' in line:
1038                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1039         return map(lambda x: (0,0,x), lines)
1040
1041     def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
1042         invoices = self.read(cr, uid, ids, ['name', 'type', 'number', 'reference', 'comment', 'date_due', 'partner_id', 'address_contact_id', 'address_invoice_id', 'partner_contact', 'partner_insite', 'partner_ref', 'payment_term', 'account_id', 'currency_id', 'invoice_line', 'tax_line', 'journal_id'])
1043
1044         new_ids = []
1045         for invoice in invoices:
1046             del invoice['id']
1047
1048             type_dict = {
1049                 'out_invoice': 'out_refund', # Customer Invoice
1050                 'in_invoice': 'in_refund',   # Supplier Invoice
1051                 'out_refund': 'out_invoice', # Customer Refund
1052                 'in_refund': 'in_invoice',   # Supplier Refund
1053             }
1054
1055             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
1056             invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1057
1058             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
1059             tax_lines = filter(lambda l: l['manual'], tax_lines)
1060             tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1061             if journal_id:
1062                 refund_journal_ids = [journal_id]
1063             elif invoice['type'] == 'in_invoice':
1064                 refund_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase_refund')])
1065             else:
1066                 refund_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','sale_refund')])
1067
1068             if not date :
1069                 date = time.strftime('%Y-%m-%d')
1070             invoice.update({
1071                 'type': type_dict[invoice['type']],
1072                 'date_invoice': date,
1073                 'state': 'draft',
1074                 'number': False,
1075                 'invoice_line': invoice_lines,
1076                 'tax_line': tax_lines,
1077                 'journal_id': refund_journal_ids
1078             })
1079             if period_id :
1080                 invoice.update({
1081                     'period_id': period_id,
1082                 })
1083             if description :
1084                 invoice.update({
1085                     'name': description,
1086                 })
1087             # take the id part of the tuple returned for many2one fields
1088             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1089                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
1090                 invoice[field] = invoice[field] and invoice[field][0]
1091             # create the new invoice
1092             new_ids.append(self.create(cr, uid, invoice))
1093         return new_ids
1094
1095     def pay_and_reconcile(self, cr, uid, ids, pay_amount, pay_account_id, period_id, pay_journal_id, writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context=None, name=''):
1096         if context is None:
1097             context = {}
1098         #TODO check if we can use different period for payment and the writeoff line
1099         assert len(ids)==1, "Can only pay one invoice at a time"
1100         invoice = self.browse(cr, uid, ids[0])
1101         src_account_id = invoice.account_id.id
1102         # Take the seq as name for move
1103         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1104         direction = types[invoice.type]
1105         #take the choosen date
1106         if 'date_p' in context and context['date_p']:
1107             date=context['date_p']
1108         else:
1109             date=time.strftime('%Y-%m-%d')
1110
1111         # Take the amount in currency and the currency of the payment
1112         if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1113             amount_currency = context['amount_currency']
1114             currency_id = context['currency_id']
1115         else:
1116             amount_currency = False
1117             currency_id = False
1118
1119         pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1120         if invoice.type in ('in_invoice', 'out_invoice'):
1121             if pay_journal['type'] == 'bank':
1122                 entry_type = 'bank_pay_voucher' # Bank payment
1123             else:
1124                 entry_type = 'pay_voucher' # Cash payment
1125         else:
1126             entry_type = 'cont_voucher'
1127         if invoice.type in ('in_invoice', 'in_refund'):
1128             ref = invoice.reference
1129         else:
1130             ref = self._convert_ref(cr, uid, invoice.number)
1131         # Pay attention to the sign for both debit/credit AND amount_currency
1132         l1 = {
1133             'debit': direction * pay_amount>0 and direction * pay_amount,
1134             'credit': direction * pay_amount<0 and - direction * pay_amount,
1135             'account_id': src_account_id,
1136             'partner_id': invoice.partner_id.id,
1137             'ref':ref,
1138             'date': date,
1139             'currency_id':currency_id,
1140             'amount_currency':amount_currency and direction * amount_currency or 0.0,
1141             'company_id': invoice.company_id.id,
1142         }
1143         l2 = {
1144             'debit': direction * pay_amount<0 and - direction * pay_amount,
1145             'credit': direction * pay_amount>0 and direction * pay_amount,
1146             'account_id': pay_account_id,
1147             'partner_id': invoice.partner_id.id,
1148             'ref':ref,
1149             'date': date,
1150             'currency_id':currency_id,
1151             'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1152             'company_id': invoice.company_id.id,
1153         }
1154
1155         if not name:
1156             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1157         l1['name'] = name
1158         l2['name'] = name
1159
1160         lines = [(0, 0, l1), (0, 0, l2)]
1161         move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date, 'type': entry_type}
1162         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1163
1164         line_ids = []
1165         total = 0.0
1166         line = self.pool.get('account.move.line')
1167         move_ids = [move_id,]
1168         if invoice.move_id:
1169             move_ids.append(invoice.move_id.id)
1170         cr.execute('SELECT id FROM account_move_line '\
1171                    'WHERE move_id IN %s',
1172                    ((move_id, invoice.move_id.id),))
1173         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1174         for l in lines+invoice.payment_ids:
1175             if l.account_id.id == src_account_id:
1176                 line_ids.append(l.id)
1177                 total += (l.debit or 0.0) - (l.credit or 0.0)
1178
1179         inv_id, name = self.name_get(cr, uid, [invoice.id], context=context)[0]
1180         if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1181             self.log(cr, uid, inv_id, _('Invoice ') + " '" + name + "' "+ _("is totally paid."))
1182             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1183         else:
1184             code = invoice.currency_id.code
1185             amt = str(pay_amount) + code + ' on ' + str(invoice.amount_total) + code + ' (' + str(total) + code + ' remaining)'
1186             self.log(cr, uid, inv_id, _('Invoice ') + " '" + name + "' "+ _("is paid partially: ") + amt)
1187             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1188
1189         # Update the stored value (fields.function), so we write to trigger recompute
1190         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1191         return True
1192 account_invoice()
1193
1194 class account_invoice_line(osv.osv):
1195     def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1196         res = {}
1197         tax_obj = self.pool.get('account.tax')
1198         cur_obj = self.pool.get('res.currency')
1199         for line in self.browse(cr, uid, ids):
1200             price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1201             taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity)
1202             res[line.id] = taxes['total']
1203             if line.invoice_id:
1204                 cur = line.invoice_id.currency_id
1205                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1206         return res
1207
1208     def _price_unit_default(self, cr, uid, context=None):
1209         if context is None:
1210             context = {}
1211         if 'check_total' in context:
1212             t = context['check_total']
1213             for l in context.get('invoice_line', {}):
1214                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1215                     tax_obj = self.pool.get('account.tax')
1216                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1217                     t = t - (p * l[2].get('quantity'))
1218                     taxes = l[2].get('invoice_line_tax_id')
1219                     if len(taxes[0]) >= 3 and taxes[0][2]:
1220                         taxes = tax_obj.browse(cr, uid, taxes[0][2])
1221                         for tax in tax_obj.compute_all(cr, uid, taxes, p,l[2].get('quantity'), context.get('address_invoice_id', False), l[2].get('product_id', False), context.get('partner_id', False))['taxes']:
1222                             t = t - tax['amount']
1223             return t
1224         return 0
1225
1226     _name = "account.invoice.line"
1227     _description = "Invoice Line"
1228     _columns = {
1229         'name': fields.char('Description', size=256, required=True),
1230         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1231         'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1232         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1233         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1234         'account_id': fields.many2one('account.account', 'Account', required=True, domain=[('type','<>','view'), ('type', '<>', 'closed')], help="The income or expense account related to the selected product."),
1235         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1236         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', type="float",
1237             digits_compute= dp.get_precision('Account'), store=True),
1238         'quantity': fields.float('Quantity', required=True),
1239         'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1240         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1241         'note': fields.text('Notes'),
1242         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1243         'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1244         'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1245     }
1246     _defaults = {
1247         'quantity': 1,
1248         'discount': 0.0,
1249         'price_unit': _price_unit_default,
1250     }
1251
1252     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1253         tax_obj = self.pool.get('account.tax')
1254         if price_unit:
1255             taxes = tax_obj.browse(cr, uid, tax_id)
1256             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1257                 price_unit = price_unit - tax['amount']
1258         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1259
1260     def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, currency_id=False, context=None):
1261         if context is None:
1262             context = {}
1263         company_id = context.get('company_id',False)
1264         if not partner_id:
1265             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1266         if not product:
1267             if type in ('in_invoice', 'in_refund'):
1268                 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1269             else:
1270                 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1271         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1272         fpos_obj = self.pool.get('account.fiscal.position')
1273         fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id) or False
1274
1275         if part.lang:
1276             context.update({'lang': part.lang})
1277         result = {}
1278         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1279
1280         if company_id:
1281             property_obj = self.pool.get('ir.property')
1282             account_obj = self.pool.get('account.account')
1283             in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1284             if not in_pro_id:
1285                 in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1286             exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1287             if not exp_pro_id:
1288                 exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1289
1290             if not in_pro_id:
1291                 in_acc = res.product_tmpl_id.property_account_income
1292                 in_acc_cate = res.categ_id.property_account_income_categ
1293                 if in_acc:
1294                     app_acc_in = in_acc
1295                 else:
1296                     app_acc_in = in_acc_cate
1297             else:
1298                 app_acc_in = account_obj.browse(cr, uid, in_pro_id)[0]
1299             if not exp_pro_id:
1300                 ex_acc = res.product_tmpl_id.property_account_expense
1301                 ex_acc_cate = res.categ_id.property_account_expense_categ
1302                 if ex_acc:
1303                     app_acc_exp = ex_acc
1304                 else:
1305                     app_acc_exp = ex_acc_cate
1306             else:
1307                 app_acc_exp = account_obj.browse(cr, uid, exp_pro_id)[0]
1308             if not in_pro_id and not exp_pro_id:
1309                 in_acc = res.product_tmpl_id.property_account_income
1310                 in_acc_cate = res.categ_id.property_account_income_categ
1311                 ex_acc = res.product_tmpl_id.property_account_expense
1312                 ex_acc_cate = res.categ_id.property_account_expense_categ
1313                 if in_acc or ex_acc:
1314                     app_acc_in = in_acc
1315                     app_acc_exp = ex_acc
1316                 else:
1317                     app_acc_in = in_acc_cate
1318                     app_acc_exp = ex_acc_cate
1319             if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1320                 in_res_id = account_obj.search(cr, uid, [('name','=',app_acc_in.name),('company_id','=',company_id)])
1321                 exp_res_id = account_obj.search(cr, uid, [('name','=',app_acc_exp.name),('company_id','=',company_id)])
1322                 if not in_res_id and not exp_res_id:
1323                     raise osv.except_osv(_('Configuration Error !'),
1324                         _('Can not find account chart for this company, Please Create account.'))
1325                 in_obj_acc = account_obj.browse(cr, uid, in_res_id)
1326                 exp_obj_acc = account_obj.browse(cr, uid, exp_res_id)
1327                 if in_acc or ex_acc:
1328                     res.product_tmpl_id.property_account_income = in_obj_acc[0]
1329                     res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1330                 else:
1331                     res.categ_id.property_account_income_categ = in_obj_acc[0]
1332                     res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1333
1334         if type in ('out_invoice','out_refund'):
1335             a = res.product_tmpl_id.property_account_income.id
1336             if not a:
1337                 a = res.categ_id.property_account_income_categ.id
1338         else:
1339             a = res.product_tmpl_id.property_account_expense.id
1340             if not a:
1341                 a = res.categ_id.property_account_expense_categ.id
1342
1343         a = fpos_obj.map_account(cr, uid, fpos, a)
1344         if a:
1345             result['account_id'] = a
1346
1347         taxep = None
1348         tax_obj = self.pool.get('account.tax')
1349         if type in ('out_invoice', 'out_refund'):
1350             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1351             tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1352         else:
1353             taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1354             tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1355         if type in ('in_invoice', 'in_refund'):
1356             to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit or res.standard_price, qty, address_invoice_id, product, partner_id, context=context)
1357             result.update(to_update)
1358         else:
1359             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1360
1361 #        if not name:
1362         result['name'] = res.partner_ref
1363
1364         domain = {}
1365         result['uos_id'] = res.uom_id.id or uom or False
1366         if result['uos_id']:
1367             res2 = res.uom_id.category_id.id
1368             if res2 :
1369                 domain = {'uos_id':[('category_id','=',res2 )]}
1370
1371         result['categ_id'] = res.categ_id.id
1372         res_final = {'value':result, 'domain':domain}
1373
1374         if not company_id or not currency_id:
1375             return res_final
1376
1377         company = self.pool.get('res.company').browse(cr, uid, company_id)
1378         currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1379
1380         if company.currency_id.id != currency.id:
1381             new_price = res_final['value']['price_unit'] * currency.rate
1382             res_final['value']['price_unit'] = new_price
1383
1384         if uom:
1385             uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1386             if res.uom_id.category_id.id == uom.category_id.id:
1387                 new_price = res_final['value']['price_unit'] * uom.factor_inv
1388                 res_final['value']['price_unit'] = new_price
1389         return res_final
1390
1391     def uos_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, currency_id=False, context=None):
1392         res = self.product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, currency_id, context)
1393         if 'uos_id' in res['value']:
1394             del res['value']['uos_id']
1395         if not uom:
1396             res['value']['price_unit'] = 0.0
1397         return res
1398
1399     def move_line_get(self, cr, uid, invoice_id, context=None):
1400         res = []
1401         tax_grouped = {}
1402         tax_obj = self.pool.get('account.tax')
1403         cur_obj = self.pool.get('res.currency')
1404         ait_obj = self.pool.get('account.invoice.tax')
1405         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1406         company_currency = inv.company_id.currency_id.id
1407         cur = inv.currency_id
1408
1409         for line in inv.invoice_line:
1410             mres = self.move_line_get_item(cr, uid, line, context)
1411             if not mres:
1412                 continue
1413             res.append(mres)
1414             tax_code_found= False
1415             for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1416                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1417                     line.quantity, inv.address_invoice_id.id, line.product_id,
1418                     inv.partner_id)['taxes']:
1419
1420                 if inv.type in ('out_invoice', 'in_invoice'):
1421                     tax_code_id = tax['base_code_id']
1422                     tax_amount = line.price_subtotal * tax['base_sign']
1423                 else:
1424                     tax_code_id = tax['ref_base_code_id']
1425                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1426
1427                 if tax_code_found:
1428                     if not tax_code_id:
1429                         continue
1430                     res.append(self.move_line_get_item(cr, uid, line, context))
1431                     res[-1]['price'] = 0.0
1432                     res[-1]['account_analytic_id'] = False
1433                 elif not tax_code_id:
1434                     continue
1435                 tax_code_found = True
1436
1437                 res[-1]['tax_code_id'] = tax_code_id
1438                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1439         return res
1440
1441     def move_line_get_item(self, cr, uid, line, context=None):
1442         return {
1443             'type':'src',
1444             'name': line.name[:64],
1445             'price_unit':line.price_unit,
1446             'quantity':line.quantity,
1447             'price':line.price_subtotal,
1448             'account_id':line.account_id.id,
1449             'product_id':line.product_id.id,
1450             'uos_id':line.uos_id.id,
1451             'account_analytic_id':line.account_analytic_id.id,
1452             'taxes':line.invoice_line_tax_id,
1453         }
1454     #
1455     # Set the tax field according to the account and the fiscal position
1456     #
1457     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1458         if not account_id:
1459             return {}
1460         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1461         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1462         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1463         r = {'value':{'invoice_line_tax_id': res}}
1464         return r
1465 account_invoice_line()
1466
1467 class account_invoice_tax(osv.osv):
1468     _name = "account.invoice.tax"
1469     _description = "Invoice Tax"
1470     _columns = {
1471         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1472         'name': fields.char('Tax Description', size=64, required=True),
1473         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1474         'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1475         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1476         'manual': fields.boolean('Manual'),
1477         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1478
1479         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1480         'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1481         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1482         'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1483         'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True),
1484     }
1485
1486     def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1487         cur_obj = self.pool.get('res.currency')
1488         company_obj = self.pool.get('res.company')
1489         company_currency=False
1490         if company_id:
1491             company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1492         if currency_id and company_currency:
1493             base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1494         return {'value': {'base_amount':base}}
1495
1496     def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1497         cur_obj = self.pool.get('res.currency')
1498         company_obj = self.pool.get('res.company')
1499         company_currency = False
1500         tax_amount = self.read(cr, uid, ids[0], ['tax_amount'])['tax_amount']
1501         tax_sign=1
1502         if tax_amount < 0:
1503             tax_sign = -1
1504         elif tax_amount == 0:
1505             tax_sign = 0
1506         if company_id:
1507             company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1508         if currency_id and company_currency:
1509             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount*tax_sign, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1510         return {'value': {'tax_amount': amount}}
1511
1512     _order = 'sequence'
1513     _defaults = {
1514         'manual': lambda *a: 1,
1515         'base_amount': lambda *a: 0.0,
1516         'tax_amount': lambda *a: 0.0,
1517     }
1518     def compute(self, cr, uid, invoice_id, context={}):
1519         tax_grouped = {}
1520         tax_obj = self.pool.get('account.tax')
1521         cur_obj = self.pool.get('res.currency')
1522         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1523         cur = inv.currency_id
1524         company_currency = inv.company_id.currency_id.id
1525
1526         for line in inv.invoice_line:
1527             for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, inv.address_invoice_id.id, line.product_id, inv.partner_id)['taxes']:
1528                 val={}
1529                 val['invoice_id'] = inv.id
1530                 val['name'] = tax['name']
1531                 val['amount'] = tax['amount']
1532                 val['manual'] = False
1533                 val['sequence'] = tax['sequence']
1534                 val['base'] = tax['price_unit'] * line['quantity']
1535
1536                 if inv.type in ('out_invoice','in_invoice'):
1537                     val['base_code_id'] = tax['base_code_id']
1538                     val['tax_code_id'] = tax['tax_code_id']
1539                     val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1540                     val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1541                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1542                 else:
1543                     val['base_code_id'] = tax['ref_base_code_id']
1544                     val['tax_code_id'] = tax['ref_tax_code_id']
1545                     val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1546                     val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1547                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1548
1549                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1550                 if not key in tax_grouped:
1551                     tax_grouped[key] = val
1552                 else:
1553                     tax_grouped[key]['amount'] += val['amount']
1554                     tax_grouped[key]['base'] += val['base']
1555                     tax_grouped[key]['base_amount'] += val['base_amount']
1556                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1557
1558         for t in tax_grouped.values():
1559             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1560             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1561             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1562         return tax_grouped
1563
1564     def move_line_get(self, cr, uid, invoice_id):
1565         res = []
1566         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1567         for t in cr.dictfetchall():
1568             if not t['amount'] \
1569                     and not t['tax_code_id'] \
1570                     and not t['tax_amount']:
1571                 continue
1572             res.append({
1573                 'type':'tax',
1574                 'name':t['name'],
1575                 'price_unit': t['amount'],
1576                 'quantity': 1,
1577                 'price': t['amount'] or 0.0,
1578                 'account_id': t['account_id'],
1579                 'tax_code_id': t['tax_code_id'],
1580                 'tax_amount': t['tax_amount']
1581             })
1582         return res
1583 account_invoice_tax()
1584
1585
1586 class res_partner(osv.osv):
1587     """ Inherits partner and adds invoice information in the partner form """
1588     _inherit = 'res.partner'
1589     _columns = {
1590         'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1591     }
1592
1593 res_partner()
1594
1595 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: