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