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