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