[MOD,ADD] account_pay_invoice converted to osv memory wizard.
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 import decimal_precision as dp
24
25 import netsvc
26 from osv import fields, osv
27 import ir
28 import pooler
29 from tools import config
30 from tools.translate import _
31
32 class account_invoice(osv.osv):
33     def _amount_all(self, cr, uid, ids, name, args, context=None):
34         res = {}
35         for invoice in self.browse(cr,uid,ids, context=context):
36             res[invoice.id] = {
37                 'amount_untaxed': 0.0,
38                 'amount_tax': 0.0,
39                 'amount_total': 0.0
40             }
41             for line in invoice.invoice_line:
42                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
43             for line in invoice.tax_line:
44                 res[invoice.id]['amount_tax'] += line.amount
45             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
46         return res
47
48     def _get_journal(self, cr, uid, context):
49         if context is None:
50             context = {}
51         type_inv = context.get('type', 'out_invoice')
52         user = self.pool.get('res.users').browse(cr, uid, uid)
53         company_id = context.get('company_id', user.company_id.id)
54         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
55         refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
56         journal_obj = self.pool.get('account.journal')
57         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
58                                             ('company_id', '=', company_id),('refund_journal', '=', refund_journal.get(type_inv, False))],
59                                                 limit=1)
60         if res:
61             return res[0]
62         else:
63             return False
64
65     def _get_currency(self, cr, uid, context):
66         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
67         if user.company_id:
68             return user.company_id.currency_id.id
69         else:
70             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
71
72     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
73         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
74         tt = type2journal.get(type_inv, 'sale')
75         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
76         if not result:
77             raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
78         return result[0]
79
80     def _get_type(self, cr, uid, context=None):
81         if context is None:
82             context = {}
83         type = context.get('type', 'out_invoice')
84         return type
85
86     def _reconciled(self, cr, uid, ids, name, args, context):
87         res = {}
88         for id in ids:
89             res[id] = self.test_paid(cr, uid, [id])
90         return res
91
92     def _get_reference_type(self, cr, uid, context=None):
93         return [('none', _('Free Reference'))]
94
95     def _amount_residual(self, cr, uid, ids, name, args, context=None):
96         res = {}
97         data_inv = self.browse(cr, uid, ids)
98         cur_obj = self.pool.get('res.currency')
99         for inv in data_inv:
100             debit = credit = 0.0
101             context.update({'date':inv.date_invoice})
102             context_unreconciled=context.copy()
103             for lines in inv.move_lines:
104                 debit_tmp = lines.debit
105                 credit_tmp = lines.credit
106                 # If currency conversion needed
107                 if inv.company_id.currency_id.id <> inv.currency_id.id:
108                     # If invoice paid, compute currency amount according to invoice date
109                     # otherwise, take the line date
110                     if not inv.reconciled:
111                         context.update({'date':lines.date})
112                     context_unreconciled.update({'date':lines.date})
113                     # If amount currency setted, compute for debit and credit in company currency
114                     if lines.amount_currency < 0:
115                         credit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False,context=context_unreconciled))
116                     elif lines.amount_currency > 0:
117                         debit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False,context=context_unreconciled))
118                     # Then, recomput into invoice currency to avoid rounding trouble !
119                     debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
120                     credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
121                 else:
122                     debit+=debit_tmp
123                     credit+=credit_tmp
124
125             if not inv.amount_total:
126                 result = 0.0
127             elif inv.type in ('out_invoice','in_refund'):
128                 amount = credit-debit
129                 result = inv.amount_total - amount
130             else:
131                 amount = debit-credit
132                 result = inv.amount_total - amount
133             # Use is_zero function to avoid rounding trouble => should be fixed into ORM
134             res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
135
136         return res
137
138     def _get_lines(self, cr, uid, ids, name, arg, context=None):
139         res = {}
140         for id in ids:
141             move_lines = self.move_line_id_payment_get(cr,uid,[id])
142             if not move_lines:
143                 res[id] = []
144                 continue
145             res[id] = []
146             data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
147             partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
148             for line in data_lines:
149                 ids_line = []
150                 if line.reconcile_id:
151                     ids_line = line.reconcile_id.line_id
152                 elif line.reconcile_partial_id:
153                     ids_line = line.reconcile_partial_id.line_partial_ids
154                 l = map(lambda x: x.id, ids_line)
155                 partial_ids.append(line.id)
156                 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
157         return res
158
159     def _get_invoice_line(self, cr, uid, ids, context=None):
160         result = {}
161         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
162             result[line.invoice_id.id] = True
163         return result.keys()
164
165     def _get_invoice_tax(self, cr, uid, ids, context=None):
166         result = {}
167         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
168             result[tax.invoice_id.id] = True
169         return result.keys()
170
171     def _compute_lines(self, cr, uid, ids, name, args, context=None):
172         result = {}
173         for invoice in self.browse(cr, uid, ids, context):
174             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
175             src = []
176             lines = []
177             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
178                 temp_lines = []#Added temp list to avoid duplicate records
179                 if m.reconcile_id:
180                     temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
181                 elif m.reconcile_partial_id:
182                     temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
183                 lines += [x for x in temp_lines if x not in lines]
184                 src.append(m.id)
185
186             lines = filter(lambda x: x not in src, lines)
187             result[invoice.id] = lines
188         return result
189
190     def _get_invoice_from_line(self, cr, uid, ids, context={}):
191         move = {}
192         for line in self.pool.get('account.move.line').browse(cr, uid, ids):
193             if line.reconcile_partial_id:
194                 for line2 in line.reconcile_partial_id.line_partial_ids:
195                     move[line2.move_id.id] = True
196             if line.reconcile_id:
197                 for line2 in line.reconcile_id.line_id:
198                     move[line2.move_id.id] = True
199         invoice_ids = []
200         if move:
201             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
202         return invoice_ids
203
204     def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
205         move = {}
206         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
207             for line in r.line_partial_ids:
208                 move[line.move_id.id] = True
209             for line in r.line_id:
210                 move[line.move_id.id] = True
211
212         invoice_ids = []
213         if move:
214             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
215         return invoice_ids
216
217     _name = "account.invoice"
218     _description = 'Invoice'
219     _order = "number"
220     _columns = {
221         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
222         'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice."),
223         'type': fields.selection([
224             ('out_invoice','Customer Invoice'),
225             ('in_invoice','Supplier Invoice'),
226             ('out_refund','Customer Refund'),
227             ('in_refund','Supplier Refund'),
228             ],'Type', readonly=True, select=True, change_default=True),
229
230         'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
231         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
232         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
233             required=True),
234         'comment': fields.text('Additional Information', translate=True),
235
236         'state': fields.selection([
237             ('draft','Draft'),
238             ('proforma','Pro-forma'),
239             ('proforma2','Pro-forma'),
240             ('open','Open'),
241             ('paid','Done'),
242             ('cancel','Cancelled')
243             ],'State', select=True, readonly=True,
244             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
245             \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
246             \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
247             \n* The \'Done\' state is set automatically when invoice is paid.\
248             \n* The \'Cancelled\' state is used when user cancel invoice.'),
249         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
250         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
251             help="If you use payment terms, the due date will be computed automatically at the generation "\
252                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."),
253         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
254         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
255         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
256         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
257             help="If you use payment terms, the due date will be computed automatically at the generation "\
258                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
259                 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
260         'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
261
262         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
263         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
264         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
265
266         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Links to the automatically generated Ledger Postings."),
267         'amount_untaxed': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'),string='Untaxed',
268             store={
269                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
270                 'account.invoice.tax': (_get_invoice_tax, None, 20),
271                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
272             },
273             multi='all'),
274         'amount_tax': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Tax',
275             store={
276                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
277                 'account.invoice.tax': (_get_invoice_tax, None, 20),
278                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
279             },
280             multi='all'),
281         'amount_total': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Total',
282             store={
283                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
284                 'account.invoice.tax': (_get_invoice_tax, None, 20),
285                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
286             },
287             multi='all'),
288         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
289         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
290         'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True),
291         'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
292         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
293             store={
294                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
295                 'account.move.line': (_get_invoice_from_line, None, 50),
296                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
297             }, help="The Ledger Postings of the invoice have been reconciled with Ledger Postings of the payment(s)."),
298         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
299             help='The bank account to pay to or to be paid from'),
300         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Entry Lines'),
301         'residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'),string='Residual',
302             store={
303                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
304                 'account.invoice.tax': (_get_invoice_tax, None, 50),
305                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
306                 'account.move.line': (_get_invoice_from_line, None, 50),
307                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
308             },
309             help="Remaining amount due."),
310         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
311         'move_name': fields.char('Ledger Posting', size=64),
312         'user_id': fields.many2one('res.users', 'Salesman'),
313         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
314     }
315     _defaults = {
316         'type': _get_type,
317         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
318         'state': lambda *a: 'draft',
319         'journal_id': _get_journal,
320         'currency_id': _get_currency,
321         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
322         'reference_type': lambda *a: 'none',
323         'check_total': lambda *a: 0.0,
324         'user_id': lambda s,cr,u,c: u,
325     }
326
327     def unlink(self, cr, uid, ids, context=None):
328         invoices = self.read(cr, uid, ids, ['state'])
329         unlink_ids = []
330         for t in invoices:
331             if t['state'] in ('draft', 'cancel'):
332                 unlink_ids.append(t['id'])
333             else:
334                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
335         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
336         return True
337
338 #   def get_invoice_address(self, cr, uid, ids):
339 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
340 #       return [{}]
341     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
342             date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
343         invoice_addr_id = False
344         contact_addr_id = False
345         partner_payment_term = False
346         acc_id = False
347         bank_id = False
348         fiscal_position = False
349
350         opt = [('uid', str(uid))]
351         if partner_id:
352
353             opt.insert(0, ('id', partner_id))
354             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
355             contact_addr_id = res['contact']
356             invoice_addr_id = res['invoice']
357             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
358             if company_id:
359                 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
360                     rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
361                     pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
362                     if not rec_pro_id:
363                         rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
364                     if not pay_pro_id:
365                         pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
366                     rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
367                     pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
368                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
369                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
370                     if not rec_res_id and not pay_res_id:
371                         raise osv.except_osv(_('Configration Error !'),
372                             _('Can not find account chart for this company, Please Create account.'))
373                     rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
374                     pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
375                     p.property_account_receivable = rec_obj_acc[0]
376                     p.property_account_payable = pay_obj_acc[0]
377
378             if type in ('out_invoice', 'out_refund'):
379                 acc_id = p.property_account_receivable.id
380             else:
381                 acc_id = p.property_account_payable.id
382             fiscal_position = p.property_account_position and p.property_account_position.id or False
383             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
384             if p.bank_ids:
385                 bank_id = p.bank_ids[0].id
386
387         result = {'value': {
388             'address_contact_id': contact_addr_id,
389             'address_invoice_id': invoice_addr_id,
390             'account_id': acc_id,
391             'payment_term': partner_payment_term,
392             'fiscal_position': fiscal_position
393             }
394         }
395
396         if type in ('in_invoice', 'in_refund'):
397             result['value']['partner_bank'] = bank_id
398
399         if payment_term != partner_payment_term:
400             if partner_payment_term:
401                 to_update = self.onchange_payment_term_date_invoice(
402                     cr,uid,ids,partner_payment_term,date_invoice)
403                 result['value'].update(to_update['value'])
404             else:
405                 result['value']['date_due'] = False
406
407         if partner_bank_id != bank_id:
408             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
409             result['value'].update(to_update['value'])
410         return result
411
412     def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
413         if curr_id and company_id:
414             currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
415             if currency.company_id.id != company_id:
416                 raise osv.except_osv(_('Configration Error !'),
417                         _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
418         return {}
419
420     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
421         if not payment_term_id:
422             return {}
423         res={}
424         pt_obj= self.pool.get('account.payment.term')
425         if not date_invoice :
426             date_invoice = time.strftime('%Y-%m-%d')
427
428         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
429
430         if pterm_list:
431             pterm_list = [line[0] for line in pterm_list]
432             pterm_list.sort()
433             res= {'value':{'date_due': pterm_list[-1]}}
434         else:
435              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
436
437         return res
438
439     def onchange_invoice_line(self, cr, uid, ids, lines):
440         return {}
441
442     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
443         return {'value': {}}
444
445     def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
446         val = {}
447         dom = {}
448         obj_journal = self.pool.get('account.journal')
449         if company_id and part_id and type:
450             acc_id = False
451             partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
452             if partner_obj.property_account_payable and partner_obj.property_account_receivable:
453                 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
454                     rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
455                     pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
456                     if not rec_pro_id:
457                         rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
458                     if not pay_pro_id:
459                         pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
460                     rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
461                     pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
462                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
463                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
464                     if not rec_res_id and not rec_res_id:
465                         raise osv.except_osv(_('Configration Error !'),
466                             _('Can not find account chart for this company, Please Create account.'))
467                     if type in ('out_invoice', 'out_refund'):
468                         acc_id = rec_res_id
469                     else:
470                         acc_id = pay_res_id
471                     val= {'account_id': acc_id}
472             if ids:
473                 if company_id:
474                     inv_obj = self.browse(cr,uid,ids)
475                     for line in inv_obj[0].invoice_line:
476                         if line.account_id:
477                             if line.account_id.company_id.id != company_id:
478                                 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
479                                 if not result_id:
480                                     raise osv.except_osv(_('Configration Error !'),
481                                         _('Can not find account chart for this company in invoice line account, Please Create account.'))
482                                 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
483             else:
484                 if invoice_line:
485                     for inv_line in invoice_line:
486                         obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
487                         if obj_l.company_id.id != company_id:
488                             raise osv.except_osv(_('Configration Error !'),
489                                 _('invoice line account company is not match with invoice company.'))
490                         else:
491                             continue
492         if company_id and type:
493             if type in ('out_invoice', 'out_refund'):
494                 journal_type = 'sale'
495             else:
496                 journal_type = 'purchase'
497             journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
498             if journal_ids:
499                 val['journal_id'] = journal_ids[0]
500             else:
501                 raise osv.except_osv(_('Configration Error !'),
502                                 _('Can not find account journal for this company in invoice, Please Create journal.'))
503             dom = {'journal_id':  [('id', 'in', journal_ids)]}
504         else:
505             journal_ids = obj_journal.search(cr, uid, [])
506
507         if currency_id and company_id:
508             currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
509             if currency.company_id.id != company_id:
510                 val['currency_id'] = False
511             else:
512                 val['currency_id'] = currency.id
513
514         if company_id:
515             company = self.pool.get('res.company').browse(cr, uid, company_id)
516             if company.currency_id.company_id.id != company_id:
517                 val['currency_id'] = False
518             else:
519                 val['currency_id'] = company.currency_id.id
520
521         return {'value' : val, 'domain': dom }
522
523     # go from canceled state to draft state
524     def action_cancel_draft(self, cr, uid, ids, *args):
525         self.write(cr, uid, ids, {'state':'draft'})
526         wf_service = netsvc.LocalService("workflow")
527         for inv_id in ids:
528             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
529         return True
530
531     # Workflow stuff
532     #################
533
534     # return the ids of the move lines which has the same account than the invoice
535     # whose id is in ids
536     def move_line_id_payment_get(self, cr, uid, ids, *args):
537         res = []
538         if not ids: return res
539         cr.execute('select \
540                 l.id \
541             from account_move_line l \
542                 left join account_invoice i on (i.move_id=l.move_id) \
543             where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
544         res = map(lambda x: x[0], cr.fetchall())
545         return res
546
547     def copy(self, cr, uid, id, default=None, context=None):
548         if default is None:
549             default = {}
550         default = default.copy()
551         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
552         if 'date_invoice' not in default:
553             default['date_invoice'] = False
554         if 'date_due' not in default:
555             default['date_due'] = False
556         return super(account_invoice, self).copy(cr, uid, id, default, context)
557
558     def test_paid(self, cr, uid, ids, *args):
559         res = self.move_line_id_payment_get(cr, uid, ids)
560         if not res:
561             return False
562         ok = True
563         for id in res:
564             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
565             ok = ok and  bool(cr.fetchone()[0])
566         return ok
567
568     def button_reset_taxes(self, cr, uid, ids, context=None):
569         if not context:
570             context = {}
571         ait_obj = self.pool.get('account.invoice.tax')
572         for id in ids:
573             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
574             partner = self.browse(cr, uid, id,context=context).partner_id
575             if partner.lang:
576                 context.update({'lang': partner.lang})
577             for taxe in ait_obj.compute(cr, uid, id, context=context).values():
578                 ait_obj.create(cr, uid, taxe)
579          # Update the stored value (fields.function), so we write to trigger recompute
580         self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
581 #        self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
582         return True
583
584     def button_compute(self, cr, uid, ids, context=None, set_total=False):
585         self.button_reset_taxes(cr, uid, ids, context)
586         for inv in self.browse(cr, uid, ids):
587             if set_total:
588                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
589         return True
590
591     def _convert_ref(self, cr, uid, ref):
592         return (ref or '').replace('/','')
593
594     def _get_analytic_lines(self, cr, uid, id):
595         inv = self.browse(cr, uid, [id])[0]
596         cur_obj = self.pool.get('res.currency')
597
598         company_currency = inv.company_id.currency_id.id
599         if inv.type in ('out_invoice', 'in_refund'):
600             sign = 1
601         else:
602             sign = -1
603
604         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
605         for il in iml:
606             if il['account_analytic_id']:
607                 if inv.type in ('in_invoice', 'in_refund'):
608                     ref = inv.reference
609                 else:
610                     ref = self._convert_ref(cr, uid, inv.number)
611                 il['analytic_lines'] = [(0,0, {
612                     'name': il['name'],
613                     'date': inv['date_invoice'],
614                     'account_id': il['account_analytic_id'],
615                     'unit_amount': il['quantity'],
616                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
617                     'product_id': il['product_id'],
618                     'product_uom_id': il['uos_id'],
619                     'general_account_id': il['account_id'],
620                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
621                     'ref': ref,
622                 })]
623         return iml
624
625     def action_date_assign(self, cr, uid, ids, *args):
626         for inv in self.browse(cr, uid, ids):
627             res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
628             if res and res['value']:
629                 self.write(cr, uid, [inv.id], res['value'])
630         return True
631
632     def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
633         if not inv.tax_line:
634             for tax in compute_taxes.values():
635                 ait_obj.create(cr, uid, tax)
636         else:
637             tax_key = []
638             for tax in inv.tax_line:
639                 if tax.manual:
640                     continue
641                 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
642                 tax_key.append(key)
643                 if not key in compute_taxes:
644                     raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
645                 base = compute_taxes[key]['base']
646                 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
647                     raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
648             for key in compute_taxes:
649                 if not key in tax_key:
650                     raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
651
652     def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
653         total = 0
654         total_currency = 0
655         cur_obj = self.pool.get('res.currency')
656         for i in invoice_move_lines:
657             if inv.currency_id.id != company_currency:
658                 i['currency_id'] = inv.currency_id.id
659                 i['amount_currency'] = i['price']
660                 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
661                         company_currency, i['price'],
662                         context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
663             else:
664                 i['amount_currency'] = False
665                 i['currency_id'] = False
666             i['ref'] = ref
667             if inv.type in ('out_invoice','in_refund'):
668                 total += i['price']
669                 total_currency += i['amount_currency'] or i['price']
670                 i['price'] = - i['price']
671             else:
672                 total -= i['price']
673                 total_currency -= i['amount_currency'] or i['price']
674         return total, total_currency, invoice_move_lines
675
676     def inv_line_characteristic_hashcode(self, invoice, invoice_line):
677         """Overridable hashcode generation for invoice lines. Lines having the same hashcode
678         will be grouped together if the journal has the 'group line' option. Of course a module
679         can add fields to invoice lines that would need to be tested too before merging lines
680         or not."""
681         return "%s-%s-%s-%s-%s"%(
682             invoice_line['account_id'],
683             invoice_line.get('tax_code_id',"False"),
684             invoice_line.get('product_id',"False"),
685             invoice_line.get('analytic_account_id',"False"),
686             invoice_line.get('date_maturity',"False"))
687
688     def group_lines(self, cr, uid, iml, line, inv):
689         """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
690         if inv.journal_id.group_invoice_lines:
691             line2 = {}
692             for x, y, l in line:
693                 tmp = self.inv_line_characteristic_hashcode(inv, l)
694
695                 if tmp in line2:
696                     am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
697                     line2[tmp]['debit'] = (am > 0) and am or 0.0
698                     line2[tmp]['credit'] = (am < 0) and -am or 0.0
699                     line2[tmp]['tax_amount'] += l['tax_amount']
700                     line2[tmp]['analytic_lines'] += l['analytic_lines']
701                 else:
702                     line2[tmp] = l
703             line = []
704             for key, val in line2.items():
705                 line.append((0,0,val))
706
707         return line
708
709     def action_move_create(self, cr, uid, ids, *args):
710         """Creates invoice related analytics and financial move lines"""
711         ait_obj = self.pool.get('account.invoice.tax')
712         cur_obj = self.pool.get('res.currency')
713         context = {}
714         for inv in self.browse(cr, uid, ids):
715             if inv.move_id:
716                 continue
717
718             if not inv.date_invoice:
719                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
720             company_currency = inv.company_id.currency_id.id
721             # create the analytical lines
722             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
723             # one move line per invoice line
724             iml = self._get_analytic_lines(cr, uid, inv.id)
725             # check if taxes are all computed
726
727             context.update({'lang': inv.partner_id.lang})
728             compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
729             self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
730
731             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
732                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
733
734             # one move line per tax line
735             iml += ait_obj.move_line_get(cr, uid, inv.id)
736
737             if inv.type in ('in_invoice', 'in_refund'):
738                 ref = inv.reference
739             else:
740                 ref = self._convert_ref(cr, uid, inv.number)
741
742             diff_currency_p = inv.currency_id.id <> company_currency
743             # create one move line for the total and possibly adjust the other lines amount
744             total = 0
745             total_currency = 0
746             total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
747             acc_id = inv.account_id.id
748
749             name = inv['name'] or '/'
750             totlines = False
751             if inv.payment_term:
752                 totlines = self.pool.get('account.payment.term').compute(cr,
753                         uid, inv.payment_term.id, total, inv.date_invoice or False)
754             if totlines:
755                 res_amount_currency = total_currency
756                 i = 0
757                 for t in totlines:
758                     if inv.currency_id.id != company_currency:
759                         amount_currency = cur_obj.compute(cr, uid,
760                                 company_currency, inv.currency_id.id, t[1])
761                     else:
762                         amount_currency = False
763
764                     # last line add the diff
765                     res_amount_currency -= amount_currency or 0
766                     i += 1
767                     if i == len(totlines):
768                         amount_currency += res_amount_currency
769
770                     iml.append({
771                         'type': 'dest',
772                         'name': name,
773                         'price': t[1],
774                         'account_id': acc_id,
775                         'date_maturity': t[0],
776                         'amount_currency': diff_currency_p \
777                                 and  amount_currency or False,
778                         'currency_id': diff_currency_p \
779                                 and inv.currency_id.id or False,
780                         'ref': ref,
781                     })
782             else:
783                 iml.append({
784                     'type': 'dest',
785                     'name': name,
786                     'price': total,
787                     'account_id': acc_id,
788                     'date_maturity' : inv.date_due or False,
789                     'amount_currency': diff_currency_p \
790                             and total_currency or False,
791                     'currency_id': diff_currency_p \
792                             and inv.currency_id.id or False,
793                     'ref': ref
794             })
795
796             date = inv.date_invoice or time.strftime('%Y-%m-%d')
797             part = inv.partner_id.id
798
799             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
800
801             line = self.group_lines(cr, uid, iml, line, inv)
802
803             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
804             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
805             if journal.centralisation:
806                 raise osv.except_osv(_('UserError'),
807                         _('Cannot create invoice move on centralised journal'))
808             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
809             period_id=inv.period_id and inv.period_id.id or False
810             if not period_id:
811                 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'))])
812                 if len(period_ids):
813                     period_id=period_ids[0]
814             if period_id:
815                 move['period_id'] = period_id
816                 for i in line:
817                     i[2]['period_id'] = period_id
818
819             move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
820             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
821             # make the invoice point to that move
822             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
823             self.pool.get('account.move').post(cr, uid, [move_id])
824         self._log_event(cr, uid, ids)
825         return True
826
827     def line_get_convert(self, cr, uid, x, part, date, context=None):
828         return {
829             'date_maturity': x.get('date_maturity', False),
830             'partner_id':part,
831             'name':x['name'][:64],
832             'date': date,
833             'debit':x['price']>0 and x['price'],
834             'credit':x['price']<0 and -x['price'],
835             'account_id':x['account_id'],
836             'analytic_lines':x.get('analytic_lines', []),
837             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
838             'currency_id':x.get('currency_id', False),
839             'tax_code_id': x.get('tax_code_id', False),
840             'tax_amount': x.get('tax_amount', False),
841             'ref':x.get('ref',False),
842             'quantity':x.get('quantity',1.00),
843             'product_id':x.get('product_id', False),
844             'product_uom_id':x.get('uos_id',False),
845             'analytic_account_id':x.get('account_analytic_id',False),
846         }
847
848     def action_number(self, cr, uid, ids, *args):
849         cr.execute('SELECT id, type, number, move_id, reference ' \
850                 'FROM account_invoice ' \
851                 'WHERE id IN ('+','.join(map(str, ids))+')')
852         obj_inv = self.browse(cr, uid, ids)[0]
853         for (id, invtype, number, move_id, reference) in cr.fetchall():
854             if not number:
855                 if obj_inv.journal_id.invoice_sequence_id:
856                     sid = obj_inv.journal_id.invoice_sequence_id.id
857                     number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
858                 else:
859                     number = self.pool.get('ir.sequence').get(cr, uid,
860                             'account.invoice.' + invtype)
861                 if invtype in ('in_invoice', 'in_refund'):
862                     ref = reference
863                 else:
864                     ref = self._convert_ref(cr, uid, number)
865                 cr.execute('UPDATE account_invoice SET number=%s ' \
866                         'WHERE id=%s', (number, id))
867                 cr.execute('UPDATE account_move SET ref=%s ' \
868                         'WHERE id=%s AND (ref is null OR ref = \'\')',
869                         (ref, move_id))
870                 cr.execute('UPDATE account_move_line SET ref=%s ' \
871                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
872                         (ref, move_id))
873                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
874                         'FROM account_move_line ' \
875                         'WHERE account_move_line.move_id = %s ' \
876                             'AND account_analytic_line.move_id = account_move_line.id',
877                             (ref, move_id))
878         return True
879
880     def action_cancel(self, cr, uid, ids, *args):
881         account_move_obj = self.pool.get('account.move')
882         invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
883         for i in invoices:
884             if i['move_id']:
885                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
886                 # delete the move this invoice was pointing to
887                 # Note that the corresponding move_lines and move_reconciles
888                 # will be automatically deleted too
889                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
890             if i['payment_ids']:
891                 self.pool.get('account.move.line').write(cr, uid, i['payment_ids'], {'reconcile_partial_id': False})
892         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
893         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
894         return True
895
896     ###################
897
898     def list_distinct_taxes(self, cr, uid, ids):
899         invoices = self.browse(cr, uid, ids)
900         taxes = {}
901         for inv in invoices:
902             for tax in inv.tax_line:
903                 if not tax['name'] in taxes:
904                     taxes[tax['name']] = {'name': tax['name']}
905         return taxes.values()
906
907     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
908         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
909         for inv in invs:
910             part=inv['partner_id'] and inv['partner_id'][0]
911             pc = pr = 0.0
912             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
913             total = inv['amount_untaxed']
914             if inv['type'] in ('in_invoice','in_refund'):
915                 partnertype='supplier'
916                 eventtype = 'purchase'
917                 pc = total*factor
918             else:
919                 partnertype = 'customer'
920                 eventtype = 'sale'
921                 pr = total*factor
922             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
923                 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})
924         return len(invs)
925
926     def name_get(self, cr, uid, ids, context=None):
927         if not len(ids):
928             return []
929         types = {
930                 'out_invoice': 'CI: ',
931                 'in_invoice': 'SI: ',
932                 'out_refund': 'OR: ',
933                 'in_refund': 'SR: ',
934                 }
935         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')]
936
937     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
938         if not args:
939             args=[]
940         if context is None:
941             context={}
942         ids = []
943         if name:
944             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
945         if not ids:
946             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
947         return self.name_get(cr, user, ids, context)
948
949     def _refund_cleanup_lines(self, cr, uid, lines):
950         for line in lines:
951             del line['id']
952             del line['invoice_id']
953             if 'account_id' in line:
954                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
955             if 'product_id' in line:
956                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
957             if 'uos_id' in line:
958                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
959             if 'invoice_line_tax_id' in line:
960                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
961             if 'account_analytic_id' in line:
962                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
963             if 'tax_code_id' in line :
964                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
965                     line['tax_code_id'] = line['tax_code_id'][0]
966             if 'base_code_id' in line :
967                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
968                     line['base_code_id'] = line['base_code_id'][0]
969         return map(lambda x: (0,0,x), lines)
970
971     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
972         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'])
973
974         new_ids = []
975         for invoice in invoices:
976             del invoice['id']
977
978             type_dict = {
979                 'out_invoice': 'out_refund', # Customer Invoice
980                 'in_invoice': 'in_refund',   # Supplier Invoice
981                 'out_refund': 'out_invoice', # Customer Refund
982                 'in_refund': 'in_invoice',   # Supplier Refund
983             }
984
985
986             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
987             invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
988
989             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
990             tax_lines = filter(lambda l: l['manual'], tax_lines)
991             tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
992             if not date :
993                 date = time.strftime('%Y-%m-%d')
994             invoice.update({
995                 'type': type_dict[invoice['type']],
996                 'date_invoice': date,
997                 'state': 'draft',
998                 'number': False,
999                 'invoice_line': invoice_lines,
1000                 'tax_line': tax_lines
1001             })
1002             if period_id :
1003                 invoice.update({
1004                     'period_id': period_id,
1005                 })
1006             if description :
1007                 invoice.update({
1008                     'name': description,
1009                 })
1010             # take the id part of the tuple returned for many2one fields
1011             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1012                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
1013                 invoice[field] = invoice[field] and invoice[field][0]
1014             # create the new invoice
1015             new_ids.append(self.create(cr, uid, invoice))
1016         return new_ids
1017
1018     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=None, name=''):
1019         if context is None:
1020             context = {}
1021         #TODO check if we can use different period for payment and the writeoff line
1022         assert len(ids)==1, "Can only pay one invoice at a time"
1023         invoice = self.browse(cr, uid, ids[0])
1024         src_account_id = invoice.account_id.id
1025         # Take the seq as name for move
1026         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1027         direction = types[invoice.type]
1028         #take the choosen date
1029         if 'date_p' in context and context['date_p']:
1030             date=context['date_p']
1031         else:
1032             date=time.strftime('%Y-%m-%d')
1033
1034         # Take the amount in currency and the currency of the payment
1035         if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1036             amount_currency = context['amount_currency']
1037             currency_id = context['currency_id']
1038         else:
1039             amount_currency = False
1040             currency_id = False
1041         if invoice.type in ('in_invoice', 'in_refund'):
1042             ref = invoice.reference
1043         else:
1044             ref = self._convert_ref(cr, uid, invoice.number)
1045         # Pay attention to the sign for both debit/credit AND amount_currency
1046         l1 = {
1047             'debit': direction * pay_amount>0 and direction * pay_amount,
1048             'credit': direction * pay_amount<0 and - direction * pay_amount,
1049             'account_id': src_account_id,
1050             'partner_id': invoice.partner_id.id,
1051             'ref':ref,
1052             'date': date,
1053             'currency_id':currency_id,
1054             'amount_currency':amount_currency and direction * amount_currency or 0.0,
1055             'company_id': invoice.company_id.id,
1056         }
1057         l2 = {
1058             'debit': direction * pay_amount<0 and - direction * pay_amount,
1059             'credit': direction * pay_amount>0 and direction * pay_amount,
1060             'account_id': pay_account_id,
1061             'partner_id': invoice.partner_id.id,
1062             'ref':ref,
1063             'date': date,
1064             'currency_id':currency_id,
1065             'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1066             'company_id': invoice.company_id.id,
1067         }
1068
1069         if not name:
1070             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1071         l1['name'] = name
1072         l2['name'] = name
1073
1074         lines = [(0, 0, l1), (0, 0, l2)]
1075         move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1076         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1077
1078         line_ids = []
1079         total = 0.0
1080         line = self.pool.get('account.move.line')
1081         move_ids = [move_id,]
1082         if invoice.move_id:
1083             move_ids.append(invoice.move_id.id)
1084         cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1085         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1086         for l in lines+invoice.payment_ids:
1087             if l.account_id.id==src_account_id:
1088                 line_ids.append(l.id)
1089                 total += (l.debit or 0.0) - (l.credit or 0.0)
1090         if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1091             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1092         else:
1093             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1094
1095         # Update the stored value (fields.function), so we write to trigger recompute
1096         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1097         return True
1098 account_invoice()
1099
1100 class account_invoice_line(osv.osv):
1101     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1102         res = {}
1103         cur_obj=self.pool.get('res.currency')
1104         for line in self.browse(cr, uid, ids):
1105             if line.invoice_id:
1106                 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1107                 cur = line.invoice_id.currency_id
1108                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1109             else:
1110                 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1111         return res
1112
1113
1114     def _price_unit_default(self, cr, uid, context=None):
1115         if context is None:
1116             context = {}
1117         if 'check_total' in context:
1118             t = context['check_total']
1119             for l in context.get('invoice_line', {}):
1120                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1121                     tax_obj = self.pool.get('account.tax')
1122                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1123                     t = t - (p * l[2].get('quantity'))
1124                     taxes = l[2].get('invoice_line_tax_id')
1125                     if len(taxes[0]) >= 3 and taxes[0][2]:
1126                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
1127                         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)):
1128                             t = t - tax['amount']
1129             return t
1130         return 0
1131
1132     _name = "account.invoice.line"
1133     _description = "Invoice line"
1134     _columns = {
1135         'name': fields.char('Description', size=256, required=True),
1136         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1137         'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1138         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1139         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1140         '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."),
1141         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1142         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits_compute= dp.get_precision('Account')),
1143         'quantity': fields.float('Quantity', required=True),
1144         'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1145         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1146         'note': fields.text('Notes', translate=True),
1147         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1148         'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1149         'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1150     }
1151     _defaults = {
1152         'quantity': lambda *a: 1,
1153         'discount': lambda *a: 0.0,
1154         'price_unit': _price_unit_default,
1155     }
1156
1157     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1158         tax_obj = self.pool.get('account.tax')
1159         if price_unit:
1160             taxes = tax_obj.browse(cr, uid, tax_id)
1161             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1162                 price_unit = price_unit - tax['amount']
1163         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1164
1165     def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, currency_id=False, context=None):
1166         if context is None:
1167             context = {}
1168         company_id = context.get('company_id',False)
1169         if not partner_id:
1170             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1171         if not product:
1172             if type in ('in_invoice', 'in_refund'):
1173                 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1174             else:
1175                 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1176         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1177         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1178
1179         lang=part.lang
1180         context.update({'lang': lang})
1181         result = {}
1182         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1183
1184         if company_id:
1185             in_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_income'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1186             if not in_pro_id:
1187                 in_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_income_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1188             exp_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_expense'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1189             if not exp_pro_id:
1190                 exp_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_expense_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1191
1192             if not in_pro_id:
1193                 in_acc = res.product_tmpl_id.property_account_income
1194                 in_acc_cate = res.categ_id.property_account_income_categ
1195                 if in_acc:
1196                     app_acc_in = in_acc
1197                 else:
1198                     app_acc_in = in_acc_cate
1199             else:
1200                 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1201             if not exp_pro_id:
1202                 ex_acc = res.product_tmpl_id.property_account_expense
1203                 ex_acc_cate = res.categ_id.property_account_expense_categ
1204                 if ex_acc:
1205                     app_acc_exp = ex_acc
1206                 else:
1207                     app_acc_exp = ex_acc_cate
1208             else:
1209                 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1210             if not in_pro_id and not exp_pro_id:
1211                 in_acc = res.product_tmpl_id.property_account_income
1212                 in_acc_cate = res.categ_id.property_account_income_categ
1213                 ex_acc = res.product_tmpl_id.property_account_expense
1214                 ex_acc_cate = res.categ_id.property_account_expense_categ
1215                 if in_acc or ex_acc:
1216                     app_acc_in = in_acc
1217                     app_acc_exp = ex_acc
1218                 else:
1219                     app_acc_in = in_acc_cate
1220                     app_acc_exp = ex_acc_cate
1221 #            else:
1222 #                app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1223 #                app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1224             if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1225                 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1226                 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1227                 if not in_res_id and not exp_res_id:
1228                     raise osv.except_osv(_('Configration Error !'),
1229                         _('Can not find account chart for this company, Please Create account.'))
1230                 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1231                 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1232                 if in_acc or ex_acc:
1233                     res.product_tmpl_id.property_account_income = in_obj_acc[0]
1234                     res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1235                 else:
1236                     res.categ_id.property_account_income_categ = in_obj_acc[0]
1237                     res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1238
1239         if type in ('out_invoice','out_refund'):
1240             a =  res.product_tmpl_id.property_account_income.id
1241             if not a:
1242                 a = res.categ_id.property_account_income_categ.id
1243         else:
1244             a =  res.product_tmpl_id.property_account_expense.id
1245             if not a:
1246                 a = res.categ_id.property_account_expense_categ.id
1247
1248         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1249         if a:
1250             result['account_id'] = a
1251
1252         taxep=None
1253         tax_obj = self.pool.get('account.tax')
1254         if type in ('out_invoice', 'out_refund'):
1255             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1256             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1257         else:
1258             taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1259             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1260         if type in ('in_invoice', 'in_refund'):
1261             to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit or res.standard_price, qty, address_invoice_id, product, partner_id, context=context)
1262             result.update(to_update)
1263         else:
1264             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1265
1266         if not name:
1267             result['name'] = res.partner_ref
1268
1269         domain = {}
1270         result['uos_id'] = uom or res.uom_id.id or False
1271         if result['uos_id']:
1272             res2 = res.uom_id.category_id.id
1273             if res2 :
1274                 domain = {'uos_id':[('category_id','=',res2 )]}
1275
1276         prod_pool=self.pool.get('product.product')
1277         result['categ_id'] = res.categ_id.id
1278         res_final = {'value':result, 'domain':domain}
1279
1280         if not company_id and not currency_id:
1281             return res_final
1282
1283         company = self.pool.get('res.company').browse(cr, uid, company_id)
1284         currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1285
1286         if not currency.company_id.id == company.id:
1287             raise osv.except_osv(_('Configration Error !'),
1288                         _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1289
1290         if company.currency_id.id != currency.id:
1291             new_price = res_final['value']['price_unit'] * currency.rate
1292             res_final['value']['price_unit'] = new_price
1293         return res_final
1294
1295     def move_line_get(self, cr, uid, invoice_id, context=None):
1296         res = []
1297         tax_grouped = {}
1298         tax_obj = self.pool.get('account.tax')
1299         cur_obj = self.pool.get('res.currency')
1300         ait_obj = self.pool.get('account.invoice.tax')
1301         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1302         company_currency = inv.company_id.currency_id.id
1303         cur = inv.currency_id
1304
1305         for line in inv.invoice_line:
1306             mres = self.move_line_get_item(cr, uid, line, context)
1307             if not mres:
1308                 continue
1309             res.append(mres)
1310             tax_code_found= False
1311             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1312                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1313                     line.quantity, inv.address_invoice_id.id, line.product_id,
1314                     inv.partner_id):
1315
1316                 if inv.type in ('out_invoice', 'in_invoice'):
1317                     tax_code_id = tax['base_code_id']
1318                     tax_amount = line.price_subtotal * tax['base_sign']
1319                 else:
1320                     tax_code_id = tax['ref_base_code_id']
1321                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1322
1323                 if tax_code_found:
1324                     if not tax_code_id:
1325                         continue
1326                     res.append(self.move_line_get_item(cr, uid, line, context))
1327                     res[-1]['price'] = 0.0
1328                     res[-1]['account_analytic_id'] = False
1329                 elif not tax_code_id:
1330                     continue
1331                 tax_code_found = True
1332
1333                 res[-1]['tax_code_id'] = tax_code_id
1334                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1335         return res
1336
1337     def move_line_get_item(self, cr, uid, line, context=None):
1338         return {
1339             'type':'src',
1340             'name': line.name[:64],
1341             'price_unit':line.price_unit,
1342             'quantity':line.quantity,
1343             'price':line.price_subtotal,
1344             'account_id':line.account_id.id,
1345             'product_id':line.product_id.id,
1346             'uos_id':line.uos_id.id,
1347             'account_analytic_id':line.account_analytic_id.id,
1348             'taxes':line.invoice_line_tax_id,
1349         }
1350     #
1351     # Set the tax field according to the account and the fiscal position
1352     #
1353     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1354         if not account_id:
1355             return {}
1356         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1357         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1358         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1359         r = {'value':{'invoice_line_tax_id': res}}
1360         return r
1361 account_invoice_line()
1362
1363 class account_invoice_tax(osv.osv):
1364     _name = "account.invoice.tax"
1365     _description = "Invoice Tax"
1366     _columns = {
1367         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1368         'name': fields.char('Tax Description', size=64, required=True),
1369         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1370         'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1371         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1372         'manual': fields.boolean('Manual'),
1373         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1374
1375         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1376         'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1377         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1378         'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1379         'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1380     }
1381
1382     def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1383         cur_obj = self.pool.get('res.currency')
1384         company_obj = self.pool.get('res.company')
1385         company_currency=False
1386         if company_id:
1387             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1388         if currency_id and company_currency:
1389             base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1390         return {'value': {'base_amount':base}}
1391
1392     def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1393         cur_obj = self.pool.get('res.currency')
1394         company_obj = self.pool.get('res.company')
1395         company_currency=False
1396         if company_id:
1397             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1398         if currency_id and company_currency:
1399             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1400         return {'value': {'tax_amount':amount}}
1401
1402     _order = 'sequence'
1403     _defaults = {
1404         'manual': lambda *a: 1,
1405         'base_amount': lambda *a: 0.0,
1406         'tax_amount': lambda *a: 0.0,
1407     }
1408     def compute(self, cr, uid, invoice_id, context={}):
1409         tax_grouped = {}
1410         tax_obj = self.pool.get('account.tax')
1411         cur_obj = self.pool.get('res.currency')
1412         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1413         cur = inv.currency_id
1414         company_currency = inv.company_id.currency_id.id
1415
1416         for line in inv.invoice_line:
1417             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):
1418                 val={}
1419                 val['invoice_id'] = inv.id
1420                 val['name'] = tax['name']
1421                 val['amount'] = tax['amount']
1422                 val['manual'] = False
1423                 val['sequence'] = tax['sequence']
1424                 val['base'] = tax['price_unit'] * line['quantity']
1425
1426                 if inv.type in ('out_invoice','in_invoice'):
1427                     val['base_code_id'] = tax['base_code_id']
1428                     val['tax_code_id'] = tax['tax_code_id']
1429                     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')}, round=False)
1430                     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')}, round=False)
1431                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1432                 else:
1433                     val['base_code_id'] = tax['ref_base_code_id']
1434                     val['tax_code_id'] = tax['ref_tax_code_id']
1435                     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')}, round=False)
1436                     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')}, round=False)
1437                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1438
1439                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1440                 if not key in tax_grouped:
1441                     tax_grouped[key] = val
1442                 else:
1443                     tax_grouped[key]['amount'] += val['amount']
1444                     tax_grouped[key]['base'] += val['base']
1445                     tax_grouped[key]['base_amount'] += val['base_amount']
1446                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1447
1448         for t in tax_grouped.values():
1449             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1450             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1451             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1452         return tax_grouped
1453
1454     def move_line_get(self, cr, uid, invoice_id):
1455         res = []
1456         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1457         for t in cr.dictfetchall():
1458             if not t['amount'] \
1459                     and not t['tax_code_id'] \
1460                     and not t['tax_amount']:
1461                 continue
1462             res.append({
1463                 'type':'tax',
1464                 'name':t['name'],
1465                 'price_unit': t['amount'],
1466                 'quantity': 1,
1467                 'price': t['amount'] or 0.0,
1468                 'account_id': t['account_id'],
1469                 'tax_code_id': t['tax_code_id'],
1470                 'tax_amount': t['tax_amount']
1471             })
1472         return res
1473 account_invoice_tax()
1474
1475 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: