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