[FIX] account: set correct journal while opening customer and supplier invoices and...
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv
26 import ir
27 import pooler
28 import mx.DateTime
29 from mx.DateTime import RelativeDateTime
30 from tools import config
31 from tools.translate import _
32
33 class fiscalyear_seq(osv.osv):
34     _name = "fiscalyear.seq"
35     _description = "Maintains Invoice sequences with Fiscal Year"
36     _rec_name = 'fiscalyear_id'
37     _columns = {
38         'journal_id': fields.many2one('account.journal', 'Journal'),
39         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
40         'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
41     }
42
43 fiscalyear_seq()
44
45 class account_invoice(osv.osv):
46     def _amount_all(self, cr, uid, ids, name, args, context=None):
47         res = {}
48         for invoice in self.browse(cr,uid,ids, context=context):
49             res[invoice.id] = {
50                 'amount_untaxed': 0.0,
51                 'amount_tax': 0.0,
52                 'amount_total': 0.0
53             }
54             for line in invoice.invoice_line:
55                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
56             for line in invoice.tax_line:
57                 res[invoice.id]['amount_tax'] += line.amount
58             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
59         return res
60
61     def _get_journal(self, cr, uid, context):
62         if context is None:
63             context = {}
64         type_inv = context.get('type', 'out_invoice')
65         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
66         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 unlink(self, cr, uid, ids, context=None):
333         invoices = self.read(cr, uid, ids, ['state'])
334         unlink_ids = []
335         for t in invoices:
336             if t['state'] in ('draft', 'cancel'):
337                 unlink_ids.append(t['id'])
338             else:
339                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
340         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
341         return True
342
343 #   def get_invoice_address(self, cr, uid, ids):
344 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
345 #       return [{}]
346
347     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
348             date_invoice=False, payment_term=False, partner_bank_id=False):
349         invoice_addr_id = False
350         contact_addr_id = False
351         partner_payment_term = False
352         acc_id = False
353         bank_id = False
354         fiscal_position = False
355
356         opt = [('uid', str(uid))]
357         if partner_id:
358
359             opt.insert(0, ('id', partner_id))
360             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
361             contact_addr_id = res['contact']
362             invoice_addr_id = res['invoice']
363             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
364             if type in ('out_invoice', 'out_refund'):
365                 acc_id = p.property_account_receivable.id
366             else:
367                 acc_id = p.property_account_payable.id
368             fiscal_position = p.property_account_position and p.property_account_position.id or False
369             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
370             if p.bank_ids:
371                 bank_id = p.bank_ids[0].id
372
373         result = {'value': {
374             'address_contact_id': contact_addr_id,
375             'address_invoice_id': invoice_addr_id,
376             'account_id': acc_id,
377             'payment_term': partner_payment_term,
378             'fiscal_position': fiscal_position
379             }
380         }
381
382         if type in ('in_invoice', 'in_refund'):
383             result['value']['partner_bank'] = bank_id
384
385         if partner_bank_id != bank_id:
386             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
387             result['value'].update(to_update['value'])
388         return result
389
390     def onchange_currency_id(self, cr, uid, ids, curr_id):
391         return {}
392
393     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
394         if not payment_term_id:
395             return {}
396         res={}
397         pt_obj= self.pool.get('account.payment.term')
398         if not date_invoice :
399             date_invoice = time.strftime('%Y-%m-%d')
400
401         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
402
403         if pterm_list:
404             pterm_list = [line[0] for line in pterm_list]
405             pterm_list.sort()
406             res= {'value':{'date_due': pterm_list[-1]}}
407         else:
408              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
409
410         return res
411
412     def onchange_invoice_line(self, cr, uid, ids, lines):
413         return {}
414
415     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
416         return {'value': {}}
417
418     # go from canceled state to draft state
419     def action_cancel_draft(self, cr, uid, ids, *args):
420         self.write(cr, uid, ids, {'state':'draft'})
421         wf_service = netsvc.LocalService("workflow")
422         for inv_id in ids:
423             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
424         return True
425
426     # Workflow stuff
427     #################
428
429     # return the ids of the move lines which has the same account than the invoice
430     # whose id is in ids
431     def move_line_id_payment_get(self, cr, uid, ids, *args):
432         res = []
433         if not ids: return res
434         cr.execute('select \
435                 l.id \
436             from account_move_line l \
437                 left join account_invoice i on (i.move_id=l.move_id) \
438             where i.id in ('+','.join(map(str,ids))+') and l.account_id=i.account_id')
439         res = map(lambda x: x[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 ('+','.join(map(str,ids))+')')
722         obj_inv = self.browse(cr, uid, ids)[0]
723         for (id, invtype, number, move_id, reference) in cr.fetchall():
724             if not number:
725                 if obj_inv.journal_id.invoice_sequence_id:
726                     sid = obj_inv.journal_id.invoice_sequence_id.id
727                     number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
728                 else:
729                     number = self.pool.get('ir.sequence').get(cr, uid,
730                             'account.invoice.' + invtype)
731                 if invtype in ('in_invoice', 'in_refund'):
732                     ref = reference
733                 else:
734                     ref = self._convert_ref(cr, uid, number)
735                 cr.execute('UPDATE account_invoice SET number=%s ' \
736                         'WHERE id=%s', (number, id))
737                 cr.execute('UPDATE account_move SET ref=%s ' \
738                         'WHERE id=%s AND (ref is null OR ref = \'\')',
739                         (ref, move_id))
740                 cr.execute('UPDATE account_move_line SET ref=%s ' \
741                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
742                         (ref, move_id))
743                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
744                         'FROM account_move_line ' \
745                         'WHERE account_move_line.move_id = %s ' \
746                             'AND account_analytic_line.move_id = account_move_line.id',
747                             (ref, move_id))
748         return True
749
750     def action_cancel(self, cr, uid, ids, *args):
751         account_move_obj = self.pool.get('account.move')
752         invoices = self.read(cr, uid, ids, ['move_id'])
753         for i in invoices:
754             if i['move_id']:
755                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
756                 # delete the move this invoice was pointing to
757                 # Note that the corresponding move_lines and move_reconciles
758                 # will be automatically deleted too
759                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
760         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
761         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
762         return True
763
764     ###################
765
766     def list_distinct_taxes(self, cr, uid, ids):
767         invoices = self.browse(cr, uid, ids)
768         taxes = {}
769         for inv in invoices:
770             for tax in inv.tax_line:
771                 if not tax['name'] in taxes:
772                     taxes[tax['name']] = {'name': tax['name']}
773         return taxes.values()
774
775     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
776         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
777         for inv in invs:
778             part=inv['partner_id'] and inv['partner_id'][0]
779             pc = pr = 0.0
780             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
781             total = inv['amount_untaxed']
782             if inv['type'] in ('in_invoice','in_refund'):
783                 partnertype='supplier'
784                 eventtype = 'purchase'
785                 pc = total*factor
786             else:
787                 partnertype = 'customer'
788                 eventtype = 'sale'
789                 pr = total*factor
790             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
791                 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})
792         return len(invs)
793
794     def name_get(self, cr, uid, ids, context=None):
795         if not len(ids):
796             return []
797         types = {
798                 'out_invoice': 'CI: ',
799                 'in_invoice': 'SI: ',
800                 'out_refund': 'OR: ',
801                 'in_refund': 'SR: ',
802                 }
803         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')]
804
805     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
806         if not args:
807             args=[]
808         if context is None:
809             context={}
810         ids = []
811         if name:
812             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
813         if not ids:
814             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
815         return self.name_get(cr, user, ids, context)
816
817     def _refund_cleanup_lines(self, cr, uid, lines):
818         for line in lines:
819             del line['id']
820             del line['invoice_id']
821             if 'account_id' in line:
822                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
823             if 'product_id' in line:
824                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
825             if 'uos_id' in line:
826                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
827             if 'invoice_line_tax_id' in line:
828                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
829             if 'account_analytic_id' in line:
830                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
831             if 'tax_code_id' in line :
832                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
833                     line['tax_code_id'] = line['tax_code_id'][0]
834             if 'base_code_id' in line :
835                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
836                     line['base_code_id'] = line['base_code_id'][0]
837         return map(lambda x: (0,0,x), lines)
838
839     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
840         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'])
841
842         new_ids = []
843         for invoice in invoices:
844             del invoice['id']
845
846             type_dict = {
847                 'out_invoice': 'out_refund', # Customer Invoice
848                 'in_invoice': 'in_refund',   # Supplier Invoice
849                 'out_refund': 'out_invoice', # Customer Refund
850                 'in_refund': 'in_invoice',   # Supplier Refund
851             }
852
853
854             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
855             invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
856
857             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
858             tax_lines = filter(lambda l: l['manual'], tax_lines)
859             tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
860             if not date :
861                 date = time.strftime('%Y-%m-%d')
862             invoice.update({
863                 'type': type_dict[invoice['type']],
864                 'date_invoice': date,
865                 'state': 'draft',
866                 'number': False,
867                 'invoice_line': invoice_lines,
868                 'tax_line': tax_lines
869             })
870             if period_id :
871                 invoice.update({
872                     'period_id': period_id,
873                 })
874             if description :
875                 invoice.update({
876                     'name': description,
877                 })
878             # take the id part of the tuple returned for many2one fields
879             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
880                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
881                 invoice[field] = invoice[field] and invoice[field][0]
882             # create the new invoice
883             new_ids.append(self.create(cr, uid, invoice))
884         return new_ids
885
886     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=''):
887         if context is None:
888             context = {}
889         #TODO check if we can use different period for payment and the writeoff line
890         assert len(ids)==1, "Can only pay one invoice at a time"
891         invoice = self.browse(cr, uid, ids[0])
892         src_account_id = invoice.account_id.id
893         # Take the seq as name for move
894         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
895         direction = types[invoice.type]
896         #take the choosen date
897         if 'date_p' in context and context['date_p']:
898             date=context['date_p']
899         else:
900             date=time.strftime('%Y-%m-%d')
901             
902         # Take the amount in currency and the currency of the payment
903         if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
904             amount_currency = context['amount_currency']
905             currency_id = context['currency_id']
906         else:
907             amount_currency = False
908             currency_id = False
909         
910         if invoice.type in ('in_invoice', 'in_refund'):
911             ref = invoice.reference
912         else:
913             ref = self._convert_ref(cr, uid, invoice.number)        
914         # Pay attention to the sign for both debit/credit AND amount_currency
915         l1 = {
916             'debit': direction * pay_amount>0 and direction * pay_amount,
917             'credit': direction * pay_amount<0 and - direction * pay_amount,
918             'account_id': src_account_id,
919             'partner_id': invoice.partner_id.id,
920             'ref':ref,
921             'date': date,
922             'currency_id':currency_id,
923             'amount_currency':amount_currency and direction * amount_currency or 0.0,
924         }
925         l2 = {
926             'debit': direction * pay_amount<0 and - direction * pay_amount,
927             'credit': direction * pay_amount>0 and direction * pay_amount,
928             'account_id': pay_account_id,
929             'partner_id': invoice.partner_id.id,
930             'ref':ref,
931             'date': date,
932             'currency_id':currency_id,
933             'amount_currency':amount_currency and - direction * amount_currency or 0.0,
934         }
935
936         if not name:
937             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
938         l1['name'] = name
939         l2['name'] = name
940
941         lines = [(0, 0, l1), (0, 0, l2)]
942         move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
943         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
944
945         line_ids = []
946         total = 0.0
947         line = self.pool.get('account.move.line')
948         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
949         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
950         for l in lines+invoice.payment_ids:
951             if l.account_id.id==src_account_id:
952                 line_ids.append(l.id)
953                 total += (l.debit or 0.0) - (l.credit or 0.0)
954         if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
955             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
956         else:
957             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
958
959         # Update the stored value (fields.function), so we write to trigger recompute
960         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
961         return True
962 account_invoice()
963
964 class account_invoice_line(osv.osv):
965     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
966         res = {}
967         cur_obj=self.pool.get('res.currency')
968         for line in self.browse(cr, uid, ids):
969             if line.invoice_id:
970                 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
971                 cur = line.invoice_id.currency_id
972                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
973             else:
974                 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
975         return res
976
977
978     def _price_unit_default(self, cr, uid, context=None):
979         if context is None:
980             context = {}
981         if 'check_total' in context:
982             t = context['check_total']
983             for l in context.get('invoice_line', {}):
984                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
985                     tax_obj = self.pool.get('account.tax')
986                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
987                     t = t - (p * l[2].get('quantity'))
988                     taxes = l[2].get('invoice_line_tax_id')
989                     if len(taxes[0]) >= 3 and taxes[0][2]:
990                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
991                         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)):
992                             t = t - tax['amount']
993             return t
994         return 0
995
996     _name = "account.invoice.line"
997     _description = "Invoice line"
998     _columns = {
999         'name': fields.char('Description', size=256, required=True),
1000         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1001         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1002         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1003         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1004         '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."),
1005         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1006         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1007         'quantity': fields.float('Quantity', required=True),
1008         'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1009         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1010         'note': fields.text('Notes'),
1011         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1012     }
1013     _defaults = {
1014         'quantity': lambda *a: 1,
1015         'discount': lambda *a: 0.0,
1016         'price_unit': _price_unit_default,
1017     }
1018
1019     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1020         tax_obj = self.pool.get('account.tax')
1021         if price_unit:
1022             taxes = tax_obj.browse(cr, uid, tax_id)
1023             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1024                 price_unit = price_unit - tax['amount']
1025         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1026
1027     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):
1028         if context is None:
1029             context = {}
1030         if not partner_id:
1031             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1032         if not product:
1033             if type in ('in_invoice', 'in_refund'):
1034                 return {'domain':{'product_uom':[]}}
1035             else:
1036                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1037         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1038         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1039
1040         lang=part.lang
1041         context.update({'lang': lang})
1042         result = {}
1043         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1044
1045         if type in ('out_invoice','out_refund'):
1046             a =  res.product_tmpl_id.property_account_income.id
1047             if not a:
1048                 a = res.categ_id.property_account_income_categ.id
1049         else:
1050             a =  res.product_tmpl_id.property_account_expense.id
1051             if not a:
1052                 a = res.categ_id.property_account_expense_categ.id
1053
1054         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1055         if a:
1056             result['account_id'] = a
1057
1058         taxep=None
1059         tax_obj = self.pool.get('account.tax')
1060         if type in ('out_invoice', 'out_refund'):
1061             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1062             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1063         else:
1064             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)
1065             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1066         if type in ('in_invoice', 'in_refund'):
1067             to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
1068             result.update(to_update)
1069         else:
1070             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1071
1072         if not name:
1073             result['name'] = res.name
1074
1075         domain = {}
1076         result['uos_id'] = uom or res.uom_id.id or False
1077         if result['uos_id']:
1078             res2 = res.uom_id.category_id.id
1079             if res2 :
1080                 domain = {'uos_id':[('category_id','=',res2 )]}
1081         return {'value':result, 'domain':domain}
1082
1083     def move_line_get(self, cr, uid, invoice_id, context=None):
1084         res = []
1085         tax_grouped = {}
1086         tax_obj = self.pool.get('account.tax')
1087         cur_obj = self.pool.get('res.currency')
1088         ait_obj = self.pool.get('account.invoice.tax')
1089         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1090         company_currency = inv.company_id.currency_id.id
1091         cur = inv.currency_id
1092
1093         for line in inv.invoice_line:
1094             mres = self.move_line_get_item(cr, uid, line, context)
1095             if not mres:
1096                 continue
1097             res.append(mres)
1098             tax_code_found= False
1099             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1100                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1101                     line.quantity, inv.address_invoice_id.id, line.product_id,
1102                     inv.partner_id):
1103
1104                 if inv.type in ('out_invoice', 'in_invoice'):
1105                     tax_code_id = tax['base_code_id']
1106                     tax_amount = line.price_subtotal * tax['base_sign']
1107                 else:
1108                     tax_code_id = tax['ref_base_code_id']
1109                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1110
1111                 if tax_code_found:
1112                     if not tax_code_id:
1113                         continue
1114                     res.append(self.move_line_get_item(cr, uid, line, context))
1115                     res[-1]['price'] = 0.0
1116                     res[-1]['account_analytic_id'] = False
1117                 elif not tax_code_id:
1118                     continue
1119                 tax_code_found = True
1120
1121                 res[-1]['tax_code_id'] = tax_code_id
1122                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1123         return res
1124
1125     def move_line_get_item(self, cr, uid, line, context=None):
1126         return {
1127             'type':'src',
1128             'name': line.name[:64],
1129             'price_unit':line.price_unit,
1130             'quantity':line.quantity,
1131             'price':line.price_subtotal,
1132             'account_id':line.account_id.id,
1133             'product_id':line.product_id.id,
1134             'uos_id':line.uos_id.id,
1135             'account_analytic_id':line.account_analytic_id.id,
1136             'taxes':line.invoice_line_tax_id,
1137         }
1138     #
1139     # Set the tax field according to the account and the fiscal position
1140     #
1141     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1142         if not account_id:
1143             return {}
1144         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1145         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1146         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1147         r = {'value':{'invoice_line_tax_id': res}}
1148         return r
1149 account_invoice_line()
1150
1151 class account_invoice_tax(osv.osv):
1152     _name = "account.invoice.tax"
1153     _description = "Invoice Tax"
1154     _columns = {
1155         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1156         'name': fields.char('Tax Description', size=64, required=True),
1157         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1158         'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1159         'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1160         'manual': fields.boolean('Manual'),
1161         'sequence': fields.integer('Sequence'),
1162
1163         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1164         'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1165         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1166         'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1167     }
1168     
1169     def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1170         cur_obj = self.pool.get('res.currency')
1171         company_obj = self.pool.get('res.company')
1172         company_currency=False
1173         if company_id:            
1174             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1175         if currency_id and company_currency:
1176             base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1177         return {'value': {'base_amount':base}}
1178
1179     def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1180         cur_obj = self.pool.get('res.currency')
1181         company_obj = self.pool.get('res.company')
1182         company_currency=False
1183         if company_id:
1184             company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1185         if currency_id and company_currency:
1186             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1187         return {'value': {'tax_amount':amount}}
1188     
1189     _order = 'sequence'
1190     _defaults = {
1191         'manual': lambda *a: 1,
1192         'base_amount': lambda *a: 0.0,
1193         'tax_amount': lambda *a: 0.0,
1194     }
1195     def compute(self, cr, uid, invoice_id, context={}):
1196         tax_grouped = {}
1197         tax_obj = self.pool.get('account.tax')
1198         cur_obj = self.pool.get('res.currency')
1199         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1200         cur = inv.currency_id
1201         company_currency = inv.company_id.currency_id.id
1202
1203         for line in inv.invoice_line:
1204             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):
1205                 val={}
1206                 val['invoice_id'] = inv.id
1207                 val['name'] = tax['name']
1208                 val['amount'] = tax['amount']
1209                 val['manual'] = False
1210                 val['sequence'] = tax['sequence']
1211                 val['base'] = tax['price_unit'] * line['quantity']
1212
1213                 if inv.type in ('out_invoice','in_invoice'):
1214                     val['base_code_id'] = tax['base_code_id']
1215                     val['tax_code_id'] = tax['tax_code_id']
1216                     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)
1217                     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)
1218                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1219                 else:
1220                     val['base_code_id'] = tax['ref_base_code_id']
1221                     val['tax_code_id'] = tax['ref_tax_code_id']
1222                     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)
1223                     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)
1224                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1225
1226                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1227                 if not key in tax_grouped:
1228                     tax_grouped[key] = val
1229                 else:
1230                     tax_grouped[key]['amount'] += val['amount']
1231                     tax_grouped[key]['base'] += val['base']
1232                     tax_grouped[key]['base_amount'] += val['base_amount']
1233                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1234
1235         for t in tax_grouped.values():
1236             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1237             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1238             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1239         return tax_grouped
1240
1241     def move_line_get(self, cr, uid, invoice_id):
1242         res = []
1243         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1244         for t in cr.dictfetchall():
1245             if not t['amount'] \
1246                     and not t['tax_code_id'] \
1247                     and not t['tax_amount']:
1248                 continue
1249             res.append({
1250                 'type':'tax',
1251                 'name':t['name'],
1252                 'price_unit': t['amount'],
1253                 'quantity': 1,
1254                 'price': t['amount'] or 0.0,
1255                 'account_id': t['account_id'],
1256                 'tax_code_id': t['tax_code_id'],
1257                 'tax_amount': t['tax_amount']
1258             })
1259         return res
1260 account_invoice_tax()
1261
1262 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1263