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