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, orm
28 from tools import config
29 from tools.translate import _
31 class account_invoice(osv.osv):
32 def _amount_all(self, cr, uid, ids, name, args, context=None):
34 for invoice in self.browse(cr,uid,ids, context=context):
36 'amount_untaxed': 0.0,
40 for line in invoice.invoice_line:
41 res[invoice.id]['amount_untaxed'] += line.price_subtotal
42 for line in invoice.tax_line:
43 res[invoice.id]['amount_tax'] += line.amount
44 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
47 def _get_journal(self, cr, uid, context):
50 type_inv = context.get('type', 'out_invoice')
51 user = self.pool.get('res.users').browse(cr, uid, uid)
52 company_id = context.get('company_id', user.company_id.id)
53 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
54 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
55 journal_obj = self.pool.get('account.journal')
56 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
57 ('company_id', '=', company_id),('refund_journal', '=', refund_journal.get(type_inv, False))],
64 def _get_currency(self, cr, uid, context):
65 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
67 return user.company_id.currency_id.id
69 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
71 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
72 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
73 tt = type2journal.get(type_inv, 'sale')
74 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
76 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
79 def _get_type(self, cr, uid, context=None):
82 type = context.get('type', 'out_invoice')
85 def _reconciled(self, cr, uid, ids, name, args, context):
88 res[id] = self.test_paid(cr, uid, [id])
91 def _get_reference_type(self, cr, uid, context=None):
92 return [('none', _('Free Reference'))]
94 def _amount_residual(self, cr, uid, ids, name, args, context=None):
96 data_inv = self.browse(cr, uid, ids)
97 cur_obj = self.pool.get('res.currency')
100 context.update({'date':inv.date_invoice})
101 context_unreconciled=context.copy()
102 for lines in inv.move_lines:
103 debit_tmp = lines.debit
104 credit_tmp = lines.credit
105 # If currency conversion needed
106 if inv.company_id.currency_id.id <> inv.currency_id.id:
107 # If invoice paid, compute currency amount according to invoice date
108 # otherwise, take the line date
109 if not inv.reconciled:
110 context.update({'date':lines.date})
111 context_unreconciled.update({'date':lines.date})
112 # If amount currency setted, compute for debit and credit in company currency
113 if lines.amount_currency < 0:
114 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))
115 elif lines.amount_currency > 0:
116 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))
117 # Then, recomput into invoice currency to avoid rounding trouble !
118 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
119 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
124 if not inv.amount_total:
126 elif inv.type in ('out_invoice','in_refund'):
127 amount = credit-debit
128 result = inv.amount_total - amount
130 amount = debit-credit
131 result = inv.amount_total - amount
132 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
133 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
137 def _get_lines(self, cr, uid, ids, name, arg, context=None):
140 move_lines = self.move_line_id_payment_get(cr,uid,[id])
145 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
146 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
147 for line in data_lines:
149 if line.reconcile_id:
150 ids_line = line.reconcile_id.line_id
151 elif line.reconcile_partial_id:
152 ids_line = line.reconcile_partial_id.line_partial_ids
153 l = map(lambda x: x.id, ids_line)
154 partial_ids.append(line.id)
155 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
158 def _get_invoice_line(self, cr, uid, ids, context=None):
160 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
161 result[line.invoice_id.id] = True
164 def _get_invoice_tax(self, cr, uid, ids, context=None):
166 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
167 result[tax.invoice_id.id] = True
170 def _compute_lines(self, cr, uid, ids, name, args, context=None):
172 for invoice in self.browse(cr, uid, ids, context):
173 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
176 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
177 temp_lines = []#Added temp list to avoid duplicate records
179 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
180 elif m.reconcile_partial_id:
181 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
182 lines += [x for x in temp_lines if x not in lines]
185 lines = filter(lambda x: x not in src, lines)
186 result[invoice.id] = lines
189 def _get_invoice_from_line(self, cr, uid, ids, context={}):
191 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
192 if line.reconcile_partial_id:
193 for line2 in line.reconcile_partial_id.line_partial_ids:
194 move[line2.move_id.id] = True
195 if line.reconcile_id:
196 for line2 in line.reconcile_id.line_id:
197 move[line2.move_id.id] = True
200 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
203 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
205 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
206 for line in r.line_partial_ids:
207 move[line.move_id.id] = True
208 for line in r.line_id:
209 move[line.move_id.id] = True
213 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
216 _name = "account.invoice"
217 _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 orm.except_orm(_('Configuration Error!'),
334 _('There is no Accounting Journal of type Sale/Purchase defined!'))
337 raise orm.except_orm(_('UnknownError'), str(e))
339 def confirm_paid(self, cr, uid, ids, context=None):
340 self.write(cr, uid, ids, {'state':'paid'}, context=context)
341 for (id,name) in self.name_get(cr, uid, ids):
342 message = _('Document ') + " '" + name + "' "+ _("has been paid.")
343 self.log(cr, uid, id, message)
346 def unlink(self, cr, uid, ids, context=None):
347 invoices = self.read(cr, uid, ids, ['state'])
350 if t['state'] in ('draft', 'cancel'):
351 unlink_ids.append(t['id'])
353 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
354 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
357 # def get_invoice_address(self, cr, uid, ids):
358 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
360 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
361 date_invoice=False, payment_term=False, partner_bank=False, company_id=False):
362 invoice_addr_id = False
363 contact_addr_id = False
364 partner_payment_term = False
367 fiscal_position = False
369 opt = [('uid', str(uid))]
372 opt.insert(0, ('id', partner_id))
373 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
374 contact_addr_id = res['contact']
375 invoice_addr_id = res['invoice']
376 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
378 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
379 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)])
380 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)])
382 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
384 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
385 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
386 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
387 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
388 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
389 if not rec_res_id and not pay_res_id:
390 raise osv.except_osv(_('Configration Error !'),
391 _('Can not find account chart for this company, Please Create account.'))
392 rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
393 pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
394 p.property_account_receivable = rec_obj_acc[0]
395 p.property_account_payable = pay_obj_acc[0]
397 if type in ('out_invoice', 'out_refund'):
398 acc_id = p.property_account_receivable.id
400 acc_id = p.property_account_payable.id
401 fiscal_position = p.property_account_position and p.property_account_position.id or False
402 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
404 bank_id = p.bank_ids[0].id
407 'address_contact_id': contact_addr_id,
408 'address_invoice_id': invoice_addr_id,
409 'account_id': acc_id,
410 'payment_term': partner_payment_term,
411 'fiscal_position': fiscal_position
415 if type in ('in_invoice', 'in_refund'):
416 result['value']['partner_bank'] = bank_id
418 if payment_term != partner_payment_term:
419 if partner_payment_term:
420 to_update = self.onchange_payment_term_date_invoice(
421 cr,uid,ids,partner_payment_term,date_invoice)
422 result['value'].update(to_update['value'])
424 result['value']['date_due'] = False
426 if partner_bank != bank_id:
427 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
428 result['value'].update(to_update['value'])
431 def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
432 if curr_id and company_id:
433 currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
434 if currency.company_id.id != company_id:
435 raise osv.except_osv(_('Configration Error !'),
436 _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
439 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
440 if not payment_term_id:
443 pt_obj= self.pool.get('account.payment.term')
444 if not date_invoice :
445 date_invoice = time.strftime('%Y-%m-%d')
447 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
450 pterm_list = [line[0] for line in pterm_list]
452 res= {'value':{'date_due': pterm_list[-1]}}
454 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
458 def onchange_invoice_line(self, cr, uid, ids, lines):
461 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
464 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
467 obj_journal = self.pool.get('account.journal')
468 if company_id and part_id and type:
470 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
471 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
472 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
473 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)])
474 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)])
476 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
478 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
479 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
480 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
481 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
482 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
483 if not rec_res_id and not rec_res_id:
484 raise osv.except_osv(_('Configration Error !'),
485 _('Can not find account chart for this company, Please Create account.'))
486 if type in ('out_invoice', 'out_refund'):
490 val= {'account_id': acc_id}
493 inv_obj = self.browse(cr,uid,ids)
494 for line in inv_obj[0].invoice_line:
496 if line.account_id.company_id.id != company_id:
497 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
499 raise osv.except_osv(_('Configration Error !'),
500 _('Can not find account chart for this company in invoice line account, Please Create account.'))
501 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
504 for inv_line in invoice_line:
505 obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
506 if obj_l.company_id.id != company_id:
507 raise osv.except_osv(_('Configration Error !'),
508 _('invoice line account company is not match with invoice company.'))
511 if company_id and type:
512 if type in ('out_invoice', 'out_refund'):
513 journal_type = 'sale'
515 journal_type = 'purchase'
516 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
518 val['journal_id'] = journal_ids[0]
520 raise osv.except_osv(_('Configration Error !'),
521 _('Can not find account journal for this company in invoice, Please Create journal.'))
522 dom = {'journal_id': [('id', 'in', journal_ids)]}
524 journal_ids = obj_journal.search(cr, uid, [])
526 if currency_id and company_id:
527 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
528 if currency.company_id.id != company_id:
529 val['currency_id'] = False
531 val['currency_id'] = currency.id
534 company = self.pool.get('res.company').browse(cr, uid, company_id)
535 if company.currency_id.company_id.id != company_id:
536 val['currency_id'] = False
538 val['currency_id'] = company.currency_id.id
540 return {'value' : val, 'domain': dom }
542 # go from canceled state to draft state
543 def action_cancel_draft(self, cr, uid, ids, *args):
544 self.write(cr, uid, ids, {'state':'draft'})
545 wf_service = netsvc.LocalService("workflow")
547 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
550 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
551 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
552 Hook method to be overridden in additional modules to verify and possibly alter the
553 move lines to be created by an invoice, for special cases.
554 :param invoice_browse: browsable record of the invoice that is generating the move lines
555 :param move_lines: list of dictionaries with the account.move.lines (as for create())
556 :return: the (possibly updated) final move_lines to create for this invoice
563 # return the ids of the move lines which has the same account than the invoice
565 def move_line_id_payment_get(self, cr, uid, ids, *args):
567 if not ids: return res
570 from account_move_line l \
571 left join account_invoice i on (i.move_id=l.move_id) \
572 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
573 res = map(lambda x: x[0], cr.fetchall())
576 def copy(self, cr, uid, id, default=None, context=None):
579 default = default.copy()
580 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
581 if 'date_invoice' not in default:
582 default['date_invoice'] = False
583 if 'date_due' not in default:
584 default['date_due'] = False
585 return super(account_invoice, self).copy(cr, uid, id, default, context)
587 def test_paid(self, cr, uid, ids, *args):
588 res = self.move_line_id_payment_get(cr, uid, ids)
593 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
594 ok = ok and bool(cr.fetchone()[0])
597 def button_reset_taxes(self, cr, uid, ids, context=None):
600 ait_obj = self.pool.get('account.invoice.tax')
602 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
603 partner = self.browse(cr, uid, id,context=context).partner_id
605 context.update({'lang': partner.lang})
606 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
607 ait_obj.create(cr, uid, taxe)
608 # Update the stored value (fields.function), so we write to trigger recompute
609 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
610 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
613 def button_compute(self, cr, uid, ids, context=None, set_total=False):
614 self.button_reset_taxes(cr, uid, ids, context)
615 for inv in self.browse(cr, uid, ids):
617 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
620 def _convert_ref(self, cr, uid, ref):
621 return (ref or '').replace('/','')
623 def _get_analytic_lines(self, cr, uid, id):
624 inv = self.browse(cr, uid, [id])[0]
625 cur_obj = self.pool.get('res.currency')
627 company_currency = inv.company_id.currency_id.id
628 if inv.type in ('out_invoice', 'in_refund'):
633 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
635 if il['account_analytic_id']:
636 if inv.type in ('in_invoice', 'in_refund'):
639 ref = self._convert_ref(cr, uid, inv.number)
640 il['analytic_lines'] = [(0,0, {
642 'date': inv['date_invoice'],
643 'account_id': il['account_analytic_id'],
644 'unit_amount': il['quantity'],
645 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
646 'product_id': il['product_id'],
647 'product_uom_id': il['uos_id'],
648 'general_account_id': il['account_id'],
649 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
654 def action_date_assign(self, cr, uid, ids, *args):
655 for inv in self.browse(cr, uid, ids):
656 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
657 if res and res['value']:
658 self.write(cr, uid, [inv.id], res['value'])
661 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
662 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
663 Hook method to be overridden in additional modules to verify and possibly alter the
664 move lines to be created by an invoice, for special cases.
665 :param invoice_browse: browsable record of the invoice that is generating the move lines
666 :param move_lines: list of dictionaries with the account.move.lines (as for create())
667 :return: the (possibly updated) final move_lines to create for this invoice
671 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
673 for tax in compute_taxes.values():
674 ait_obj.create(cr, uid, tax)
677 for tax in inv.tax_line:
680 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
682 if not key in compute_taxes:
683 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
684 base = compute_taxes[key]['base']
685 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
686 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
687 for key in compute_taxes:
688 if not key in tax_key:
689 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
691 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
694 cur_obj = self.pool.get('res.currency')
695 for i in invoice_move_lines:
696 if inv.currency_id.id != company_currency:
697 i['currency_id'] = inv.currency_id.id
698 i['amount_currency'] = i['price']
699 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
700 company_currency, i['price'],
701 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
703 i['amount_currency'] = False
704 i['currency_id'] = False
706 if inv.type in ('out_invoice','in_refund'):
708 total_currency += i['amount_currency'] or i['price']
709 i['price'] = - i['price']
712 total_currency -= i['amount_currency'] or i['price']
713 return total, total_currency, invoice_move_lines
715 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
716 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
717 will be grouped together if the journal has the 'group line' option. Of course a module
718 can add fields to invoice lines that would need to be tested too before merging lines
720 return "%s-%s-%s-%s-%s"%(
721 invoice_line['account_id'],
722 invoice_line.get('tax_code_id',"False"),
723 invoice_line.get('product_id',"False"),
724 invoice_line.get('analytic_account_id',"False"),
725 invoice_line.get('date_maturity',"False"))
727 def group_lines(self, cr, uid, iml, line, inv):
728 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
729 if inv.journal_id.group_invoice_lines:
732 tmp = self.inv_line_characteristic_hashcode(inv, l)
735 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
736 line2[tmp]['debit'] = (am > 0) and am or 0.0
737 line2[tmp]['credit'] = (am < 0) and -am or 0.0
738 line2[tmp]['tax_amount'] += l['tax_amount']
739 line2[tmp]['analytic_lines'] += l['analytic_lines']
743 for key, val in line2.items():
744 line.append((0,0,val))
748 def action_move_create(self, cr, uid, ids, *args):
749 """Creates invoice related analytics and financial move lines"""
750 ait_obj = self.pool.get('account.invoice.tax')
751 cur_obj = self.pool.get('res.currency')
753 for inv in self.browse(cr, uid, ids):
757 if not inv.date_invoice:
758 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
759 company_currency = inv.company_id.currency_id.id
760 # create the analytical lines
761 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
762 # one move line per invoice line
763 iml = self._get_analytic_lines(cr, uid, inv.id)
764 # check if taxes are all computed
766 context.update({'lang': inv.partner_id.lang})
767 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
768 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
770 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
771 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
773 # one move line per tax line
774 iml += ait_obj.move_line_get(cr, uid, inv.id)
776 if inv.type in ('in_invoice', 'in_refund'):
779 ref = self._convert_ref(cr, uid, inv.number)
781 diff_currency_p = inv.currency_id.id <> company_currency
782 # create one move line for the total and possibly adjust the other lines amount
785 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
786 acc_id = inv.account_id.id
788 name = inv['name'] or '/'
791 totlines = self.pool.get('account.payment.term').compute(cr,
792 uid, inv.payment_term.id, total, inv.date_invoice or False)
794 res_amount_currency = total_currency
797 if inv.currency_id.id != company_currency:
798 amount_currency = cur_obj.compute(cr, uid,
799 company_currency, inv.currency_id.id, t[1])
801 amount_currency = False
803 # last line add the diff
804 res_amount_currency -= amount_currency or 0
806 if i == len(totlines):
807 amount_currency += res_amount_currency
813 'account_id': acc_id,
814 'date_maturity': t[0],
815 'amount_currency': diff_currency_p \
816 and amount_currency or False,
817 'currency_id': diff_currency_p \
818 and inv.currency_id.id or False,
826 'account_id': acc_id,
827 'date_maturity' : inv.date_due or False,
828 'amount_currency': diff_currency_p \
829 and total_currency or False,
830 'currency_id': diff_currency_p \
831 and inv.currency_id.id or False,
835 date = inv.date_invoice or time.strftime('%Y-%m-%d')
836 part = inv.partner_id.id
838 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
840 line = self.group_lines(cr, uid, iml, line, inv)
842 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
843 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
844 if journal.centralisation:
845 raise osv.except_osv(_('UserError'),
846 _('Cannot create invoice move on centralised journal'))
848 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
850 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
851 period_id=inv.period_id and inv.period_id.id or False
853 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'))])
855 period_id=period_ids[0]
857 move['period_id'] = period_id
859 i[2]['period_id'] = period_id
861 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
862 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
863 # make the invoice point to that move
864 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
865 self.pool.get('account.move').post(cr, uid, [move_id])
866 self._log_event(cr, uid, ids)
869 def line_get_convert(self, cr, uid, x, part, date, context=None):
871 'date_maturity': x.get('date_maturity', False),
873 'name':x['name'][:64],
875 'debit':x['price']>0 and x['price'],
876 'credit':x['price']<0 and -x['price'],
877 'account_id':x['account_id'],
878 'analytic_lines':x.get('analytic_lines', []),
879 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
880 'currency_id':x.get('currency_id', False),
881 'tax_code_id': x.get('tax_code_id', False),
882 'tax_amount': x.get('tax_amount', False),
883 'ref':x.get('ref',False),
884 'quantity':x.get('quantity',1.00),
885 'product_id':x.get('product_id', False),
886 'product_uom_id':x.get('uos_id',False),
887 'analytic_account_id':x.get('account_analytic_id',False),
890 def action_number(self, cr, uid, ids, *args):
891 cr.execute('SELECT id, type, number, move_id, reference ' \
892 'FROM account_invoice ' \
893 'WHERE id IN ('+','.join(map(str, ids))+')')
894 obj_inv = self.browse(cr, uid, ids)[0]
895 for (id, invtype, number, move_id, reference) in cr.fetchall():
897 if obj_inv.journal_id.invoice_sequence_id:
898 sid = obj_inv.journal_id.invoice_sequence_id.id
899 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
901 number = self.pool.get('ir.sequence').get(cr, uid,
902 'account.invoice.' + invtype)
903 if invtype in ('in_invoice', 'in_refund'):
906 ref = self._convert_ref(cr, uid, number)
907 cr.execute('UPDATE account_invoice SET number=%s ' \
908 'WHERE id=%s', (number, id))
909 cr.execute('UPDATE account_move SET ref=%s ' \
910 'WHERE id=%s AND (ref is null OR ref = \'\')',
912 cr.execute('UPDATE account_move_line SET ref=%s ' \
913 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
915 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
916 'FROM account_move_line ' \
917 'WHERE account_move_line.move_id = %s ' \
918 'AND account_analytic_line.move_id = account_move_line.id',
922 def action_cancel(self, cr, uid, ids, *args):
923 account_move_obj = self.pool.get('account.move')
924 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
927 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
928 # delete the move this invoice was pointing to
929 # Note that the corresponding move_lines and move_reconciles
930 # will be automatically deleted too
931 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
933 account_move_line_obj = self.pool.get('account.move.line')
934 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
935 for move_line in pay_ids:
936 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
937 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
939 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
940 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
945 def list_distinct_taxes(self, cr, uid, ids):
946 invoices = self.browse(cr, uid, ids)
949 for tax in inv.tax_line:
950 if not tax['name'] in taxes:
951 taxes[tax['name']] = {'name': tax['name']}
952 return taxes.values()
954 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
955 #TODO: implement messages system
958 def name_get(self, cr, uid, ids, context=None):
962 'out_invoice': 'CI: ',
963 'in_invoice': 'SI: ',
964 'out_refund': 'OR: ',
967 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')]
969 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
976 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
978 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
979 return self.name_get(cr, user, ids, context)
981 def _refund_cleanup_lines(self, cr, uid, lines):
984 del line['invoice_id']
985 if 'account_id' in line:
986 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
987 if 'product_id' in line:
988 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
990 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
991 if 'invoice_line_tax_id' in line:
992 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
993 if 'account_analytic_id' in line:
994 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
995 if 'tax_code_id' in line :
996 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
997 line['tax_code_id'] = line['tax_code_id'][0]
998 if 'base_code_id' in line :
999 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
1000 line['base_code_id'] = line['base_code_id'][0]
1001 return map(lambda x: (0,0,x), lines)
1003 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
1004 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'])
1007 for invoice in invoices:
1011 'out_invoice': 'out_refund', # Customer Invoice
1012 'in_invoice': 'in_refund', # Supplier Invoice
1013 'out_refund': 'out_invoice', # Customer Refund
1014 'in_refund': 'in_invoice', # Supplier Refund
1018 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
1019 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1021 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
1022 tax_lines = filter(lambda l: l['manual'], tax_lines)
1023 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1025 date = time.strftime('%Y-%m-%d')
1027 'type': type_dict[invoice['type']],
1028 'date_invoice': date,
1031 'invoice_line': invoice_lines,
1032 'tax_line': tax_lines
1036 'period_id': period_id,
1040 'name': description,
1042 # take the id part of the tuple returned for many2one fields
1043 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1044 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1045 invoice[field] = invoice[field] and invoice[field][0]
1046 # create the new invoice
1047 new_ids.append(self.create(cr, uid, invoice))
1050 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=''):
1053 #TODO check if we can use different period for payment and the writeoff line
1054 assert len(ids)==1, "Can only pay one invoice at a time"
1055 invoice = self.browse(cr, uid, ids[0])
1056 src_account_id = invoice.account_id.id
1057 # Take the seq as name for move
1058 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1059 direction = types[invoice.type]
1060 #take the choosen date
1061 if 'date_p' in context and context['date_p']:
1062 date=context['date_p']
1064 date=time.strftime('%Y-%m-%d')
1066 # Take the amount in currency and the currency of the payment
1067 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1068 amount_currency = context['amount_currency']
1069 currency_id = context['currency_id']
1071 amount_currency = False
1073 if invoice.type in ('in_invoice', 'in_refund'):
1074 ref = invoice.reference
1076 ref = self._convert_ref(cr, uid, invoice.number)
1077 # Pay attention to the sign for both debit/credit AND amount_currency
1079 'debit': direction * pay_amount>0 and direction * pay_amount,
1080 'credit': direction * pay_amount<0 and - direction * pay_amount,
1081 'account_id': src_account_id,
1082 'partner_id': invoice.partner_id.id,
1085 'currency_id':currency_id,
1086 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1087 'company_id': invoice.company_id.id,
1090 'debit': direction * pay_amount<0 and - direction * pay_amount,
1091 'credit': direction * pay_amount>0 and direction * pay_amount,
1092 'account_id': pay_account_id,
1093 'partner_id': invoice.partner_id.id,
1096 'currency_id':currency_id,
1097 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1098 'company_id': invoice.company_id.id,
1102 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1106 lines = [(0, 0, l1), (0, 0, l2)]
1107 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1108 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1112 line = self.pool.get('account.move.line')
1113 move_ids = [move_id,]
1115 move_ids.append(invoice.move_id.id)
1116 cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1117 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1118 for l in lines+invoice.payment_ids:
1119 if l.account_id.id==src_account_id:
1120 line_ids.append(l.id)
1121 total += (l.debit or 0.0) - (l.credit or 0.0)
1122 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1123 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1125 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1127 # Update the stored value (fields.function), so we write to trigger recompute
1128 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1132 class account_invoice_line(osv.osv):
1133 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1135 tax_obj = self.pool.get('account.tax')
1136 cur_obj = self.pool.get('res.currency')
1137 for line in self.browse(cr, uid, ids):
1138 price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1139 taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity)
1140 res[line.id] = taxes['total']
1142 cur = line.invoice_id.currency_id
1143 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1146 def _price_unit_default(self, cr, uid, context=None):
1149 if 'check_total' in context:
1150 t = context['check_total']
1151 for l in context.get('invoice_line', {}):
1152 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1153 tax_obj = self.pool.get('account.tax')
1154 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1155 t = t - (p * l[2].get('quantity'))
1156 taxes = l[2].get('invoice_line_tax_id')
1157 if len(taxes[0]) >= 3 and taxes[0][2]:
1158 taxes = tax_obj.browse(cr, uid, taxes[0][2])
1159 for tax in tax_obj.compute_all(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))['taxes']:
1160 t = t - tax['amount']
1164 _name = "account.invoice.line"
1165 _description = "Invoice Line"
1167 'name': fields.char('Description', size=256, required=True),
1168 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1169 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1170 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1171 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1172 '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."),
1173 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1174 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', type="float",
1175 digits_compute= dp.get_precision('Account'), store=True),
1176 'quantity': fields.float('Quantity', required=True),
1177 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1178 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1179 'note': fields.text('Notes', translate=True),
1180 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1181 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1182 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1185 'quantity': lambda *a: 1,
1186 'discount': lambda *a: 0.0,
1187 'price_unit': _price_unit_default,
1190 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1191 tax_obj = self.pool.get('account.tax')
1193 taxes = tax_obj.browse(cr, uid, tax_id)
1194 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1195 price_unit = price_unit - tax['amount']
1196 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1198 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):
1201 company_id = context.get('company_id',False)
1203 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1205 if type in ('in_invoice', 'in_refund'):
1206 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1208 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1209 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1210 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1213 context.update({'lang': part.lang})
1215 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1218 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)])
1220 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)])
1221 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)])
1223 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)])
1226 in_acc = res.product_tmpl_id.property_account_income
1227 in_acc_cate = res.categ_id.property_account_income_categ
1231 app_acc_in = in_acc_cate
1233 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1235 ex_acc = res.product_tmpl_id.property_account_expense
1236 ex_acc_cate = res.categ_id.property_account_expense_categ
1238 app_acc_exp = ex_acc
1240 app_acc_exp = ex_acc_cate
1242 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1243 if not in_pro_id and not exp_pro_id:
1244 in_acc = res.product_tmpl_id.property_account_income
1245 in_acc_cate = res.categ_id.property_account_income_categ
1246 ex_acc = res.product_tmpl_id.property_account_expense
1247 ex_acc_cate = res.categ_id.property_account_expense_categ
1248 if in_acc or ex_acc:
1250 app_acc_exp = ex_acc
1252 app_acc_in = in_acc_cate
1253 app_acc_exp = ex_acc_cate
1255 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1256 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1257 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1258 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1259 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1260 if not in_res_id and not exp_res_id:
1261 raise osv.except_osv(_('Configration Error !'),
1262 _('Can not find account chart for this company, Please Create account.'))
1263 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1264 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1265 if in_acc or ex_acc:
1266 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1267 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1269 res.categ_id.property_account_income_categ = in_obj_acc[0]
1270 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1272 if type in ('out_invoice','out_refund'):
1273 a = res.product_tmpl_id.property_account_income.id
1275 a = res.categ_id.property_account_income_categ.id
1277 a = res.product_tmpl_id.property_account_expense.id
1279 a = res.categ_id.property_account_expense_categ.id
1281 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1283 result['account_id'] = a
1286 tax_obj = self.pool.get('account.tax')
1287 if type in ('out_invoice', 'out_refund'):
1288 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1289 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1291 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)
1292 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1293 if type in ('in_invoice', 'in_refund'):
1294 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)
1295 result.update(to_update)
1297 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1300 result['name'] = res.partner_ref
1303 result['uos_id'] = uom or res.uom_id.id or False
1304 if result['uos_id']:
1305 res2 = res.uom_id.category_id.id
1307 domain = {'uos_id':[('category_id','=',res2 )]}
1309 prod_pool=self.pool.get('product.product')
1310 result['categ_id'] = res.categ_id.id
1311 res_final = {'value':result, 'domain':domain}
1313 if not company_id and not currency_id:
1316 company = self.pool.get('res.company').browse(cr, uid, company_id)
1317 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1319 if not currency.company_id.id == company.id:
1320 raise osv.except_osv(_('Configration Error !'),
1321 _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1323 if company.currency_id.id != currency.id:
1324 new_price = res_final['value']['price_unit'] * currency.rate
1325 res_final['value']['price_unit'] = new_price
1328 def move_line_get(self, cr, uid, invoice_id, context=None):
1331 tax_obj = self.pool.get('account.tax')
1332 cur_obj = self.pool.get('res.currency')
1333 ait_obj = self.pool.get('account.invoice.tax')
1334 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1335 company_currency = inv.company_id.currency_id.id
1336 cur = inv.currency_id
1338 for line in inv.invoice_line:
1339 mres = self.move_line_get_item(cr, uid, line, context)
1343 tax_code_found= False
1344 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1345 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1346 line.quantity, inv.address_invoice_id.id, line.product_id,
1347 inv.partner_id)['taxes']:
1349 if inv.type in ('out_invoice', 'in_invoice'):
1350 tax_code_id = tax['base_code_id']
1351 tax_amount = line.price_subtotal * tax['base_sign']
1353 tax_code_id = tax['ref_base_code_id']
1354 tax_amount = line.price_subtotal * tax['ref_base_sign']
1359 res.append(self.move_line_get_item(cr, uid, line, context))
1360 res[-1]['price'] = 0.0
1361 res[-1]['account_analytic_id'] = False
1362 elif not tax_code_id:
1364 tax_code_found = True
1366 res[-1]['tax_code_id'] = tax_code_id
1367 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1370 def move_line_get_item(self, cr, uid, line, context=None):
1373 'name': line.name[:64],
1374 'price_unit':line.price_unit,
1375 'quantity':line.quantity,
1376 'price':line.price_subtotal,
1377 'account_id':line.account_id.id,
1378 'product_id':line.product_id.id,
1379 'uos_id':line.uos_id.id,
1380 'account_analytic_id':line.account_analytic_id.id,
1381 'taxes':line.invoice_line_tax_id,
1384 # Set the tax field according to the account and the fiscal position
1386 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1389 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1390 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1391 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1392 r = {'value':{'invoice_line_tax_id': res}}
1394 account_invoice_line()
1396 class account_invoice_tax(osv.osv):
1397 _name = "account.invoice.tax"
1398 _description = "Invoice Tax"
1400 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1401 'name': fields.char('Tax Description', size=64, required=True),
1402 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1403 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1404 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1405 'manual': fields.boolean('Manual'),
1406 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1408 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1409 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1410 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1411 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1412 'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1415 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1416 cur_obj = self.pool.get('res.currency')
1417 company_obj = self.pool.get('res.company')
1418 company_currency=False
1420 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1421 if currency_id and company_currency:
1422 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1423 return {'value': {'base_amount':base}}
1425 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1426 cur_obj = self.pool.get('res.currency')
1427 company_obj = self.pool.get('res.company')
1428 company_currency=False
1430 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1431 if currency_id and company_currency:
1432 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1433 return {'value': {'tax_amount':amount}}
1437 'manual': lambda *a: 1,
1438 'base_amount': lambda *a: 0.0,
1439 'tax_amount': lambda *a: 0.0,
1441 def compute(self, cr, uid, invoice_id, context={}):
1443 tax_obj = self.pool.get('account.tax')
1444 cur_obj = self.pool.get('res.currency')
1445 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1446 cur = inv.currency_id
1447 company_currency = inv.company_id.currency_id.id
1449 for line in inv.invoice_line:
1450 for tax in tax_obj.compute_all(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)['taxes']:
1452 val['invoice_id'] = inv.id
1453 val['name'] = tax['name']
1454 val['amount'] = tax['amount']
1455 val['manual'] = False
1456 val['sequence'] = tax['sequence']
1457 val['base'] = tax['price_unit'] * line['quantity']
1459 if inv.type in ('out_invoice','in_invoice'):
1460 val['base_code_id'] = tax['base_code_id']
1461 val['tax_code_id'] = tax['tax_code_id']
1462 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)
1463 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)
1464 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1466 val['base_code_id'] = tax['ref_base_code_id']
1467 val['tax_code_id'] = tax['ref_tax_code_id']
1468 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)
1469 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)
1470 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1472 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1473 if not key in tax_grouped:
1474 tax_grouped[key] = val
1476 tax_grouped[key]['amount'] += val['amount']
1477 tax_grouped[key]['base'] += val['base']
1478 tax_grouped[key]['base_amount'] += val['base_amount']
1479 tax_grouped[key]['tax_amount'] += val['tax_amount']
1481 for t in tax_grouped.values():
1482 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1483 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1484 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1487 def move_line_get(self, cr, uid, invoice_id):
1489 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1490 for t in cr.dictfetchall():
1491 if not t['amount'] \
1492 and not t['tax_code_id'] \
1493 and not t['tax_amount']:
1498 'price_unit': t['amount'],
1500 'price': t['amount'] or 0.0,
1501 'account_id': t['account_id'],
1502 'tax_code_id': t['tax_code_id'],
1503 'tax_amount': t['tax_amount']
1506 account_invoice_tax()
1509 class res_partner(osv.osv):
1510 """ Inherits partner and adds invoice information in the partner form """
1511 _inherit = 'res.partner'
1513 'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),