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