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