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