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