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