improvement
[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):
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, cursor, user, 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_tax(self, cr, uid, ids, context={}):
117         result = {}
118         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
119             result[tax.invoice_id.id] = True
120         return result.keys()
121
122     def _compute_lines(self, cr, uid, ids, name, args, context={}):
123         result = {}
124         for invoice in self.browse(cr, uid, ids, context):
125             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
126             src = []
127             lines = []
128             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
129                 if m.reconcile_id:
130                     lines += map(lambda x: x.id, m.reconcile_id.line_id)
131                 elif m.reconcile_partial_id:
132                     lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
133                 src.append(m.id)
134             lines = filter(lambda x: x not in src, lines)
135             result[invoice.id] = lines
136         return result
137
138     _name = "account.invoice"
139     _description = 'Invoice'
140     _order = "number"
141     _columns = {
142         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
143         'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
144         'type': fields.selection([
145             ('out_invoice','Customer Invoice'),
146             ('in_invoice','Supplier Invoice'),
147             ('out_refund','Customer Refund'),
148             ('in_refund','Supplier Refund'),
149             ],'Type', readonly=True, select=True),
150
151         'number': fields.char('Invoice Number', size=32, readonly=True, help="Uniq number of the invoice, computed automatically when the invoice is created."),
152         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
153         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
154             required=True),
155         'comment': fields.text('Additionnal Information'),
156
157         'state': fields.selection([
158             ('draft','Draft'),
159             ('proforma','Pro-forma'),
160             ('proforma2','Pro-forma'),
161             ('open','Open'),
162             ('paid','Done'),
163             ('cancel','Canceled')
164         ],'State', select=True, readonly=True),
165
166         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
167         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
168
169         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
170         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
171         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
172
173         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]} ),
174
175         'period_id': fields.many2one('account.period', 'Force Period', help="Keep empty to use the period of the validation date."),
176
177         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
178         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
179         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
180
181         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
182         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
183             store={
184                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 10),
185                 'account.invoice.tax': (_get_invoice_tax, None, 10),
186             },
187             multi='all'),
188         'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
189             store={
190                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 10),
191                 'account.invoice.tax': (_get_invoice_tax, None, 10),
192             },
193             multi='all'),
194         'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
195             store={
196                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 10),
197                 'account.invoice.tax': (_get_invoice_tax, None, 10),
198             },
199             multi='all'),
200         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
201         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
202         'company_id': fields.many2one('res.company', 'Company', required=True),
203         'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
204         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
205             store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
206         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
207             help='The bank account to pay to or to be paid from'),
208         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
209         'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual', store=True, help="Remaining amount due."),
210         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
211         'move_name': fields.char('Account Move', size=64),
212     }
213     _defaults = {
214         'type': _get_type,
215         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
216         'state': lambda *a: 'draft',
217         'journal_id': _get_journal,
218         'currency_id': _get_currency,
219         'company_id': lambda self, cr, uid, context: \
220                 self.pool.get('res.users').browse(cr, uid, uid,
221                     context=context).company_id.id,
222         'reference_type': lambda *a: 'none',
223     }
224
225     def unlink(self, cr, uid, ids):
226         invoices = self.read(cr, uid, ids, ['state'])
227         unlink_ids = []
228         for t in invoices:
229             if t['state'] in ('draft', 'cancel'):
230                 unlink_ids.append(t['id'])
231             else:
232                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
233         osv.osv.unlink(self, cr, uid, unlink_ids)
234         return True
235
236 #   def get_invoice_address(self, cr, uid, ids):
237 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
238 #       return [{}]
239
240     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
241             date_invoice=False, payment_term=False, partner_bank_id=False):
242         invoice_addr_id = False
243         contact_addr_id = False
244         partner_payment_term = False
245         acc_id = False
246         bank_id = False
247
248         opt = [('uid', str(uid))]
249         if partner_id:
250
251             opt.insert(0, ('id', partner_id))
252             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
253             contact_addr_id = res['contact']
254             invoice_addr_id = res['invoice']
255             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
256             if type in ('out_invoice', 'out_refund'):
257                 acc_id = p.property_account_receivable.id
258             else:
259                 acc_id = p.property_account_payable.id
260
261             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
262             if p.bank_ids:
263                 bank_id = p.bank_ids[0].id
264
265         result = {'value': {
266             'address_contact_id': contact_addr_id,
267             'address_invoice_id': invoice_addr_id,
268             'account_id': acc_id,
269             'payment_term': partner_payment_term,
270             }
271         }
272
273         if type in ('in_invoice', 'in_refund'):
274             result['value']['partner_bank'] = bank_id
275
276         if payment_term != partner_payment_term:
277             if partner_payment_term:
278                 to_update = self.onchange_payment_term_date_invoice(
279                     cr,uid,ids,partner_payment_term,date_invoice)
280                 result['value'].update(to_update['value'])
281             else:
282                 result['value']['date_due'] = False
283
284         if partner_bank_id != bank_id:
285             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
286             result['value'].update(to_update['value'])
287         return result
288
289     def onchange_currency_id(self, cr, uid, ids, curr_id):
290         return {}
291
292     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
293         if not payment_term_id:
294             return {}
295         res={}
296         pt_obj= self.pool.get('account.payment.term')
297
298         if not date_invoice :
299             date_invoice = time.strftime('%Y-%m-%d')
300
301         pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
302
303         if pterm_list:
304             pterm_list = [line[0] for line in pterm_list]
305             pterm_list.sort()
306             res= {'value':{'date_due': pterm_list[-1]}}
307
308         return res
309
310     def onchange_invoice_line(self, cr, uid, ids, lines):
311         return {}
312
313     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
314         return {'value': {}}
315
316     # go from canceled state to draft state
317     def action_cancel_draft(self, cr, uid, ids, *args):
318         self.write(cr, uid, ids, {'state':'draft'})
319         wf_service = netsvc.LocalService("workflow")
320         for inv_id in ids:
321             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
322         return True
323
324     # Workflow stuff
325     #################
326
327     # return the ids of the move lines which has the same account than the invoice
328     # whose id is in ids
329     def move_line_id_payment_get(self, cr, uid, ids, *args):
330         ml = self.pool.get('account.move.line')
331         res = []
332         for inv in self.read(cr, uid, ids, ['move_id','account_id']):
333             if inv['move_id']:
334                 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
335                 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
336                     if line['account_id']==inv['account_id']:
337                         res.append(line['id'])
338         return res
339
340     def copy(self, cr, uid, id, default=None, context=None):
341         if default is None:
342             default = {}
343         default = default.copy()
344         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
345         if 'date_invoice' not in default:
346             default['date_invoice'] = False
347         if 'date_due' not in default:
348             default['date_due'] = False
349         return super(account_invoice, self).copy(cr, uid, id, default, context)
350
351     def test_paid(self, cr, uid, ids, *args):
352         res = self.move_line_id_payment_get(cr, uid, ids)
353         if not res:
354             return False
355         ok = True
356         for id in res:
357             cr.execute('select reconcile_id from account_move_line where id=%d', (id,))
358             ok = ok and  bool(cr.fetchone()[0])
359         return ok
360
361     def button_reset_taxes(self, cr, uid, ids, context={}):
362         ait_obj = self.pool.get('account.invoice.tax')
363         for id in ids:
364             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%d", (id,))
365             for taxe in ait_obj.compute(cr, uid, id).values():
366                 ait_obj.create(cr, uid, taxe)
367         return True
368
369     def button_compute(self, cr, uid, ids, context={}, set_total=False):
370         ait_obj = self.pool.get('account.invoice.tax')
371         cur_obj = self.pool.get('res.currency')
372         for inv in self.browse(cr, uid, ids):
373             company_currency = inv.company_id.currency_id.id
374             compute_taxes = ait_obj.compute(cr, uid, inv.id)
375             if not inv.tax_line:
376                 for tax in compute_taxes.values():
377                     ait_obj.create(cr, uid, tax)
378             else:
379                 tax_key = []
380                 for tax in inv.tax_line:
381                     if tax.manual:
382                         continue
383                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
384                     tax_key.append(key)
385                     if not key in compute_taxes:
386                         ait_obj.unlink(cr, uid, [tax.id])
387                         continue
388                     compute_taxes[key]['base'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, compute_taxes[key]['base'], context={'date': inv.date_invoice})
389                     if abs(compute_taxes[key]['base'] - tax.base) > inv.company_id.currency_id.rounding:
390                         ait_obj.write(cr, uid, [tax.id], compute_taxes[key])
391                 for key in compute_taxes:
392                     if not key in tax_key:
393                         ait_obj.create(cr, uid, compute_taxes[key])
394             if set_total:
395                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
396         return True
397
398     def _convert_ref(self, cr, uid, ref):
399         return (ref or '').replace('/','')
400
401     def _get_analytic_lines(self, cr, uid, id):
402         inv = self.browse(cr, uid, [id])[0]
403         cur_obj = self.pool.get('res.currency')
404
405         company_currency = inv.company_id.currency_id.id
406         if inv.type in ('out_invoice', 'in_refund'):
407             sign = 1
408         else:
409             sign = -1
410
411         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
412         for il in iml:
413             if il['account_analytic_id']:
414                 if inv.type in ('in_invoice', 'in_refund'):
415                     ref = inv.reference
416                 else:
417                     ref = self._convert_ref(cr, uid, inv.number)
418                 il['analytic_lines'] = [(0,0, {
419                     'name': il['name'],
420                     'date': inv['date_invoice'],
421                     'account_id': il['account_analytic_id'],
422                     'unit_amount': il['quantity'],
423                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
424                     'product_id': il['product_id'],
425                     'product_uom_id': il['uos_id'],
426                     'general_account_id': il['account_id'],
427                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
428                     'ref': ref,
429                 })]
430         return iml
431
432     def action_move_create(self, cr, uid, ids, *args):
433         ait_obj = self.pool.get('account.invoice.tax')
434         cur_obj = self.pool.get('res.currency')
435         acc_obj = self.pool.get('account.account')
436         for inv in self.browse(cr, uid, ids):
437             if inv.move_id:
438                 continue
439             self.button_compute(cr, uid, ids, context={},set_total=True)
440             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
441                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
442             if not inv.date_invoice:
443                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
444             company_currency = inv.company_id.currency_id.id
445             # create the analytical lines
446             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
447             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
448             # one move line per invoice line
449             iml = self._get_analytic_lines(cr, uid, inv.id)
450             # check if taxes are all computed
451             compute_taxes = ait_obj.compute(cr, uid, inv.id)
452             if not inv.tax_line:
453                 for tax in compute_taxes.values():
454                     ait_obj.create(cr, uid, tax)
455             else:
456                 tax_key = []
457                 for tax in inv.tax_line:
458                     if tax.manual:
459                         continue
460                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
461                     tax_key.append(key)
462                     if not key in compute_taxes:
463                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
464                     base = compute_taxes[key]['base']
465                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
466                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
467                 for key in compute_taxes:
468                     if not key in tax_key:
469                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
470
471             # one move line per tax line
472             iml += ait_obj.move_line_get(cr, uid, inv.id)
473
474             if inv.type in ('in_invoice', 'in_refund'):
475                 ref = inv.reference
476             else:
477                 ref = self._convert_ref(cr, uid, inv.number)
478
479             diff_currency_p = inv.currency_id.id <> company_currency
480             # create one move line for the total and possibly adjust the other lines amount
481             total = 0
482             total_currency = 0
483             key_line=[]
484             for i in iml:
485                 if i.has_key('account_id') and i.has_key('taxes'):
486                     if not (i['account_id'],i['taxes']) in key_line:
487                         key_line.append((i['account_id'],i['taxes']))
488                 if inv.currency_id.id != company_currency:
489                     i['currency_id'] = inv.currency_id.id
490                     i['amount_currency'] = i['price']
491                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
492                             company_currency, i['price'],
493                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
494                 else:
495                     i['amount_currency'] = False
496                     i['currency_id'] = False
497                 i['ref'] = ref
498                 if inv.type in ('out_invoice','in_refund'):
499                     total += i['price']
500                     total_currency += i['amount_currency'] or i['price']
501                     i['price'] = - i['price']
502                 else:
503                     total -= i['price']
504                     total_currency -= i['amount_currency'] or i['price']
505             acc_id = inv.account_id.id
506
507             name = inv['name'] or '/'
508             iml_temp=[]
509             move_list=[]
510
511             for item in key_line:
512                 move_temp={}
513                 if acc_obj.browse(cr,uid,item[0]).merge_invoice:
514                     repeat=False
515                     for move_line in iml:
516                         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]):
517                             move_list.append(move_line)
518                             if repeat:
519                                 for key in move_line:
520                                     if key in ['name','amount_currency','price_unit','price','quantity']:
521                                         if key=='name':
522                                             move_temp[key]=move_temp[key] + "," +move_line[key]
523                                         else:
524                                             move_temp[key] +=move_line[key]
525                             else:
526                                 for key in move_line:
527                                     move_temp[key]=move_line[key]
528                                 repeat=True
529                 if move_temp:
530                     iml_temp.append(move_temp)
531
532             if len(iml_temp)<len(move_list):
533                 for old_elem in move_list:
534                     iml.remove(old_elem)
535                 for new_elem in iml_temp:
536                     iml.append(new_elem)
537
538             totlines = False
539             if inv.payment_term:
540                 totlines = self.pool.get('account.payment.term').compute(cr,
541                         uid, inv.payment_term.id, total, inv.date_invoice or False)
542             if totlines:
543                 res_amount_currency = total_currency
544                 i = 0
545                 for t in totlines:
546                     if inv.currency_id.id != company_currency:
547                         amount_currency = cur_obj.compute(cr, uid,
548                                 company_currency, inv.currency_id.id, t[1])
549                     else:
550                         amount_currency = False
551
552                     # last line add the diff
553                     res_amount_currency -= amount_currency or 0
554                     i += 1
555                     if i == len(totlines):
556                         amount_currency += res_amount_currency
557
558                     iml.append({
559                         'type': 'dest',
560                         'name': name,
561                         'price': t[1],
562                         'account_id': acc_id,
563                         'date_maturity': t[0],
564                         'amount_currency': diff_currency_p \
565                                 and  amount_currency or False,
566                         'currency_id': diff_currency_p \
567                                 and inv.currency_id.id or False,
568                         'ref': ref,
569                     })
570             else:
571                 iml.append({
572                     'type': 'dest',
573                     'name': name,
574                     'price': total,
575                     'account_id': acc_id,
576                     'date_maturity' : inv.date_due or False,
577                     'amount_currency': diff_currency_p \
578                             and total_currency or False,
579                     'currency_id': diff_currency_p \
580                             and inv.currency_id.id or False,
581                     'ref': ref
582             })
583
584             date = inv.date_invoice or time.strftime('%Y-%m-%d')
585             part = inv.partner_id.id
586             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
587
588             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
589             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
590             if journal.sequence_id and not inv.move_name:
591                 name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
592             else:
593                 name = inv.move_name
594             if journal.centralisation:
595                 raise osv.except_osv(_('UserError'),
596                         _('Can not create invoice move on centralized journal'))
597
598             move = {'name': name, 'line_id': line, 'journal_id': journal_id}
599             period_id=inv.period_id and inv.period_id.id or False
600             if not period_id:
601                 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'))])
602                 if len(period_ids):
603                     period_id=period_ids[0]
604             if period_id:
605                 move['period_id'] = period_id
606                 for i in line:
607                     i[2]['period_id'] = period_id
608             move_id = self.pool.get('account.move').create(cr, uid, move)
609             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
610             # make the invoice point to that move
611             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
612             self.pool.get('account.move').post(cr, uid, [move_id])
613         self._log_event(cr, uid, ids)
614         return True
615
616     def line_get_convert(self, cr, uid, x, part, date, context={}):
617         return {
618             'date':date,
619             'date_maturity': x.get('date_maturity', False),
620             'partner_id':part,
621             'name':x['name'][:64],
622             'debit':x['price']>0 and x['price'],
623             'credit':x['price']<0 and -x['price'],
624             'account_id':x['account_id'],
625             'analytic_lines':x.get('analytic_lines', []),
626             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
627             'currency_id':x.get('currency_id', False),
628             'tax_code_id': x.get('tax_code_id', False),
629             'tax_amount': x.get('tax_amount', False),
630             'ref':x.get('ref',False),
631             'quantity':x.get('quantity',1.00),
632             'product_id':x.get('product_id', False),
633             'product_uom_id':x.get('uos_id',False),
634             'analytic_account_id':x.get('account_analytic_id',False),
635         }
636
637     def action_number(self, cr, uid, ids, *args):
638         cr.execute('SELECT id, type, number, move_id, reference ' \
639                 'FROM account_invoice ' \
640                 'WHERE id IN ('+','.join(map(str,ids))+')')
641         for (id, invtype, number, move_id, reference) in cr.fetchall():
642             if not number:
643                 number = self.pool.get('ir.sequence').get(cr, uid,
644                         'account.invoice.' + invtype)
645                 if invtype in ('in_invoice', 'in_refund'):
646                     ref = reference
647                 else:
648                     ref = self._convert_ref(cr, uid, number)
649                 cr.execute('UPDATE account_invoice SET number=%s ' \
650                         'WHERE id=%d', (number, id))
651                 cr.execute('UPDATE account_move_line SET ref=%s ' \
652                         'WHERE move_id=%d AND (ref is null OR ref = \'\')',
653                         (ref, move_id))
654                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
655                         'FROM account_move_line ' \
656                         'WHERE account_move_line.move_id = %d ' \
657                             'AND account_analytic_line.move_id = account_move_line.id',
658                             (ref, move_id))
659         return True
660
661     def action_cancel(self, cr, uid, ids, *args):
662         account_move_obj = self.pool.get('account.move')
663         invoices = self.read(cr, uid, ids, ['move_id'])
664         for i in invoices:
665             if i['move_id']:
666                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
667                 # delete the move this invoice was pointing to
668                 # Note that the corresponding move_lines and move_reconciles
669                 # will be automatically deleted too
670                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
671         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
672         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
673         return True
674
675     ###################
676
677     def list_distinct_taxes(self, cr, uid, ids):
678         invoices = self.browse(cr, uid, ids)
679         taxes = {}
680         for inv in invoices:
681             for tax in inv.tax_line:
682                 if not tax['name'] in taxes:
683                     taxes[tax['name']] = {'name': tax['name']}
684         return taxes.values()
685
686     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
687         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
688         for inv in invs:
689             part=inv['partner_id'] and inv['partner_id'][0]
690             pc = pr = 0.0
691             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%d', (inv['id'],))
692             total = inv['amount_untaxed']
693             if inv['type'] in ('in_invoice','in_refund'):
694                 partnertype='supplier'
695                 eventtype = 'purchase'
696                 pc = total*factor
697             else:
698                 partnertype = 'customer'
699                 eventtype = 'sale'
700                 pr = total*factor
701             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
702                 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})
703         return len(invs)
704
705     def name_get(self, cr, uid, ids, context={}):
706         if not len(ids):
707             return []
708         types = {
709                 'out_invoice': 'CI: ',
710                 'in_invoice': 'SI: ',
711                 'out_refund': 'OR: ',
712                 'in_refund': 'SR: ',
713                 }
714         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')]
715
716     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
717         if not args:
718             args=[]
719         if not context:
720             context={}
721         ids = []
722         if name:
723             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
724         if not ids:
725             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
726         return self.name_get(cr, user, ids, context)
727
728     def _refund_cleanup_lines(self, lines):
729         for line in lines:
730             del line['id']
731             del line['invoice_id']
732             if 'account_id' in line:
733                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
734             if 'product_id' in line:
735                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
736             if 'uos_id' in line:
737                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
738             if 'invoice_line_tax_id' in line:
739                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
740             if 'account_analytic_id' in line:
741                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
742             if 'tax_code_id' in line :
743                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
744                     line['tax_code_id'] = line['tax_code_id'][0]
745             if 'base_code_id' in line :
746                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
747                     line['base_code_id'] = line['base_code_id'][0]
748         return map(lambda x: (0,0,x), lines)
749
750     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
751         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'])
752
753         new_ids = []
754         for invoice in invoices:
755             del invoice['id']
756
757             type_dict = {
758                 'out_invoice': 'out_refund', # Customer Invoice
759                 'in_invoice': 'in_refund',   # Supplier Invoice
760                 'out_refund': 'out_invoice', # Customer Refund
761                 'in_refund': 'in_invoice',   # Supplier Refund
762             }
763
764
765             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
766             invoice_lines = self._refund_cleanup_lines(invoice_lines)
767
768             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
769             tax_lines = filter(lambda l: l['manual'], tax_lines)
770             tax_lines = self._refund_cleanup_lines(tax_lines)
771             if not date :
772                 date = time.strftime('%Y-%m-%d')
773             invoice.update({
774                 'type': type_dict[invoice['type']],
775                 'date_invoice': date,
776                 'state': 'draft',
777                 'number': False,
778                 'invoice_line': invoice_lines,
779                 'tax_line': tax_lines
780             })
781             if period_id :
782                 invoice.update({
783                     'period_id': period_id,
784                 })
785             if description :
786                 invoice.update({
787                     'name': description,
788                 })
789             # take the id part of the tuple returned for many2one fields
790             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
791                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
792                 invoice[field] = invoice[field] and invoice[field][0]
793             # create the new invoice
794             new_ids.append(self.create(cr, uid, invoice))
795         return new_ids
796
797     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=''):
798         #TODO check if we can use different period for payment and the writeoff line
799         assert len(ids)==1, "Can only pay one invoice at a time"
800         invoice = self.browse(cr, uid, ids[0])
801         src_account_id = invoice.account_id.id
802         journal = self.pool.get('account.journal').browse(cr, uid, pay_journal_id)
803         if journal.sequence_id:
804             name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
805         else:
806             raise osv.except_osv(_('No piece number !'), _('Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.'))
807         # Take the seq as name for move
808         if journal.sequence_id:
809             seq = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
810         else:
811             raise osv.except_osv('No piece number !', 'Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.')
812         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
813         direction = types[invoice.type]
814         #take the choosen date
815         if context.has_key('date_p') and context['date_p']:
816             date=context['date_p']
817         else:
818             date=time.strftime('%Y-%m-%d')
819         l1 = {
820             'name': name,
821             'debit': direction * pay_amount>0 and direction * pay_amount,
822             'credit': direction * pay_amount<0 and - direction * pay_amount,
823             'account_id': src_account_id,
824             'partner_id': invoice.partner_id.id,
825             'date': date,
826             'ref':invoice.number,
827         }
828         l2 = {
829             'name':name,
830             'debit': direction * pay_amount<0 and - direction * pay_amount,
831             'credit': direction * pay_amount>0 and direction * pay_amount,
832             'account_id': pay_account_id,
833             'partner_id': invoice.partner_id.id,
834             'date': date,
835             'ref':invoice.number,
836         }
837
838         lines = [(0, 0, l1), (0, 0, l2)]
839         move = {'name': seq, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id}
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=%d', (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