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