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