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