[FIX] Account : USer will be notified for unreconciling partial reconcilatins if...
[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 from osv.orm import except_orm
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 create(self, cr, uid, vals, context={}):
328         try:
329             res = super(account_invoice, self).create(cr, uid, vals, context)
330             return res
331         except Exception,e:
332             if '"journal_id" viol' in e.args[0]:
333                 raise except_orm(_('Configuration Error!'),
334                      _('There is no Accounting Journal of type Sale/Purchase defined!'))
335             else:
336                 raise except_orm(_('UnknownError'), str(e))
337             
338     def unlink(self, cr, uid, ids, context=None):
339         invoices = self.read(cr, uid, ids, ['state'])
340         unlink_ids = []
341         for t in invoices:
342             if t['state'] in ('draft', 'cancel'):
343                 unlink_ids.append(t['id'])
344             else:
345                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
346         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
347         return True
348
349 #   def get_invoice_address(self, cr, uid, ids):
350 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
351 #       return [{}]
352     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
353             date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
354         invoice_addr_id = False
355         contact_addr_id = False
356         partner_payment_term = False
357         acc_id = False
358         bank_id = False
359         fiscal_position = False
360
361         opt = [('uid', str(uid))]
362         if partner_id:
363
364             opt.insert(0, ('id', partner_id))
365             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
366             contact_addr_id = res['contact']
367             invoice_addr_id = res['invoice']
368             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
369             if company_id:
370                 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
371                     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)])
372                     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)])
373                     if not rec_pro_id:
374                         rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
375                     if not pay_pro_id:
376                         pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
377                     rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
378                     pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
379                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
380                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
381                     if not rec_res_id and not pay_res_id:
382                         raise osv.except_osv(_('Configration Error !'),
383                             _('Can not find account chart for this company, Please Create account.'))
384                     rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
385                     pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
386                     p.property_account_receivable = rec_obj_acc[0]
387                     p.property_account_payable = pay_obj_acc[0]
388
389             if type in ('out_invoice', 'out_refund'):
390                 acc_id = p.property_account_receivable.id
391             else:
392                 acc_id = p.property_account_payable.id
393             fiscal_position = p.property_account_position and p.property_account_position.id or False
394             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
395             if p.bank_ids:
396                 bank_id = p.bank_ids[0].id
397
398         result = {'value': {
399             'address_contact_id': contact_addr_id,
400             'address_invoice_id': invoice_addr_id,
401             'account_id': acc_id,
402             'payment_term': partner_payment_term,
403             'fiscal_position': fiscal_position
404             }
405         }
406
407         if type in ('in_invoice', 'in_refund'):
408             result['value']['partner_bank'] = bank_id
409
410         if payment_term != partner_payment_term:
411             if partner_payment_term:
412                 to_update = self.onchange_payment_term_date_invoice(
413                     cr,uid,ids,partner_payment_term,date_invoice)
414                 result['value'].update(to_update['value'])
415             else:
416                 result['value']['date_due'] = False
417
418         if partner_bank_id != bank_id:
419             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
420             result['value'].update(to_update['value'])
421         return result
422
423     def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
424         if curr_id and company_id:
425             currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
426             if currency.company_id.id != company_id:
427                 raise osv.except_osv(_('Configration Error !'),
428                         _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
429         return {}
430
431     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
432         if not payment_term_id:
433             return {}
434         res={}
435         pt_obj= self.pool.get('account.payment.term')
436         if not date_invoice :
437             date_invoice = time.strftime('%Y-%m-%d')
438
439         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
440
441         if pterm_list:
442             pterm_list = [line[0] for line in pterm_list]
443             pterm_list.sort()
444             res= {'value':{'date_due': pterm_list[-1]}}
445         else:
446              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
447
448         return res
449
450     def onchange_invoice_line(self, cr, uid, ids, lines):
451         return {}
452
453     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
454         return {'value': {}}
455
456     def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
457         val = {}
458         dom = {}
459         obj_journal = self.pool.get('account.journal')
460         if company_id and part_id and type:
461             acc_id = False
462             partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
463             if partner_obj.property_account_payable and partner_obj.property_account_receivable:
464                 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
465                     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)])
466                     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)])
467                     if not rec_pro_id:
468                         rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
469                     if not pay_pro_id:
470                         pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
471                     rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
472                     pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
473                     rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
474                     pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
475                     if not rec_res_id and not rec_res_id:
476                         raise osv.except_osv(_('Configration Error !'),
477                             _('Can not find account chart for this company, Please Create account.'))
478                     if type in ('out_invoice', 'out_refund'):
479                         acc_id = rec_res_id
480                     else:
481                         acc_id = pay_res_id
482                     val= {'account_id': acc_id}
483             if ids:
484                 if company_id:
485                     inv_obj = self.browse(cr,uid,ids)
486                     for line in inv_obj[0].invoice_line:
487                         if line.account_id:
488                             if line.account_id.company_id.id != company_id:
489                                 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
490                                 if not result_id:
491                                     raise osv.except_osv(_('Configration Error !'),
492                                         _('Can not find account chart for this company in invoice line account, Please Create account.'))
493                                 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
494             else:
495                 if invoice_line:
496                     for inv_line in invoice_line:
497                         obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
498                         if obj_l.company_id.id != company_id:
499                             raise osv.except_osv(_('Configration Error !'),
500                                 _('invoice line account company is not match with invoice company.'))
501                         else:
502                             continue
503         if company_id and type:
504             if type in ('out_invoice', 'out_refund'):
505                 journal_type = 'sale'
506             else:
507                 journal_type = 'purchase'
508             journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
509             if journal_ids:
510                 val['journal_id'] = journal_ids[0]
511             else:
512                 raise osv.except_osv(_('Configration Error !'),
513                                 _('Can not find account journal for this company in invoice, Please Create journal.'))
514             dom = {'journal_id':  [('id', 'in', journal_ids)]}
515         else:
516             journal_ids = obj_journal.search(cr, uid, [])
517
518         if currency_id and company_id:
519             currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
520             if currency.company_id.id != company_id:
521                 val['currency_id'] = False
522             else:
523                 val['currency_id'] = currency.id
524
525         if company_id:
526             company = self.pool.get('res.company').browse(cr, uid, company_id)
527             if company.currency_id.company_id.id != company_id:
528                 val['currency_id'] = False
529             else:
530                 val['currency_id'] = company.currency_id.id
531
532         return {'value' : val, 'domain': dom }
533
534     # go from canceled state to draft state
535     def action_cancel_draft(self, cr, uid, ids, *args):
536         self.write(cr, uid, ids, {'state':'draft'})
537         wf_service = netsvc.LocalService("workflow")
538         for inv_id in ids:
539             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
540         return True
541
542     def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
543         """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
544         Hook method to be overridden in additional modules to verify and possibly alter the
545         move lines to be created by an invoice, for special cases.
546         :param invoice_browse: browsable record of the invoice that is generating the move lines
547         :param move_lines: list of dictionaries with the account.move.lines (as for create())
548         :return: the (possibly updated) final move_lines to create for this invoice
549         """
550         return move_lines
551
552     # Workflow stuff
553     #################
554
555     # return the ids of the move lines which has the same account than the invoice
556     # whose id is in ids
557     def move_line_id_payment_get(self, cr, uid, ids, *args):
558         res = []
559         if not ids: return res
560         cr.execute('select \
561                 l.id \
562             from account_move_line l \
563                 left join account_invoice i on (i.move_id=l.move_id) \
564             where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
565         res = map(lambda x: x[0], cr.fetchall())
566         return res
567
568     def copy(self, cr, uid, id, default=None, context=None):
569         if default is None:
570             default = {}
571         default = default.copy()
572         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
573         if 'date_invoice' not in default:
574             default['date_invoice'] = False
575         if 'date_due' not in default:
576             default['date_due'] = False
577         return super(account_invoice, self).copy(cr, uid, id, default, context)
578
579     def test_paid(self, cr, uid, ids, *args):
580         res = self.move_line_id_payment_get(cr, uid, ids)
581         if not res:
582             return False
583         ok = True
584         for id in res:
585             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
586             ok = ok and  bool(cr.fetchone()[0])
587         return ok
588
589     def button_reset_taxes(self, cr, uid, ids, context=None):
590         if not context:
591             context = {}
592         ait_obj = self.pool.get('account.invoice.tax')
593         for id in ids:
594             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
595             partner = self.browse(cr, uid, id,context=context).partner_id
596             if partner.lang:
597                 context.update({'lang': partner.lang})
598             for taxe in ait_obj.compute(cr, uid, id, context=context).values():
599                 ait_obj.create(cr, uid, taxe)
600          # Update the stored value (fields.function), so we write to trigger recompute
601         self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
602 #        self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
603         return True
604
605     def button_compute(self, cr, uid, ids, context=None, set_total=False):
606         self.button_reset_taxes(cr, uid, ids, context)
607         for inv in self.browse(cr, uid, ids):
608             if set_total:
609                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
610         return True
611
612     def _convert_ref(self, cr, uid, ref):
613         return (ref or '').replace('/','')
614
615     def _get_analytic_lines(self, cr, uid, id):
616         inv = self.browse(cr, uid, [id])[0]
617         cur_obj = self.pool.get('res.currency')
618
619         company_currency = inv.company_id.currency_id.id
620         if inv.type in ('out_invoice', 'in_refund'):
621             sign = 1
622         else:
623             sign = -1
624
625         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
626         for il in iml:
627             if il['account_analytic_id']:
628                 if inv.type in ('in_invoice', 'in_refund'):
629                     ref = inv.reference
630                 else:
631                     ref = self._convert_ref(cr, uid, inv.number)
632                 il['analytic_lines'] = [(0,0, {
633                     'name': il['name'],
634                     'date': inv['date_invoice'],
635                     'account_id': il['account_analytic_id'],
636                     'unit_amount': il['quantity'],
637                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
638                     'product_id': il['product_id'],
639                     'product_uom_id': il['uos_id'],
640                     'general_account_id': il['account_id'],
641                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
642                     'ref': ref,
643                 })]
644         return iml
645
646     def action_date_assign(self, cr, uid, ids, *args):
647         for inv in self.browse(cr, uid, ids):
648             res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
649             if res and res['value']:
650                 self.write(cr, uid, [inv.id], res['value'])
651         return True
652     
653     def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
654         """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
655         Hook method to be overridden in additional modules to verify and possibly alter the 
656         move lines to be created by an invoice, for special cases.
657         :param invoice_browse: browsable record of the invoice that is generating the move lines
658         :param move_lines: list of dictionaries with the account.move.lines (as for create())
659         :return: the (possibly updated) final move_lines to create for this invoice 
660         """
661         return move_lines
662
663     def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
664         if not inv.tax_line:
665             for tax in compute_taxes.values():
666                 ait_obj.create(cr, uid, tax)
667         else:
668             tax_key = []
669             for tax in inv.tax_line:
670                 if tax.manual:
671                     continue
672                 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
673                 tax_key.append(key)
674                 if not key in compute_taxes:
675                     raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
676                 base = compute_taxes[key]['base']
677                 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
678                     raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
679             for key in compute_taxes:
680                 if not key in tax_key:
681                     raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
682
683     def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
684         total = 0
685         total_currency = 0
686         cur_obj = self.pool.get('res.currency')
687         for i in invoice_move_lines:
688             if inv.currency_id.id != company_currency:
689                 i['currency_id'] = inv.currency_id.id
690                 i['amount_currency'] = i['price']
691                 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
692                         company_currency, i['price'],
693                         context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
694             else:
695                 i['amount_currency'] = False
696                 i['currency_id'] = False
697             i['ref'] = ref
698             if inv.type in ('out_invoice','in_refund'):
699                 total += i['price']
700                 total_currency += i['amount_currency'] or i['price']
701                 i['price'] = - i['price']
702             else:
703                 total -= i['price']
704                 total_currency -= i['amount_currency'] or i['price']
705         return total, total_currency, invoice_move_lines
706
707     def inv_line_characteristic_hashcode(self, invoice, invoice_line):
708         """Overridable hashcode generation for invoice lines. Lines having the same hashcode
709         will be grouped together if the journal has the 'group line' option. Of course a module
710         can add fields to invoice lines that would need to be tested too before merging lines
711         or not."""
712         return "%s-%s-%s-%s-%s"%(
713             invoice_line['account_id'],
714             invoice_line.get('tax_code_id',"False"),
715             invoice_line.get('product_id',"False"),
716             invoice_line.get('analytic_account_id',"False"),
717             invoice_line.get('date_maturity',"False"))
718
719     def group_lines(self, cr, uid, iml, line, inv):
720         """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
721         if inv.journal_id.group_invoice_lines:
722             line2 = {}
723             for x, y, l in line:
724                 tmp = self.inv_line_characteristic_hashcode(inv, l)
725
726                 if tmp in line2:
727                     am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
728                     line2[tmp]['debit'] = (am > 0) and am or 0.0
729                     line2[tmp]['credit'] = (am < 0) and -am or 0.0
730                     line2[tmp]['tax_amount'] += l['tax_amount']
731                     line2[tmp]['analytic_lines'] += l['analytic_lines']
732                 else:
733                     line2[tmp] = l
734             line = []
735             for key, val in line2.items():
736                 line.append((0,0,val))
737
738         return line
739
740     def action_move_create(self, cr, uid, ids, *args):
741         """Creates invoice related analytics and financial move lines"""
742         ait_obj = self.pool.get('account.invoice.tax')
743         cur_obj = self.pool.get('res.currency')
744         context = {}
745         for inv in self.browse(cr, uid, ids):
746             if inv.move_id:
747                 continue
748
749             if not inv.date_invoice:
750                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
751             company_currency = inv.company_id.currency_id.id
752             # create the analytical lines
753             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
754             # one move line per invoice line
755             iml = self._get_analytic_lines(cr, uid, inv.id)
756             # check if taxes are all computed
757
758             context.update({'lang': inv.partner_id.lang})
759             compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
760             self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
761
762             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
763                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
764
765             # one move line per tax line
766             iml += ait_obj.move_line_get(cr, uid, inv.id)
767
768             if inv.type in ('in_invoice', 'in_refund'):
769                 ref = inv.reference
770             else:
771                 ref = self._convert_ref(cr, uid, inv.number)
772
773             diff_currency_p = inv.currency_id.id <> company_currency
774             # create one move line for the total and possibly adjust the other lines amount
775             total = 0
776             total_currency = 0
777             total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
778             acc_id = inv.account_id.id
779
780             name = inv['name'] or '/'
781             totlines = False
782             if inv.payment_term:
783                 totlines = self.pool.get('account.payment.term').compute(cr,
784                         uid, inv.payment_term.id, total, inv.date_invoice or False)
785             if totlines:
786                 res_amount_currency = total_currency
787                 i = 0
788                 for t in totlines:
789                     if inv.currency_id.id != company_currency:
790                         amount_currency = cur_obj.compute(cr, uid,
791                                 company_currency, inv.currency_id.id, t[1])
792                     else:
793                         amount_currency = False
794
795                     # last line add the diff
796                     res_amount_currency -= amount_currency or 0
797                     i += 1
798                     if i == len(totlines):
799                         amount_currency += res_amount_currency
800
801                     iml.append({
802                         'type': 'dest',
803                         'name': name,
804                         'price': t[1],
805                         'account_id': acc_id,
806                         'date_maturity': t[0],
807                         'amount_currency': diff_currency_p \
808                                 and  amount_currency or False,
809                         'currency_id': diff_currency_p \
810                                 and inv.currency_id.id or False,
811                         'ref': ref,
812                     })
813             else:
814                 iml.append({
815                     'type': 'dest',
816                     'name': name,
817                     'price': total,
818                     'account_id': acc_id,
819                     'date_maturity' : inv.date_due or False,
820                     'amount_currency': diff_currency_p \
821                             and total_currency or False,
822                     'currency_id': diff_currency_p \
823                             and inv.currency_id.id or False,
824                     'ref': ref
825             })
826
827             date = inv.date_invoice or time.strftime('%Y-%m-%d')
828             part = inv.partner_id.id
829
830             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
831
832             line = self.group_lines(cr, uid, iml, line, inv)
833
834             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
835             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
836             if journal.centralisation:
837                 raise osv.except_osv(_('UserError'),
838                         _('Cannot create invoice move on centralised journal'))
839
840             line = self.finalize_invoice_move_lines(cr, uid, inv, line)
841
842             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
843             period_id=inv.period_id and inv.period_id.id or False
844             if not period_id:
845                 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'))])
846                 if len(period_ids):
847                     period_id=period_ids[0]
848             if period_id:
849                 move['period_id'] = period_id
850                 for i in line:
851                     i[2]['period_id'] = period_id
852
853             move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
854             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
855             # make the invoice point to that move
856             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
857             self.pool.get('account.move').post(cr, uid, [move_id])
858         self._log_event(cr, uid, ids)
859         return True
860
861     def line_get_convert(self, cr, uid, x, part, date, context=None):
862         return {
863             'date_maturity': x.get('date_maturity', False),
864             'partner_id':part,
865             'name':x['name'][:64],
866             'date': date,
867             'debit':x['price']>0 and x['price'],
868             'credit':x['price']<0 and -x['price'],
869             'account_id':x['account_id'],
870             'analytic_lines':x.get('analytic_lines', []),
871             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
872             'currency_id':x.get('currency_id', False),
873             'tax_code_id': x.get('tax_code_id', False),
874             'tax_amount': x.get('tax_amount', False),
875             'ref':x.get('ref',False),
876             'quantity':x.get('quantity',1.00),
877             'product_id':x.get('product_id', False),
878             'product_uom_id':x.get('uos_id',False),
879             'analytic_account_id':x.get('account_analytic_id',False),
880         }
881
882     def action_number(self, cr, uid, ids, *args):
883         cr.execute('SELECT id, type, number, move_id, reference ' \
884                 'FROM account_invoice ' \
885                 'WHERE id IN ('+','.join(map(str, ids))+')')
886         obj_inv = self.browse(cr, uid, ids)[0]
887         for (id, invtype, number, move_id, reference) in cr.fetchall():
888             if not number:
889                 if obj_inv.journal_id.invoice_sequence_id:
890                     sid = obj_inv.journal_id.invoice_sequence_id.id
891                     number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
892                 else:
893                     number = self.pool.get('ir.sequence').get(cr, uid,
894                             'account.invoice.' + invtype)
895                 if invtype in ('in_invoice', 'in_refund'):
896                     ref = reference
897                 else:
898                     ref = self._convert_ref(cr, uid, number)
899                 cr.execute('UPDATE account_invoice SET number=%s ' \
900                         'WHERE id=%s', (number, id))
901                 cr.execute('UPDATE account_move SET ref=%s ' \
902                         'WHERE id=%s AND (ref is null OR ref = \'\')',
903                         (ref, move_id))
904                 cr.execute('UPDATE account_move_line SET ref=%s ' \
905                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
906                         (ref, move_id))
907                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
908                         'FROM account_move_line ' \
909                         'WHERE account_move_line.move_id = %s ' \
910                             'AND account_analytic_line.move_id = account_move_line.id',
911                             (ref, move_id))
912         return True
913
914     def action_cancel(self, cr, uid, ids, *args):
915         account_move_obj = self.pool.get('account.move')
916         invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
917         for i in invoices:
918             if i['move_id']:
919                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
920                 # delete the move this invoice was pointing to
921                 # Note that the corresponding move_lines and move_reconciles
922                 # will be automatically deleted too
923                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
924             if i['payment_ids']:
925                 account_move_line_obj = self.pool.get('account.move.line')
926                 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
927                 for move_line in pay_ids:
928                     if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
929                         raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
930
931         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
932         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
933         return True
934
935     ###################
936
937     def list_distinct_taxes(self, cr, uid, ids):
938         invoices = self.browse(cr, uid, ids)
939         taxes = {}
940         for inv in invoices:
941             for tax in inv.tax_line:
942                 if not tax['name'] in taxes:
943                     taxes[tax['name']] = {'name': tax['name']}
944         return taxes.values()
945
946     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
947         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
948         for inv in invs:
949             part=inv['partner_id'] and inv['partner_id'][0]
950             pc = pr = 0.0
951             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
952             total = inv['amount_untaxed']
953             if inv['type'] in ('in_invoice','in_refund'):
954                 partnertype='supplier'
955                 eventtype = 'purchase'
956                 pc = total*factor
957             else:
958                 partnertype = 'customer'
959                 eventtype = 'sale'
960                 pr = total*factor
961             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
962                 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})
963         return len(invs)
964
965     def name_get(self, cr, uid, ids, context=None):
966         if not len(ids):
967             return []
968         types = {
969                 'out_invoice': 'CI: ',
970                 'in_invoice': 'SI: ',
971                 'out_refund': 'OR: ',
972                 'in_refund': 'SR: ',
973                 }
974         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')]
975
976     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
977         if not args:
978             args=[]
979         if context is None:
980             context={}
981         ids = []
982         if name:
983             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
984         if not ids:
985             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
986         return self.name_get(cr, user, ids, context)
987
988     def _refund_cleanup_lines(self, cr, uid, lines):
989         for line in lines:
990             del line['id']
991             del line['invoice_id']
992             if 'account_id' in line:
993                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
994             if 'product_id' in line:
995                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
996             if 'uos_id' in line:
997                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
998             if 'invoice_line_tax_id' in line:
999                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1000             if 'account_analytic_id' in line:
1001                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
1002             if 'tax_code_id' in line :
1003                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
1004                     line['tax_code_id'] = line['tax_code_id'][0]
1005             if 'base_code_id' in line :
1006                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
1007                     line['base_code_id'] = line['base_code_id'][0]
1008         return map(lambda x: (0,0,x), lines)
1009
1010     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
1011         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'])
1012
1013         new_ids = []
1014         for invoice in invoices:
1015             del invoice['id']
1016
1017             type_dict = {
1018                 'out_invoice': 'out_refund', # Customer Invoice
1019                 'in_invoice': 'in_refund',   # Supplier Invoice
1020                 'out_refund': 'out_invoice', # Customer Refund
1021                 'in_refund': 'in_invoice',   # Supplier Refund
1022             }
1023
1024
1025             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
1026             invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1027
1028             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
1029             tax_lines = filter(lambda l: l['manual'], tax_lines)
1030             tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1031             if not date :
1032                 date = time.strftime('%Y-%m-%d')
1033             invoice.update({
1034                 'type': type_dict[invoice['type']],
1035                 'date_invoice': date,
1036                 'state': 'draft',
1037                 'number': False,
1038                 'invoice_line': invoice_lines,
1039                 'tax_line': tax_lines
1040             })
1041             if period_id :
1042                 invoice.update({
1043                     'period_id': period_id,
1044                 })
1045             if description :
1046                 invoice.update({
1047                     'name': description,
1048                 })
1049             # take the id part of the tuple returned for many2one fields
1050             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1051                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
1052                 invoice[field] = invoice[field] and invoice[field][0]
1053             # create the new invoice
1054             new_ids.append(self.create(cr, uid, invoice))
1055         return new_ids
1056
1057     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=''):
1058         if context is None:
1059             context = {}
1060         #TODO check if we can use different period for payment and the writeoff line
1061         assert len(ids)==1, "Can only pay one invoice at a time"
1062         invoice = self.browse(cr, uid, ids[0])
1063         src_account_id = invoice.account_id.id
1064         # Take the seq as name for move
1065         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1066         direction = types[invoice.type]
1067         #take the choosen date
1068         if 'date_p' in context and context['date_p']:
1069             date=context['date_p']
1070         else:
1071             date=time.strftime('%Y-%m-%d')
1072
1073         # Take the amount in currency and the currency of the payment
1074         if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1075             amount_currency = context['amount_currency']
1076             currency_id = context['currency_id']
1077         else:
1078             amount_currency = False
1079             currency_id = False
1080         if invoice.type in ('in_invoice', 'in_refund'):
1081             ref = invoice.reference
1082         else:
1083             ref = self._convert_ref(cr, uid, invoice.number)
1084         # Pay attention to the sign for both debit/credit AND amount_currency
1085         l1 = {
1086             'debit': direction * pay_amount>0 and direction * pay_amount,
1087             'credit': direction * pay_amount<0 and - direction * pay_amount,
1088             'account_id': src_account_id,
1089             'partner_id': invoice.partner_id.id,
1090             'ref':ref,
1091             'date': date,
1092             'currency_id':currency_id,
1093             'amount_currency':amount_currency and direction * amount_currency or 0.0,
1094             'company_id': invoice.company_id.id,
1095         }
1096         l2 = {
1097             'debit': direction * pay_amount<0 and - direction * pay_amount,
1098             'credit': direction * pay_amount>0 and direction * pay_amount,
1099             'account_id': pay_account_id,
1100             'partner_id': invoice.partner_id.id,
1101             'ref':ref,
1102             'date': date,
1103             'currency_id':currency_id,
1104             'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1105             'company_id': invoice.company_id.id,
1106         }
1107
1108         if not name:
1109             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1110         l1['name'] = name
1111         l2['name'] = name
1112
1113         lines = [(0, 0, l1), (0, 0, l2)]
1114         move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1115         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1116
1117         line_ids = []
1118         total = 0.0
1119         line = self.pool.get('account.move.line')
1120         move_ids = [move_id,]
1121         if invoice.move_id:
1122             move_ids.append(invoice.move_id.id)
1123         cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1124         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1125         for l in lines+invoice.payment_ids:
1126             if l.account_id.id==src_account_id:
1127                 line_ids.append(l.id)
1128                 total += (l.debit or 0.0) - (l.credit or 0.0)
1129         if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1130             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1131         else:
1132             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1133
1134         # Update the stored value (fields.function), so we write to trigger recompute
1135         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1136         return True
1137 account_invoice()
1138
1139 class account_invoice_line(osv.osv):
1140     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1141         res = {}
1142         cur_obj=self.pool.get('res.currency')
1143         for line in self.browse(cr, uid, ids):
1144             if line.invoice_id:
1145                 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1146                 cur = line.invoice_id.currency_id
1147                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1148             else:
1149                 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'))
1150         return res
1151
1152
1153     def _price_unit_default(self, cr, uid, context=None):
1154         if context is None:
1155             context = {}
1156         if 'check_total' in context:
1157             t = context['check_total']
1158             for l in context.get('invoice_line', {}):
1159                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1160                     tax_obj = self.pool.get('account.tax')
1161                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1162                     t = t - (p * l[2].get('quantity'))
1163                     taxes = l[2].get('invoice_line_tax_id')
1164                     if len(taxes[0]) >= 3 and taxes[0][2]:
1165                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
1166                         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)):
1167                             t = t - tax['amount']
1168             return t
1169         return 0
1170
1171     _name = "account.invoice.line"
1172     _description = "Invoice line"
1173     _columns = {
1174         'name': fields.char('Description', size=256, required=True),
1175         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1176         'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1177         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1178         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1179         '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."),
1180         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1181         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits_compute= dp.get_precision('Account')),
1182         'quantity': fields.float('Quantity', required=True),
1183         'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1184         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1185         'note': fields.text('Notes', translate=True),
1186         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1187         'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1188         'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1189     }
1190     _defaults = {
1191         'quantity': lambda *a: 1,
1192         'discount': lambda *a: 0.0,
1193         'price_unit': _price_unit_default,
1194     }
1195
1196     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1197         tax_obj = self.pool.get('account.tax')
1198         if price_unit:
1199             taxes = tax_obj.browse(cr, uid, tax_id)
1200             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1201                 price_unit = price_unit - tax['amount']
1202         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1203
1204     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):
1205         if context is None:
1206             context = {}
1207         company_id = context.get('company_id',False)
1208         if not partner_id:
1209             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1210         if not product:
1211             if type in ('in_invoice', 'in_refund'):
1212                 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1213             else:
1214                 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1215         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1216         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1217
1218         if part.lang:
1219             context.update({'lang': part.lang})
1220         result = {}
1221         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1222
1223         if company_id:
1224             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)])
1225             if not in_pro_id:
1226                 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)])
1227             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)])
1228             if not exp_pro_id:
1229                 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)])
1230
1231             if not in_pro_id:
1232                 in_acc = res.product_tmpl_id.property_account_income
1233                 in_acc_cate = res.categ_id.property_account_income_categ
1234                 if in_acc:
1235                     app_acc_in = in_acc
1236                 else:
1237                     app_acc_in = in_acc_cate
1238             else:
1239                 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1240             if not exp_pro_id:
1241                 ex_acc = res.product_tmpl_id.property_account_expense
1242                 ex_acc_cate = res.categ_id.property_account_expense_categ
1243                 if ex_acc:
1244                     app_acc_exp = ex_acc
1245                 else:
1246                     app_acc_exp = ex_acc_cate
1247             else:
1248                 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1249             if not in_pro_id and not exp_pro_id:
1250                 in_acc = res.product_tmpl_id.property_account_income
1251                 in_acc_cate = res.categ_id.property_account_income_categ
1252                 ex_acc = res.product_tmpl_id.property_account_expense
1253                 ex_acc_cate = res.categ_id.property_account_expense_categ
1254                 if in_acc or ex_acc:
1255                     app_acc_in = in_acc
1256                     app_acc_exp = ex_acc
1257                 else:
1258                     app_acc_in = in_acc_cate
1259                     app_acc_exp = ex_acc_cate
1260 #            else:
1261 #                app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1262 #                app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1263             if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1264                 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1265                 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1266                 if not in_res_id and not exp_res_id:
1267                     raise osv.except_osv(_('Configration Error !'),
1268                         _('Can not find account chart for this company, Please Create account.'))
1269                 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1270                 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1271                 if in_acc or ex_acc:
1272                     res.product_tmpl_id.property_account_income = in_obj_acc[0]
1273                     res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1274                 else:
1275                     res.categ_id.property_account_income_categ = in_obj_acc[0]
1276                     res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1277
1278         if type in ('out_invoice','out_refund'):
1279             a =  res.product_tmpl_id.property_account_income.id
1280             if not a:
1281                 a = res.categ_id.property_account_income_categ.id
1282         else:
1283             a =  res.product_tmpl_id.property_account_expense.id
1284             if not a:
1285                 a = res.categ_id.property_account_expense_categ.id
1286
1287         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1288         if a:
1289             result['account_id'] = a
1290
1291         taxep=None
1292         tax_obj = self.pool.get('account.tax')
1293         if type in ('out_invoice', 'out_refund'):
1294             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1295             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1296         else:
1297             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)
1298             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1299         if type in ('in_invoice', 'in_refund'):
1300             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)
1301             result.update(to_update)
1302         else:
1303             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1304
1305         if not name:
1306             result['name'] = res.partner_ref
1307
1308         domain = {}
1309         result['uos_id'] = uom or res.uom_id.id or False
1310         if result['uos_id']:
1311             res2 = res.uom_id.category_id.id
1312             if res2 :
1313                 domain = {'uos_id':[('category_id','=',res2 )]}
1314
1315         prod_pool=self.pool.get('product.product')
1316         result['categ_id'] = res.categ_id.id
1317         res_final = {'value':result, 'domain':domain}
1318
1319         if not company_id and not currency_id:
1320             return res_final
1321
1322         company = self.pool.get('res.company').browse(cr, uid, company_id)
1323         currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1324
1325         if not currency.company_id.id == company.id:
1326             raise osv.except_osv(_('Configration Error !'),
1327                         _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1328
1329         if company.currency_id.id != currency.id:
1330             new_price = res_final['value']['price_unit'] * currency.rate
1331             res_final['value']['price_unit'] = new_price
1332         return res_final
1333
1334     def move_line_get(self, cr, uid, invoice_id, context=None):
1335         res = []
1336         tax_grouped = {}
1337         tax_obj = self.pool.get('account.tax')
1338         cur_obj = self.pool.get('res.currency')
1339         ait_obj = self.pool.get('account.invoice.tax')
1340         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1341         company_currency = inv.company_id.currency_id.id
1342         cur = inv.currency_id
1343
1344         for line in inv.invoice_line:
1345             mres = self.move_line_get_item(cr, uid, line, context)
1346             if not mres:
1347                 continue
1348             res.append(mres)
1349             tax_code_found= False
1350             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1351                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1352                     line.quantity, inv.address_invoice_id.id, line.product_id,
1353                     inv.partner_id):
1354
1355                 if inv.type in ('out_invoice', 'in_invoice'):
1356                     tax_code_id = tax['base_code_id']
1357                     tax_amount = line.price_subtotal * tax['base_sign']
1358                 else:
1359                     tax_code_id = tax['ref_base_code_id']
1360                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1361
1362                 if tax_code_found:
1363                     if not tax_code_id:
1364                         continue
1365                     res.append(self.move_line_get_item(cr, uid, line, context))
1366                     res[-1]['price'] = 0.0
1367                     res[-1]['account_analytic_id'] = False
1368                 elif not tax_code_id:
1369                     continue
1370                 tax_code_found = True
1371
1372                 res[-1]['tax_code_id'] = tax_code_id
1373                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1374         return res
1375
1376     def move_line_get_item(self, cr, uid, line, context=None):
1377         return {
1378             'type':'src',
1379             'name': line.name[:64],
1380             'price_unit':line.price_unit,
1381             'quantity':line.quantity,
1382             'price':line.price_subtotal,
1383             'account_id':line.account_id.id,
1384             'product_id':line.product_id.id,
1385             'uos_id':line.uos_id.id,
1386             'account_analytic_id':line.account_analytic_id.id,
1387             'taxes':line.invoice_line_tax_id,
1388         }
1389     #
1390     # Set the tax field according to the account and the fiscal position
1391     #
1392     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1393         if not account_id:
1394             return {}
1395         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1396         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1397         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1398         r = {'value':{'invoice_line_tax_id': res}}
1399         return r
1400 account_invoice_line()
1401
1402 class account_invoice_tax(osv.osv):
1403     _name = "account.invoice.tax"
1404     _description = "Invoice Tax"
1405     _columns = {
1406         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1407         'name': fields.char('Tax Description', size=64, required=True),
1408         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1409         'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1410         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1411         'manual': fields.boolean('Manual'),
1412         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1413
1414         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1415         'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1416         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1417         'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1418         'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1419     }
1420
1421     def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1422         cur_obj = self.pool.get('res.currency')
1423         company_obj = self.pool.get('res.company')
1424         company_currency=False
1425         if company_id:
1426             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1427         if currency_id and company_currency:
1428             base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1429         return {'value': {'base_amount':base}}
1430
1431     def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1432         cur_obj = self.pool.get('res.currency')
1433         company_obj = self.pool.get('res.company')
1434         company_currency=False
1435         if company_id:
1436             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1437         if currency_id and company_currency:
1438             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1439         return {'value': {'tax_amount':amount}}
1440
1441     _order = 'sequence'
1442     _defaults = {
1443         'manual': lambda *a: 1,
1444         'base_amount': lambda *a: 0.0,
1445         'tax_amount': lambda *a: 0.0,
1446     }
1447     def compute(self, cr, uid, invoice_id, context={}):
1448         tax_grouped = {}
1449         tax_obj = self.pool.get('account.tax')
1450         cur_obj = self.pool.get('res.currency')
1451         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1452         cur = inv.currency_id
1453         company_currency = inv.company_id.currency_id.id
1454
1455         for line in inv.invoice_line:
1456             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):
1457                 val={}
1458                 val['invoice_id'] = inv.id
1459                 val['name'] = tax['name']
1460                 val['amount'] = tax['amount']
1461                 val['manual'] = False
1462                 val['sequence'] = tax['sequence']
1463                 val['base'] = tax['price_unit'] * line['quantity']
1464
1465                 if inv.type in ('out_invoice','in_invoice'):
1466                     val['base_code_id'] = tax['base_code_id']
1467                     val['tax_code_id'] = tax['tax_code_id']
1468                     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)
1469                     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)
1470                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1471                 else:
1472                     val['base_code_id'] = tax['ref_base_code_id']
1473                     val['tax_code_id'] = tax['ref_tax_code_id']
1474                     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)
1475                     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)
1476                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1477
1478                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1479                 if not key in tax_grouped:
1480                     tax_grouped[key] = val
1481                 else:
1482                     tax_grouped[key]['amount'] += val['amount']
1483                     tax_grouped[key]['base'] += val['base']
1484                     tax_grouped[key]['base_amount'] += val['base_amount']
1485                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1486
1487         for t in tax_grouped.values():
1488             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1489             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1490             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1491         return tax_grouped
1492
1493     def move_line_get(self, cr, uid, invoice_id):
1494         res = []
1495         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1496         for t in cr.dictfetchall():
1497             if not t['amount'] \
1498                     and not t['tax_code_id'] \
1499                     and not t['tax_amount']:
1500                 continue
1501             res.append({
1502                 'type':'tax',
1503                 'name':t['name'],
1504                 'price_unit': t['amount'],
1505                 'quantity': 1,
1506                 'price': t['amount'] or 0.0,
1507                 'account_id': t['account_id'],
1508                 'tax_code_id': t['tax_code_id'],
1509                 'tax_amount': t['tax_amount']
1510             })
1511         return res
1512 account_invoice_tax()
1513
1514 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: