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