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