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