bugfixes:
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv
26 import ir
27 import pooler
28 import mx.DateTime
29 from mx.DateTime import RelativeDateTime
30 from tools import config
31 from tools.translate import _
32
33 class account_invoice(osv.osv):
34     def _amount_all(self, cr, uid, ids, name, args, context={}):
35         res = {}
36         for invoice in self.browse(cr,uid,ids, context=context):
37             res[invoice.id] = {
38                 'amount_untaxed': 0.0,
39                 'amount_tax': 0.0,
40                 'amount_total': 0.0
41             }
42             for line in invoice.invoice_line:
43                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
44             for line in invoice.tax_line:
45                 res[invoice.id]['amount_tax'] += line.amount
46             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
47         return res
48
49     def _get_journal(self, cr, uid, context):
50         type_inv = context.get('type', 'out_invoice')
51         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
52         journal_obj = self.pool.get('account.journal')
53         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
54         if res:
55             return res[0]
56         else:
57             return False
58
59     def _get_currency(self, cr, uid, context):
60         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
61         if user.company_id:
62             return user.company_id.currency_id.id
63         else:
64             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
65
66     def _get_journal_analytic(self, cr, uid, type_inv, context={}):
67         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
68         tt = type2journal.get(type_inv, 'sale')
69         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
70         if not result:
71             raise osv.except_osv(_('No Analytic Journal !'),("You have to define an analytic journal of type '%s' !") % (tt,))
72         return result[0]
73
74     def _get_type(self, cr, uid, context={}):
75         type = context.get('type', 'out_invoice')
76         return type
77
78     def _reconciled(self, cr, uid, ids, name, args, context):
79         res = {}
80         for id in ids:
81             res[id] = self.test_paid(cr, uid, [id])
82         return res
83
84     def _get_reference_type(self, cr, uid, context=None):
85         return [('none', _('Free Reference'))]
86
87     def _amount_residual(self, cr, uid, ids, name, args, context={}):
88         res = {}
89         data_inv = self.browse(cr, uid, ids)
90         for inv in data_inv:
91             paid_amt = 0.0
92             to_pay = inv.amount_total
93             for lines in inv.move_lines:
94                 paid_amt = paid_amt + lines.credit
95             res[inv.id] = to_pay - paid_amt
96         return res
97
98     def _get_lines(self, cr, uid, ids, name, arg, context=None):
99         res = {}
100         for id in ids:
101             move_lines = self.move_line_id_payment_get(cr,uid,[id])
102             if not move_lines:
103                 res[id] = []
104                 continue
105             data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
106             for line in data_lines:
107                 ids_line = []
108                 if line.reconcile_id:
109                     ids_line = line.reconcile_id.line_id
110                 elif line.reconcile_partial_id:
111                     ids_line = line.reconcile_partial_id.line_partial_ids
112                 l = map(lambda x: x.id, ids_line)
113                 res[id]=[x for x in l if x <> line.id]
114         return res
115
116     def _get_invoice_line(self, cr, uid, ids, context={}):
117         result = {}
118         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
119             result[line.invoice_id.id] = True
120         return result.keys()
121
122     def _get_invoice_tax(self, cr, uid, ids, context={}):
123         result = {}
124         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
125             result[tax.invoice_id.id] = True
126         return result.keys()
127
128     def _compute_lines(self, cr, uid, ids, name, args, context={}):
129         result = {}
130         for invoice in self.browse(cr, uid, ids, context):
131             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
132             src = []
133             lines = []
134             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
135                 if m.reconcile_id:
136                     lines += map(lambda x: x.id, m.reconcile_id.line_id)
137                 elif m.reconcile_partial_id:
138                     lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
139                 src.append(m.id)
140             lines = filter(lambda x: x not in src, lines)
141             result[invoice.id] = lines
142         return result
143
144     _name = "account.invoice"
145     _description = 'Invoice'
146     _order = "number"
147     _columns = {
148         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
149         'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
150         'type': fields.selection([
151             ('out_invoice','Customer Invoice'),
152             ('in_invoice','Supplier Invoice'),
153             ('out_refund','Customer Refund'),
154             ('in_refund','Supplier Refund'),
155             ],'Type', readonly=True, select=True),
156
157         'number': fields.char('Invoice Number', size=32, readonly=True, help="Uniq number of the invoice, computed automatically when the invoice is created."),
158         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
159         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
160             required=True),
161         'comment': fields.text('Additionnal Information'),
162
163         'state': fields.selection([
164             ('draft','Draft'),
165             ('proforma','Pro-forma'),
166             ('proforma2','Pro-forma'),
167             ('open','Open'),
168             ('paid','Done'),
169             ('cancel','Canceled')
170         ],'State', select=True, readonly=True),
171
172         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
173         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
174
175         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
176         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
177         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
178
179         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]} ),
180
181         'period_id': fields.many2one('account.period', 'Force Period', help="Keep empty to use the period of the validation date."),
182
183         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
184         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
185         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
186
187         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
188         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
189             store={
190                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
191                 'account.invoice.tax': (_get_invoice_tax, None, 20),
192                 'account.invoice.line': (_get_invoice_line, None, 20),
193             },
194             multi='all'),
195         'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
196             store={
197                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
198                 'account.invoice.tax': (_get_invoice_tax, None, 20),
199                 'account.invoice.line': (_get_invoice_line, None, 20),
200             },
201             multi='all'),
202         'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
203             store={
204                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
205                 'account.invoice.tax': (_get_invoice_tax, None, 20),
206                 'account.invoice.line': (_get_invoice_line, None, 20),
207             },
208             multi='all'),
209         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
210         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
211         'company_id': fields.many2one('res.company', 'Company', required=True),
212         'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
213         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
214             store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
215         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
216             help='The bank account to pay to or to be paid from'),
217         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
218         'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
219             store={
220                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
221                 'account.invoice.tax': (_get_invoice_tax, None, 50),
222                 'account.invoice.line': (_get_invoice_line, None, 50),
223             },
224             help="Remaining amount due."),
225         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
226         'move_name': fields.char('Account Move', size=64),
227     }
228     _defaults = {
229         'type': _get_type,
230         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
231         'state': lambda *a: 'draft',
232         'journal_id': _get_journal,
233         'currency_id': _get_currency,
234         'company_id': lambda self, cr, uid, context: \
235                 self.pool.get('res.users').browse(cr, uid, uid,
236                     context=context).company_id.id,
237         'reference_type': lambda *a: 'none',
238     }
239
240     def unlink(self, cr, uid, ids):
241         invoices = self.read(cr, uid, ids, ['state'])
242         unlink_ids = []
243         for t in invoices:
244             if t['state'] in ('draft', 'cancel'):
245                 unlink_ids.append(t['id'])
246             else:
247                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
248         osv.osv.unlink(self, cr, uid, unlink_ids)
249         return True
250
251 #   def get_invoice_address(self, cr, uid, ids):
252 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
253 #       return [{}]
254
255     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
256             date_invoice=False, payment_term=False, partner_bank_id=False):
257         invoice_addr_id = False
258         contact_addr_id = False
259         partner_payment_term = False
260         acc_id = False
261         bank_id = False
262
263         opt = [('uid', str(uid))]
264         if partner_id:
265
266             opt.insert(0, ('id', partner_id))
267             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
268             contact_addr_id = res['contact']
269             invoice_addr_id = res['invoice']
270             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
271             if type in ('out_invoice', 'out_refund'):
272                 acc_id = p.property_account_receivable.id
273             else:
274                 acc_id = p.property_account_payable.id
275
276             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
277             if p.bank_ids:
278                 bank_id = p.bank_ids[0].id
279
280         result = {'value': {
281             'address_contact_id': contact_addr_id,
282             'address_invoice_id': invoice_addr_id,
283             'account_id': acc_id,
284             'payment_term': partner_payment_term,
285             }
286         }
287
288         if type in ('in_invoice', 'in_refund'):
289             result['value']['partner_bank'] = bank_id
290
291         if payment_term != partner_payment_term:
292             if partner_payment_term:
293                 to_update = self.onchange_payment_term_date_invoice(
294                     cr,uid,ids,partner_payment_term,date_invoice)
295                 result['value'].update(to_update['value'])
296             else:
297                 result['value']['date_due'] = False
298
299         if partner_bank_id != bank_id:
300             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
301             result['value'].update(to_update['value'])
302         return result
303
304     def onchange_currency_id(self, cr, uid, ids, curr_id):
305         return {}
306
307     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
308         if not payment_term_id:
309             return {}
310         res={}
311         pt_obj= self.pool.get('account.payment.term')
312
313         if not date_invoice :
314             date_invoice = time.strftime('%Y-%m-%d')
315
316         pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
317
318         if pterm_list:
319             pterm_list = [line[0] for line in pterm_list]
320             pterm_list.sort()
321             res= {'value':{'date_due': pterm_list[-1]}}
322
323         return res
324
325     def onchange_invoice_line(self, cr, uid, ids, lines):
326         return {}
327
328     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
329         return {'value': {}}
330
331     # go from canceled state to draft state
332     def action_cancel_draft(self, cr, uid, ids, *args):
333         self.write(cr, uid, ids, {'state':'draft'})
334         wf_service = netsvc.LocalService("workflow")
335         for inv_id in ids:
336             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
337         return True
338
339     # Workflow stuff
340     #################
341
342     # return the ids of the move lines which has the same account than the invoice
343     # whose id is in ids
344     def move_line_id_payment_get(self, cr, uid, ids, *args):
345         ml = self.pool.get('account.move.line')
346         res = []
347         for inv in self.read(cr, uid, ids, ['move_id','account_id']):
348             if inv['move_id']:
349                 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
350                 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
351                     if line['account_id']==inv['account_id']:
352                         res.append(line['id'])
353         return res
354
355     def copy(self, cr, uid, id, default=None, context=None):
356         if default is None:
357             default = {}
358         default = default.copy()
359         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
360         if 'date_invoice' not in default:
361             default['date_invoice'] = False
362         if 'date_due' not in default:
363             default['date_due'] = False
364         return super(account_invoice, self).copy(cr, uid, id, default, context)
365
366     def test_paid(self, cr, uid, ids, *args):
367         res = self.move_line_id_payment_get(cr, uid, ids)
368         if not res:
369             return False
370         ok = True
371         for id in res:
372             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
373             ok = ok and  bool(cr.fetchone()[0])
374         return ok
375
376     def button_reset_taxes(self, cr, uid, ids, context={}):
377         ait_obj = self.pool.get('account.invoice.tax')
378         for id in ids:
379             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
380             for taxe in ait_obj.compute(cr, uid, id).values():
381                 ait_obj.create(cr, uid, taxe)
382         return True
383
384     def button_compute(self, cr, uid, ids, context={}, set_total=False):
385         ait_obj = self.pool.get('account.invoice.tax')
386         cur_obj = self.pool.get('res.currency')
387         for inv in self.browse(cr, uid, ids):
388             company_currency = inv.company_id.currency_id.id
389             compute_taxes = ait_obj.compute(cr, uid, inv.id)
390             if not inv.tax_line:
391                 for tax in compute_taxes.values():
392                     ait_obj.create(cr, uid, tax)
393             else:
394                 tax_key = []
395                 for tax in inv.tax_line:
396                     if tax.manual:
397                         continue
398                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
399                     tax_key.append(key)
400                     if not key in compute_taxes:
401                         ait_obj.unlink(cr, uid, [tax.id])
402                         continue
403                     compute_taxes[key]['base'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
404                     if abs(compute_taxes[key]['base'] - tax.base) > inv.company_id.currency_id.rounding:
405                         ait_obj.write(cr, uid, [tax.id], compute_taxes[key])
406                 for key in compute_taxes:
407                     if not key in tax_key:
408                         ait_obj.create(cr, uid, compute_taxes[key])
409             if set_total:
410                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
411         return True
412
413     def _convert_ref(self, cr, uid, ref):
414         return (ref or '').replace('/','')
415
416     def _get_analytic_lines(self, cr, uid, id):
417         inv = self.browse(cr, uid, [id])[0]
418         cur_obj = self.pool.get('res.currency')
419
420         company_currency = inv.company_id.currency_id.id
421         if inv.type in ('out_invoice', 'in_refund'):
422             sign = 1
423         else:
424             sign = -1
425
426         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
427         for il in iml:
428             if il['account_analytic_id']:
429                 if inv.type in ('in_invoice', 'in_refund'):
430                     ref = inv.reference
431                 else:
432                     ref = self._convert_ref(cr, uid, inv.number)
433                 il['analytic_lines'] = [(0,0, {
434                     'name': il['name'],
435                     'date': inv['date_invoice'],
436                     'account_id': il['account_analytic_id'],
437                     'unit_amount': il['quantity'],
438                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
439                     'product_id': il['product_id'],
440                     'product_uom_id': il['uos_id'],
441                     'general_account_id': il['account_id'],
442                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
443                     'ref': ref,
444                 })]
445         return iml
446
447     def action_move_create(self, cr, uid, ids, *args):
448         ait_obj = self.pool.get('account.invoice.tax')
449         cur_obj = self.pool.get('res.currency')
450         acc_obj = self.pool.get('account.account')
451         for inv in self.browse(cr, uid, ids):
452             if inv.move_id:
453                 continue
454             self.button_compute(cr, uid, ids, context={},set_total=True)
455             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
456                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
457             if not inv.date_invoice:
458                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
459             company_currency = inv.company_id.currency_id.id
460             # create the analytical lines
461             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
462             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
463             # one move line per invoice line
464             iml = self._get_analytic_lines(cr, uid, inv.id)
465             # check if taxes are all computed
466             compute_taxes = ait_obj.compute(cr, uid, inv.id)
467             if not inv.tax_line:
468                 for tax in compute_taxes.values():
469                     ait_obj.create(cr, uid, tax)
470             else:
471                 tax_key = []
472                 for tax in inv.tax_line:
473                     if tax.manual:
474                         continue
475                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
476                     tax_key.append(key)
477                     if not key in compute_taxes:
478                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
479                     base = compute_taxes[key]['base']
480                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
481                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
482                 for key in compute_taxes:
483                     if not key in tax_key:
484                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
485
486             # one move line per tax line
487             iml += ait_obj.move_line_get(cr, uid, inv.id)
488
489             if inv.type in ('in_invoice', 'in_refund'):
490                 ref = inv.reference
491             else:
492                 ref = self._convert_ref(cr, uid, inv.number)
493
494             diff_currency_p = inv.currency_id.id <> company_currency
495             # create one move line for the total and possibly adjust the other lines amount
496             total = 0
497             total_currency = 0
498             key_line=[]
499             for i in iml:
500                 if i.has_key('account_id') and i.has_key('taxes'):
501                     if not (i['account_id'],i['taxes']) in key_line:
502                         key_line.append((i['account_id'],i['taxes']))
503                 if inv.currency_id.id != company_currency:
504                     i['currency_id'] = inv.currency_id.id
505                     i['amount_currency'] = i['price']
506                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
507                             company_currency, i['price'],
508                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
509                 else:
510                     i['amount_currency'] = False
511                     i['currency_id'] = False
512                 i['ref'] = ref
513                 if inv.type in ('out_invoice','in_refund'):
514                     total += i['price']
515                     total_currency += i['amount_currency'] or i['price']
516                     i['price'] = - i['price']
517                 else:
518                     total -= i['price']
519                     total_currency -= i['amount_currency'] or i['price']
520             acc_id = inv.account_id.id
521
522             name = inv['name'] or '/'
523             iml_temp=[]
524             move_list=[]
525
526             for item in key_line:
527                 move_temp={}
528                 if acc_obj.browse(cr,uid,item[0]).merge_invoice:
529                     repeat=False
530                     for move_line in iml:
531                         if (move_line.has_key('account_id') and move_line['account_id']==item[0]) and (move_line.has_key('taxes') and move_line['taxes']==item[1]):
532                             move_list.append(move_line)
533                             if repeat:
534                                 for key in move_line:
535                                     if key in ['name','amount_currency','price_unit','price','quantity']:
536                                         if key=='name':
537                                             move_temp[key]=move_temp[key] + "," +move_line[key]
538                                         else:
539                                             move_temp[key] +=move_line[key]
540                             else:
541                                 for key in move_line:
542                                     move_temp[key]=move_line[key]
543                                 repeat=True
544                 if move_temp:
545                     iml_temp.append(move_temp)
546
547             if len(iml_temp)<len(move_list):
548                 for old_elem in move_list:
549                     iml.remove(old_elem)
550                 for new_elem in iml_temp:
551                     iml.append(new_elem)
552
553             totlines = False
554             if inv.payment_term:
555                 totlines = self.pool.get('account.payment.term').compute(cr,
556                         uid, inv.payment_term.id, total, inv.date_invoice or False)
557             if totlines:
558                 res_amount_currency = total_currency
559                 i = 0
560                 for t in totlines:
561                     if inv.currency_id.id != company_currency:
562                         amount_currency = cur_obj.compute(cr, uid,
563                                 company_currency, inv.currency_id.id, t[1])
564                     else:
565                         amount_currency = False
566
567                     # last line add the diff
568                     res_amount_currency -= amount_currency or 0
569                     i += 1
570                     if i == len(totlines):
571                         amount_currency += res_amount_currency
572
573                     iml.append({
574                         'type': 'dest',
575                         'name': name,
576                         'price': t[1],
577                         'account_id': acc_id,
578                         'date_maturity': t[0],
579                         'amount_currency': diff_currency_p \
580                                 and  amount_currency or False,
581                         'currency_id': diff_currency_p \
582                                 and inv.currency_id.id or False,
583                         'ref': ref,
584                     })
585             else:
586                 iml.append({
587                     'type': 'dest',
588                     'name': name,
589                     'price': total,
590                     'account_id': acc_id,
591                     'date_maturity' : inv.date_due or False,
592                     'amount_currency': diff_currency_p \
593                             and total_currency or False,
594                     'currency_id': diff_currency_p \
595                             and inv.currency_id.id or False,
596                     'ref': ref
597             })
598
599             date = inv.date_invoice or time.strftime('%Y-%m-%d')
600             part = inv.partner_id.id
601             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
602
603             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
604             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
605             if journal.centralisation:
606                 raise osv.except_osv(_('UserError'),
607                         _('Can not create invoice move on centralized journal'))
608
609             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
610             period_id=inv.period_id and inv.period_id.id or False
611             if not period_id:
612                 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'))])
613                 if len(period_ids):
614                     period_id=period_ids[0]
615             if period_id:
616                 move['period_id'] = period_id
617                 for i in line:
618                     i[2]['period_id'] = period_id
619
620             if not 'name' in move:
621                 move['name'] = inv.name
622
623             move_id = self.pool.get('account.move').create(cr, uid, move)
624             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
625             # make the invoice point to that move
626             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
627             self.pool.get('account.move').post(cr, uid, [move_id])
628         self._log_event(cr, uid, ids)
629         return True
630
631     def line_get_convert(self, cr, uid, x, part, date, context={}):
632         return {
633             'date_maturity': x.get('date_maturity', False),
634             'partner_id':part,
635             'name':x['name'][:64],
636             'debit':x['price']>0 and x['price'],
637             'credit':x['price']<0 and -x['price'],
638             'account_id':x['account_id'],
639             'analytic_lines':x.get('analytic_lines', []),
640             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
641             'currency_id':x.get('currency_id', False),
642             'tax_code_id': x.get('tax_code_id', False),
643             'tax_amount': x.get('tax_amount', False),
644             'ref':x.get('ref',False),
645             'quantity':x.get('quantity',1.00),
646             'product_id':x.get('product_id', False),
647             'product_uom_id':x.get('uos_id',False),
648             'analytic_account_id':x.get('account_analytic_id',False),
649         }
650
651     def action_number(self, cr, uid, ids, *args):
652         cr.execute('SELECT id, type, number, move_id, reference ' \
653                 'FROM account_invoice ' \
654                 'WHERE id IN ('+','.join(map(str,ids))+')')
655         for (id, invtype, number, move_id, reference) in cr.fetchall():
656             if not number:
657                 number = self.pool.get('ir.sequence').get(cr, uid,
658                         'account.invoice.' + invtype)
659                 if invtype in ('in_invoice', 'in_refund'):
660                     ref = reference
661                 else:
662                     ref = self._convert_ref(cr, uid, number)
663                 cr.execute('UPDATE account_invoice SET number=%s ' \
664                         'WHERE id=%s', (number, id))
665                 cr.execute('UPDATE account_move SET ref=%s ' \
666                         'WHERE id=%s AND (ref is null OR ref = \'\')',
667                         (ref, move_id))
668                 cr.execute('UPDATE account_move_line SET ref=%s ' \
669                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
670                         (ref, move_id))
671                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
672                         'FROM account_move_line ' \
673                         'WHERE account_move_line.move_id = %s ' \
674                             'AND account_analytic_line.move_id = account_move_line.id',
675                             (ref, move_id))
676         return True
677
678     def action_cancel(self, cr, uid, ids, *args):
679         account_move_obj = self.pool.get('account.move')
680         invoices = self.read(cr, uid, ids, ['move_id'])
681         for i in invoices:
682             if i['move_id']:
683                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
684                 # delete the move this invoice was pointing to
685                 # Note that the corresponding move_lines and move_reconciles
686                 # will be automatically deleted too
687                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
688         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
689         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
690         return True
691
692     ###################
693
694     def list_distinct_taxes(self, cr, uid, ids):
695         invoices = self.browse(cr, uid, ids)
696         taxes = {}
697         for inv in invoices:
698             for tax in inv.tax_line:
699                 if not tax['name'] in taxes:
700                     taxes[tax['name']] = {'name': tax['name']}
701         return taxes.values()
702
703     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
704         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
705         for inv in invs:
706             part=inv['partner_id'] and inv['partner_id'][0]
707             pc = pr = 0.0
708             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
709             total = inv['amount_untaxed']
710             if inv['type'] in ('in_invoice','in_refund'):
711                 partnertype='supplier'
712                 eventtype = 'purchase'
713                 pc = total*factor
714             else:
715                 partnertype = 'customer'
716                 eventtype = 'sale'
717                 pr = total*factor
718             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
719                 self.pool.get('res.partner.event').create(cr, uid, {'name':'Invoice: '+name, 'som':False, 'description':name+' '+str(inv['id']), 'document':name, 'partner_id':part, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'canal_id':False, 'user_id':uid, 'partner_type':partnertype, 'probability':1.0, 'planned_revenue':pr, 'planned_cost':pc, 'type':eventtype})
720         return len(invs)
721
722     def name_get(self, cr, uid, ids, context={}):
723         if not len(ids):
724             return []
725         types = {
726                 'out_invoice': 'CI: ',
727                 'in_invoice': 'SI: ',
728                 'out_refund': 'OR: ',
729                 'in_refund': 'SR: ',
730                 }
731         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')]
732
733     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
734         if not args:
735             args=[]
736         if not context:
737             context={}
738         ids = []
739         if name:
740             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
741         if not ids:
742             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
743         return self.name_get(cr, user, ids, context)
744
745     def _refund_cleanup_lines(self, lines):
746         for line in lines:
747             del line['id']
748             del line['invoice_id']
749             if 'account_id' in line:
750                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
751             if 'product_id' in line:
752                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
753             if 'uos_id' in line:
754                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
755             if 'invoice_line_tax_id' in line:
756                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
757             if 'account_analytic_id' in line:
758                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
759             if 'tax_code_id' in line :
760                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
761                     line['tax_code_id'] = line['tax_code_id'][0]
762             if 'base_code_id' in line :
763                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
764                     line['base_code_id'] = line['base_code_id'][0]
765         return map(lambda x: (0,0,x), lines)
766
767     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
768         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'])
769
770         new_ids = []
771         for invoice in invoices:
772             del invoice['id']
773
774             type_dict = {
775                 'out_invoice': 'out_refund', # Customer Invoice
776                 'in_invoice': 'in_refund',   # Supplier Invoice
777                 'out_refund': 'out_invoice', # Customer Refund
778                 'in_refund': 'in_invoice',   # Supplier Refund
779             }
780
781
782             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
783             invoice_lines = self._refund_cleanup_lines(invoice_lines)
784
785             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
786             tax_lines = filter(lambda l: l['manual'], tax_lines)
787             tax_lines = self._refund_cleanup_lines(tax_lines)
788             if not date :
789                 date = time.strftime('%Y-%m-%d')
790             invoice.update({
791                 'type': type_dict[invoice['type']],
792                 'date_invoice': date,
793                 'state': 'draft',
794                 'number': False,
795                 'invoice_line': invoice_lines,
796                 'tax_line': tax_lines
797             })
798             if period_id :
799                 invoice.update({
800                     'period_id': period_id,
801                 })
802             if description :
803                 invoice.update({
804                     'name': description,
805                 })
806             # take the id part of the tuple returned for many2one fields
807             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
808                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
809                 invoice[field] = invoice[field] and invoice[field][0]
810             # create the new invoice
811             new_ids.append(self.create(cr, uid, invoice))
812         return new_ids
813
814     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={}, name=''):
815         #TODO check if we can use different period for payment and the writeoff line
816         assert len(ids)==1, "Can only pay one invoice at a time"
817         invoice = self.browse(cr, uid, ids[0])
818         src_account_id = invoice.account_id.id
819         # Take the seq as name for move
820         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
821         direction = types[invoice.type]
822         #take the choosen date
823         if context.has_key('date_p') and context['date_p']:
824             date=context['date_p']
825         else:
826             date=time.strftime('%Y-%m-%d')
827         l1 = {
828             'debit': direction * pay_amount>0 and direction * pay_amount,
829             'credit': direction * pay_amount<0 and - direction * pay_amount,
830             'account_id': src_account_id,
831             'partner_id': invoice.partner_id.id,
832             'ref':invoice.number,
833         }
834         l2 = {
835             'debit': direction * pay_amount<0 and - direction * pay_amount,
836             'credit': direction * pay_amount>0 and direction * pay_amount,
837             'account_id': pay_account_id,
838             'partner_id': invoice.partner_id.id,
839             'ref':invoice.number,
840         }
841
842         lines = [(0, 0, l1), (0, 0, l2)]
843         move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
844         move_id = self.pool.get('account.move').create(cr, uid, move)
845
846         line_ids = []
847         total = 0.0
848         line = self.pool.get('account.move.line')
849         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
850         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
851         for l in lines:
852             if l.account_id.id==src_account_id:
853                 line_ids.append(l.id)
854                 total += (l.debit or 0.0) - (l.credit or 0.0)
855         if (not total) or writeoff_acc_id:
856             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
857         else:
858             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
859         return True
860 account_invoice()
861
862 class account_invoice_line(osv.osv):
863     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
864         res = {}
865         for line in self.browse(cr, uid, ids):
866             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
867         return res
868
869     def _price_unit_default(self, cr, uid, context={}):
870         if 'check_total' in context:
871             t = context['check_total']
872             for l in context.get('invoice_line', {}):
873                 if len(l) >= 3 and l[2]:
874                     tax_obj = self.pool.get('account.tax')
875                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
876                     t = t - (p * l[2].get('quantity'))
877                     taxes = l[2].get('invoice_line_tax_id')
878                     if len(taxes[0]) >= 3 and taxes[0][2]:
879                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
880                         for tax in tax_obj.compute(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)):
881                             t = t - tax['amount']
882             return t
883         return 0
884
885     _name = "account.invoice.line"
886     _description = "Invoice line"
887     _columns = {
888         'name': fields.char('Description', size=256, required=True),
889         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
890         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
891         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
892         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
893         '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."),
894         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
895         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
896         'quantity': fields.float('Quantity', required=True),
897         'discount': fields.float('Discount (%)', digits=(16,2)),
898         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
899         'note': fields.text('Notes'),
900         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
901     }
902     _defaults = {
903         'quantity': lambda *a: 1,
904         'discount': lambda *a: 0.0,
905         'price_unit': _price_unit_default,
906     }
907
908     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context={}):
909         tax_obj = self.pool.get('account.tax')
910         if price_unit:
911             taxes = tax_obj.browse(cr, uid, tax_id)
912             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
913                 price_unit = price_unit - tax['amount']
914         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
915
916     def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, price_unit=False, address_invoice_id=False, context={}):
917         if not partner_id:
918             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
919         if not product:
920             if type in ('in_invoice', 'in_refund'):
921                 return {'domain':{'product_uom':[]}}
922             else:
923                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
924         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
925         lang=part.lang
926         context.update({'lang': lang})
927         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
928         taxep=None
929         tax_obj = self.pool.get('account.tax')
930         if type in ('out_invoice', 'out_refund'):
931             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.taxes_id)
932         else:
933             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.supplier_taxes_id)
934         if type in ('in_invoice', 'in_refund'):
935             result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
936         else:
937             result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
938
939         if not name:
940             result['name'] = res.name
941
942         if type in ('out_invoice','out_refund'):
943             a =  res.product_tmpl_id.property_account_income.id
944             if not a:
945                 a = res.categ_id.property_account_income_categ.id
946         else:
947             a =  res.product_tmpl_id.property_account_expense.id
948             if not a:
949                 a = res.categ_id.property_account_expense_categ.id
950
951         a = self.pool.get('account.fiscal.position').map_account(cr, uid, part, a)
952         if a:
953             result['account_id'] = a
954
955         domain = {}
956         result['uos_id'] = uom or res.uom_id.id or False
957         if result['uos_id']:
958             res2 = res.uom_id.category_id.id
959             if res2 :
960                 domain = {'uos_id':[('category_id','=',res2 )]}
961         return {'value':result, 'domain':domain}
962
963     def move_line_get(self, cr, uid, invoice_id, context={}):
964         res = []
965         tax_grouped = {}
966         tax_obj = self.pool.get('account.tax')
967         cur_obj = self.pool.get('res.currency')
968         ait_obj = self.pool.get('account.invoice.tax')
969         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
970         company_currency = inv.company_id.currency_id.id
971         cur = inv.currency_id
972
973         for line in inv.invoice_line:
974             mres = self.move_line_get_item(cr, uid, line, context)
975             if not mres:
976                 continue
977             res.append(mres)
978             tax_code_found= False
979             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
980                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
981                     line.quantity, inv.address_invoice_id.id, line.product_id,
982                     inv.partner_id):
983
984                 if inv.type in ('out_invoice', 'in_invoice'):
985                     tax_code_id = tax['base_code_id']
986                     tax_amount = line.price_subtotal * tax['base_sign']
987                 else:
988                     tax_code_id = tax['ref_base_code_id']
989                     tax_amount = line.price_subtotal * tax['ref_base_sign']
990
991                 if tax_code_found:
992                     if not tax_code_id:
993                         continue
994                     res.append(self.move_line_get_item(cr, uid, line, context))
995                     res[-1]['price'] = 0.0
996                     res[-1]['account_analytic_id'] = False
997                 elif not tax_code_id:
998                     continue
999                 tax_code_found = True
1000
1001                 res[-1]['tax_code_id'] = tax_code_id
1002                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1003         return res
1004
1005     def move_line_get_item(self, cr, uid, line, context={}):
1006         return {
1007             'type':'src',
1008             'name': line.name[:64],
1009             'price_unit':line.price_unit,
1010             'quantity':line.quantity,
1011             'price':line.price_subtotal,
1012             'account_id':line.account_id.id,
1013             'product_id':line.product_id.id,
1014             'uos_id':line.uos_id.id,
1015             'account_analytic_id':line.account_analytic_id.id,
1016             'taxes':line.invoice_line_tax_id,
1017         }
1018     #
1019     # Set the tax field according to the account and the partner
1020     #
1021     def onchange_account_id(self, cr, uid, ids, partner_id,account_id):
1022         if not (partner_id and account_id):
1023             return {}
1024         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1025         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1026
1027         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, taxes)
1028         r = {'value':{'invoice_line_tax_id': res}}
1029         return r
1030 account_invoice_line()
1031
1032 class account_invoice_tax(osv.osv):
1033     _name = "account.invoice.tax"
1034     _description = "Invoice Tax"
1035     _columns = {
1036         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1037         'name': fields.char('Tax Description', size=64, required=True),
1038         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1039         'base': fields.float('Base', digits=(16,2)),
1040         'amount': fields.float('Amount', digits=(16,2)),
1041         'manual': fields.boolean('Manual'),
1042         'sequence': fields.integer('Sequence'),
1043
1044         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1045         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1046         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1047         'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1048     }
1049     def base_change(self, cr, uid, ids, base):
1050         return {'value': {'base_amount':base}}
1051     def amount_change(self, cr, uid, ids, amount):
1052         return {'value': {'tax_amount':amount}}
1053     _order = 'sequence'
1054     _defaults = {
1055         'manual': lambda *a: 1,
1056         'base_amount': lambda *a: 0.0,
1057         'tax_amount': lambda *a: 0.0,
1058     }
1059     def compute(self, cr, uid, invoice_id):
1060         tax_grouped = {}
1061         tax_obj = self.pool.get('account.tax')
1062         cur_obj = self.pool.get('res.currency')
1063         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1064         cur = inv.currency_id
1065         company_currency = inv.company_id.currency_id.id
1066
1067         for line in inv.invoice_line:
1068             for tax in tax_obj.compute(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):
1069                 val={}
1070                 val['invoice_id'] = inv.id
1071                 val['name'] = tax['name']
1072                 val['amount'] = tax['amount']
1073                 val['manual'] = False
1074                 val['sequence'] = tax['sequence']
1075                 val['base'] = tax['price_unit'] * line['quantity']
1076
1077                 if inv.type in ('out_invoice','in_invoice'):
1078                     val['base_code_id'] = tax['base_code_id']
1079                     val['tax_code_id'] = tax['tax_code_id']
1080                     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')})
1081                     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')})
1082                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1083                 else:
1084                     val['base_code_id'] = tax['ref_base_code_id']
1085                     val['tax_code_id'] = tax['ref_tax_code_id']
1086                     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')})
1087                     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')})
1088                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1089
1090                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1091                 if not key in tax_grouped:
1092                     tax_grouped[key] = val
1093                 else:
1094                     tax_grouped[key]['amount'] += val['amount']
1095                     tax_grouped[key]['base'] += val['base']
1096                     tax_grouped[key]['base_amount'] += val['base_amount']
1097                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1098
1099         for t in tax_grouped.values():
1100             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1101         return tax_grouped
1102
1103     def move_line_get(self, cr, uid, invoice_id):
1104         res = []
1105         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1106         for t in cr.dictfetchall():
1107             if not t['amount'] \
1108                     and not t['tax_code_id'] \
1109                     and not t['tax_amount']:
1110                 continue
1111             res.append({
1112                 'type':'tax',
1113                 'name':t['name'],
1114                 'price_unit': t['amount'],
1115                 'quantity': 1,
1116                 'price': t['amount'] or 0.0,
1117                 'account_id': t['account_id'],
1118                 'tax_code_id': t['tax_code_id'],
1119                 'tax_amount': t['tax_amount']
1120             })
1121         return res
1122 account_invoice_tax()
1123
1124 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1125