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