109873a13c70bda173692c102e8829bc5eecc89c
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 import time
23 import netsvc
24 from osv import fields, osv
25 import ir
26 import pooler
27 import mx.DateTime
28 from mx.DateTime import RelativeDateTime
29 from tools import config
30 from tools.translate import _
31
32 class fiscalyear_seq(osv.osv):
33     _name = "fiscalyear.seq"
34     _description = "Maintains Invoice sequences with Fiscal Year"
35     _rec_name = 'fiscalyear_id'
36     _columns = {
37         'journal_id': fields.many2one('account.journal', 'Journal'),
38         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
39         'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
40     }
41
42 fiscalyear_seq()
43
44 class account_invoice(osv.osv):
45     def _amount_all(self, cr, uid, ids, name, args, context=None):
46         res = {}
47         for invoice in self.browse(cr,uid,ids, context=context):
48             res[invoice.id] = {
49                 'amount_untaxed': 0.0,
50                 'amount_tax': 0.0,
51                 'amount_total': 0.0
52             }
53             for line in invoice.invoice_line:
54                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
55             for line in invoice.tax_line:
56                 res[invoice.id]['amount_tax'] += line.amount
57             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
58         return res
59
60     def _get_journal(self, cr, uid, context):
61         if context is None:
62             context = {}
63         type_inv = context.get('type', 'out_invoice')
64         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
65         journal_obj = self.pool.get('account.journal')
66         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
67         if res:
68             return res[0]
69         else:
70             return False
71
72     def _get_currency(self, cr, uid, context):
73         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
74         if user.company_id:
75             return user.company_id.currency_id.id
76         else:
77             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
78
79     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
80         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
81         tt = type2journal.get(type_inv, 'sale')
82         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
83         if not result:
84             raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
85         return result[0]
86
87     def _get_type(self, cr, uid, context=None):
88         if context is None:
89             context = {}
90         type = context.get('type', 'out_invoice')
91         return type
92
93     def _reconciled(self, cr, uid, ids, name, args, context):
94         res = {}
95         for id in ids:
96             res[id] = self.test_paid(cr, uid, [id])
97         return res
98
99     def _get_reference_type(self, cr, uid, context=None):
100         return [('none', _('Free Reference'))]
101
102     def _amount_residual(self, cr, uid, ids, name, args, context=None):
103         res = {}
104         data_inv = self.browse(cr, uid, ids)
105         cur_obj = self.pool.get('res.currency')
106         for inv in data_inv:
107             debit = credit = 0.0
108             for lines in inv.move_lines:
109                 if lines.account_id.company_currency_id.id <> inv.currency_id.id:
110                     if lines.debit:
111                         debit += cur_obj.compute(cr, uid, lines.account_id.company_currency_id.id, inv.currency_id.id, lines.debit)
112                     if lines.credit:
113                         credit += cur_obj.compute(cr, uid, lines.account_id.company_currency_id.id, inv.currency_id.id, lines.credit)
114                 else:
115                     debit += lines.debit
116                     credit += lines.credit
117
118             if not inv.amount_total:
119                 result = 0.0
120             elif inv.type in ('out_invoice','in_refund'):
121                 result = inv.amount_total * (1.0 - credit / (debit + inv.amount_total))
122             else:
123                 result = inv.amount_total * (1.0 - debit / (credit + inv.amount_total))
124             res[inv.id] = round(result,int(config['price_accuracy']))
125         return res
126
127     def _get_lines(self, cr, uid, ids, name, arg, context=None):
128         res = {}
129         for id in ids:
130             move_lines = self.move_line_id_payment_get(cr,uid,[id])
131             if not move_lines:
132                 res[id] = []
133                 continue
134             data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
135             for line in data_lines:
136                 ids_line = []
137                 if line.reconcile_id:
138                     ids_line = line.reconcile_id.line_id
139                 elif line.reconcile_partial_id:
140                     ids_line = line.reconcile_partial_id.line_partial_ids
141                 l = map(lambda x: x.id, ids_line)
142                 res[id]=[x for x in l if x <> line.id]
143         return res
144
145     def _get_invoice_line(self, cr, uid, ids, context=None):
146         result = {}
147         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
148             result[line.invoice_id.id] = True
149         return result.keys()
150
151     def _get_invoice_tax(self, cr, uid, ids, context=None):
152         result = {}
153         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
154             result[tax.invoice_id.id] = True
155         return result.keys()
156
157     def _compute_lines(self, cr, uid, ids, name, args, context=None):
158         result = {}
159         for invoice in self.browse(cr, uid, ids, context):
160             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
161             src = []
162             lines = []
163             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
164                 if m.reconcile_id:
165                     lines += map(lambda x: x.id, m.reconcile_id.line_id)
166                 elif m.reconcile_partial_id:
167                     lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
168                 src.append(m.id)
169             lines = filter(lambda x: x not in src, lines)
170             result[invoice.id] = lines
171         return result
172
173     def _get_invoice_from_line(self, cr, uid, ids, context={}):
174         move = {}
175         for line in self.pool.get('account.move.line').browse(cr, uid, ids):
176             if line.reconcile_partial_id:
177                 for line2 in line.reconcile_partial_id.line_partial_ids:
178                     move[line2.move_id.id] = True
179             if line.reconcile_id:
180                 for line2 in line.reconcile_id.line_id:
181                     move[line2.move_id.id] = True
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     def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
188         move = {}
189         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
190             for line in r.line_partial_ids:
191                 move[line.move_id.id] = True
192             for line in r.line_id:
193                 move[line.move_id.id] = True
194         
195         invoice_ids = []
196         if move:
197             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
198         return invoice_ids
199
200     _name = "account.invoice"
201     _description = 'Invoice'
202     _order = "number"
203     _columns = {
204         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
205         'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
206         'type': fields.selection([
207             ('out_invoice','Customer Invoice'),
208             ('in_invoice','Supplier Invoice'),
209             ('out_refund','Customer Refund'),
210             ('in_refund','Supplier Refund'),
211             ],'Type', readonly=True, select=True),
212
213         'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
214         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
215         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
216             required=True),
217         'comment': fields.text('Additional Information'),
218
219         'state': fields.selection([
220             ('draft','Draft'),
221             ('proforma','Pro-forma'),
222             ('proforma2','Pro-forma'),
223             ('open','Open'),
224             ('paid','Done'),
225             ('cancel','Cancelled')
226         ],'State', select=True, readonly=True),
227
228         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
229         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
230             help="If you use payment terms, the due date will be computed automatically at the generation "\
231                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."),
232         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
233         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
234         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
235         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
236             help="If you use payment terms, the due date will be computed automatically at the generation "\
237                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
238                 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
239         'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
240
241         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
242         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
243         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
244
245         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
246         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
247             store={
248                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
249                 'account.invoice.tax': (_get_invoice_tax, None, 20),
250                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
251             },
252             multi='all'),
253         'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
254             store={
255                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
256                 'account.invoice.tax': (_get_invoice_tax, None, 20),
257                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
258             },
259             multi='all'),
260         'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
261             store={
262                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
263                 'account.invoice.tax': (_get_invoice_tax, None, 20),
264                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
265             },
266             multi='all'),
267         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
268         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
269         'company_id': fields.many2one('res.company', 'Company', required=True),
270         'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
271         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
272             store={
273                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
274                 'account.move.line': (_get_invoice_from_line, None, 50),
275                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
276             }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
277         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
278             help='The bank account to pay to or to be paid from'),
279         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
280         'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
281             store={
282                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
283                 'account.invoice.tax': (_get_invoice_tax, None, 50),
284                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
285                 'account.move.line': (_get_invoice_from_line, None, 50),
286                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
287             },
288             help="Remaining amount due."),
289         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
290         'move_name': fields.char('Account Move', size=64),
291         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
292     }
293     _defaults = {
294         'type': _get_type,
295         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
296         'state': lambda *a: 'draft',
297         'journal_id': _get_journal,
298         'currency_id': _get_currency,
299         'company_id': lambda self, cr, uid, context: \
300                 self.pool.get('res.users').browse(cr, uid, uid,
301                     context=context).company_id.id,
302         'reference_type': lambda *a: 'none',
303     }
304
305     def unlink(self, cr, uid, ids, context=None):
306         invoices = self.read(cr, uid, ids, ['state'])
307         unlink_ids = []
308         for t in invoices:
309             if t['state'] in ('draft', 'cancel'):
310                 unlink_ids.append(t['id'])
311             else:
312                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
313         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
314         return True
315
316 #   def get_invoice_address(self, cr, uid, ids):
317 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
318 #       return [{}]
319
320     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
321             date_invoice=False, payment_term=False, partner_bank_id=False):
322         invoice_addr_id = False
323         contact_addr_id = False
324         partner_payment_term = False
325         acc_id = False
326         bank_id = False
327         fiscal_position = False
328
329         opt = [('uid', str(uid))]
330         if partner_id:
331
332             opt.insert(0, ('id', partner_id))
333             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
334             contact_addr_id = res['contact']
335             invoice_addr_id = res['invoice']
336             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
337             if type in ('out_invoice', 'out_refund'):
338                 acc_id = p.property_account_receivable.id
339             else:
340                 acc_id = p.property_account_payable.id
341             fiscal_position = p.property_account_position and p.property_account_position.id or False
342             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
343             if p.bank_ids:
344                 bank_id = p.bank_ids[0].id
345
346         result = {'value': {
347             'address_contact_id': contact_addr_id,
348             'address_invoice_id': invoice_addr_id,
349             'account_id': acc_id,
350             'payment_term': partner_payment_term,
351             'fiscal_position': fiscal_position
352             }
353         }
354
355         if type in ('in_invoice', 'in_refund'):
356             result['value']['partner_bank'] = bank_id
357
358         if partner_bank_id != bank_id:
359             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
360             result['value'].update(to_update['value'])
361         return result
362
363     def onchange_currency_id(self, cr, uid, ids, curr_id):
364         return {}
365
366     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
367         if not payment_term_id:
368             return {}
369         res={}
370         pt_obj= self.pool.get('account.payment.term')
371         if not date_invoice :
372             date_invoice = time.strftime('%Y-%m-%d')
373
374         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
375
376         if pterm_list:
377             pterm_list = [line[0] for line in pterm_list]
378             pterm_list.sort()
379             res= {'value':{'date_due': pterm_list[-1]}}
380         else:
381              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
382
383         return res
384
385     def onchange_invoice_line(self, cr, uid, ids, lines):
386         return {}
387
388     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
389         return {'value': {}}
390
391     # go from canceled state to draft state
392     def action_cancel_draft(self, cr, uid, ids, *args):
393         self.write(cr, uid, ids, {'state':'draft'})
394         wf_service = netsvc.LocalService("workflow")
395         for inv_id in ids:
396             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
397         return True
398
399     # Workflow stuff
400     #################
401
402     # return the ids of the move lines which has the same account than the invoice
403     # whose id is in ids
404     def move_line_id_payment_get(self, cr, uid, ids, *args):
405         res = []
406         if not ids: return res
407         cr.execute('select \
408                 l.id \
409             from account_move_line l \
410                 left join account_invoice i on (i.move_id=l.move_id) \
411             where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
412         res = map(lambda x: x[0], cr.fetchall())
413         return res
414
415     def copy(self, cr, uid, id, default=None, context=None):
416         if default is None:
417             default = {}
418         default = default.copy()
419         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
420         if 'date_invoice' not in default:
421             default['date_invoice'] = False
422         if 'date_due' not in default:
423             default['date_due'] = False
424         return super(account_invoice, self).copy(cr, uid, id, default, context)
425
426     def test_paid(self, cr, uid, ids, *args):
427         res = self.move_line_id_payment_get(cr, uid, ids)
428         if not res:
429             return False
430         ok = True
431         for id in res:
432             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
433             ok = ok and  bool(cr.fetchone()[0])
434         return ok
435
436     def button_reset_taxes(self, cr, uid, ids, context=None):
437         if not context:
438             context = {}
439         ait_obj = self.pool.get('account.invoice.tax')
440         for id in ids:
441             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
442             partner = self.browse(cr, uid, id,context=context).partner_id
443             if partner.lang:
444                 context.update({'lang': partner.lang})
445             for taxe in ait_obj.compute(cr, uid, id, context=context).values():
446                 ait_obj.create(cr, uid, taxe)
447          # Update the stored value (fields.function), so we write to trigger recompute
448         self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)    
449 #        self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
450         return True
451
452     def button_compute(self, cr, uid, ids, context=None, set_total=False):
453         self.button_reset_taxes(cr, uid, ids, context)
454         for inv in self.browse(cr, uid, ids):
455             if set_total:
456                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
457         return True
458
459     def _convert_ref(self, cr, uid, ref):
460         return (ref or '').replace('/','')
461
462     def _get_analytic_lines(self, cr, uid, id):
463         inv = self.browse(cr, uid, [id])[0]
464         cur_obj = self.pool.get('res.currency')
465
466         company_currency = inv.company_id.currency_id.id
467         if inv.type in ('out_invoice', 'in_refund'):
468             sign = 1
469         else:
470             sign = -1
471
472         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
473         for il in iml:
474             if il['account_analytic_id']:
475                 if inv.type in ('in_invoice', 'in_refund'):
476                     ref = inv.reference
477                 else:
478                     ref = self._convert_ref(cr, uid, inv.number)
479                 il['analytic_lines'] = [(0,0, {
480                     'name': il['name'],
481                     'date': inv['date_invoice'],
482                     'account_id': il['account_analytic_id'],
483                     'unit_amount': il['quantity'],
484                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
485                     'product_id': il['product_id'],
486                     'product_uom_id': il['uos_id'],
487                     'general_account_id': il['account_id'],
488                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
489                     'ref': ref,
490                 })]
491         return iml
492
493     def action_date_assign(self, cr, uid, ids, *args):
494         for inv in self.browse(cr, uid, ids):
495             res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
496             if res and res['value']:
497                 self.write(cr, uid, [inv.id], res['value'])
498         return True
499
500     def action_move_create(self, cr, uid, ids, *args):
501         ait_obj = self.pool.get('account.invoice.tax')
502         cur_obj = self.pool.get('res.currency')
503         context = {}
504         for inv in self.browse(cr, uid, ids):
505             if inv.move_id:
506                 continue
507
508             if not inv.date_invoice:
509                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
510             company_currency = inv.company_id.currency_id.id
511             # create the analytical lines
512             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
513             # one move line per invoice line
514             iml = self._get_analytic_lines(cr, uid, inv.id)
515             # check if taxes are all computed
516
517             context.update({'lang': inv.partner_id.lang})
518             compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
519             if not inv.tax_line:
520                 for tax in compute_taxes.values():
521                     ait_obj.create(cr, uid, tax)
522             else:
523                 tax_key = []
524                 for tax in inv.tax_line:
525                     if tax.manual:
526                         continue
527                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
528                     tax_key.append(key)
529                     if not key in compute_taxes:
530                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
531                     base = compute_taxes[key]['base']
532                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
533                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
534                 for key in compute_taxes:
535                     if not key in tax_key:
536                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
537
538             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
539                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
540
541             # one move line per tax line
542             iml += ait_obj.move_line_get(cr, uid, inv.id)
543
544             if inv.type in ('in_invoice', 'in_refund'):
545                 ref = inv.reference
546             else:
547                 ref = self._convert_ref(cr, uid, inv.number)
548
549             diff_currency_p = inv.currency_id.id <> company_currency
550             # create one move line for the total and possibly adjust the other lines amount
551             total = 0
552             total_currency = 0
553             for i in iml:
554                 if inv.currency_id.id != company_currency:
555                     i['currency_id'] = inv.currency_id.id
556                     i['amount_currency'] = i['price']
557                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
558                             company_currency, i['price'],
559                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
560                 else:
561                     i['amount_currency'] = False
562                     i['currency_id'] = False
563                 i['ref'] = ref
564                 if inv.type in ('out_invoice','in_refund'):
565                     total += i['price']
566                     total_currency += i['amount_currency'] or i['price']
567                     i['price'] = - i['price']
568                 else:
569                     total -= i['price']
570                     total_currency -= i['amount_currency'] or i['price']
571             acc_id = inv.account_id.id
572
573             name = inv['name'] or '/'
574             totlines = False
575             if inv.payment_term:
576                 totlines = self.pool.get('account.payment.term').compute(cr,
577                         uid, inv.payment_term.id, total, inv.date_invoice or False)
578             if totlines:
579                 res_amount_currency = total_currency
580                 i = 0
581                 for t in totlines:
582                     if inv.currency_id.id != company_currency:
583                         amount_currency = cur_obj.compute(cr, uid,
584                                 company_currency, inv.currency_id.id, t[1])
585                     else:
586                         amount_currency = False
587
588                     # last line add the diff
589                     res_amount_currency -= amount_currency or 0
590                     i += 1
591                     if i == len(totlines):
592                         amount_currency += res_amount_currency
593
594                     iml.append({
595                         'type': 'dest',
596                         'name': name,
597                         'price': t[1],
598                         'account_id': acc_id,
599                         'date_maturity': t[0],
600                         'amount_currency': diff_currency_p \
601                                 and  amount_currency or False,
602                         'currency_id': diff_currency_p \
603                                 and inv.currency_id.id or False,
604                         'ref': ref,
605                     })
606             else:
607                 iml.append({
608                     'type': 'dest',
609                     'name': name,
610                     'price': total,
611                     'account_id': acc_id,
612                     'date_maturity' : inv.date_due or False,
613                     'amount_currency': diff_currency_p \
614                             and total_currency or False,
615                     'currency_id': diff_currency_p \
616                             and inv.currency_id.id or False,
617                     'ref': ref
618             })
619
620             date = inv.date_invoice or time.strftime('%Y-%m-%d')
621             part = inv.partner_id.id
622
623             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
624
625             if inv.journal_id.group_invoice_lines:
626                 line2 = {}
627                 for x, y, l in line:
628                     tmp = str(l['account_id'])
629                     tmp += '-'+str(l.get('tax_code_id',"False"))
630                     tmp += '-'+str(l.get('product_id',"False"))
631                     tmp += '-'+str(l.get('analytic_account_id',"False"))
632                     tmp += '-'+str(l.get('date_maturity',"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, cr, uid, 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(cr, uid, 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(cr, uid, 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             'date': date,
881         }
882         l2 = {
883             'debit': direction * pay_amount<0 and - direction * pay_amount,
884             'credit': direction * pay_amount>0 and direction * pay_amount,
885             'account_id': pay_account_id,
886             'partner_id': invoice.partner_id.id,
887             'ref':invoice.number,
888             'date': date,
889         }
890
891         if not name:
892             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
893         l1['name'] = name
894         l2['name'] = name
895
896         lines = [(0, 0, l1), (0, 0, l2)]
897         move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
898         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
899
900         line_ids = []
901         total = 0.0
902         line = self.pool.get('account.move.line')
903         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
904         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
905         for l in lines+invoice.payment_ids:
906             if l.account_id.id==src_account_id:
907                 line_ids.append(l.id)
908                 total += (l.debit or 0.0) - (l.credit or 0.0)
909         if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
910             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
911         else:
912             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
913
914         # Update the stored value (fields.function), so we write to trigger recompute
915         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
916         return True
917 account_invoice()
918
919 class account_invoice_line(osv.osv):
920     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
921         res = {}
922         cur_obj=self.pool.get('res.currency')
923         for line in self.browse(cr, uid, ids):
924             if line.invoice_id:
925                 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
926                 cur = line.invoice_id.currency_id
927                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
928             else:
929                 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
930         return res
931
932
933     def _price_unit_default(self, cr, uid, context=None):
934         if context is None:
935             context = {}
936         if 'check_total' in context:
937             t = context['check_total']
938             for l in context.get('invoice_line', {}):
939                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
940                     tax_obj = self.pool.get('account.tax')
941                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
942                     t = t - (p * l[2].get('quantity'))
943                     taxes = l[2].get('invoice_line_tax_id')
944                     if len(taxes[0]) >= 3 and taxes[0][2]:
945                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
946                         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)):
947                             t = t - tax['amount']
948             return t
949         return 0
950
951     _name = "account.invoice.line"
952     _description = "Invoice line"
953     _columns = {
954         'name': fields.char('Description', size=256, required=True),
955         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
956         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
957         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
958         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
959         '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."),
960         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
961         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
962         'quantity': fields.float('Quantity', required=True),
963         'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
964         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
965         'note': fields.text('Notes'),
966         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
967     }
968     _defaults = {
969         'quantity': lambda *a: 1,
970         'discount': lambda *a: 0.0,
971         'price_unit': _price_unit_default,
972     }
973
974     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
975         tax_obj = self.pool.get('account.tax')
976         if price_unit:
977             taxes = tax_obj.browse(cr, uid, tax_id)
978             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
979                 price_unit = price_unit - tax['amount']
980         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
981
982     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):
983         if context is None:
984             context = {}
985         if not partner_id:
986             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
987         if not product:
988             if type in ('in_invoice', 'in_refund'):
989                 return {'domain':{'product_uom':[]}}
990             else:
991                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
992         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
993         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
994
995         lang=part.lang
996         context.update({'lang': lang})
997         result = {}
998         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
999
1000         if type in ('out_invoice','out_refund'):
1001             a =  res.product_tmpl_id.property_account_income.id
1002             if not a:
1003                 a = res.categ_id.property_account_income_categ.id
1004         else:
1005             a =  res.product_tmpl_id.property_account_expense.id
1006             if not a:
1007                 a = res.categ_id.property_account_expense_categ.id
1008
1009         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1010         if a:
1011             result['account_id'] = a
1012
1013         taxep=None
1014         tax_obj = self.pool.get('account.tax')
1015         if type in ('out_invoice', 'out_refund'):
1016             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1017             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1018         else:
1019             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)
1020             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1021         if type in ('in_invoice', 'in_refund'):
1022             to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
1023             result.update(to_update)
1024         else:
1025             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1026
1027         if not name:
1028             result['name'] = res.name
1029
1030         domain = {}
1031         result['uos_id'] = uom or res.uom_id.id or False
1032         if result['uos_id']:
1033             res2 = res.uom_id.category_id.id
1034             if res2 :
1035                 domain = {'uos_id':[('category_id','=',res2 )]}
1036         return {'value':result, 'domain':domain}
1037
1038     def move_line_get(self, cr, uid, invoice_id, context=None):
1039         res = []
1040         tax_grouped = {}
1041         tax_obj = self.pool.get('account.tax')
1042         cur_obj = self.pool.get('res.currency')
1043         ait_obj = self.pool.get('account.invoice.tax')
1044         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1045         company_currency = inv.company_id.currency_id.id
1046         cur = inv.currency_id
1047
1048         for line in inv.invoice_line:
1049             mres = self.move_line_get_item(cr, uid, line, context)
1050             if not mres:
1051                 continue
1052             res.append(mres)
1053             tax_code_found= False
1054             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1055                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1056                     line.quantity, inv.address_invoice_id.id, line.product_id,
1057                     inv.partner_id):
1058
1059                 if inv.type in ('out_invoice', 'in_invoice'):
1060                     tax_code_id = tax['base_code_id']
1061                     tax_amount = line.price_subtotal * tax['base_sign']
1062                 else:
1063                     tax_code_id = tax['ref_base_code_id']
1064                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1065
1066                 if tax_code_found:
1067                     if not tax_code_id:
1068                         continue
1069                     res.append(self.move_line_get_item(cr, uid, line, context))
1070                     res[-1]['price'] = 0.0
1071                     res[-1]['account_analytic_id'] = False
1072                 elif not tax_code_id:
1073                     continue
1074                 tax_code_found = True
1075
1076                 res[-1]['tax_code_id'] = tax_code_id
1077                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1078         return res
1079
1080     def move_line_get_item(self, cr, uid, line, context=None):
1081         return {
1082             'type':'src',
1083             'name': line.name[:64],
1084             'price_unit':line.price_unit,
1085             'quantity':line.quantity,
1086             'price':line.price_subtotal,
1087             'account_id':line.account_id.id,
1088             'product_id':line.product_id.id,
1089             'uos_id':line.uos_id.id,
1090             'account_analytic_id':line.account_analytic_id.id,
1091             'taxes':line.invoice_line_tax_id,
1092         }
1093     #
1094     # Set the tax field according to the account and the fiscal position
1095     #
1096     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1097         if not account_id:
1098             return {}
1099         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1100         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1101         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1102         r = {'value':{'invoice_line_tax_id': res}}
1103         return r
1104 account_invoice_line()
1105
1106 class account_invoice_tax(osv.osv):
1107     _name = "account.invoice.tax"
1108     _description = "Invoice Tax"
1109     _columns = {
1110         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1111         'name': fields.char('Tax Description', size=64, required=True),
1112         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1113         'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1114         'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1115         'manual': fields.boolean('Manual'),
1116         'sequence': fields.integer('Sequence'),
1117
1118         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1119         'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1120         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1121         'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1122     }
1123     
1124     def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1125         cur_obj = self.pool.get('res.currency')
1126         company_obj = self.pool.get('res.company')
1127         company_currency=False
1128         if company_id:            
1129             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1130         if currency_id and company_currency:
1131             base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1132         return {'value': {'base_amount':base}}
1133
1134     def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1135         cur_obj = self.pool.get('res.currency')
1136         company_obj = self.pool.get('res.company')
1137         company_currency=False
1138         if company_id:
1139             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1140         if currency_id and company_currency:
1141             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1142         return {'value': {'tax_amount':amount}}
1143     
1144     _order = 'sequence'
1145     _defaults = {
1146         'manual': lambda *a: 1,
1147         'base_amount': lambda *a: 0.0,
1148         'tax_amount': lambda *a: 0.0,
1149     }
1150     def compute(self, cr, uid, invoice_id, context={}):
1151         tax_grouped = {}
1152         tax_obj = self.pool.get('account.tax')
1153         cur_obj = self.pool.get('res.currency')
1154         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1155         cur = inv.currency_id
1156         company_currency = inv.company_id.currency_id.id
1157
1158         for line in inv.invoice_line:
1159             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):
1160                 val={}
1161                 val['invoice_id'] = inv.id
1162                 val['name'] = tax['name']
1163                 val['amount'] = tax['amount']
1164                 val['manual'] = False
1165                 val['sequence'] = tax['sequence']
1166                 val['base'] = tax['price_unit'] * line['quantity']
1167
1168                 if inv.type in ('out_invoice','in_invoice'):
1169                     val['base_code_id'] = tax['base_code_id']
1170                     val['tax_code_id'] = tax['tax_code_id']
1171                     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)
1172                     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)
1173                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1174                 else:
1175                     val['base_code_id'] = tax['ref_base_code_id']
1176                     val['tax_code_id'] = tax['ref_tax_code_id']
1177                     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)
1178                     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)
1179                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1180
1181                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1182                 if not key in tax_grouped:
1183                     tax_grouped[key] = val
1184                 else:
1185                     tax_grouped[key]['amount'] += val['amount']
1186                     tax_grouped[key]['base'] += val['base']
1187                     tax_grouped[key]['base_amount'] += val['base_amount']
1188                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1189
1190         for t in tax_grouped.values():
1191             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1192             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1193             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1194         return tax_grouped
1195
1196     def move_line_get(self, cr, uid, invoice_id):
1197         res = []
1198         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1199         for t in cr.dictfetchall():
1200             if not t['amount'] \
1201                     and not t['tax_code_id'] \
1202                     and not t['tax_amount']:
1203                 continue
1204             res.append({
1205                 'type':'tax',
1206                 'name':t['name'],
1207                 'price_unit': t['amount'],
1208                 'quantity': 1,
1209                 'price': t['amount'] or 0.0,
1210                 'account_id': t['account_id'],
1211                 'tax_code_id': t['tax_code_id'],
1212                 'tax_amount': t['tax_amount']
1213             })
1214         return res
1215 account_invoice_tax()
1216
1217 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1218