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