[FIX] bugfixed the priority of tax setting on onchange product in invoice line
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv
26 import ir
27 import pooler
28 import mx.DateTime
29 from mx.DateTime import RelativeDateTime
30 from tools import config
31 from tools.translate import _
32
33 class fiscalyear_seq(osv.osv):
34     _name = "fiscalyear.seq"
35     _description = "Maintains Invoice sequences with Fiscal Year"
36     _rec_name = 'fiscalyear_id'
37     _columns = {
38         'journal_id': fields.many2one('account.journal', 'Journal'),
39         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
40         'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
41     }
42
43 fiscalyear_seq()
44
45 class account_invoice(osv.osv):
46     def _amount_all(self, cr, uid, ids, name, args, context=None):
47         res = {}
48         for invoice in self.browse(cr,uid,ids, context=context):
49             res[invoice.id] = {
50                 'amount_untaxed': 0.0,
51                 'amount_tax': 0.0,
52                 'amount_total': 0.0
53             }
54             for line in invoice.invoice_line:
55                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
56             for line in invoice.tax_line:
57                 res[invoice.id]['amount_tax'] += line.amount
58             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
59         return res
60
61     def _get_journal(self, cr, uid, context):
62         if context is None:
63             context = {}
64         type_inv = context.get('type', 'out_invoice')
65         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
66         journal_obj = self.pool.get('account.journal')
67         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
68         if res:
69             return res[0]
70         else:
71             return False
72
73     def _get_currency(self, cr, uid, context):
74         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
75         if user.company_id:
76             return user.company_id.currency_id.id
77         else:
78             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
79
80     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
81         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
82         tt = type2journal.get(type_inv, 'sale')
83         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
84         if not result:
85             raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal of type '%s' !") % (tt,))
86         return result[0]
87
88     def _get_type(self, cr, uid, context=None):
89         if context is None:
90             context = {}
91         type = context.get('type', 'out_invoice')
92         return type
93
94     def _reconciled(self, cr, uid, ids, name, args, context):
95         res = {}
96         for id in ids:
97             res[id] = self.test_paid(cr, uid, [id])
98         return res
99
100     def _get_reference_type(self, cr, uid, context=None):
101         return [('none', _('Free Reference'))]
102
103     def _amount_residual(self, cr, uid, ids, name, args, context=None):
104         res = {}
105         data_inv = self.browse(cr, uid, ids)
106         for inv in data_inv:
107             paid_amt = 0.0
108             to_pay = inv.amount_total
109             for lines in inv.move_lines:
110                 paid_amt = paid_amt - lines.credit + lines.debit
111             res[inv.id] = to_pay - abs(paid_amt)
112         return res
113
114     def _get_lines(self, cr, uid, ids, name, arg, context=None):
115         res = {}
116         for id in ids:
117             move_lines = self.move_line_id_payment_get(cr,uid,[id])
118             if not move_lines:
119                 res[id] = []
120                 continue
121             data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
122             for line in data_lines:
123                 ids_line = []
124                 if line.reconcile_id:
125                     ids_line = line.reconcile_id.line_id
126                 elif line.reconcile_partial_id:
127                     ids_line = line.reconcile_partial_id.line_partial_ids
128                 l = map(lambda x: x.id, ids_line)
129                 res[id]=[x for x in l if x <> line.id]
130         return res
131
132     def _get_invoice_line(self, cr, uid, ids, context=None):
133         result = {}
134         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
135             result[line.invoice_id.id] = True
136         return result.keys()
137
138     def _get_invoice_tax(self, cr, uid, ids, context=None):
139         result = {}
140         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
141             result[tax.invoice_id.id] = True
142         return result.keys()
143
144     def _compute_lines(self, cr, uid, ids, name, args, context=None):
145         result = {}
146         for invoice in self.browse(cr, uid, ids, context):
147             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
148             src = []
149             lines = []
150             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
151                 if m.reconcile_id:
152                     lines += map(lambda x: x.id, m.reconcile_id.line_id)
153                 elif m.reconcile_partial_id:
154                     lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
155                 src.append(m.id)
156             lines = filter(lambda x: x not in src, lines)
157             result[invoice.id] = lines
158         return result
159
160     def _get_invoice_from_line(self, cr, uid, ids, context={}):
161         move = {}
162         for line in self.pool.get('account.move.line').browse(cr, uid, ids):
163             if line.reconcile_partial_id:
164                 for line2 in line.reconcile_partial_id.line_partial_ids:
165                     move[line2.move_id.id] = True
166             if line.reconcile_id:
167                 for line2 in line.reconcile_id.line_id:
168                     move[line2.move_id.id] = True
169         invoice_ids = []
170         if move:
171             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
172         return invoice_ids
173
174     def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
175         move = {}
176         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
177             for line in r.line_partial_ids:
178                 move[line.move_id.id] = True
179             for line in r.line_id:
180                 move[line.move_id.id] = True
181         invoice_ids = []
182         if move:
183             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
184         return invoice_ids
185
186     _name = "account.invoice"
187     _description = 'Invoice'
188     _order = "number"
189     _columns = {
190         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
191         'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
192         'type': fields.selection([
193             ('out_invoice','Customer Invoice'),
194             ('in_invoice','Supplier Invoice'),
195             ('out_refund','Customer Refund'),
196             ('in_refund','Supplier Refund'),
197             ],'Type', readonly=True, select=True),
198
199         'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
200         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
201         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
202             required=True),
203         'comment': fields.text('Additional Information'),
204
205         'state': fields.selection([
206             ('draft','Draft'),
207             ('proforma','Pro-forma'),
208             ('proforma2','Pro-forma'),
209             ('open','Open'),
210             ('paid','Done'),
211             ('cancel','Cancelled')
212         ],'State', select=True, readonly=True),
213
214         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
215         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
216             help="If you use payment terms, the due date will be computed automatically at the generation "\
217                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment."),
218         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
219         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
220         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
221         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
222             help="If you use payment terms, the due date will be computed automatically at the generation "\
223                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
224                 "The payment term may compute several due dates: 50% now, 50% in one month."),
225         'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation date."),
226
227         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
228         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
229         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
230
231         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
232         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
233             store={
234                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
235                 'account.invoice.tax': (_get_invoice_tax, None, 20),
236                 'account.invoice.line': (_get_invoice_line, None, 20),
237             },
238             multi='all'),
239         'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
240             store={
241                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
242                 'account.invoice.tax': (_get_invoice_tax, None, 20),
243                 'account.invoice.line': (_get_invoice_line, None, 20),
244             },
245             multi='all'),
246         'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
247             store={
248                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
249                 'account.invoice.tax': (_get_invoice_tax, None, 20),
250                 'account.invoice.line': (_get_invoice_line, None, 20),
251             },
252             multi='all'),
253         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
254         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
255         'company_id': fields.many2one('res.company', 'Company', required=True),
256         'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
257         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
258             store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
259         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
260             help='The bank account to pay to or to be paid from'),
261         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
262         'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
263             store={
264                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
265                 'account.invoice.tax': (_get_invoice_tax, None, 50),
266                 'account.invoice.line': (_get_invoice_line, None, 50),
267                 'account.move.line': (_get_invoice_from_line, None, 50),
268                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
269             },
270             help="Remaining amount due."),
271         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
272         'move_name': fields.char('Account Move', size=64),
273         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
274     }
275     _defaults = {
276         'type': _get_type,
277         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
278         'state': lambda *a: 'draft',
279         'journal_id': _get_journal,
280         'currency_id': _get_currency,
281         'company_id': lambda self, cr, uid, context: \
282                 self.pool.get('res.users').browse(cr, uid, uid,
283                     context=context).company_id.id,
284         'reference_type': lambda *a: 'none',
285     }
286
287     def unlink(self, cr, uid, ids, context=None):
288         invoices = self.read(cr, uid, ids, ['state'])
289         unlink_ids = []
290         for t in invoices:
291             if t['state'] in ('draft', 'cancel'):
292                 unlink_ids.append(t['id'])
293             else:
294                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
295         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
296         return True
297
298 #   def get_invoice_address(self, cr, uid, ids):
299 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
300 #       return [{}]
301
302     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
303             date_invoice=False, payment_term=False, partner_bank_id=False):
304         invoice_addr_id = False
305         contact_addr_id = False
306         partner_payment_term = False
307         acc_id = False
308         bank_id = False
309         fiscal_position = False
310
311         opt = [('uid', str(uid))]
312         if partner_id:
313
314             opt.insert(0, ('id', partner_id))
315             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
316             contact_addr_id = res['contact']
317             invoice_addr_id = res['invoice']
318             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
319             if type in ('out_invoice', 'out_refund'):
320                 acc_id = p.property_account_receivable.id
321             else:
322                 acc_id = p.property_account_payable.id
323             fiscal_position = p.property_account_position and p.property_account_position.id or False
324             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
325             if p.bank_ids:
326                 bank_id = p.bank_ids[0].id
327
328         result = {'value': {
329             'address_contact_id': contact_addr_id,
330             'address_invoice_id': invoice_addr_id,
331             'account_id': acc_id,
332             'payment_term': partner_payment_term,
333             'fiscal_position': fiscal_position
334             }
335         }
336
337         if type in ('in_invoice', 'in_refund'):
338             result['value']['partner_bank'] = bank_id
339
340         if payment_term != partner_payment_term:
341             if partner_payment_term:
342                 to_update = self.onchange_payment_term_date_invoice(
343                     cr,uid,ids,partner_payment_term,date_invoice)
344                 result['value'].update(to_update['value'])
345             else:
346                 result['value']['date_due'] = False
347
348         if partner_bank_id != bank_id:
349             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
350             result['value'].update(to_update['value'])
351         return result
352
353     def onchange_currency_id(self, cr, uid, ids, curr_id):
354         return {}
355
356     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
357         if not payment_term_id:
358             return {}
359         res={}
360         pt_obj= self.pool.get('account.payment.term')
361
362         if not date_invoice :
363             date_invoice = time.strftime('%Y-%m-%d')
364
365         pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
366
367         if pterm_list:
368             pterm_list = [line[0] for line in pterm_list]
369             pterm_list.sort()
370             res= {'value':{'date_due': pterm_list[-1]}}
371
372         return res
373
374     def onchange_invoice_line(self, cr, uid, ids, lines):
375         return {}
376
377     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
378         return {'value': {}}
379
380     # go from canceled state to draft state
381     def action_cancel_draft(self, cr, uid, ids, *args):
382         self.write(cr, uid, ids, {'state':'draft'})
383         wf_service = netsvc.LocalService("workflow")
384         for inv_id in ids:
385             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
386         return True
387
388     # Workflow stuff
389     #################
390
391     # return the ids of the move lines which has the same account than the invoice
392     # whose id is in ids
393     def move_line_id_payment_get(self, cr, uid, ids, *args):
394         ml = self.pool.get('account.move.line')
395         res = []
396         for inv in self.read(cr, uid, ids, ['move_id','account_id']):
397             if inv['move_id']:
398                 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
399                 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
400                     if line['account_id']==inv['account_id']:
401                         res.append(line['id'])
402         return res
403
404     def copy(self, cr, uid, id, default=None, context=None):
405         if default is None:
406             default = {}
407         default = default.copy()
408         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
409         if 'date_invoice' not in default:
410             default['date_invoice'] = False
411         if 'date_due' not in default:
412             default['date_due'] = False
413         return super(account_invoice, self).copy(cr, uid, id, default, context)
414
415     def test_paid(self, cr, uid, ids, *args):
416         res = self.move_line_id_payment_get(cr, uid, ids)
417         if not res:
418             return False
419         ok = True
420         for id in res:
421             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
422             ok = ok and  bool(cr.fetchone()[0])
423         return ok
424
425     def button_reset_taxes(self, cr, uid, ids, context=None):
426         ait_obj = self.pool.get('account.invoice.tax')
427         for id in ids:
428             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
429             for taxe in ait_obj.compute(cr, uid, id).values():
430                 ait_obj.create(cr, uid, taxe)
431          # Update the stored value (fields.function), so we write to trigger recompute
432         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)    
433 #        self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
434         return True
435
436     def button_compute(self, cr, uid, ids, context=None, set_total=False):
437         self.button_reset_taxes(cr, uid, ids, context)
438         for inv in self.browse(cr, uid, ids):
439             if set_total:
440                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
441         return True
442
443     def _convert_ref(self, cr, uid, ref):
444         return (ref or '').replace('/','')
445
446     def _get_analytic_lines(self, cr, uid, id):
447         inv = self.browse(cr, uid, [id])[0]
448         cur_obj = self.pool.get('res.currency')
449
450         company_currency = inv.company_id.currency_id.id
451         if inv.type in ('out_invoice', 'in_refund'):
452             sign = 1
453         else:
454             sign = -1
455
456         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
457         for il in iml:
458             if il['account_analytic_id']:
459                 if inv.type in ('in_invoice', 'in_refund'):
460                     ref = inv.reference
461                 else:
462                     ref = self._convert_ref(cr, uid, inv.number)
463                 il['analytic_lines'] = [(0,0, {
464                     'name': il['name'],
465                     'date': inv['date_invoice'],
466                     'account_id': il['account_analytic_id'],
467                     'unit_amount': il['quantity'],
468                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
469                     'product_id': il['product_id'],
470                     'product_uom_id': il['uos_id'],
471                     'general_account_id': il['account_id'],
472                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
473                     'ref': ref,
474                 })]
475         return iml
476
477     def action_move_create(self, cr, uid, ids, *args):
478         ait_obj = self.pool.get('account.invoice.tax')
479         cur_obj = self.pool.get('res.currency')
480
481         for inv in self.browse(cr, uid, ids):
482             if not inv.tax_line:
483                 self.button_compute(cr, uid, [inv.id], context={}, set_total=False)
484             if inv.move_id:
485                 continue
486
487             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
488                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
489             if not inv.date_invoice:
490                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
491             company_currency = inv.company_id.currency_id.id
492             # create the analytical lines
493             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
494             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
495             # one move line per invoice line
496             iml = self._get_analytic_lines(cr, uid, inv.id)
497             # check if taxes are all computed
498             compute_taxes = ait_obj.compute(cr, uid, inv.id)
499             if not inv.tax_line:
500                 for tax in compute_taxes.values():
501                     ait_obj.create(cr, uid, tax)
502             else:
503                 tax_key = []
504                 for tax in inv.tax_line:
505                     if tax.manual:
506                         continue
507                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
508                     tax_key.append(key)
509                     if not key in compute_taxes:
510                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
511                     base = compute_taxes[key]['base']
512                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
513                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
514                 for key in compute_taxes:
515                     if not key in tax_key:
516                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
517
518             # one move line per tax line
519             iml += ait_obj.move_line_get(cr, uid, inv.id)
520
521             if inv.type in ('in_invoice', 'in_refund'):
522                 ref = inv.reference
523             else:
524                 ref = self._convert_ref(cr, uid, inv.number)
525
526             diff_currency_p = inv.currency_id.id <> company_currency
527             # create one move line for the total and possibly adjust the other lines amount
528             total = 0
529             total_currency = 0
530             for i in iml:
531                 if inv.currency_id.id != company_currency:
532                     i['currency_id'] = inv.currency_id.id
533                     i['amount_currency'] = i['price']
534                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
535                             company_currency, i['price'],
536                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
537                 else:
538                     i['amount_currency'] = False
539                     i['currency_id'] = False
540                 i['ref'] = ref
541                 if inv.type in ('out_invoice','in_refund'):
542                     total += i['price']
543                     total_currency += i['amount_currency'] or i['price']
544                     i['price'] = - i['price']
545                 else:
546                     total -= i['price']
547                     total_currency -= i['amount_currency'] or i['price']
548             acc_id = inv.account_id.id
549
550             name = inv['name'] or '/'
551             totlines = False
552             if inv.payment_term:
553                 totlines = self.pool.get('account.payment.term').compute(cr,
554                         uid, inv.payment_term.id, total, inv.date_invoice or False)
555             if totlines:
556                 res_amount_currency = total_currency
557                 i = 0
558                 for t in totlines:
559                     if inv.currency_id.id != company_currency:
560                         amount_currency = cur_obj.compute(cr, uid,
561                                 company_currency, inv.currency_id.id, t[1])
562                     else:
563                         amount_currency = False
564
565                     # last line add the diff
566                     res_amount_currency -= amount_currency or 0
567                     i += 1
568                     if i == len(totlines):
569                         amount_currency += res_amount_currency
570
571                     iml.append({
572                         'type': 'dest',
573                         'name': name,
574                         'price': t[1],
575                         'account_id': acc_id,
576                         'date_maturity': t[0],
577                         'amount_currency': diff_currency_p \
578                                 and  amount_currency or False,
579                         'currency_id': diff_currency_p \
580                                 and inv.currency_id.id or False,
581                         'ref': ref,
582                     })
583             else:
584                 iml.append({
585                     'type': 'dest',
586                     'name': name,
587                     'price': total,
588                     'account_id': acc_id,
589                     'date_maturity' : inv.date_due or False,
590                     'amount_currency': diff_currency_p \
591                             and total_currency or False,
592                     'currency_id': diff_currency_p \
593                             and inv.currency_id.id or False,
594                     'ref': ref
595             })
596
597             date = inv.date_invoice or time.strftime('%Y-%m-%d')
598             part = inv.partner_id.id
599
600             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
601
602             if inv.journal_id.group_invoice_lines:
603                 line2 = {}
604                 for x, y, l in line:
605                     tmp = str(l['account_id'])
606                     tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
607                     tmp += '-'+str('product_id' in l and l['product_id'] or "False")
608                     tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
609
610                     if tmp in line2:
611                         am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
612                         line2[tmp]['debit'] = (am > 0) and am or 0.0
613                         line2[tmp]['credit'] = (am < 0) and -am or 0.0
614                         line2[tmp]['tax_amount'] += l['tax_amount']
615                         line2[tmp]['analytic_lines'] += l['analytic_lines']
616                     else:
617                         line2[tmp] = l
618                 line = []
619                 for key, val in line2.items():
620                     line.append((0,0,val))
621
622             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
623             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
624             if journal.centralisation:
625                 raise osv.except_osv(_('UserError'),
626                         _('Can not create invoice move on centralised journal'))
627             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
628             period_id=inv.period_id and inv.period_id.id or False
629             if not period_id:
630                 period_ids= self.pool.get('account.period').search(cr,uid,[('date_start','<=',inv.date_invoice or time.strftime('%Y-%m-%d')),('date_stop','>=',inv.date_invoice or time.strftime('%Y-%m-%d'))])
631                 if len(period_ids):
632                     period_id=period_ids[0]
633             if period_id:
634                 move['period_id'] = period_id
635                 for i in line:
636                     i[2]['period_id'] = period_id
637
638             if not 'name' in move:
639                 move['name'] = inv.name or '/'
640
641             move_id = self.pool.get('account.move').create(cr, uid, move)
642             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
643             # make the invoice point to that move
644             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
645             self.pool.get('account.move').post(cr, uid, [move_id])
646         self._log_event(cr, uid, ids)
647         return True
648
649     def line_get_convert(self, cr, uid, x, part, date, context=None):
650         return {
651             'date_maturity': x.get('date_maturity', False),
652             'partner_id':part,
653             'name':x['name'][:64],
654             'debit':x['price']>0 and x['price'],
655             'credit':x['price']<0 and -x['price'],
656             'account_id':x['account_id'],
657             'analytic_lines':x.get('analytic_lines', []),
658             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
659             'currency_id':x.get('currency_id', False),
660             'tax_code_id': x.get('tax_code_id', False),
661             'tax_amount': x.get('tax_amount', False),
662             'ref':x.get('ref',False),
663             'quantity':x.get('quantity',1.00),
664             'product_id':x.get('product_id', False),
665             'product_uom_id':x.get('uos_id',False),
666             'analytic_account_id':x.get('account_analytic_id',False),
667         }
668
669     def action_number(self, cr, uid, ids, *args):
670         cr.execute('SELECT id, type, number, move_id, reference ' \
671                 'FROM account_invoice ' \
672                 'WHERE id IN ('+','.join(map(str,ids))+')')
673         obj_inv = self.browse(cr, uid, ids)[0]
674         for (id, invtype, number, move_id, reference) in cr.fetchall():
675             if not number:
676                 flag = True
677                 for seq in obj_inv.journal_id.fy_seq_id:
678                     if seq.fiscalyear_id.id == obj_inv.move_id.period_id.fiscalyear_id.id:
679                         number =  self.pool.get('ir.sequence').get_id(cr, uid,seq.sequence_id.id)
680                         flag = False
681                         break
682                 if flag:
683                     number = self.pool.get('ir.sequence').get(cr, uid,
684                             'account.invoice.' + invtype)
685                 if invtype in ('in_invoice', 'in_refund'):
686                     ref = reference
687                 else:
688                     ref = self._convert_ref(cr, uid, number)
689                 cr.execute('UPDATE account_invoice SET number=%s ' \
690                         'WHERE id=%s', (number, id))
691                 cr.execute('UPDATE account_move SET ref=%s ' \
692                         'WHERE id=%s AND (ref is null OR ref = \'\')',
693                         (ref, move_id))
694                 cr.execute('UPDATE account_move_line SET ref=%s ' \
695                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
696                         (ref, move_id))
697                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
698                         'FROM account_move_line ' \
699                         'WHERE account_move_line.move_id = %s ' \
700                             'AND account_analytic_line.move_id = account_move_line.id',
701                             (ref, move_id))
702         return True
703
704     def action_cancel(self, cr, uid, ids, *args):
705         account_move_obj = self.pool.get('account.move')
706         invoices = self.read(cr, uid, ids, ['move_id'])
707         for i in invoices:
708             if i['move_id']:
709                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
710                 # delete the move this invoice was pointing to
711                 # Note that the corresponding move_lines and move_reconciles
712                 # will be automatically deleted too
713                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
714         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
715         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
716         return True
717
718     ###################
719
720     def list_distinct_taxes(self, cr, uid, ids):
721         invoices = self.browse(cr, uid, ids)
722         taxes = {}
723         for inv in invoices:
724             for tax in inv.tax_line:
725                 if not tax['name'] in taxes:
726                     taxes[tax['name']] = {'name': tax['name']}
727         return taxes.values()
728
729     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
730         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
731         for inv in invs:
732             part=inv['partner_id'] and inv['partner_id'][0]
733             pc = pr = 0.0
734             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
735             total = inv['amount_untaxed']
736             if inv['type'] in ('in_invoice','in_refund'):
737                 partnertype='supplier'
738                 eventtype = 'purchase'
739                 pc = total*factor
740             else:
741                 partnertype = 'customer'
742                 eventtype = 'sale'
743                 pr = total*factor
744             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
745                 self.pool.get('res.partner.event').create(cr, uid, {'name':'Invoice: '+name, 'som':False, 'description':name+' '+str(inv['id']), 'document':name, 'partner_id':part, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'canal_id':False, 'user_id':uid, 'partner_type':partnertype, 'probability':1.0, 'planned_revenue':pr, 'planned_cost':pc, 'type':eventtype})
746         return len(invs)
747
748     def name_get(self, cr, uid, ids, context=None):
749         if not len(ids):
750             return []
751         types = {
752                 'out_invoice': 'CI: ',
753                 'in_invoice': 'SI: ',
754                 'out_refund': 'OR: ',
755                 'in_refund': 'SR: ',
756                 }
757         return [(r['id'], types[r['type']]+(r['number'] or '')+' '+(r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
758
759     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
760         if not args:
761             args=[]
762         if context is None:
763             context={}
764         ids = []
765         if name:
766             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
767         if not ids:
768             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
769         return self.name_get(cr, user, ids, context)
770
771     def _refund_cleanup_lines(self, lines):
772         for line in lines:
773             del line['id']
774             del line['invoice_id']
775             if 'account_id' in line:
776                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
777             if 'product_id' in line:
778                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
779             if 'uos_id' in line:
780                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
781             if 'invoice_line_tax_id' in line:
782                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
783             if 'account_analytic_id' in line:
784                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
785             if 'tax_code_id' in line :
786                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
787                     line['tax_code_id'] = line['tax_code_id'][0]
788             if 'base_code_id' in line :
789                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
790                     line['base_code_id'] = line['base_code_id'][0]
791         return map(lambda x: (0,0,x), lines)
792
793     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
794         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'])
795
796         new_ids = []
797         for invoice in invoices:
798             del invoice['id']
799
800             type_dict = {
801                 'out_invoice': 'out_refund', # Customer Invoice
802                 'in_invoice': 'in_refund',   # Supplier Invoice
803                 'out_refund': 'out_invoice', # Customer Refund
804                 'in_refund': 'in_invoice',   # Supplier Refund
805             }
806
807
808             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
809             invoice_lines = self._refund_cleanup_lines(invoice_lines)
810
811             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
812             tax_lines = filter(lambda l: l['manual'], tax_lines)
813             tax_lines = self._refund_cleanup_lines(tax_lines)
814             if not date :
815                 date = time.strftime('%Y-%m-%d')
816             invoice.update({
817                 'type': type_dict[invoice['type']],
818                 'date_invoice': date,
819                 'state': 'draft',
820                 'number': False,
821                 'invoice_line': invoice_lines,
822                 'tax_line': tax_lines
823             })
824             if period_id :
825                 invoice.update({
826                     'period_id': period_id,
827                 })
828             if description :
829                 invoice.update({
830                     'name': description,
831                 })
832             # take the id part of the tuple returned for many2one fields
833             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
834                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
835                 invoice[field] = invoice[field] and invoice[field][0]
836             # create the new invoice
837             new_ids.append(self.create(cr, uid, invoice))
838         return new_ids
839
840     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=''):
841         if context is None:
842             context = {}
843         #TODO check if we can use different period for payment and the writeoff line
844         assert len(ids)==1, "Can only pay one invoice at a time"
845         invoice = self.browse(cr, uid, ids[0])
846         src_account_id = invoice.account_id.id
847         # Take the seq as name for move
848         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
849         direction = types[invoice.type]
850         #take the choosen date
851         if 'date_p' in context and context['date_p']:
852             date=context['date_p']
853         else:
854             date=time.strftime('%Y-%m-%d')
855         l1 = {
856             'debit': direction * pay_amount>0 and direction * pay_amount,
857             'credit': direction * pay_amount<0 and - direction * pay_amount,
858             'account_id': src_account_id,
859             'partner_id': invoice.partner_id.id,
860             'ref':invoice.number,
861         }
862         l2 = {
863             'debit': direction * pay_amount<0 and - direction * pay_amount,
864             'credit': direction * pay_amount>0 and direction * pay_amount,
865             'account_id': pay_account_id,
866             'partner_id': invoice.partner_id.id,
867             'ref':invoice.number,
868         }
869
870         name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
871         l1['name'] = name
872         l2['name'] = name
873
874         lines = [(0, 0, l1), (0, 0, l2)]
875         move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
876         move_id = self.pool.get('account.move').create(cr, uid, move)
877
878         line_ids = []
879         total = 0.0
880         line = self.pool.get('account.move.line')
881         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
882         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
883         for l in lines:
884             if l.account_id.id==src_account_id:
885                 line_ids.append(l.id)
886                 total += (l.debit or 0.0) - (l.credit or 0.0)
887         if (not total) or writeoff_acc_id:
888             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
889         else:
890             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
891
892         # Update the stored value (fields.function), so we write to trigger recompute
893         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
894         return True
895 account_invoice()
896
897 class account_invoice_line(osv.osv):
898     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
899         res = {}
900         for line in self.browse(cr, uid, ids):
901             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
902         return res
903
904     def _price_unit_default(self, cr, uid, context=None):
905         if context is None:
906             context = {}
907         if 'check_total' in context:
908             t = context['check_total']
909             for l in context.get('invoice_line', {}):
910                 if len(l) >= 3 and l[2]:
911                     tax_obj = self.pool.get('account.tax')
912                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
913                     t = t - (p * l[2].get('quantity'))
914                     taxes = l[2].get('invoice_line_tax_id')
915                     if len(taxes[0]) >= 3 and taxes[0][2]:
916                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
917                         for tax in tax_obj.compute(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)):
918                             t = t - tax['amount']
919             return t
920         return 0
921
922     _name = "account.invoice.line"
923     _description = "Invoice line"
924     _columns = {
925         'name': fields.char('Description', size=256, required=True),
926         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
927         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
928         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
929         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
930         '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."),
931         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
932         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
933         'quantity': fields.float('Quantity', required=True),
934         'discount': fields.float('Discount (%)', digits=(16,2)),
935         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
936         'note': fields.text('Notes'),
937         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
938     }
939     _defaults = {
940         'quantity': lambda *a: 1,
941         'discount': lambda *a: 0.0,
942         'price_unit': _price_unit_default,
943     }
944
945     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
946         tax_obj = self.pool.get('account.tax')
947         if price_unit:
948             taxes = tax_obj.browse(cr, uid, tax_id)
949             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
950                 price_unit = price_unit - tax['amount']
951         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
952
953     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, context=None):
954         if context is None:
955             context = {}
956         if not partner_id:
957             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
958         if not product:
959             if type in ('in_invoice', 'in_refund'):
960                 return {'domain':{'product_uom':[]}}
961             else:
962                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
963         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
964         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
965
966         lang=part.lang
967         context.update({'lang': lang})
968         result = {}
969         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
970
971         if type in ('out_invoice','out_refund'):
972             a =  res.product_tmpl_id.property_account_income.id
973             if not a:
974                 a = res.categ_id.property_account_income_categ.id
975         else:
976             a =  res.product_tmpl_id.property_account_expense.id
977             if not a:
978                 a = res.categ_id.property_account_expense_categ.id
979
980         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
981         if a:
982             result['account_id'] = a
983
984         taxep=None
985         tax_obj = self.pool.get('account.tax')
986         if type in ('out_invoice', 'out_refund'):
987             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
988             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
989         else:
990             taxes = res.supplier_taxes_id and res.supplier_taxes_id or a.tax_ids
991             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
992         if type in ('in_invoice', 'in_refund'):
993             to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
994             result.update(to_update['value'])
995         else:
996             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
997
998         if not name:
999             result['name'] = res.name
1000
1001         domain = {}
1002         result['uos_id'] = uom or res.uom_id.id or False
1003         if result['uos_id']:
1004             res2 = res.uom_id.category_id.id
1005             if res2 :
1006                 domain = {'uos_id':[('category_id','=',res2 )]}
1007         return {'value':result, 'domain':domain}
1008
1009     def move_line_get(self, cr, uid, invoice_id, context=None):
1010         res = []
1011         tax_grouped = {}
1012         tax_obj = self.pool.get('account.tax')
1013         cur_obj = self.pool.get('res.currency')
1014         ait_obj = self.pool.get('account.invoice.tax')
1015         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1016         company_currency = inv.company_id.currency_id.id
1017         cur = inv.currency_id
1018
1019         for line in inv.invoice_line:
1020             mres = self.move_line_get_item(cr, uid, line, context)
1021             if not mres:
1022                 continue
1023             res.append(mres)
1024             tax_code_found= False
1025             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1026                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1027                     line.quantity, inv.address_invoice_id.id, line.product_id,
1028                     inv.partner_id):
1029
1030                 if inv.type in ('out_invoice', 'in_invoice'):
1031                     tax_code_id = tax['base_code_id']
1032                     tax_amount = line.price_subtotal * tax['base_sign']
1033                 else:
1034                     tax_code_id = tax['ref_base_code_id']
1035                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1036
1037                 if tax_code_found:
1038                     if not tax_code_id:
1039                         continue
1040                     res.append(self.move_line_get_item(cr, uid, line, context))
1041                     res[-1]['price'] = 0.0
1042                     res[-1]['account_analytic_id'] = False
1043                 elif not tax_code_id:
1044                     continue
1045                 tax_code_found = True
1046
1047                 res[-1]['tax_code_id'] = tax_code_id
1048                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1049         return res
1050
1051     def move_line_get_item(self, cr, uid, line, context=None):
1052         return {
1053             'type':'src',
1054             'name': line.name[:64],
1055             'price_unit':line.price_unit,
1056             'quantity':line.quantity,
1057             'price':line.price_subtotal,
1058             'account_id':line.account_id.id,
1059             'product_id':line.product_id.id,
1060             'uos_id':line.uos_id.id,
1061             'account_analytic_id':line.account_analytic_id.id,
1062             'taxes':line.invoice_line_tax_id,
1063         }
1064     #
1065     # Set the tax field according to the account and the fiscal position
1066     #
1067     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1068         if not account_id:
1069             return {}
1070         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1071         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1072         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1073         r = {'value':{'invoice_line_tax_id': res}}
1074         return r
1075 account_invoice_line()
1076
1077 class account_invoice_tax(osv.osv):
1078     _name = "account.invoice.tax"
1079     _description = "Invoice Tax"
1080     _columns = {
1081         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1082         'name': fields.char('Tax Description', size=64, required=True),
1083         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1084         'base': fields.float('Base', digits=(16,2)),
1085         'amount': fields.float('Amount', digits=(16,2)),
1086         'manual': fields.boolean('Manual'),
1087         'sequence': fields.integer('Sequence'),
1088
1089         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1090         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1091         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1092         'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1093     }
1094     def base_change(self, cr, uid, ids, base):
1095         return {'value': {'base_amount':base}}
1096     def amount_change(self, cr, uid, ids, amount):
1097         return {'value': {'tax_amount':amount}}
1098     _order = 'sequence'
1099     _defaults = {
1100         'manual': lambda *a: 1,
1101         'base_amount': lambda *a: 0.0,
1102         'tax_amount': lambda *a: 0.0,
1103     }
1104     def compute(self, cr, uid, invoice_id):
1105         tax_grouped = {}
1106         tax_obj = self.pool.get('account.tax')
1107         cur_obj = self.pool.get('res.currency')
1108         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1109         cur = inv.currency_id
1110         company_currency = inv.company_id.currency_id.id
1111
1112         for line in inv.invoice_line:
1113             for tax in tax_obj.compute(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):
1114                 val={}
1115                 val['invoice_id'] = inv.id
1116                 val['name'] = tax['name']
1117                 val['amount'] = tax['amount']
1118                 val['manual'] = False
1119                 val['sequence'] = tax['sequence']
1120                 val['base'] = tax['price_unit'] * line['quantity']
1121
1122                 if inv.type in ('out_invoice','in_invoice'):
1123                     val['base_code_id'] = tax['base_code_id']
1124                     val['tax_code_id'] = tax['tax_code_id']
1125                     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')})
1126                     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')})
1127                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1128                 else:
1129                     val['base_code_id'] = tax['ref_base_code_id']
1130                     val['tax_code_id'] = tax['ref_tax_code_id']
1131                     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')})
1132                     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')})
1133                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1134
1135                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1136                 if not key in tax_grouped:
1137                     tax_grouped[key] = val
1138                 else:
1139                     tax_grouped[key]['amount'] += val['amount']
1140                     tax_grouped[key]['base'] += val['base']
1141                     tax_grouped[key]['base_amount'] += val['base_amount']
1142                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1143
1144         for t in tax_grouped.values():
1145             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1146         return tax_grouped
1147
1148     def move_line_get(self, cr, uid, invoice_id):
1149         res = []
1150         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1151         for t in cr.dictfetchall():
1152             if not t['amount'] \
1153                     and not t['tax_code_id'] \
1154                     and not t['tax_amount']:
1155                 continue
1156             res.append({
1157                 'type':'tax',
1158                 'name':t['name'],
1159                 'price_unit': t['amount'],
1160                 'quantity': 1,
1161                 'price': t['amount'] or 0.0,
1162                 'account_id': t['account_id'],
1163                 'tax_code_id': t['tax_code_id'],
1164                 'tax_amount': t['tax_amount']
1165             })
1166         return res
1167 account_invoice_tax()
1168
1169 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1170