Fixed two "Invalid function definition" (mst use store={object:(fnct, fields, priority)}
[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             move_id = self.pool.get('account.move').create(cr, uid, move)
620             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
621             # make the invoice point to that move
622             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
623             self.pool.get('account.move').post(cr, uid, [move_id])
624         self._log_event(cr, uid, ids)
625         return True
626
627     def line_get_convert(self, cr, uid, x, part, date, context={}):
628         return {
629             'date_maturity': x.get('date_maturity', False),
630             'partner_id':part,
631             'name':x['name'][:64],
632             'debit':x['price']>0 and x['price'],
633             'credit':x['price']<0 and -x['price'],
634             'account_id':x['account_id'],
635             'analytic_lines':x.get('analytic_lines', []),
636             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
637             'currency_id':x.get('currency_id', False),
638             'tax_code_id': x.get('tax_code_id', False),
639             'tax_amount': x.get('tax_amount', False),
640             'ref':x.get('ref',False),
641             'quantity':x.get('quantity',1.00),
642             'product_id':x.get('product_id', False),
643             'product_uom_id':x.get('uos_id',False),
644             'analytic_account_id':x.get('account_analytic_id',False),
645         }
646
647     def action_number(self, cr, uid, ids, *args):
648         cr.execute('SELECT id, type, number, move_id, reference ' \
649                 'FROM account_invoice ' \
650                 'WHERE id IN ('+','.join(map(str,ids))+')')
651         for (id, invtype, number, move_id, reference) in cr.fetchall():
652             if not number:
653                 number = self.pool.get('ir.sequence').get(cr, uid,
654                         'account.invoice.' + invtype)
655                 if invtype in ('in_invoice', 'in_refund'):
656                     ref = reference
657                 else:
658                     ref = self._convert_ref(cr, uid, number)
659                 cr.execute('UPDATE account_invoice SET number=%s ' \
660                         'WHERE id=%s', (number, id))
661                 cr.execute('UPDATE account_move SET ref=%s ' \
662                         'WHERE id=%s AND (ref is null OR ref = \'\')',
663                         (ref, move_id))
664                 cr.execute('UPDATE account_move_line SET ref=%s ' \
665                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
666                         (ref, move_id))
667                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
668                         'FROM account_move_line ' \
669                         'WHERE account_move_line.move_id = %s ' \
670                             'AND account_analytic_line.move_id = account_move_line.id',
671                             (ref, move_id))
672         return True
673
674     def action_cancel(self, cr, uid, ids, *args):
675         account_move_obj = self.pool.get('account.move')
676         invoices = self.read(cr, uid, ids, ['move_id'])
677         for i in invoices:
678             if i['move_id']:
679                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
680                 # delete the move this invoice was pointing to
681                 # Note that the corresponding move_lines and move_reconciles
682                 # will be automatically deleted too
683                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
684         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
685         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
686         return True
687
688     ###################
689
690     def list_distinct_taxes(self, cr, uid, ids):
691         invoices = self.browse(cr, uid, ids)
692         taxes = {}
693         for inv in invoices:
694             for tax in inv.tax_line:
695                 if not tax['name'] in taxes:
696                     taxes[tax['name']] = {'name': tax['name']}
697         return taxes.values()
698
699     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
700         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
701         for inv in invs:
702             part=inv['partner_id'] and inv['partner_id'][0]
703             pc = pr = 0.0
704             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
705             total = inv['amount_untaxed']
706             if inv['type'] in ('in_invoice','in_refund'):
707                 partnertype='supplier'
708                 eventtype = 'purchase'
709                 pc = total*factor
710             else:
711                 partnertype = 'customer'
712                 eventtype = 'sale'
713                 pr = total*factor
714             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
715                 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})
716         return len(invs)
717
718     def name_get(self, cr, uid, ids, context={}):
719         if not len(ids):
720             return []
721         types = {
722                 'out_invoice': 'CI: ',
723                 'in_invoice': 'SI: ',
724                 'out_refund': 'OR: ',
725                 'in_refund': 'SR: ',
726                 }
727         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')]
728
729     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
730         if not args:
731             args=[]
732         if not context:
733             context={}
734         ids = []
735         if name:
736             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
737         if not ids:
738             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
739         return self.name_get(cr, user, ids, context)
740
741     def _refund_cleanup_lines(self, lines):
742         for line in lines:
743             del line['id']
744             del line['invoice_id']
745             if 'account_id' in line:
746                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
747             if 'product_id' in line:
748                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
749             if 'uos_id' in line:
750                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
751             if 'invoice_line_tax_id' in line:
752                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
753             if 'account_analytic_id' in line:
754                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
755             if 'tax_code_id' in line :
756                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
757                     line['tax_code_id'] = line['tax_code_id'][0]
758             if 'base_code_id' in line :
759                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
760                     line['base_code_id'] = line['base_code_id'][0]
761         return map(lambda x: (0,0,x), lines)
762
763     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
764         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'])
765
766         new_ids = []
767         for invoice in invoices:
768             del invoice['id']
769
770             type_dict = {
771                 'out_invoice': 'out_refund', # Customer Invoice
772                 'in_invoice': 'in_refund',   # Supplier Invoice
773                 'out_refund': 'out_invoice', # Customer Refund
774                 'in_refund': 'in_invoice',   # Supplier Refund
775             }
776
777
778             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
779             invoice_lines = self._refund_cleanup_lines(invoice_lines)
780
781             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
782             tax_lines = filter(lambda l: l['manual'], tax_lines)
783             tax_lines = self._refund_cleanup_lines(tax_lines)
784             if not date :
785                 date = time.strftime('%Y-%m-%d')
786             invoice.update({
787                 'type': type_dict[invoice['type']],
788                 'date_invoice': date,
789                 'state': 'draft',
790                 'number': False,
791                 'invoice_line': invoice_lines,
792                 'tax_line': tax_lines
793             })
794             if period_id :
795                 invoice.update({
796                     'period_id': period_id,
797                 })
798             if description :
799                 invoice.update({
800                     'name': description,
801                 })
802             # take the id part of the tuple returned for many2one fields
803             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
804                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
805                 invoice[field] = invoice[field] and invoice[field][0]
806             # create the new invoice
807             new_ids.append(self.create(cr, uid, invoice))
808         return new_ids
809
810     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=''):
811         #TODO check if we can use different period for payment and the writeoff line
812         assert len(ids)==1, "Can only pay one invoice at a time"
813         invoice = self.browse(cr, uid, ids[0])
814         src_account_id = invoice.account_id.id
815         # Take the seq as name for move
816         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
817         direction = types[invoice.type]
818         #take the choosen date
819         if context.has_key('date_p') and context['date_p']:
820             date=context['date_p']
821         else:
822             date=time.strftime('%Y-%m-%d')
823         l1 = {
824             'debit': direction * pay_amount>0 and direction * pay_amount,
825             'credit': direction * pay_amount<0 and - direction * pay_amount,
826             'account_id': src_account_id,
827             'partner_id': invoice.partner_id.id,
828             'ref':invoice.number,
829         }
830         l2 = {
831             'debit': direction * pay_amount<0 and - direction * pay_amount,
832             'credit': direction * pay_amount>0 and direction * pay_amount,
833             'account_id': pay_account_id,
834             'partner_id': invoice.partner_id.id,
835             'ref':invoice.number,
836         }
837
838         lines = [(0, 0, l1), (0, 0, l2)]
839         move = {'ref': inv.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
840         move_id = self.pool.get('account.move').create(cr, uid, move)
841
842         line_ids = []
843         total = 0.0
844         line = self.pool.get('account.move.line')
845         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
846         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
847         for l in lines:
848             if l.account_id.id==src_account_id:
849                 line_ids.append(l.id)
850                 total += (l.debit or 0.0) - (l.credit or 0.0)
851         if (not total) or writeoff_acc_id:
852             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
853         else:
854             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
855         return True
856 account_invoice()
857
858 class account_invoice_line(osv.osv):
859     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
860         res = {}
861         for line in self.browse(cr, uid, ids):
862             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
863         return res
864
865     def _price_unit_default(self, cr, uid, context={}):
866         if 'check_total' in context:
867             t = context['check_total']
868             for l in context.get('invoice_line', {}):
869                 if len(l) >= 3 and l[2]:
870                     tax_obj = self.pool.get('account.tax')
871                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
872                     t = t - (p * l[2].get('quantity'))
873                     taxes = l[2].get('invoice_line_tax_id')
874                     if len(taxes[0]) >= 3 and taxes[0][2]:
875                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
876                         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)):
877                             t = t - tax['amount']
878             return t
879         return 0
880
881     _name = "account.invoice.line"
882     _description = "Invoice line"
883     _columns = {
884         'name': fields.char('Description', size=256, required=True),
885         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
886         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
887         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
888         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
889         '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."),
890         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
891         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
892         'quantity': fields.float('Quantity', required=True),
893         'discount': fields.float('Discount (%)', digits=(16,2)),
894         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
895         'note': fields.text('Notes'),
896         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
897     }
898     _defaults = {
899         'quantity': lambda *a: 1,
900         'discount': lambda *a: 0.0,
901         'price_unit': _price_unit_default,
902     }
903
904     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context={}):
905         tax_obj = self.pool.get('account.tax')
906         if price_unit:
907             taxes = tax_obj.browse(cr, uid, tax_id)
908             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
909                 price_unit = price_unit - tax['amount']
910         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
911
912     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={}):
913         if not partner_id:
914             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
915         if not product:
916             if type in ('in_invoice', 'in_refund'):
917                 return {'domain':{'product_uom':[]}}
918             else:
919                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
920         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
921         lang=part.lang
922         context.update({'lang': lang})
923         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
924         taxep=None
925         tax_obj = self.pool.get('account.tax')
926         if type in ('out_invoice', 'out_refund'):
927             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.taxes_id)
928         else:
929             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.supplier_taxes_id)
930         if type in ('in_invoice', 'in_refund'):
931             result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
932         else:
933             result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
934
935         if not name:
936             result['name'] = res.name
937
938         if type in ('out_invoice','out_refund'):
939             a =  res.product_tmpl_id.property_account_income.id
940             if not a:
941                 a = res.categ_id.property_account_income_categ.id
942         else:
943             a =  res.product_tmpl_id.property_account_expense.id
944             if not a:
945                 a = res.categ_id.property_account_expense_categ.id
946
947         a = self.pool.get('account.fiscal.position').map_account(cr, uid, part, a)
948         if a:
949             result['account_id'] = a
950
951         domain = {}
952         result['uos_id'] = uom or res.uom_id.id or False
953         if result['uos_id']:
954             res2 = res.uom_id.category_id.id
955             if res2 :
956                 domain = {'uos_id':[('category_id','=',res2 )]}
957         return {'value':result, 'domain':domain}
958
959     def move_line_get(self, cr, uid, invoice_id, context={}):
960         res = []
961         tax_grouped = {}
962         tax_obj = self.pool.get('account.tax')
963         cur_obj = self.pool.get('res.currency')
964         ait_obj = self.pool.get('account.invoice.tax')
965         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
966         company_currency = inv.company_id.currency_id.id
967         cur = inv.currency_id
968
969         for line in inv.invoice_line:
970             mres = self.move_line_get_item(cr, uid, line, context)
971             if not mres:
972                 continue
973             res.append(mres)
974             tax_code_found= False
975             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
976                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
977                     line.quantity, inv.address_invoice_id.id, line.product_id,
978                     inv.partner_id):
979
980                 if inv.type in ('out_invoice', 'in_invoice'):
981                     tax_code_id = tax['base_code_id']
982                     tax_amount = line.price_subtotal * tax['base_sign']
983                 else:
984                     tax_code_id = tax['ref_base_code_id']
985                     tax_amount = line.price_subtotal * tax['ref_base_sign']
986
987                 if tax_code_found:
988                     if not tax_code_id:
989                         continue
990                     res.append(self.move_line_get_item(cr, uid, line, context))
991                     res[-1]['price'] = 0.0
992                     res[-1]['account_analytic_id'] = False
993                 elif not tax_code_id:
994                     continue
995                 tax_code_found = True
996
997                 res[-1]['tax_code_id'] = tax_code_id
998                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
999         return res
1000
1001     def move_line_get_item(self, cr, uid, line, context={}):
1002         return {
1003             'type':'src',
1004             'name': line.name[:64],
1005             'price_unit':line.price_unit,
1006             'quantity':line.quantity,
1007             'price':line.price_subtotal,
1008             'account_id':line.account_id.id,
1009             'product_id':line.product_id.id,
1010             'uos_id':line.uos_id.id,
1011             'account_analytic_id':line.account_analytic_id.id,
1012             'taxes':line.invoice_line_tax_id,
1013         }
1014     #
1015     # Set the tax field according to the account and the partner
1016     #
1017     def onchange_account_id(self, cr, uid, ids, partner_id,account_id):
1018         if not (partner_id and account_id):
1019             return {}
1020         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1021         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1022
1023         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, taxes)
1024         r = {'value':{'invoice_line_tax_id': res}}
1025         return r
1026 account_invoice_line()
1027
1028 class account_invoice_tax(osv.osv):
1029     _name = "account.invoice.tax"
1030     _description = "Invoice Tax"
1031     _columns = {
1032         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1033         'name': fields.char('Tax Description', size=64, required=True),
1034         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1035         'base': fields.float('Base', digits=(16,2)),
1036         'amount': fields.float('Amount', digits=(16,2)),
1037         'manual': fields.boolean('Manual'),
1038         'sequence': fields.integer('Sequence'),
1039
1040         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1041         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1042         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1043         'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1044     }
1045     def base_change(self, cr, uid, ids, base):
1046         return {'value': {'base_amount':base}}
1047     def amount_change(self, cr, uid, ids, amount):
1048         return {'value': {'tax_amount':amount}}
1049     _order = 'sequence'
1050     _defaults = {
1051         'manual': lambda *a: 1,
1052         'base_amount': lambda *a: 0.0,
1053         'tax_amount': lambda *a: 0.0,
1054     }
1055     def compute(self, cr, uid, invoice_id):
1056         tax_grouped = {}
1057         tax_obj = self.pool.get('account.tax')
1058         cur_obj = self.pool.get('res.currency')
1059         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1060         cur = inv.currency_id
1061         company_currency = inv.company_id.currency_id.id
1062
1063         for line in inv.invoice_line:
1064             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):
1065                 val={}
1066                 val['invoice_id'] = inv.id
1067                 val['name'] = tax['name']
1068                 val['amount'] = tax['amount']
1069                 val['manual'] = False
1070                 val['sequence'] = tax['sequence']
1071                 val['base'] = tax['price_unit'] * line['quantity']
1072
1073                 if inv.type in ('out_invoice','in_invoice'):
1074                     val['base_code_id'] = tax['base_code_id']
1075                     val['tax_code_id'] = tax['tax_code_id']
1076                     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')})
1077                     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')})
1078                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1079                 else:
1080                     val['base_code_id'] = tax['ref_base_code_id']
1081                     val['tax_code_id'] = tax['ref_tax_code_id']
1082                     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')})
1083                     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')})
1084                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1085
1086                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1087                 if not key in tax_grouped:
1088                     tax_grouped[key] = val
1089                 else:
1090                     tax_grouped[key]['amount'] += val['amount']
1091                     tax_grouped[key]['base'] += val['base']
1092                     tax_grouped[key]['base_amount'] += val['base_amount']
1093                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1094
1095         for t in tax_grouped.values():
1096             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1097         return tax_grouped
1098
1099     def move_line_get(self, cr, uid, invoice_id):
1100         res = []
1101         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1102         for t in cr.dictfetchall():
1103             if not t['amount'] \
1104                     and not t['tax_code_id'] \
1105                     and not t['tax_amount']:
1106                 continue
1107             res.append({
1108                 'type':'tax',
1109                 'name':t['name'],
1110                 'price_unit': t['amount'],
1111                 'quantity': 1,
1112                 'price': t['amount'] or 0.0,
1113                 'account_id': t['account_id'],
1114                 'tax_code_id': t['tax_code_id'],
1115                 'tax_amount': t['tax_amount']
1116             })
1117         return res
1118 account_invoice_tax()
1119
1120 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1121