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