merge
[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         self.button_reset_taxes(cr, uid, ids, context)
386         for inv in self.browse(cr, uid, ids):
387             if set_total:
388                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
389         return True
390
391     def _convert_ref(self, cr, uid, ref):
392         return (ref or '').replace('/','')
393
394     def _get_analytic_lines(self, cr, uid, id):
395         inv = self.browse(cr, uid, [id])[0]
396         cur_obj = self.pool.get('res.currency')
397
398         company_currency = inv.company_id.currency_id.id
399         if inv.type in ('out_invoice', 'in_refund'):
400             sign = 1
401         else:
402             sign = -1
403
404         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
405         for il in iml:
406             if il['account_analytic_id']:
407                 if inv.type in ('in_invoice', 'in_refund'):
408                     ref = inv.reference
409                 else:
410                     ref = self._convert_ref(cr, uid, inv.number)
411                 il['analytic_lines'] = [(0,0, {
412                     'name': il['name'],
413                     'date': inv['date_invoice'],
414                     'account_id': il['account_analytic_id'],
415                     'unit_amount': il['quantity'],
416                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
417                     'product_id': il['product_id'],
418                     'product_uom_id': il['uos_id'],
419                     'general_account_id': il['account_id'],
420                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
421                     'ref': ref,
422                 })]
423         return iml
424
425     def action_move_create(self, cr, uid, ids, *args):
426         ait_obj = self.pool.get('account.invoice.tax')
427         cur_obj = self.pool.get('res.currency')
428         acc_obj = self.pool.get('account.account')
429         self.button_compute(cr, uid, ids, context={}, set_total=False)
430         for inv in self.browse(cr, uid, ids):
431             if inv.move_id:
432                 continue
433             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
434                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
435             if not inv.date_invoice:
436                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
437             company_currency = inv.company_id.currency_id.id
438             # create the analytical lines
439             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
440             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
441             # one move line per invoice line
442             iml = self._get_analytic_lines(cr, uid, inv.id)
443             # check if taxes are all computed
444             compute_taxes = ait_obj.compute(cr, uid, inv.id)
445             if not inv.tax_line:
446                 for tax in compute_taxes.values():
447                     ait_obj.create(cr, uid, tax)
448             else:
449                 tax_key = []
450                 for tax in inv.tax_line:
451                     if tax.manual:
452                         continue
453                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
454                     tax_key.append(key)
455                     if not key in compute_taxes:
456                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
457                     base = compute_taxes[key]['base']
458                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
459                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
460                 for key in compute_taxes:
461                     if not key in tax_key:
462                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
463
464             # one move line per tax line
465             iml += ait_obj.move_line_get(cr, uid, inv.id)
466
467             if inv.type in ('in_invoice', 'in_refund'):
468                 ref = inv.reference
469             else:
470                 ref = self._convert_ref(cr, uid, inv.number)
471
472             diff_currency_p = inv.currency_id.id <> company_currency
473             # create one move line for the total and possibly adjust the other lines amount
474             total = 0
475             total_currency = 0
476             key_line=[]
477             for i in iml:
478                 if 'account_id' in i and 'taxes' in i:
479                     if not (i['account_id'],i['taxes']) in key_line:
480                         key_line.append((i['account_id'],i['taxes']))
481                 if inv.currency_id.id != company_currency:
482                     i['currency_id'] = inv.currency_id.id
483                     i['amount_currency'] = i['price']
484                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
485                             company_currency, i['price'],
486                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
487                 else:
488                     i['amount_currency'] = False
489                     i['currency_id'] = False
490                 i['ref'] = ref
491                 if inv.type in ('out_invoice','in_refund'):
492                     total += i['price']
493                     total_currency += i['amount_currency'] or i['price']
494                     i['price'] = - i['price']
495                 else:
496                     total -= i['price']
497                     total_currency -= i['amount_currency'] or i['price']
498             acc_id = inv.account_id.id
499
500             name = inv['name'] or '/'
501             iml_temp=[]
502             move_list=[]
503
504             for item in key_line:
505                 move_temp={}
506                 if acc_obj.browse(cr,uid,item[0]).merge_invoice:
507                     repeat=False
508                     for move_line in iml:
509                         if (move_line.has_key('account_id') and move_line['account_id']==item[0]) and ('taxes' in move_line and move_line['taxes']==item[1]):
510                             move_list.append(move_line)
511                             if repeat:
512                                 for key in move_line:
513                                     if key in ['name','amount_currency','price_unit','price','quantity']:
514                                         if key=='name':
515                                             move_temp[key]=move_temp[key] + "," +move_line[key]
516                                         else:
517                                             move_temp[key] +=move_line[key]
518                             else:
519                                 for key in move_line:
520                                     move_temp[key]=move_line[key]
521                                 repeat=True
522                 if move_temp:
523                     iml_temp.append(move_temp)
524
525             if len(iml_temp)<len(move_list):
526                 for old_elem in move_list:
527                     iml.remove(old_elem)
528                 for new_elem in iml_temp:
529                     iml.append(new_elem)
530
531             totlines = False
532             if inv.payment_term:
533                 totlines = self.pool.get('account.payment.term').compute(cr,
534                         uid, inv.payment_term.id, total, inv.date_invoice or False)
535             if totlines:
536                 res_amount_currency = total_currency
537                 i = 0
538                 for t in totlines:
539                     if inv.currency_id.id != company_currency:
540                         amount_currency = cur_obj.compute(cr, uid,
541                                 company_currency, inv.currency_id.id, t[1])
542                     else:
543                         amount_currency = False
544
545                     # last line add the diff
546                     res_amount_currency -= amount_currency or 0
547                     i += 1
548                     if i == len(totlines):
549                         amount_currency += res_amount_currency
550
551                     iml.append({
552                         'type': 'dest',
553                         'name': name,
554                         'price': t[1],
555                         'account_id': acc_id,
556                         'date_maturity': t[0],
557                         'amount_currency': diff_currency_p \
558                                 and  amount_currency or False,
559                         'currency_id': diff_currency_p \
560                                 and inv.currency_id.id or False,
561                         'ref': ref,
562                     })
563             else:
564                 iml.append({
565                     'type': 'dest',
566                     'name': name,
567                     'price': total,
568                     'account_id': acc_id,
569                     'date_maturity' : inv.date_due or False,
570                     'amount_currency': diff_currency_p \
571                             and total_currency or False,
572                     'currency_id': diff_currency_p \
573                             and inv.currency_id.id or False,
574                     'ref': ref
575             })
576
577             date = inv.date_invoice or time.strftime('%Y-%m-%d')
578             part = inv.partner_id.id
579
580             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
581
582             if inv.journal_id.group_invoice_lines:
583                 line2 = {}
584                 for x, y, l in line:
585                     tmp = str(l['account_id'])
586                     tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
587                     tmp += '-'+str('product_id' in l and l['product_id'] or "False")
588                     tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
589
590                     if tmp in line2:
591                         am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
592                         line2[tmp]['debit'] = (am > 0) and am or 0.0
593                         line2[tmp]['credit'] = (am < 0) and -am or 0.0
594                         line2[tmp]['tax_amount'] += l['tax_amount']
595                         line2[tmp]['analytic_lines'] += l['analytic_lines']
596                     else:
597                         line2[tmp] = l
598                 line = []
599                 for key, val in line2.items():
600                     line.append((0,0,val))
601
602             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
603             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
604             if journal.centralisation:
605                 raise osv.except_osv(_('UserError'),
606                         _('Can not create invoice move on centralized journal'))
607             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
608             period_id=inv.period_id and inv.period_id.id or False
609             if not period_id:
610                 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'))])
611                 if len(period_ids):
612                     period_id=period_ids[0]
613             if period_id:
614                 move['period_id'] = period_id
615                 for i in line:
616                     i[2]['period_id'] = period_id
617
618             if not 'name' in move:
619                 move['name'] = inv.name or '/'
620
621             move_id = self.pool.get('account.move').create(cr, uid, move)
622             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
623             # make the invoice point to that move
624             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
625             self.pool.get('account.move').post(cr, uid, [move_id])
626         self._log_event(cr, uid, ids)
627         return True
628
629     def line_get_convert(self, cr, uid, x, part, date, context={}):
630         return {
631             'date_maturity': x.get('date_maturity', False),
632             'partner_id':part,
633             'name':x['name'][:64],
634             'debit':x['price']>0 and x['price'],
635             'credit':x['price']<0 and -x['price'],
636             'account_id':x['account_id'],
637             'analytic_lines':x.get('analytic_lines', []),
638             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
639             'currency_id':x.get('currency_id', False),
640             'tax_code_id': x.get('tax_code_id', False),
641             'tax_amount': x.get('tax_amount', False),
642             'ref':x.get('ref',False),
643             'quantity':x.get('quantity',1.00),
644             'product_id':x.get('product_id', False),
645             'product_uom_id':x.get('uos_id',False),
646             'analytic_account_id':x.get('account_analytic_id',False),
647         }
648
649     def action_number(self, cr, uid, ids, *args):
650         cr.execute('SELECT id, type, number, move_id, reference ' \
651                 'FROM account_invoice ' \
652                 'WHERE id IN ('+','.join(map(str,ids))+')')
653         for (id, invtype, number, move_id, reference) in cr.fetchall():
654             if not number:
655                 number = self.pool.get('ir.sequence').get(cr, uid,
656                         'account.invoice.' + invtype)
657                 if invtype in ('in_invoice', 'in_refund'):
658                     ref = reference
659                 else:
660                     ref = self._convert_ref(cr, uid, number)
661                 cr.execute('UPDATE account_invoice SET number=%s ' \
662                         'WHERE id=%s', (number, id))
663                 cr.execute('UPDATE account_move SET ref=%s ' \
664                         'WHERE id=%s AND (ref is null OR ref = \'\')',
665                         (ref, move_id))
666                 cr.execute('UPDATE account_move_line SET ref=%s ' \
667                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
668                         (ref, move_id))
669                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
670                         'FROM account_move_line ' \
671                         'WHERE account_move_line.move_id = %s ' \
672                             'AND account_analytic_line.move_id = account_move_line.id',
673                             (ref, move_id))
674         return True
675
676     def action_cancel(self, cr, uid, ids, *args):
677         account_move_obj = self.pool.get('account.move')
678         invoices = self.read(cr, uid, ids, ['move_id'])
679         for i in invoices:
680             if i['move_id']:
681                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
682                 # delete the move this invoice was pointing to
683                 # Note that the corresponding move_lines and move_reconciles
684                 # will be automatically deleted too
685                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
686         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
687         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
688         return True
689
690     ###################
691
692     def list_distinct_taxes(self, cr, uid, ids):
693         invoices = self.browse(cr, uid, ids)
694         taxes = {}
695         for inv in invoices:
696             for tax in inv.tax_line:
697                 if not tax['name'] in taxes:
698                     taxes[tax['name']] = {'name': tax['name']}
699         return taxes.values()
700
701     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
702         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
703         for inv in invs:
704             part=inv['partner_id'] and inv['partner_id'][0]
705             pc = pr = 0.0
706             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
707             total = inv['amount_untaxed']
708             if inv['type'] in ('in_invoice','in_refund'):
709                 partnertype='supplier'
710                 eventtype = 'purchase'
711                 pc = total*factor
712             else:
713                 partnertype = 'customer'
714                 eventtype = 'sale'
715                 pr = total*factor
716             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
717                 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})
718         return len(invs)
719
720     def name_get(self, cr, uid, ids, context={}):
721         if not len(ids):
722             return []
723         types = {
724                 'out_invoice': 'CI: ',
725                 'in_invoice': 'SI: ',
726                 'out_refund': 'OR: ',
727                 'in_refund': 'SR: ',
728                 }
729         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')]
730
731     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
732         if not args:
733             args=[]
734         if not context:
735             context={}
736         ids = []
737         if name:
738             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
739         if not ids:
740             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
741         return self.name_get(cr, user, ids, context)
742
743     def _refund_cleanup_lines(self, lines):
744         for line in lines:
745             del line['id']
746             del line['invoice_id']
747             if 'account_id' in line:
748                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
749             if 'product_id' in line:
750                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
751             if 'uos_id' in line:
752                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
753             if 'invoice_line_tax_id' in line:
754                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
755             if 'account_analytic_id' in line:
756                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
757             if 'tax_code_id' in line :
758                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
759                     line['tax_code_id'] = line['tax_code_id'][0]
760             if 'base_code_id' in line :
761                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
762                     line['base_code_id'] = line['base_code_id'][0]
763         return map(lambda x: (0,0,x), lines)
764
765     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
766         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'])
767
768         new_ids = []
769         for invoice in invoices:
770             del invoice['id']
771
772             type_dict = {
773                 'out_invoice': 'out_refund', # Customer Invoice
774                 'in_invoice': 'in_refund',   # Supplier Invoice
775                 'out_refund': 'out_invoice', # Customer Refund
776                 'in_refund': 'in_invoice',   # Supplier Refund
777             }
778
779
780             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
781             invoice_lines = self._refund_cleanup_lines(invoice_lines)
782
783             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
784             tax_lines = filter(lambda l: l['manual'], tax_lines)
785             tax_lines = self._refund_cleanup_lines(tax_lines)
786             if not date :
787                 date = time.strftime('%Y-%m-%d')
788             invoice.update({
789                 'type': type_dict[invoice['type']],
790                 'date_invoice': date,
791                 'state': 'draft',
792                 'number': False,
793                 'invoice_line': invoice_lines,
794                 'tax_line': tax_lines
795             })
796             if period_id :
797                 invoice.update({
798                     'period_id': period_id,
799                 })
800             if description :
801                 invoice.update({
802                     'name': description,
803                 })
804             # take the id part of the tuple returned for many2one fields
805             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
806                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
807                 invoice[field] = invoice[field] and invoice[field][0]
808             # create the new invoice
809             new_ids.append(self.create(cr, uid, invoice))
810         return new_ids
811
812     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=''):
813         #TODO check if we can use different period for payment and the writeoff line
814         assert len(ids)==1, "Can only pay one invoice at a time"
815         invoice = self.browse(cr, uid, ids[0])
816         src_account_id = invoice.account_id.id
817         # Take the seq as name for move
818         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
819         direction = types[invoice.type]
820         #take the choosen date
821         if 'date_p' in context and context['date_p']:
822             date=context['date_p']
823         else:
824             date=time.strftime('%Y-%m-%d')
825         l1 = {
826             'debit': direction * pay_amount>0 and direction * pay_amount,
827             'credit': direction * pay_amount<0 and - direction * pay_amount,
828             'account_id': src_account_id,
829             'partner_id': invoice.partner_id.id,
830             'ref':invoice.number,
831         }
832         l2 = {
833             'debit': direction * pay_amount<0 and - direction * pay_amount,
834             'credit': direction * pay_amount>0 and direction * pay_amount,
835             'account_id': pay_account_id,
836             'partner_id': invoice.partner_id.id,
837             'ref':invoice.number,
838         }
839
840         name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
841         l1['name'] = name
842         l2['name'] = name
843
844         lines = [(0, 0, l1), (0, 0, l2)]
845         move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
846         move_id = self.pool.get('account.move').create(cr, uid, move)
847
848         line_ids = []
849         total = 0.0
850         line = self.pool.get('account.move.line')
851         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
852         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
853         for l in lines:
854             if l.account_id.id==src_account_id:
855                 line_ids.append(l.id)
856                 total += (l.debit or 0.0) - (l.credit or 0.0)
857         if (not total) or writeoff_acc_id:
858             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
859         else:
860             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
861
862         # Update the stored value (fields.function), so we write to trigger recompute
863         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
864         return True
865 account_invoice()
866
867 class account_invoice_line(osv.osv):
868     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
869         res = {}
870         for line in self.browse(cr, uid, ids):
871             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
872         return res
873
874     def _price_unit_default(self, cr, uid, context={}):
875         if 'check_total' in context:
876             t = context['check_total']
877             for l in context.get('invoice_line', {}):
878                 if len(l) >= 3 and l[2]:
879                     tax_obj = self.pool.get('account.tax')
880                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
881                     t = t - (p * l[2].get('quantity'))
882                     taxes = l[2].get('invoice_line_tax_id')
883                     if len(taxes[0]) >= 3 and taxes[0][2]:
884                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
885                         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)):
886                             t = t - tax['amount']
887             return t
888         return 0
889
890     _name = "account.invoice.line"
891     _description = "Invoice line"
892     _columns = {
893         'name': fields.char('Description', size=256, required=True),
894         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
895         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
896         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
897         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
898         '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."),
899         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
900         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
901         'quantity': fields.float('Quantity', required=True),
902         'discount': fields.float('Discount (%)', digits=(16,2)),
903         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
904         'note': fields.text('Notes'),
905         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
906     }
907     _defaults = {
908         'quantity': lambda *a: 1,
909         'discount': lambda *a: 0.0,
910         'price_unit': _price_unit_default,
911     }
912
913     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context={}):
914         tax_obj = self.pool.get('account.tax')
915         if price_unit:
916             taxes = tax_obj.browse(cr, uid, tax_id)
917             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
918                 price_unit = price_unit - tax['amount']
919         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
920
921     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={}):
922         if not partner_id:
923             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
924         if not product:
925             if type in ('in_invoice', 'in_refund'):
926                 return {'domain':{'product_uom':[]}}
927             else:
928                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
929         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
930         lang=part.lang
931         context.update({'lang': lang})
932         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
933         taxep=None
934         tax_obj = self.pool.get('account.tax')
935         if type in ('out_invoice', 'out_refund'):
936             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.taxes_id)
937         else:
938             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.supplier_taxes_id)
939         if type in ('in_invoice', 'in_refund'):
940             result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
941         else:
942             result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
943
944         if not name:
945             result['name'] = res.name
946
947         if type in ('out_invoice','out_refund'):
948             a =  res.product_tmpl_id.property_account_income.id
949             if not a:
950                 a = res.categ_id.property_account_income_categ.id
951         else:
952             a =  res.product_tmpl_id.property_account_expense.id
953             if not a:
954                 a = res.categ_id.property_account_expense_categ.id
955
956         a = self.pool.get('account.fiscal.position').map_account(cr, uid, part, a)
957         if a:
958             result['account_id'] = a
959
960         domain = {}
961         result['uos_id'] = uom or res.uom_id.id or False
962         if result['uos_id']:
963             res2 = res.uom_id.category_id.id
964             if res2 :
965                 domain = {'uos_id':[('category_id','=',res2 )]}
966         return {'value':result, 'domain':domain}
967
968     def move_line_get(self, cr, uid, invoice_id, context={}):
969         res = []
970         tax_grouped = {}
971         tax_obj = self.pool.get('account.tax')
972         cur_obj = self.pool.get('res.currency')
973         ait_obj = self.pool.get('account.invoice.tax')
974         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
975         company_currency = inv.company_id.currency_id.id
976         cur = inv.currency_id
977
978         for line in inv.invoice_line:
979             mres = self.move_line_get_item(cr, uid, line, context)
980             if not mres:
981                 continue
982             res.append(mres)
983             tax_code_found= False
984             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
985                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
986                     line.quantity, inv.address_invoice_id.id, line.product_id,
987                     inv.partner_id):
988
989                 if inv.type in ('out_invoice', 'in_invoice'):
990                     tax_code_id = tax['base_code_id']
991                     tax_amount = line.price_subtotal * tax['base_sign']
992                 else:
993                     tax_code_id = tax['ref_base_code_id']
994                     tax_amount = line.price_subtotal * tax['ref_base_sign']
995
996                 if tax_code_found:
997                     if not tax_code_id:
998                         continue
999                     res.append(self.move_line_get_item(cr, uid, line, context))
1000                     res[-1]['price'] = 0.0
1001                     res[-1]['account_analytic_id'] = False
1002                 elif not tax_code_id:
1003                     continue
1004                 tax_code_found = True
1005
1006                 res[-1]['tax_code_id'] = tax_code_id
1007                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1008         return res
1009
1010     def move_line_get_item(self, cr, uid, line, context={}):
1011         return {
1012             'type':'src',
1013             'name': line.name[:64],
1014             'price_unit':line.price_unit,
1015             'quantity':line.quantity,
1016             'price':line.price_subtotal,
1017             'account_id':line.account_id.id,
1018             'product_id':line.product_id.id,
1019             'uos_id':line.uos_id.id,
1020             'account_analytic_id':line.account_analytic_id.id,
1021             'taxes':line.invoice_line_tax_id,
1022         }
1023     #
1024     # Set the tax field according to the account and the partner
1025     #
1026     def onchange_account_id(self, cr, uid, ids, partner_id,account_id):
1027         if not (partner_id and account_id):
1028             return {}
1029         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1030         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1031
1032         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, taxes)
1033         r = {'value':{'invoice_line_tax_id': res}}
1034         return r
1035 account_invoice_line()
1036
1037 class account_invoice_tax(osv.osv):
1038     _name = "account.invoice.tax"
1039     _description = "Invoice Tax"
1040     _columns = {
1041         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1042         'name': fields.char('Tax Description', size=64, required=True),
1043         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1044         'base': fields.float('Base', digits=(16,2)),
1045         'amount': fields.float('Amount', digits=(16,2)),
1046         'manual': fields.boolean('Manual'),
1047         'sequence': fields.integer('Sequence'),
1048
1049         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1050         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1051         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1052         'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1053     }
1054     def base_change(self, cr, uid, ids, base):
1055         return {'value': {'base_amount':base}}
1056     def amount_change(self, cr, uid, ids, amount):
1057         return {'value': {'tax_amount':amount}}
1058     _order = 'sequence'
1059     _defaults = {
1060         'manual': lambda *a: 1,
1061         'base_amount': lambda *a: 0.0,
1062         'tax_amount': lambda *a: 0.0,
1063     }
1064     def compute(self, cr, uid, invoice_id):
1065         tax_grouped = {}
1066         tax_obj = self.pool.get('account.tax')
1067         cur_obj = self.pool.get('res.currency')
1068         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1069         cur = inv.currency_id
1070         company_currency = inv.company_id.currency_id.id
1071
1072         for line in inv.invoice_line:
1073             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):
1074                 val={}
1075                 val['invoice_id'] = inv.id
1076                 val['name'] = tax['name']
1077                 val['amount'] = tax['amount']
1078                 val['manual'] = False
1079                 val['sequence'] = tax['sequence']
1080                 val['base'] = tax['price_unit'] * line['quantity']
1081
1082                 if inv.type in ('out_invoice','in_invoice'):
1083                     val['base_code_id'] = tax['base_code_id']
1084                     val['tax_code_id'] = tax['tax_code_id']
1085                     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')})
1086                     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')})
1087                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1088                 else:
1089                     val['base_code_id'] = tax['ref_base_code_id']
1090                     val['tax_code_id'] = tax['ref_tax_code_id']
1091                     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')})
1092                     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')})
1093                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1094
1095                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1096                 if not key in tax_grouped:
1097                     tax_grouped[key] = val
1098                 else:
1099                     tax_grouped[key]['amount'] += val['amount']
1100                     tax_grouped[key]['base'] += val['base']
1101                     tax_grouped[key]['base_amount'] += val['base_amount']
1102                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1103
1104         for t in tax_grouped.values():
1105             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1106         return tax_grouped
1107
1108     def move_line_get(self, cr, uid, invoice_id):
1109         res = []
1110         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1111         for t in cr.dictfetchall():
1112             if not t['amount'] \
1113                     and not t['tax_code_id'] \
1114                     and not t['tax_amount']:
1115                 continue
1116             res.append({
1117                 'type':'tax',
1118                 'name':t['name'],
1119                 'price_unit': t['amount'],
1120                 'quantity': 1,
1121                 'price': t['amount'] or 0.0,
1122                 'account_id': t['account_id'],
1123                 'tax_code_id': t['tax_code_id'],
1124                 'tax_amount': t['tax_amount']
1125             })
1126         return res
1127 account_invoice_tax()
1128
1129 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1130