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