1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 import decimal_precision as dp
26 from osv import fields, osv
27 from osv.orm import except_orm
29 from tools import config
30 from tools.translate import _
32 class account_invoice(osv.osv):
33 def _amount_all(self, cr, uid, ids, name, args, context=None):
35 for invoice in self.browse(cr,uid,ids, context=context):
37 'amount_untaxed': 0.0,
41 for line in invoice.invoice_line:
42 res[invoice.id]['amount_untaxed'] += line.price_subtotal
43 for line in invoice.tax_line:
44 res[invoice.id]['amount_tax'] += line.amount
45 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
48 def _get_journal(self, cr, uid, context):
51 type_inv = context.get('type', 'out_invoice')
52 user = self.pool.get('res.users').browse(cr, uid, uid)
53 company_id = context.get('company_id', user.company_id.id)
54 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
55 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
56 journal_obj = self.pool.get('account.journal')
57 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
58 ('company_id', '=', company_id),('refund_journal', '=', refund_journal.get(type_inv, False))],
65 def _get_currency(self, cr, uid, context):
66 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
68 return user.company_id.currency_id.id
70 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
72 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
73 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
74 tt = type2journal.get(type_inv, 'sale')
75 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
77 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
80 def _get_type(self, cr, uid, context=None):
83 type = context.get('type', 'out_invoice')
86 def _reconciled(self, cr, uid, ids, name, args, context):
89 res[id] = self.test_paid(cr, uid, [id])
92 def _get_reference_type(self, cr, uid, context=None):
93 return [('none', _('Free Reference'))]
95 def _amount_residual(self, cr, uid, ids, name, args, context=None):
97 data_inv = self.browse(cr, uid, ids)
98 cur_obj = self.pool.get('res.currency')
101 context.update({'date':inv.date_invoice})
102 context_unreconciled=context.copy()
103 for lines in inv.move_lines:
104 debit_tmp = lines.debit
105 credit_tmp = lines.credit
106 # If currency conversion needed
107 if inv.company_id.currency_id.id <> inv.currency_id.id:
108 # If invoice paid, compute currency amount according to invoice date
109 # otherwise, take the line date
110 if not inv.reconciled:
111 context.update({'date':lines.date})
112 context_unreconciled.update({'date':lines.date})
113 # If amount currency setted, compute for debit and credit in company currency
114 if lines.amount_currency < 0:
115 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))
116 elif lines.amount_currency > 0:
117 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))
118 # Then, recomput into invoice currency to avoid rounding trouble !
119 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
120 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
125 if not inv.amount_total:
127 elif inv.type in ('out_invoice','in_refund'):
128 amount = credit-debit
129 result = inv.amount_total - amount
131 amount = debit-credit
132 result = inv.amount_total - amount
133 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
134 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
138 def _get_lines(self, cr, uid, ids, name, arg, context=None):
141 move_lines = self.move_line_id_payment_get(cr,uid,[id])
146 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
147 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
148 for line in data_lines:
150 if line.reconcile_id:
151 ids_line = line.reconcile_id.line_id
152 elif line.reconcile_partial_id:
153 ids_line = line.reconcile_partial_id.line_partial_ids
154 l = map(lambda x: x.id, ids_line)
155 partial_ids.append(line.id)
156 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
159 def _get_invoice_line(self, cr, uid, ids, context=None):
161 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
162 result[line.invoice_id.id] = True
165 def _get_invoice_tax(self, cr, uid, ids, context=None):
167 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
168 result[tax.invoice_id.id] = True
171 def _compute_lines(self, cr, uid, ids, name, args, context=None):
173 for invoice in self.browse(cr, uid, ids, context):
174 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
177 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
178 temp_lines = []#Added temp list to avoid duplicate records
180 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
181 elif m.reconcile_partial_id:
182 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
183 lines += [x for x in temp_lines if x not in lines]
186 lines = filter(lambda x: x not in src, lines)
187 result[invoice.id] = lines
190 def _get_invoice_from_line(self, cr, uid, ids, context={}):
192 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
193 if line.reconcile_partial_id:
194 for line2 in line.reconcile_partial_id.line_partial_ids:
195 move[line2.move_id.id] = True
196 if line.reconcile_id:
197 for line2 in line.reconcile_id.line_id:
198 move[line2.move_id.id] = True
201 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
204 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
206 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
207 for line in r.line_partial_ids:
208 move[line.move_id.id] = True
209 for line in r.line_id:
210 move[line.move_id.id] = True
214 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
217 _name = "account.invoice"
218 _description = 'Invoice'
221 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
222 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice."),
223 'type': fields.selection([
224 ('out_invoice','Customer Invoice'),
225 ('in_invoice','Supplier Invoice'),
226 ('out_refund','Customer Refund'),
227 ('in_refund','Supplier Refund'),
228 ],'Type', readonly=True, select=True, change_default=True),
230 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
231 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
232 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
234 'comment': fields.text('Additional Information', translate=True),
236 'state': fields.selection([
238 ('proforma','Pro-forma'),
239 ('proforma2','Pro-forma'),
242 ('cancel','Cancelled')
243 ],'State', select=True, readonly=True,
244 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
245 \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
246 \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
247 \n* The \'Done\' state is set automatically when invoice is paid.\
248 \n* The \'Cancelled\' state is used when user cancel invoice.'),
249 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
250 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
251 help="If you use payment terms, the due date will be computed automatically at the generation "\
252 "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."),
253 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
254 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
255 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
256 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
257 help="If you use payment terms, the due date will be computed automatically at the generation "\
258 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
259 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
260 '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 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
263 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
264 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
266 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Links to the automatically generated Ledger Postings."),
267 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'),string='Untaxed',
269 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
270 'account.invoice.tax': (_get_invoice_tax, None, 20),
271 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
274 'amount_tax': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Tax',
276 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
277 'account.invoice.tax': (_get_invoice_tax, None, 20),
278 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
281 'amount_total': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Total',
283 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
284 'account.invoice.tax': (_get_invoice_tax, None, 20),
285 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
288 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
289 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
290 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True),
291 'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
292 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
294 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
295 'account.move.line': (_get_invoice_from_line, None, 50),
296 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
297 }, help="The Ledger Postings of the invoice have been reconciled with Ledger Postings of the payment(s)."),
298 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
299 help='The bank account to pay to or to be paid from'),
300 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Entry Lines'),
301 'residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'),string='Residual',
303 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
304 'account.invoice.tax': (_get_invoice_tax, None, 50),
305 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
306 'account.move.line': (_get_invoice_from_line, None, 50),
307 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
309 help="Remaining amount due."),
310 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
311 'move_name': fields.char('Ledger Posting', size=64),
312 'user_id': fields.many2one('res.users', 'Salesman'),
313 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
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', context=c),
322 'reference_type': lambda *a: 'none',
323 'check_total': lambda *a: 0.0,
324 'user_id': lambda s,cr,u,c: u,
327 def create(self, cr, uid, vals, context={}):
329 res = super(account_invoice, self).create(cr, uid, vals, context)
332 if '"journal_id" viol' in e.args[0]:
333 raise except_orm(_('Configuration Error!'),
334 _('There is no Accounting Journal of type Sale/Purchase defined!'))
336 raise except_orm(_('UnknownError'), str(e))
338 def unlink(self, cr, uid, ids, context=None):
339 invoices = self.read(cr, uid, ids, ['state'])
342 if t['state'] in ('draft', 'cancel'):
343 unlink_ids.append(t['id'])
345 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
346 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
349 # def get_invoice_address(self, cr, uid, ids):
350 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
352 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
353 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
354 invoice_addr_id = False
355 contact_addr_id = False
356 partner_payment_term = False
359 fiscal_position = False
361 opt = [('uid', str(uid))]
364 opt.insert(0, ('id', partner_id))
365 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
366 contact_addr_id = res['contact']
367 invoice_addr_id = res['invoice']
368 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
370 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
371 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)])
372 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)])
374 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
376 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
377 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
378 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
379 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
380 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
381 if not rec_res_id and not pay_res_id:
382 raise osv.except_osv(_('Configration Error !'),
383 _('Can not find account chart for this company, Please Create account.'))
384 rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
385 pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
386 p.property_account_receivable = rec_obj_acc[0]
387 p.property_account_payable = pay_obj_acc[0]
389 if type in ('out_invoice', 'out_refund'):
390 acc_id = p.property_account_receivable.id
392 acc_id = p.property_account_payable.id
393 fiscal_position = p.property_account_position and p.property_account_position.id or False
394 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
396 bank_id = p.bank_ids[0].id
399 'address_contact_id': contact_addr_id,
400 'address_invoice_id': invoice_addr_id,
401 'account_id': acc_id,
402 'payment_term': partner_payment_term,
403 'fiscal_position': fiscal_position
407 if type in ('in_invoice', 'in_refund'):
408 result['value']['partner_bank'] = bank_id
410 if payment_term != partner_payment_term:
411 if partner_payment_term:
412 to_update = self.onchange_payment_term_date_invoice(
413 cr,uid,ids,partner_payment_term,date_invoice)
414 result['value'].update(to_update['value'])
416 result['value']['date_due'] = False
418 if partner_bank_id != bank_id:
419 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
420 result['value'].update(to_update['value'])
423 def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
424 if curr_id and company_id:
425 currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
426 if currency.company_id.id != company_id:
427 raise osv.except_osv(_('Configration Error !'),
428 _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
431 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
432 if not payment_term_id:
435 pt_obj= self.pool.get('account.payment.term')
436 if not date_invoice :
437 date_invoice = time.strftime('%Y-%m-%d')
439 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
442 pterm_list = [line[0] for line in pterm_list]
444 res= {'value':{'date_due': pterm_list[-1]}}
446 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
450 def onchange_invoice_line(self, cr, uid, ids, lines):
453 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
456 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
459 obj_journal = self.pool.get('account.journal')
460 if company_id and part_id and type:
462 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
463 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
464 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
465 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)])
466 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)])
468 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
470 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
471 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
472 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
473 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
474 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
475 if not rec_res_id and not rec_res_id:
476 raise osv.except_osv(_('Configration Error !'),
477 _('Can not find account chart for this company, Please Create account.'))
478 if type in ('out_invoice', 'out_refund'):
482 val= {'account_id': acc_id}
485 inv_obj = self.browse(cr,uid,ids)
486 for line in inv_obj[0].invoice_line:
488 if line.account_id.company_id.id != company_id:
489 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
491 raise osv.except_osv(_('Configration Error !'),
492 _('Can not find account chart for this company in invoice line account, Please Create account.'))
493 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
496 for inv_line in invoice_line:
497 obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
498 if obj_l.company_id.id != company_id:
499 raise osv.except_osv(_('Configration Error !'),
500 _('invoice line account company is not match with invoice company.'))
503 if company_id and type:
504 if type in ('out_invoice', 'out_refund'):
505 journal_type = 'sale'
507 journal_type = 'purchase'
508 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
510 val['journal_id'] = journal_ids[0]
512 raise osv.except_osv(_('Configration Error !'),
513 _('Can not find account journal for this company in invoice, Please Create journal.'))
514 dom = {'journal_id': [('id', 'in', journal_ids)]}
516 journal_ids = obj_journal.search(cr, uid, [])
518 if currency_id and company_id:
519 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
520 if currency.company_id.id != company_id:
521 val['currency_id'] = False
523 val['currency_id'] = currency.id
526 company = self.pool.get('res.company').browse(cr, uid, company_id)
527 if company.currency_id.company_id.id != company_id:
528 val['currency_id'] = False
530 val['currency_id'] = company.currency_id.id
532 return {'value' : val, 'domain': dom }
534 # go from canceled state to draft state
535 def action_cancel_draft(self, cr, uid, ids, *args):
536 self.write(cr, uid, ids, {'state':'draft'})
537 wf_service = netsvc.LocalService("workflow")
539 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
542 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
543 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
544 Hook method to be overridden in additional modules to verify and possibly alter the
545 move lines to be created by an invoice, for special cases.
546 :param invoice_browse: browsable record of the invoice that is generating the move lines
547 :param move_lines: list of dictionaries with the account.move.lines (as for create())
548 :return: the (possibly updated) final move_lines to create for this invoice
555 # return the ids of the move lines which has the same account than the invoice
557 def move_line_id_payment_get(self, cr, uid, ids, *args):
559 if not ids: return res
562 from account_move_line l \
563 left join account_invoice i on (i.move_id=l.move_id) \
564 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
565 res = map(lambda x: x[0], cr.fetchall())
568 def copy(self, cr, uid, id, default=None, context=None):
571 default = default.copy()
572 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
573 if 'date_invoice' not in default:
574 default['date_invoice'] = False
575 if 'date_due' not in default:
576 default['date_due'] = False
577 return super(account_invoice, self).copy(cr, uid, id, default, context)
579 def test_paid(self, cr, uid, ids, *args):
580 res = self.move_line_id_payment_get(cr, uid, ids)
585 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
586 ok = ok and bool(cr.fetchone()[0])
589 def button_reset_taxes(self, cr, uid, ids, context=None):
592 ait_obj = self.pool.get('account.invoice.tax')
594 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
595 partner = self.browse(cr, uid, id,context=context).partner_id
597 context.update({'lang': partner.lang})
598 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
599 ait_obj.create(cr, uid, taxe)
600 # Update the stored value (fields.function), so we write to trigger recompute
601 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
602 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
605 def button_compute(self, cr, uid, ids, context=None, set_total=False):
606 self.button_reset_taxes(cr, uid, ids, context)
607 for inv in self.browse(cr, uid, ids):
609 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
612 def _convert_ref(self, cr, uid, ref):
613 return (ref or '').replace('/','')
615 def _get_analytic_lines(self, cr, uid, id):
616 inv = self.browse(cr, uid, [id])[0]
617 cur_obj = self.pool.get('res.currency')
619 company_currency = inv.company_id.currency_id.id
620 if inv.type in ('out_invoice', 'in_refund'):
625 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
627 if il['account_analytic_id']:
628 if inv.type in ('in_invoice', 'in_refund'):
631 ref = self._convert_ref(cr, uid, inv.number)
632 il['analytic_lines'] = [(0,0, {
634 'date': inv['date_invoice'],
635 'account_id': il['account_analytic_id'],
636 'unit_amount': il['quantity'],
637 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
638 'product_id': il['product_id'],
639 'product_uom_id': il['uos_id'],
640 'general_account_id': il['account_id'],
641 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
646 def action_date_assign(self, cr, uid, ids, *args):
647 for inv in self.browse(cr, uid, ids):
648 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
649 if res and res['value']:
650 self.write(cr, uid, [inv.id], res['value'])
653 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
654 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
655 Hook method to be overridden in additional modules to verify and possibly alter the
656 move lines to be created by an invoice, for special cases.
657 :param invoice_browse: browsable record of the invoice that is generating the move lines
658 :param move_lines: list of dictionaries with the account.move.lines (as for create())
659 :return: the (possibly updated) final move_lines to create for this invoice
663 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
665 for tax in compute_taxes.values():
666 ait_obj.create(cr, uid, tax)
669 for tax in inv.tax_line:
672 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
674 if not key in compute_taxes:
675 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
676 base = compute_taxes[key]['base']
677 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
678 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
679 for key in compute_taxes:
680 if not key in tax_key:
681 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
683 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
686 cur_obj = self.pool.get('res.currency')
687 for i in invoice_move_lines:
688 if inv.currency_id.id != company_currency:
689 i['currency_id'] = inv.currency_id.id
690 i['amount_currency'] = i['price']
691 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
692 company_currency, i['price'],
693 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
695 i['amount_currency'] = False
696 i['currency_id'] = False
698 if inv.type in ('out_invoice','in_refund'):
700 total_currency += i['amount_currency'] or i['price']
701 i['price'] = - i['price']
704 total_currency -= i['amount_currency'] or i['price']
705 return total, total_currency, invoice_move_lines
707 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
708 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
709 will be grouped together if the journal has the 'group line' option. Of course a module
710 can add fields to invoice lines that would need to be tested too before merging lines
712 return "%s-%s-%s-%s-%s"%(
713 invoice_line['account_id'],
714 invoice_line.get('tax_code_id',"False"),
715 invoice_line.get('product_id',"False"),
716 invoice_line.get('analytic_account_id',"False"),
717 invoice_line.get('date_maturity',"False"))
719 def group_lines(self, cr, uid, iml, line, inv):
720 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
721 if inv.journal_id.group_invoice_lines:
724 tmp = self.inv_line_characteristic_hashcode(inv, l)
727 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
728 line2[tmp]['debit'] = (am > 0) and am or 0.0
729 line2[tmp]['credit'] = (am < 0) and -am or 0.0
730 line2[tmp]['tax_amount'] += l['tax_amount']
731 line2[tmp]['analytic_lines'] += l['analytic_lines']
735 for key, val in line2.items():
736 line.append((0,0,val))
740 def action_move_create(self, cr, uid, ids, *args):
741 """Creates invoice related analytics and financial move lines"""
742 ait_obj = self.pool.get('account.invoice.tax')
743 cur_obj = self.pool.get('res.currency')
745 for inv in self.browse(cr, uid, ids):
749 if not inv.date_invoice:
750 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
751 company_currency = inv.company_id.currency_id.id
752 # create the analytical lines
753 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
754 # one move line per invoice line
755 iml = self._get_analytic_lines(cr, uid, inv.id)
756 # check if taxes are all computed
758 context.update({'lang': inv.partner_id.lang})
759 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
760 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
762 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
763 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
765 # one move line per tax line
766 iml += ait_obj.move_line_get(cr, uid, inv.id)
768 if inv.type in ('in_invoice', 'in_refund'):
771 ref = self._convert_ref(cr, uid, inv.number)
773 diff_currency_p = inv.currency_id.id <> company_currency
774 # create one move line for the total and possibly adjust the other lines amount
777 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
778 acc_id = inv.account_id.id
780 name = inv['name'] or '/'
783 totlines = self.pool.get('account.payment.term').compute(cr,
784 uid, inv.payment_term.id, total, inv.date_invoice or False)
786 res_amount_currency = total_currency
789 if inv.currency_id.id != company_currency:
790 amount_currency = cur_obj.compute(cr, uid,
791 company_currency, inv.currency_id.id, t[1])
793 amount_currency = False
795 # last line add the diff
796 res_amount_currency -= amount_currency or 0
798 if i == len(totlines):
799 amount_currency += res_amount_currency
805 'account_id': acc_id,
806 'date_maturity': t[0],
807 'amount_currency': diff_currency_p \
808 and amount_currency or False,
809 'currency_id': diff_currency_p \
810 and inv.currency_id.id or False,
818 'account_id': acc_id,
819 'date_maturity' : inv.date_due or False,
820 'amount_currency': diff_currency_p \
821 and total_currency or False,
822 'currency_id': diff_currency_p \
823 and inv.currency_id.id or False,
827 date = inv.date_invoice or time.strftime('%Y-%m-%d')
828 part = inv.partner_id.id
830 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
832 line = self.group_lines(cr, uid, iml, line, inv)
834 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
835 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
836 if journal.centralisation:
837 raise osv.except_osv(_('UserError'),
838 _('Cannot create invoice move on centralised journal'))
840 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
842 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
843 period_id=inv.period_id and inv.period_id.id or False
845 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'))])
847 period_id=period_ids[0]
849 move['period_id'] = period_id
851 i[2]['period_id'] = period_id
853 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
854 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
855 # make the invoice point to that move
856 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
857 self.pool.get('account.move').post(cr, uid, [move_id])
858 self._log_event(cr, uid, ids)
861 def line_get_convert(self, cr, uid, x, part, date, context=None):
863 'date_maturity': x.get('date_maturity', False),
865 'name':x['name'][:64],
867 'debit':x['price']>0 and x['price'],
868 'credit':x['price']<0 and -x['price'],
869 'account_id':x['account_id'],
870 'analytic_lines':x.get('analytic_lines', []),
871 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
872 'currency_id':x.get('currency_id', False),
873 'tax_code_id': x.get('tax_code_id', False),
874 'tax_amount': x.get('tax_amount', False),
875 'ref':x.get('ref',False),
876 'quantity':x.get('quantity',1.00),
877 'product_id':x.get('product_id', False),
878 'product_uom_id':x.get('uos_id',False),
879 'analytic_account_id':x.get('account_analytic_id',False),
882 def action_number(self, cr, uid, ids, *args):
883 cr.execute('SELECT id, type, number, move_id, reference ' \
884 'FROM account_invoice ' \
885 'WHERE id IN ('+','.join(map(str, ids))+')')
886 obj_inv = self.browse(cr, uid, ids)[0]
887 for (id, invtype, number, move_id, reference) in cr.fetchall():
889 if obj_inv.journal_id.invoice_sequence_id:
890 sid = obj_inv.journal_id.invoice_sequence_id.id
891 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
893 number = self.pool.get('ir.sequence').get(cr, uid,
894 'account.invoice.' + invtype)
895 if invtype in ('in_invoice', 'in_refund'):
898 ref = self._convert_ref(cr, uid, number)
899 cr.execute('UPDATE account_invoice SET number=%s ' \
900 'WHERE id=%s', (number, id))
901 cr.execute('UPDATE account_move SET ref=%s ' \
902 'WHERE id=%s AND (ref is null OR ref = \'\')',
904 cr.execute('UPDATE account_move_line SET ref=%s ' \
905 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
907 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
908 'FROM account_move_line ' \
909 'WHERE account_move_line.move_id = %s ' \
910 'AND account_analytic_line.move_id = account_move_line.id',
914 def action_cancel(self, cr, uid, ids, *args):
915 account_move_obj = self.pool.get('account.move')
916 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
919 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
920 # delete the move this invoice was pointing to
921 # Note that the corresponding move_lines and move_reconciles
922 # will be automatically deleted too
923 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
925 account_move_line_obj = self.pool.get('account.move.line')
926 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
927 for move_line in pay_ids:
928 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
929 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
931 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
932 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
937 def list_distinct_taxes(self, cr, uid, ids):
938 invoices = self.browse(cr, uid, ids)
941 for tax in inv.tax_line:
942 if not tax['name'] in taxes:
943 taxes[tax['name']] = {'name': tax['name']}
944 return taxes.values()
946 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
947 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
949 part=inv['partner_id'] and inv['partner_id'][0]
951 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
952 total = inv['amount_untaxed']
953 if inv['type'] in ('in_invoice','in_refund'):
954 partnertype='supplier'
955 eventtype = 'purchase'
958 partnertype = 'customer'
961 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
962 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})
965 def name_get(self, cr, uid, ids, context=None):
969 'out_invoice': 'CI: ',
970 'in_invoice': 'SI: ',
971 'out_refund': 'OR: ',
974 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')]
976 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
983 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
985 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
986 return self.name_get(cr, user, ids, context)
988 def _refund_cleanup_lines(self, cr, uid, lines):
991 del line['invoice_id']
992 if 'account_id' in line:
993 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
994 if 'product_id' in line:
995 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
997 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
998 if 'invoice_line_tax_id' in line:
999 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1000 if 'account_analytic_id' in line:
1001 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
1002 if 'tax_code_id' in line :
1003 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
1004 line['tax_code_id'] = line['tax_code_id'][0]
1005 if 'base_code_id' in line :
1006 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
1007 line['base_code_id'] = line['base_code_id'][0]
1008 return map(lambda x: (0,0,x), lines)
1010 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
1011 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'])
1014 for invoice in invoices:
1018 'out_invoice': 'out_refund', # Customer Invoice
1019 'in_invoice': 'in_refund', # Supplier Invoice
1020 'out_refund': 'out_invoice', # Customer Refund
1021 'in_refund': 'in_invoice', # Supplier Refund
1025 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
1026 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1028 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
1029 tax_lines = filter(lambda l: l['manual'], tax_lines)
1030 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1032 date = time.strftime('%Y-%m-%d')
1034 'type': type_dict[invoice['type']],
1035 'date_invoice': date,
1038 'invoice_line': invoice_lines,
1039 'tax_line': tax_lines
1043 'period_id': period_id,
1047 'name': description,
1049 # take the id part of the tuple returned for many2one fields
1050 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1051 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1052 invoice[field] = invoice[field] and invoice[field][0]
1053 # create the new invoice
1054 new_ids.append(self.create(cr, uid, invoice))
1057 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=''):
1060 #TODO check if we can use different period for payment and the writeoff line
1061 assert len(ids)==1, "Can only pay one invoice at a time"
1062 invoice = self.browse(cr, uid, ids[0])
1063 src_account_id = invoice.account_id.id
1064 # Take the seq as name for move
1065 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1066 direction = types[invoice.type]
1067 #take the choosen date
1068 if 'date_p' in context and context['date_p']:
1069 date=context['date_p']
1071 date=time.strftime('%Y-%m-%d')
1073 # Take the amount in currency and the currency of the payment
1074 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1075 amount_currency = context['amount_currency']
1076 currency_id = context['currency_id']
1078 amount_currency = False
1080 if invoice.type in ('in_invoice', 'in_refund'):
1081 ref = invoice.reference
1083 ref = self._convert_ref(cr, uid, invoice.number)
1084 # Pay attention to the sign for both debit/credit AND amount_currency
1086 'debit': direction * pay_amount>0 and direction * pay_amount,
1087 'credit': direction * pay_amount<0 and - direction * pay_amount,
1088 'account_id': src_account_id,
1089 'partner_id': invoice.partner_id.id,
1092 'currency_id':currency_id,
1093 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1094 'company_id': invoice.company_id.id,
1097 'debit': direction * pay_amount<0 and - direction * pay_amount,
1098 'credit': direction * pay_amount>0 and direction * pay_amount,
1099 'account_id': pay_account_id,
1100 'partner_id': invoice.partner_id.id,
1103 'currency_id':currency_id,
1104 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1105 'company_id': invoice.company_id.id,
1109 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1113 lines = [(0, 0, l1), (0, 0, l2)]
1114 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1115 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1119 line = self.pool.get('account.move.line')
1120 move_ids = [move_id,]
1122 move_ids.append(invoice.move_id.id)
1123 cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1124 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1125 for l in lines+invoice.payment_ids:
1126 if l.account_id.id==src_account_id:
1127 line_ids.append(l.id)
1128 total += (l.debit or 0.0) - (l.credit or 0.0)
1129 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1130 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1132 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1134 # Update the stored value (fields.function), so we write to trigger recompute
1135 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1139 class account_invoice_line(osv.osv):
1140 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1142 cur_obj=self.pool.get('res.currency')
1143 for line in self.browse(cr, uid, ids):
1145 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1146 cur = line.invoice_id.currency_id
1147 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1149 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1153 def _price_unit_default(self, cr, uid, context=None):
1156 if 'check_total' in context:
1157 t = context['check_total']
1158 for l in context.get('invoice_line', {}):
1159 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1160 tax_obj = self.pool.get('account.tax')
1161 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1162 t = t - (p * l[2].get('quantity'))
1163 taxes = l[2].get('invoice_line_tax_id')
1164 if len(taxes[0]) >= 3 and taxes[0][2]:
1165 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1166 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)):
1167 t = t - tax['amount']
1171 _name = "account.invoice.line"
1172 _description = "Invoice line"
1174 'name': fields.char('Description', size=256, required=True),
1175 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1176 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1177 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1178 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1179 '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."),
1180 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1181 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits_compute= dp.get_precision('Account')),
1182 'quantity': fields.float('Quantity', required=True),
1183 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1184 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1185 'note': fields.text('Notes', translate=True),
1186 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1187 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1188 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1191 'quantity': lambda *a: 1,
1192 'discount': lambda *a: 0.0,
1193 'price_unit': _price_unit_default,
1196 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1197 tax_obj = self.pool.get('account.tax')
1199 taxes = tax_obj.browse(cr, uid, tax_id)
1200 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1201 price_unit = price_unit - tax['amount']
1202 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1204 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):
1207 company_id = context.get('company_id',False)
1209 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1211 if type in ('in_invoice', 'in_refund'):
1212 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1214 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1215 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1216 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1219 context.update({'lang': part.lang})
1221 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1224 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)])
1226 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)])
1227 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)])
1229 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)])
1232 in_acc = res.product_tmpl_id.property_account_income
1233 in_acc_cate = res.categ_id.property_account_income_categ
1237 app_acc_in = in_acc_cate
1239 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1241 ex_acc = res.product_tmpl_id.property_account_expense
1242 ex_acc_cate = res.categ_id.property_account_expense_categ
1244 app_acc_exp = ex_acc
1246 app_acc_exp = ex_acc_cate
1248 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1249 if not in_pro_id and not exp_pro_id:
1250 in_acc = res.product_tmpl_id.property_account_income
1251 in_acc_cate = res.categ_id.property_account_income_categ
1252 ex_acc = res.product_tmpl_id.property_account_expense
1253 ex_acc_cate = res.categ_id.property_account_expense_categ
1254 if in_acc or ex_acc:
1256 app_acc_exp = ex_acc
1258 app_acc_in = in_acc_cate
1259 app_acc_exp = ex_acc_cate
1261 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1262 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1263 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1264 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1265 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1266 if not in_res_id and not exp_res_id:
1267 raise osv.except_osv(_('Configration Error !'),
1268 _('Can not find account chart for this company, Please Create account.'))
1269 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1270 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1271 if in_acc or ex_acc:
1272 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1273 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1275 res.categ_id.property_account_income_categ = in_obj_acc[0]
1276 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1278 if type in ('out_invoice','out_refund'):
1279 a = res.product_tmpl_id.property_account_income.id
1281 a = res.categ_id.property_account_income_categ.id
1283 a = res.product_tmpl_id.property_account_expense.id
1285 a = res.categ_id.property_account_expense_categ.id
1287 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1289 result['account_id'] = a
1292 tax_obj = self.pool.get('account.tax')
1293 if type in ('out_invoice', 'out_refund'):
1294 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1295 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1297 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)
1298 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1299 if type in ('in_invoice', 'in_refund'):
1300 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit or res.standard_price, qty, address_invoice_id, product, partner_id, context=context)
1301 result.update(to_update)
1303 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1306 result['name'] = res.partner_ref
1309 result['uos_id'] = uom or res.uom_id.id or False
1310 if result['uos_id']:
1311 res2 = res.uom_id.category_id.id
1313 domain = {'uos_id':[('category_id','=',res2 )]}
1315 prod_pool=self.pool.get('product.product')
1316 result['categ_id'] = res.categ_id.id
1317 res_final = {'value':result, 'domain':domain}
1319 if not company_id and not currency_id:
1322 company = self.pool.get('res.company').browse(cr, uid, company_id)
1323 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1325 if not currency.company_id.id == company.id:
1326 raise osv.except_osv(_('Configration Error !'),
1327 _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1329 if company.currency_id.id != currency.id:
1330 new_price = res_final['value']['price_unit'] * currency.rate
1331 res_final['value']['price_unit'] = new_price
1334 def move_line_get(self, cr, uid, invoice_id, context=None):
1337 tax_obj = self.pool.get('account.tax')
1338 cur_obj = self.pool.get('res.currency')
1339 ait_obj = self.pool.get('account.invoice.tax')
1340 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1341 company_currency = inv.company_id.currency_id.id
1342 cur = inv.currency_id
1344 for line in inv.invoice_line:
1345 mres = self.move_line_get_item(cr, uid, line, context)
1349 tax_code_found= False
1350 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1351 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1352 line.quantity, inv.address_invoice_id.id, line.product_id,
1355 if inv.type in ('out_invoice', 'in_invoice'):
1356 tax_code_id = tax['base_code_id']
1357 tax_amount = line.price_subtotal * tax['base_sign']
1359 tax_code_id = tax['ref_base_code_id']
1360 tax_amount = line.price_subtotal * tax['ref_base_sign']
1365 res.append(self.move_line_get_item(cr, uid, line, context))
1366 res[-1]['price'] = 0.0
1367 res[-1]['account_analytic_id'] = False
1368 elif not tax_code_id:
1370 tax_code_found = True
1372 res[-1]['tax_code_id'] = tax_code_id
1373 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1376 def move_line_get_item(self, cr, uid, line, context=None):
1379 'name': line.name[:64],
1380 'price_unit':line.price_unit,
1381 'quantity':line.quantity,
1382 'price':line.price_subtotal,
1383 'account_id':line.account_id.id,
1384 'product_id':line.product_id.id,
1385 'uos_id':line.uos_id.id,
1386 'account_analytic_id':line.account_analytic_id.id,
1387 'taxes':line.invoice_line_tax_id,
1390 # Set the tax field according to the account and the fiscal position
1392 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1395 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1396 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1397 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1398 r = {'value':{'invoice_line_tax_id': res}}
1400 account_invoice_line()
1402 class account_invoice_tax(osv.osv):
1403 _name = "account.invoice.tax"
1404 _description = "Invoice Tax"
1406 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1407 'name': fields.char('Tax Description', size=64, required=True),
1408 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1409 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1410 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1411 'manual': fields.boolean('Manual'),
1412 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1414 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1415 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1416 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1417 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1418 'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1421 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1422 cur_obj = self.pool.get('res.currency')
1423 company_obj = self.pool.get('res.company')
1424 company_currency=False
1426 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1427 if currency_id and company_currency:
1428 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1429 return {'value': {'base_amount':base}}
1431 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1432 cur_obj = self.pool.get('res.currency')
1433 company_obj = self.pool.get('res.company')
1434 company_currency=False
1436 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1437 if currency_id and company_currency:
1438 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1439 return {'value': {'tax_amount':amount}}
1443 'manual': lambda *a: 1,
1444 'base_amount': lambda *a: 0.0,
1445 'tax_amount': lambda *a: 0.0,
1447 def compute(self, cr, uid, invoice_id, context={}):
1449 tax_obj = self.pool.get('account.tax')
1450 cur_obj = self.pool.get('res.currency')
1451 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1452 cur = inv.currency_id
1453 company_currency = inv.company_id.currency_id.id
1455 for line in inv.invoice_line:
1456 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):
1458 val['invoice_id'] = inv.id
1459 val['name'] = tax['name']
1460 val['amount'] = tax['amount']
1461 val['manual'] = False
1462 val['sequence'] = tax['sequence']
1463 val['base'] = tax['price_unit'] * line['quantity']
1465 if inv.type in ('out_invoice','in_invoice'):
1466 val['base_code_id'] = tax['base_code_id']
1467 val['tax_code_id'] = tax['tax_code_id']
1468 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)
1469 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)
1470 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1472 val['base_code_id'] = tax['ref_base_code_id']
1473 val['tax_code_id'] = tax['ref_tax_code_id']
1474 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)
1475 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)
1476 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1478 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1479 if not key in tax_grouped:
1480 tax_grouped[key] = val
1482 tax_grouped[key]['amount'] += val['amount']
1483 tax_grouped[key]['base'] += val['base']
1484 tax_grouped[key]['base_amount'] += val['base_amount']
1485 tax_grouped[key]['tax_amount'] += val['tax_amount']
1487 for t in tax_grouped.values():
1488 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1489 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1490 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1493 def move_line_get(self, cr, uid, invoice_id):
1495 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1496 for t in cr.dictfetchall():
1497 if not t['amount'] \
1498 and not t['tax_code_id'] \
1499 and not t['tax_amount']:
1504 'price_unit': t['amount'],
1506 'price': t['amount'] or 0.0,
1507 'account_id': t['account_id'],
1508 'tax_code_id': t['tax_code_id'],
1509 'tax_amount': t['tax_amount']
1512 account_invoice_tax()
1514 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: