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