*corrected proforma stuff: a proforma invoice do NOT have to create entries
[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', required=True, 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 = self._defaults["date_invoice"](cr,uid,{})
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'] = time.strftime('%Y-%m-%d')
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             company_currency = inv.company_id.currency_id.id
432             # create the analytical lines
433             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
434             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
435             # one move line per invoice line
436             iml = self._get_analityc_lines(cr, uid, inv.id)
437             # check if taxes are all computed
438             compute_taxes = ait_obj.compute(cr, uid, inv.id)
439             if not inv.tax_line:
440                 for tax in compute_taxes.values():
441                     ait_obj.create(cr, uid, tax)
442             else:
443                 tax_key = []
444                 for tax in inv.tax_line:
445                     if tax.manual:
446                         continue
447                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
448                     tax_key.append(key)
449                     if not key in compute_taxes:
450                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
451                     base = compute_taxes[key]['base']
452                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
453                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
454                 for key in compute_taxes:
455                     if not key in tax_key:
456                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
457
458             # one move line per tax line
459             iml += ait_obj.move_line_get(cr, uid, inv.id)
460
461             if inv.type in ('in_invoice', 'in_refund'):
462                 ref = inv.reference
463             else:
464                 ref = self._convert_ref(cr, uid, inv.number)
465
466             diff_currency_p = inv.currency_id.id <> company_currency
467             # create one move line for the total and possibly adjust the other lines amount
468             total = 0
469             total_currency = 0
470             for i in iml:
471                 if inv.currency_id.id != company_currency:
472                     i['currency_id'] = inv.currency_id.id
473                     i['amount_currency'] = i['price']
474                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
475                             company_currency, i['price'],
476                             context={'date': inv.date_invoice})
477                 else:
478                     i['amount_currency'] = False
479                     i['currency_id'] = False
480                 i['ref'] = ref
481                 if inv.type in ('out_invoice','in_refund'):
482                     total += i['price']
483                     total_currency += i['amount_currency'] or i['price']
484                     i['price'] = - i['price']
485                 else:
486                     total -= i['price']
487                     total_currency -= i['amount_currency'] or i['price']
488             acc_id = inv.account_id.id
489
490             name = inv['name'] or '/'
491             totlines = False
492             if inv.payment_term:
493                 totlines = self.pool.get('account.payment.term').compute(cr,
494                         uid, inv.payment_term.id, total, inv.date_invoice or False)
495             if totlines:
496                 res_amount_currency = total_currency
497                 i = 0
498                 for t in totlines:
499                     if inv.currency_id.id != company_currency:
500                         amount_currency = cur_obj.compute(cr, uid,
501                                 company_currency, inv.currency_id.id, t[1])
502                     else:
503                         amount_currency = False
504
505                     # last line add the diff
506                     res_amount_currency -= amount_currency or 0
507                     i += 1
508                     if i == len(totlines):
509                         amount_currency += res_amount_currency
510
511                     iml.append({
512                         'type': 'dest',
513                         'name': name,
514                         'price': t[1],
515                         'account_id': acc_id,
516                         'date_maturity': t[0],
517                         'amount_currency': diff_currency_p \
518                                 and  amount_currency or False,
519                         'currency_id': diff_currency_p \
520                                 and inv.currency_id.id or False,
521                         'ref': ref,
522                     })
523             else:
524                 iml.append({
525                     'type': 'dest',
526                     'name': name,
527                     'price': total,
528                     'account_id': acc_id,
529                     'date_maturity' : inv.date_due or False,
530                     'amount_currency': diff_currency_p \
531                             and total_currency or False,
532                     'currency_id': diff_currency_p \
533                             and inv.currency_id.id or False,
534                     'ref': ref
535             })
536
537             date = inv.date_invoice
538             part = inv.partner_id.id
539             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
540
541             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
542             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
543             if journal.sequence_id:
544                 name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
545             if journal.centralisation:
546                 raise osv.except_osv(_('UserError'),
547                         _('Can not create invoice move on centralized journal'))
548
549             move = {'name': name, 'line_id': line, 'journal_id': journal_id}
550             period_id=inv.period_id and inv.period_id.id or False
551             if not period_id:
552                 period_ids= self.pool.get('account.period').search(cr,uid,[('date_start','<=',inv.date_invoice),('date_stop','>=',inv.date_invoice)])
553                 if len(period_ids):
554                     period_id=period_ids[0]
555             if period_id:
556                 move['period_id'] = period_id
557                 for i in line:
558                     i[2]['period_id'] = period_id
559             move_id = self.pool.get('account.move').create(cr, uid, move)
560             # make the invoice point to that move
561             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id})
562             self.pool.get('account.move').post(cr, uid, [move_id])
563         self._log_event(cr, uid, ids)
564         return True
565
566     def line_get_convert(self, cr, uid, x, part, date, context={}):
567         return {
568             'date':date,
569             'date_maturity': x.get('date_maturity', False),
570             'partner_id':part,
571             'name':x['name'][:64],
572             'debit':x['price']>0 and x['price'],
573             'credit':x['price']<0 and -x['price'],
574             'account_id':x['account_id'],
575             'analytic_lines':x.get('analytic_lines', []),
576             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
577             'currency_id':x.get('currency_id', False),
578             'tax_code_id': x.get('tax_code_id', False),
579             'tax_amount': x.get('tax_amount', False),
580             'ref':x.get('ref',False)
581         }
582
583     def action_number(self, cr, uid, ids, *args):
584         cr.execute('SELECT id, type, number, move_id, reference ' \
585                 'FROM account_invoice ' \
586                 'WHERE id IN ('+','.join(map(str,ids))+')')
587         for (id, invtype, number, move_id, reference) in cr.fetchall():
588             if not number:
589                 number = self.pool.get('ir.sequence').get(cr, uid,
590                         'account.invoice.' + invtype)
591                 if invtype in ('in_invoice', 'in_refund'):
592                     ref = reference
593                 else:
594                     ref = self._convert_ref(cr, uid, number)
595                 cr.execute('UPDATE account_invoice SET number=%s ' \
596                         'WHERE id=%d', (number, id))
597                 cr.execute('UPDATE account_move_line SET ref=%s ' \
598                         'WHERE move_id=%d AND (ref is null OR ref = \'\')',
599                         (ref, move_id))
600                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
601                         'FROM account_move_line ' \
602                         'WHERE account_move_line.move_id = %d ' \
603                             'AND account_analytic_line.move_id = account_move_line.id',
604                             (ref, move_id))
605         return True
606
607     def action_cancel(self, cr, uid, ids, *args):
608         account_move_obj = self.pool.get('account.move')
609         invoices = self.read(cr, uid, ids, ['move_id'])
610         for i in invoices:
611             if i['move_id']:
612                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
613                 # delete the move this invoice was pointing to
614                 # Note that the corresponding move_lines and move_reconciles
615                 # will be automatically deleted too
616                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
617         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
618         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
619         return True
620
621     ###################
622
623     def list_distinct_taxes(self, cr, uid, ids):
624         invoices = self.browse(cr, uid, ids)
625         taxes = {}
626         for inv in invoices:
627             for tax in inv.tax_line:
628                 if not tax['name'] in taxes:
629                     taxes[tax['name']] = {'name': tax['name']}
630         return taxes.values()
631
632     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
633         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
634         for inv in invs:
635             part=inv['partner_id'] and inv['partner_id'][0]
636             pc = pr = 0.0
637             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%d', (inv['id'],))
638             total = inv['amount_untaxed']
639             if inv['type'] in ('in_invoice','in_refund'):
640                 partnertype='supplier'
641                 eventtype = 'purchase'
642                 pc = total*factor
643             else:
644                 partnertype = 'customer'
645                 eventtype = 'sale'
646                 pr = total*factor
647             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
648                 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})
649         return len(invs)
650
651     def name_get(self, cr, uid, ids, context={}):
652         if not len(ids):
653             return []
654         types = {
655                 'out_invoice': 'CI: ',
656                 'in_invoice': 'SI: ',
657                 'out_refund': 'OR: ',
658                 'in_refund': 'SR: ',
659                 }
660         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')]
661
662     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
663         if not args:
664             args=[]
665         if not context:
666             context={}
667         ids = []
668         if name:
669             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
670         if not ids:
671             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
672         return self.name_get(cr, user, ids, context)
673
674     def _refund_cleanup_lines(self, lines):
675         for line in lines:
676             del line['id']
677             del line['invoice_id']
678             if 'account_id' in line:
679                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
680             if 'product_id' in line:
681                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
682             if 'uos_id' in line:
683                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
684             if 'invoice_line_tax_id' in line:
685                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
686             if 'account_analytic_id' in line:
687                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
688         return map(lambda x: (0,0,x), lines)
689
690     def refund(self, cr, uid, ids):
691         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'])
692
693         new_ids = []
694         for invoice in invoices:
695             del invoice['id']
696
697             type_dict = {
698                 'out_invoice': 'out_refund', # Customer Invoice
699                 'in_invoice': 'in_refund',   # Supplier Invoice
700                 'out_refund': 'out_invoice', # Customer Refund
701                 'in_refund': 'in_invoice',   # Supplier Refund
702             }
703
704
705             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
706             invoice_lines = self._refund_cleanup_lines(invoice_lines)
707
708             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
709             tax_lines = filter(lambda l: l['manual'], tax_lines)
710             tax_lines = self._refund_cleanup_lines(tax_lines)
711
712             invoice.update({
713                 'type': type_dict[invoice['type']],
714                 'date_invoice': time.strftime('%Y-%m-%d'),
715                 'state': 'draft',
716                 'number': False,
717                 'invoice_line': invoice_lines,
718                 'tax_line': tax_lines
719             })
720
721             # take the id part of the tuple returned for many2one fields
722             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
723                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
724                 invoice[field] = invoice[field] and invoice[field][0]
725
726             # create the new invoice
727             new_ids.append(self.create(cr, uid, invoice))
728         return new_ids
729
730     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=''):
731         #TODO check if we can use different period for payment and the writeoff line
732         assert len(ids)==1, "Can only pay one invoice at a time"
733         invoice = self.browse(cr, uid, ids[0])
734         src_account_id = invoice.account_id.id
735         journal = self.pool.get('account.journal').browse(cr, uid, pay_journal_id)
736         if journal.sequence_id:
737             name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
738         else:
739             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.'))
740         # Take the seq as name for move
741         if journal.sequence_id:
742             seq = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
743         else:
744             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.')
745         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
746         direction = types[invoice.type]
747         #take the choosen date
748         if context.has_key('date_p') and context['date_p']:
749             date=context['date_p']
750         else:
751             date=time.strftime('%Y-%m-%d')
752         l1 = {
753             'name': name,
754             'debit': direction * pay_amount>0 and direction * pay_amount,
755             'credit': direction * pay_amount<0 and - direction * pay_amount,
756             'account_id': src_account_id,
757             'partner_id': invoice.partner_id.id,
758             'date': date,
759             'ref':invoice.number,
760         }
761         l2 = {
762             'name':name,
763             'debit': direction * pay_amount<0 and - direction * pay_amount,
764             'credit': direction * pay_amount>0 and direction * pay_amount,
765             'account_id': pay_account_id,
766             'partner_id': invoice.partner_id.id,
767             'date': date,
768             'ref':invoice.number,
769         }
770
771         lines = [(0, 0, l1), (0, 0, l2)]
772         move = {'name': seq, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id}
773         move_id = self.pool.get('account.move').create(cr, uid, move)
774
775         line_ids = []
776         total = 0.0
777         line = self.pool.get('account.move.line')
778         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
779         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
780         for l in lines:
781             if l.account_id.id==src_account_id:
782                 line_ids.append(l.id)
783                 total += (l.debit or 0.0) - (l.credit or 0.0)
784         if (not total) or writeoff_acc_id:
785             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
786         else:
787             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
788         return True
789 account_invoice()
790
791 class account_invoice_line(osv.osv):
792     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
793         res = {}
794         for line in self.browse(cr, uid, ids):
795             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
796         return res
797
798     def _price_unit_default(self, cr, uid, context={}):
799         if 'check_total' in context:
800             t = context['check_total']
801             for l in context.get('invoice_line', {}):
802                 if len(l) >= 3 and l[2]:
803                     tax_obj = self.pool.get('account.tax')
804                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
805                     t = t - (p * l[2].get('quantity'))
806                     taxes = l[2].get('invoice_line_tax_id')
807                     if len(taxes[0]) >= 3 and taxes[0][2]:
808                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
809                         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)):
810                             t = t - tax['amount']
811             return t
812         return 0
813
814     _name = "account.invoice.line"
815     _description = "Invoice line"
816     _columns = {
817         'name': fields.char('Description', size=256, required=True),
818         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
819         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
820         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
821         '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."),
822         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
823         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
824         'quantity': fields.float('Quantity', required=True),
825         'discount': fields.float('Discount (%)', digits=(16,2)),
826         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
827         'note': fields.text('Notes'),
828         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
829     }
830     _defaults = {
831         'quantity': lambda *a: 1,
832         'discount': lambda *a: 0.0,
833         'price_unit': _price_unit_default,
834     }
835
836     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context={}):
837         tax_obj = self.pool.get('account.tax')
838         if price_unit:
839             taxes = tax_obj.browse(cr, uid, tax_id)
840             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
841                 price_unit = price_unit - tax['amount']
842         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
843
844     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={}):
845         if not partner_id:
846             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
847         if not product:
848             if type in ('in_invoice', 'in_refund'):
849                 return {'domain':{'product_uom':[]}}
850             else:
851                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
852         lang=False
853         context.update({'lang': lang})
854         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
855         taxep=None
856         lang=self.pool.get('res.partner').read(cr, uid, [partner_id])[0]['lang']
857         tax_obj = self.pool.get('account.tax')
858         if type in ('out_invoice', 'out_refund'):
859             taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_tax
860             if not taxep or not taxep.id:
861                 tax_id = map(lambda x: x.id, res.taxes_id)
862             else:
863                 tax_id = [taxep.id]
864                 for t in res.taxes_id:
865                     if not t.tax_group==taxep.tax_group:
866                         tax_id.append(t.id)
867         else:
868             taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_supplier_tax
869             if not taxep or not taxep.id:
870                 tax_id = map(lambda x: x.id, res.supplier_taxes_id)
871             else:
872                 tax_id = [taxep.id]
873                 for t in res.supplier_taxes_id:
874                     if not t.tax_group==taxep.tax_group:
875                         tax_id.append(t.id)
876         if type in ('in_invoice', 'in_refund'):
877             result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
878         else:
879             result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
880
881         if not name:
882             result['name'] = res.name
883
884         if type in ('out_invoice','out_refund'):
885             a =  res.product_tmpl_id.property_account_income.id
886             if not a:
887                 a = res.categ_id.property_account_income_categ.id
888         else:
889             a =  res.product_tmpl_id.property_account_expense.id
890             if not a:
891                 a = res.categ_id.property_account_expense_categ.id
892         if a:
893             result['account_id'] = a
894
895         domain = {}
896         result['uos_id'] = uom or res.uom_id.id or False
897         if result['uos_id']:
898             res2 = res.uom_id.category_id.id
899             if res2 :
900                 domain = {'uos_id':[('category_id','=',res2 )]}
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', help="The case of the tax declaration."),
988         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
989         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
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