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
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 unlink(self, cr, uid, ids, context=None):
328 invoices = self.read(cr, uid, ids, ['state'])
331 if t['state'] in ('draft', 'cancel'):
332 unlink_ids.append(t['id'])
334 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
335 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
338 # def get_invoice_address(self, cr, uid, ids):
339 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
341 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
342 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
343 invoice_addr_id = False
344 contact_addr_id = False
345 partner_payment_term = False
348 fiscal_position = False
350 opt = [('uid', str(uid))]
353 opt.insert(0, ('id', partner_id))
354 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
355 contact_addr_id = res['contact']
356 invoice_addr_id = res['invoice']
357 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
359 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
360 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)])
361 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)])
363 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
365 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
366 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
367 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
368 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
369 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
370 if not rec_res_id and not pay_res_id:
371 raise osv.except_osv(_('Configration Error !'),
372 _('Can not find account chart for this company, Please Create account.'))
373 rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
374 pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
375 p.property_account_receivable = rec_obj_acc[0]
376 p.property_account_payable = pay_obj_acc[0]
378 if type in ('out_invoice', 'out_refund'):
379 acc_id = p.property_account_receivable.id
381 acc_id = p.property_account_payable.id
382 fiscal_position = p.property_account_position and p.property_account_position.id or False
383 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
385 bank_id = p.bank_ids[0].id
388 'address_contact_id': contact_addr_id,
389 'address_invoice_id': invoice_addr_id,
390 'account_id': acc_id,
391 'payment_term': partner_payment_term,
392 'fiscal_position': fiscal_position
396 if type in ('in_invoice', 'in_refund'):
397 result['value']['partner_bank'] = bank_id
399 if payment_term != partner_payment_term:
400 if partner_payment_term:
401 to_update = self.onchange_payment_term_date_invoice(
402 cr,uid,ids,partner_payment_term,date_invoice)
403 result['value'].update(to_update['value'])
405 result['value']['date_due'] = False
407 if partner_bank_id != bank_id:
408 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
409 result['value'].update(to_update['value'])
412 def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
413 if curr_id and company_id:
414 currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
415 if currency.company_id.id != company_id:
416 raise osv.except_osv(_('Configration Error !'),
417 _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
420 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
421 if not payment_term_id:
424 pt_obj= self.pool.get('account.payment.term')
425 if not date_invoice :
426 date_invoice = time.strftime('%Y-%m-%d')
428 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
431 pterm_list = [line[0] for line in pterm_list]
433 res= {'value':{'date_due': pterm_list[-1]}}
435 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
439 def onchange_invoice_line(self, cr, uid, ids, lines):
442 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
445 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
448 if company_id and part_id and type:
450 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
451 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
452 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
453 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)])
454 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)])
456 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
458 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
459 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
460 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
461 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
462 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
463 if not rec_res_id and not rec_res_id:
464 raise osv.except_osv(_('Configration Error !'),
465 _('Can not find account chart for this company, Please Create account.'))
466 if type in ('out_invoice', 'out_refund'):
470 val= {'account_id': acc_id}
473 inv_obj = self.browse(cr,uid,ids)
474 for line in inv_obj[0].invoice_line:
476 if line.account_id.company_id.id != company_id:
477 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
479 raise osv.except_osv(_('Configration Error !'),
480 _('Can not find account chart for this company in invoice line account, Please Create account.'))
481 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
484 for inv_line in invoice_line:
485 obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
486 if obj_l.company_id.id != company_id:
487 raise osv.except_osv(_('Configration Error !'),
488 _('invoice line account company is not match with invoice company.'))
492 val['journal_id']=False
493 journal_ids=self.pool.get('account.journal').search(cr,uid,[('company_id','=',company_id)])
494 dom={'journal_id': [('id','in',journal_ids)]}
496 journal_ids=self.pool.get('account.journal').search(cr,uid,[])
497 dom={'journal_id': [('id','in',journal_ids)]}
499 if currency_id and company_id:
500 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
501 if currency.company_id.id != company_id:
502 val['currency_id'] = False
504 val['currency_id'] = currency.id
507 company = self.pool.get('res.company').browse(cr, uid, company_id)
508 if company.currency_id.company_id.id != company_id:
509 val['currency_id'] = False
511 val['currency_id'] = company.currency_id.id
513 return {'value' : val, 'domain': dom }
515 # go from canceled state to draft state
516 def action_cancel_draft(self, cr, uid, ids, *args):
517 self.write(cr, uid, ids, {'state':'draft'})
518 wf_service = netsvc.LocalService("workflow")
520 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
526 # return the ids of the move lines which has the same account than the invoice
528 def move_line_id_payment_get(self, cr, uid, ids, *args):
530 if not ids: return res
533 from account_move_line l \
534 left join account_invoice i on (i.move_id=l.move_id) \
535 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
536 res = map(lambda x: x[0], cr.fetchall())
539 def copy(self, cr, uid, id, default=None, context=None):
542 default = default.copy()
543 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
544 if 'date_invoice' not in default:
545 default['date_invoice'] = False
546 if 'date_due' not in default:
547 default['date_due'] = False
548 return super(account_invoice, self).copy(cr, uid, id, default, context)
550 def test_paid(self, cr, uid, ids, *args):
551 res = self.move_line_id_payment_get(cr, uid, ids)
556 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
557 ok = ok and bool(cr.fetchone()[0])
560 def button_reset_taxes(self, cr, uid, ids, context=None):
563 ait_obj = self.pool.get('account.invoice.tax')
565 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
566 partner = self.browse(cr, uid, id,context=context).partner_id
568 context.update({'lang': partner.lang})
569 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
570 ait_obj.create(cr, uid, taxe)
571 # Update the stored value (fields.function), so we write to trigger recompute
572 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
573 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
576 def button_compute(self, cr, uid, ids, context=None, set_total=False):
577 self.button_reset_taxes(cr, uid, ids, context)
578 for inv in self.browse(cr, uid, ids):
580 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
583 def _convert_ref(self, cr, uid, ref):
584 return (ref or '').replace('/','')
586 def _get_analytic_lines(self, cr, uid, id):
587 inv = self.browse(cr, uid, [id])[0]
588 cur_obj = self.pool.get('res.currency')
590 company_currency = inv.company_id.currency_id.id
591 if inv.type in ('out_invoice', 'in_refund'):
596 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
598 if il['account_analytic_id']:
599 if inv.type in ('in_invoice', 'in_refund'):
602 ref = self._convert_ref(cr, uid, inv.number)
603 il['analytic_lines'] = [(0,0, {
605 'date': inv['date_invoice'],
606 'account_id': il['account_analytic_id'],
607 'unit_amount': il['quantity'],
608 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
609 'product_id': il['product_id'],
610 'product_uom_id': il['uos_id'],
611 'general_account_id': il['account_id'],
612 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
617 def action_date_assign(self, cr, uid, ids, *args):
618 for inv in self.browse(cr, uid, ids):
619 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
620 if res and res['value']:
621 self.write(cr, uid, [inv.id], res['value'])
624 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
626 for tax in compute_taxes.values():
627 ait_obj.create(cr, uid, tax)
630 for tax in inv.tax_line:
633 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
635 if not key in compute_taxes:
636 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
637 base = compute_taxes[key]['base']
638 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
639 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
640 for key in compute_taxes:
641 if not key in tax_key:
642 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
644 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
647 for i in invoice_move_lines:
648 if inv.currency_id.id != company_currency:
649 i['currency_id'] = inv.currency_id.id
650 i['amount_currency'] = i['price']
651 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
652 company_currency, i['price'],
653 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
655 i['amount_currency'] = False
656 i['currency_id'] = False
658 if inv.type in ('out_invoice','in_refund'):
660 total_currency += i['amount_currency'] or i['price']
661 i['price'] = - i['price']
664 total_currency -= i['amount_currency'] or i['price']
665 return total, total_currency, invoice_move_lines
667 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
668 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
669 will be grouped together if the journal has the 'group line' option. Of course a module
670 can add fields to invoice lines that would need to be tested too before merging lines
672 return "%s-%s-%s-%s-%s"%(
673 invoice_line['account_id'],
674 invoice_line.get('tax_code_id',"False"),
675 invoice_line.get('product_id',"False"),
676 invoice_line.get('analytic_account_id',"False"),
677 invoice_line.get('date_maturity',"False"))
679 def group_lines(self, cr, uid, iml, line, inv):
680 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
681 if inv.journal_id.group_invoice_lines:
684 tmp = self.inv_line_characteristic_hashcode(inv, l)
687 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
688 line2[tmp]['debit'] = (am > 0) and am or 0.0
689 line2[tmp]['credit'] = (am < 0) and -am or 0.0
690 line2[tmp]['tax_amount'] += l['tax_amount']
691 line2[tmp]['analytic_lines'] += l['analytic_lines']
695 for key, val in line2.items():
696 line.append((0,0,val))
700 def action_move_create(self, cr, uid, ids, *args):
701 """Creates invoice related analytics and financial move lines"""
702 ait_obj = self.pool.get('account.invoice.tax')
703 cur_obj = self.pool.get('res.currency')
705 for inv in self.browse(cr, uid, ids):
709 if not inv.date_invoice:
710 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
711 company_currency = inv.company_id.currency_id.id
712 # create the analytical lines
713 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
714 # one move line per invoice line
715 iml = self._get_analytic_lines(cr, uid, inv.id)
716 # check if taxes are all computed
718 context.update({'lang': inv.partner_id.lang})
719 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
720 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
722 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
723 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
725 # one move line per tax line
726 iml += ait_obj.move_line_get(cr, uid, inv.id)
728 if inv.type in ('in_invoice', 'in_refund'):
731 ref = self._convert_ref(cr, uid, inv.number)
733 diff_currency_p = inv.currency_id.id <> company_currency
734 # create one move line for the total and possibly adjust the other lines amount
737 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
738 acc_id = inv.account_id.id
740 name = inv['name'] or '/'
743 totlines = self.pool.get('account.payment.term').compute(cr,
744 uid, inv.payment_term.id, total, inv.date_invoice or False)
746 res_amount_currency = total_currency
749 if inv.currency_id.id != company_currency:
750 amount_currency = cur_obj.compute(cr, uid,
751 company_currency, inv.currency_id.id, t[1])
753 amount_currency = False
755 # last line add the diff
756 res_amount_currency -= amount_currency or 0
758 if i == len(totlines):
759 amount_currency += res_amount_currency
765 'account_id': acc_id,
766 'date_maturity': t[0],
767 'amount_currency': diff_currency_p \
768 and amount_currency or False,
769 'currency_id': diff_currency_p \
770 and inv.currency_id.id or False,
778 'account_id': acc_id,
779 'date_maturity' : inv.date_due or False,
780 'amount_currency': diff_currency_p \
781 and total_currency or False,
782 'currency_id': diff_currency_p \
783 and inv.currency_id.id or False,
787 date = inv.date_invoice or time.strftime('%Y-%m-%d')
788 part = inv.partner_id.id
790 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
792 line = self.group_lines(cr, uid, iml, line, inv)
794 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
795 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
796 if journal.centralisation:
797 raise osv.except_osv(_('UserError'),
798 _('Cannot create invoice move on centralised journal'))
799 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
800 period_id=inv.period_id and inv.period_id.id or False
802 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'))])
804 period_id=period_ids[0]
806 move['period_id'] = period_id
808 i[2]['period_id'] = period_id
810 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
811 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
812 # make the invoice point to that move
813 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
814 self.pool.get('account.move').post(cr, uid, [move_id])
815 self._log_event(cr, uid, ids)
818 def line_get_convert(self, cr, uid, x, part, date, context=None):
820 'date_maturity': x.get('date_maturity', False),
822 'name':x['name'][:64],
824 'debit':x['price']>0 and x['price'],
825 'credit':x['price']<0 and -x['price'],
826 'account_id':x['account_id'],
827 'analytic_lines':x.get('analytic_lines', []),
828 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
829 'currency_id':x.get('currency_id', False),
830 'tax_code_id': x.get('tax_code_id', False),
831 'tax_amount': x.get('tax_amount', False),
832 'ref':x.get('ref',False),
833 'quantity':x.get('quantity',1.00),
834 'product_id':x.get('product_id', False),
835 'product_uom_id':x.get('uos_id',False),
836 'analytic_account_id':x.get('account_analytic_id',False),
839 def action_number(self, cr, uid, ids, *args):
840 cr.execute('SELECT id, type, number, move_id, reference ' \
841 'FROM account_invoice ' \
842 'WHERE id IN ('+','.join(map(str, ids))+')')
843 obj_inv = self.browse(cr, uid, ids)[0]
844 for (id, invtype, number, move_id, reference) in cr.fetchall():
846 if obj_inv.journal_id.invoice_sequence_id:
847 sid = obj_inv.journal_id.invoice_sequence_id.id
848 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
850 number = self.pool.get('ir.sequence').get(cr, uid,
851 'account.invoice.' + invtype)
852 if invtype in ('in_invoice', 'in_refund'):
855 ref = self._convert_ref(cr, uid, number)
856 cr.execute('UPDATE account_invoice SET number=%s ' \
857 'WHERE id=%s', (number, id))
858 cr.execute('UPDATE account_move SET ref=%s ' \
859 'WHERE id=%s AND (ref is null OR ref = \'\')',
861 cr.execute('UPDATE account_move_line SET ref=%s ' \
862 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
864 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
865 'FROM account_move_line ' \
866 'WHERE account_move_line.move_id = %s ' \
867 'AND account_analytic_line.move_id = account_move_line.id',
871 def action_cancel(self, cr, uid, ids, *args):
872 account_move_obj = self.pool.get('account.move')
873 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
876 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
877 # delete the move this invoice was pointing to
878 # Note that the corresponding move_lines and move_reconciles
879 # will be automatically deleted too
880 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
882 self.pool.get('account.move.line').write(cr, uid, i['payment_ids'], {'reconcile_partial_id': False})
883 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
884 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
889 def list_distinct_taxes(self, cr, uid, ids):
890 invoices = self.browse(cr, uid, ids)
893 for tax in inv.tax_line:
894 if not tax['name'] in taxes:
895 taxes[tax['name']] = {'name': tax['name']}
896 return taxes.values()
898 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
899 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
901 part=inv['partner_id'] and inv['partner_id'][0]
903 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
904 total = inv['amount_untaxed']
905 if inv['type'] in ('in_invoice','in_refund'):
906 partnertype='supplier'
907 eventtype = 'purchase'
910 partnertype = 'customer'
913 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
914 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})
917 def name_get(self, cr, uid, ids, context=None):
921 'out_invoice': 'CI: ',
922 'in_invoice': 'SI: ',
923 'out_refund': 'OR: ',
926 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')]
928 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
935 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
937 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
938 return self.name_get(cr, user, ids, context)
940 def _refund_cleanup_lines(self, cr, uid, lines):
943 del line['invoice_id']
944 if 'account_id' in line:
945 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
946 if 'product_id' in line:
947 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
949 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
950 if 'invoice_line_tax_id' in line:
951 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
952 if 'account_analytic_id' in line:
953 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
954 if 'tax_code_id' in line :
955 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
956 line['tax_code_id'] = line['tax_code_id'][0]
957 if 'base_code_id' in line :
958 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
959 line['base_code_id'] = line['base_code_id'][0]
960 return map(lambda x: (0,0,x), lines)
962 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
963 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'])
966 for invoice in invoices:
970 'out_invoice': 'out_refund', # Customer Invoice
971 'in_invoice': 'in_refund', # Supplier Invoice
972 'out_refund': 'out_invoice', # Customer Refund
973 'in_refund': 'in_invoice', # Supplier Refund
977 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
978 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
980 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
981 tax_lines = filter(lambda l: l['manual'], tax_lines)
982 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
984 date = time.strftime('%Y-%m-%d')
986 'type': type_dict[invoice['type']],
987 'date_invoice': date,
990 'invoice_line': invoice_lines,
991 'tax_line': tax_lines
995 'period_id': period_id,
1001 # take the id part of the tuple returned for many2one fields
1002 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1003 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1004 invoice[field] = invoice[field] and invoice[field][0]
1005 # create the new invoice
1006 new_ids.append(self.create(cr, uid, invoice))
1009 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=''):
1012 #TODO check if we can use different period for payment and the writeoff line
1013 assert len(ids)==1, "Can only pay one invoice at a time"
1014 invoice = self.browse(cr, uid, ids[0])
1015 src_account_id = invoice.account_id.id
1016 # Take the seq as name for move
1017 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1018 direction = types[invoice.type]
1019 #take the choosen date
1020 if 'date_p' in context and context['date_p']:
1021 date=context['date_p']
1023 date=time.strftime('%Y-%m-%d')
1025 # Take the amount in currency and the currency of the payment
1026 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1027 amount_currency = context['amount_currency']
1028 currency_id = context['currency_id']
1030 amount_currency = False
1032 if invoice.type in ('in_invoice', 'in_refund'):
1033 ref = invoice.reference
1035 ref = self._convert_ref(cr, uid, invoice.number)
1036 # Pay attention to the sign for both debit/credit AND amount_currency
1038 'debit': direction * pay_amount>0 and direction * pay_amount,
1039 'credit': direction * pay_amount<0 and - direction * pay_amount,
1040 'account_id': src_account_id,
1041 'partner_id': invoice.partner_id.id,
1044 'currency_id':currency_id,
1045 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1046 'company_id': invoice.company_id.id,
1049 'debit': direction * pay_amount<0 and - direction * pay_amount,
1050 'credit': direction * pay_amount>0 and direction * pay_amount,
1051 'account_id': pay_account_id,
1052 'partner_id': invoice.partner_id.id,
1055 'currency_id':currency_id,
1056 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1057 'company_id': invoice.company_id.id,
1061 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1065 lines = [(0, 0, l1), (0, 0, l2)]
1066 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1067 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1071 line = self.pool.get('account.move.line')
1072 move_ids = [move_id,]
1074 move_ids.append(invoice.move_id.id)
1075 cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1076 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1077 for l in lines+invoice.payment_ids:
1078 if l.account_id.id==src_account_id:
1079 line_ids.append(l.id)
1080 total += (l.debit or 0.0) - (l.credit or 0.0)
1081 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1082 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1084 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1086 # Update the stored value (fields.function), so we write to trigger recompute
1087 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1091 class account_invoice_line(osv.osv):
1092 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1094 cur_obj=self.pool.get('res.currency')
1095 for line in self.browse(cr, uid, ids):
1097 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1098 cur = line.invoice_id.currency_id
1099 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1101 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'))
1105 def _price_unit_default(self, cr, uid, context=None):
1108 if 'check_total' in context:
1109 t = context['check_total']
1110 for l in context.get('invoice_line', {}):
1111 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1112 tax_obj = self.pool.get('account.tax')
1113 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1114 t = t - (p * l[2].get('quantity'))
1115 taxes = l[2].get('invoice_line_tax_id')
1116 if len(taxes[0]) >= 3 and taxes[0][2]:
1117 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1118 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)):
1119 t = t - tax['amount']
1123 _name = "account.invoice.line"
1124 _description = "Invoice line"
1126 'name': fields.char('Description', size=256, required=True),
1127 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1128 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1129 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1130 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1131 '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."),
1132 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1133 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits_compute= dp.get_precision('Account')),
1134 'quantity': fields.float('Quantity', required=True),
1135 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1136 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1137 'note': fields.text('Notes', translate=True),
1138 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1139 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1140 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1143 'quantity': lambda *a: 1,
1144 'discount': lambda *a: 0.0,
1145 'price_unit': _price_unit_default,
1148 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1149 tax_obj = self.pool.get('account.tax')
1151 taxes = tax_obj.browse(cr, uid, tax_id)
1152 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1153 price_unit = price_unit - tax['amount']
1154 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1156 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):
1159 company_id = context.get('company_id',False)
1161 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1163 if type in ('in_invoice', 'in_refund'):
1164 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1166 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1167 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1168 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1171 context.update({'lang': lang})
1173 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1176 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)])
1178 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)])
1179 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)])
1181 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)])
1184 in_acc = res.product_tmpl_id.property_account_income
1185 in_acc_cate = res.categ_id.property_account_income_categ
1189 app_acc_in = in_acc_cate
1191 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1193 ex_acc = res.product_tmpl_id.property_account_expense
1194 ex_acc_cate = res.categ_id.property_account_expense_categ
1196 app_acc_exp = ex_acc
1198 app_acc_exp = ex_acc_cate
1200 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1201 if not in_pro_id and not exp_pro_id:
1202 in_acc = res.product_tmpl_id.property_account_income
1203 in_acc_cate = res.categ_id.property_account_income_categ
1204 ex_acc = res.product_tmpl_id.property_account_expense
1205 ex_acc_cate = res.categ_id.property_account_expense_categ
1206 if in_acc or ex_acc:
1208 app_acc_exp = ex_acc
1210 app_acc_in = in_acc_cate
1211 app_acc_exp = ex_acc_cate
1213 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1214 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1215 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1216 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1217 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1218 if not in_res_id and not exp_res_id:
1219 raise osv.except_osv(_('Configration Error !'),
1220 _('Can not find account chart for this company, Please Create account.'))
1221 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1222 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1223 if in_acc or ex_acc:
1224 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1225 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1227 res.categ_id.property_account_income_categ = in_obj_acc[0]
1228 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1230 if type in ('out_invoice','out_refund'):
1231 a = res.product_tmpl_id.property_account_income.id
1233 a = res.categ_id.property_account_income_categ.id
1235 a = res.product_tmpl_id.property_account_expense.id
1237 a = res.categ_id.property_account_expense_categ.id
1239 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1241 result['account_id'] = a
1244 tax_obj = self.pool.get('account.tax')
1245 if type in ('out_invoice', 'out_refund'):
1246 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1247 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1249 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)
1250 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1251 if type in ('in_invoice', 'in_refund'):
1252 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)
1253 result.update(to_update)
1255 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1258 result['name'] = res.partner_ref
1261 result['uos_id'] = uom or res.uom_id.id or False
1262 if result['uos_id']:
1263 res2 = res.uom_id.category_id.id
1265 domain = {'uos_id':[('category_id','=',res2 )]}
1267 prod_pool=self.pool.get('product.product')
1268 result['categ_id'] = res.categ_id.id
1269 res_final = {'value':result, 'domain':domain}
1271 if not company_id and not currency_id:
1274 company = self.pool.get('res.company').browse(cr, uid, company_id)
1275 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1277 if not currency.company_id.id == company.id:
1278 raise osv.except_osv(_('Configration Error !'),
1279 _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1281 if company.currency_id.id != currency.id:
1282 new_price = res_final['value']['price_unit'] * currency.rate
1283 res_final['value']['price_unit'] = new_price
1286 def move_line_get(self, cr, uid, invoice_id, context=None):
1289 tax_obj = self.pool.get('account.tax')
1290 cur_obj = self.pool.get('res.currency')
1291 ait_obj = self.pool.get('account.invoice.tax')
1292 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1293 company_currency = inv.company_id.currency_id.id
1294 cur = inv.currency_id
1296 for line in inv.invoice_line:
1297 mres = self.move_line_get_item(cr, uid, line, context)
1301 tax_code_found= False
1302 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1303 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1304 line.quantity, inv.address_invoice_id.id, line.product_id,
1307 if inv.type in ('out_invoice', 'in_invoice'):
1308 tax_code_id = tax['base_code_id']
1309 tax_amount = line.price_subtotal * tax['base_sign']
1311 tax_code_id = tax['ref_base_code_id']
1312 tax_amount = line.price_subtotal * tax['ref_base_sign']
1317 res.append(self.move_line_get_item(cr, uid, line, context))
1318 res[-1]['price'] = 0.0
1319 res[-1]['account_analytic_id'] = False
1320 elif not tax_code_id:
1322 tax_code_found = True
1324 res[-1]['tax_code_id'] = tax_code_id
1325 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1328 def move_line_get_item(self, cr, uid, line, context=None):
1331 'name': line.name[:64],
1332 'price_unit':line.price_unit,
1333 'quantity':line.quantity,
1334 'price':line.price_subtotal,
1335 'account_id':line.account_id.id,
1336 'product_id':line.product_id.id,
1337 'uos_id':line.uos_id.id,
1338 'account_analytic_id':line.account_analytic_id.id,
1339 'taxes':line.invoice_line_tax_id,
1342 # Set the tax field according to the account and the fiscal position
1344 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1347 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1348 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1349 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1350 r = {'value':{'invoice_line_tax_id': res}}
1352 account_invoice_line()
1354 class account_invoice_tax(osv.osv):
1355 _name = "account.invoice.tax"
1356 _description = "Invoice Tax"
1358 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1359 'name': fields.char('Tax Description', size=64, required=True),
1360 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1361 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1362 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1363 'manual': fields.boolean('Manual'),
1364 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1366 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1367 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1368 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1369 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1370 'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1373 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1374 cur_obj = self.pool.get('res.currency')
1375 company_obj = self.pool.get('res.company')
1376 company_currency=False
1378 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1379 if currency_id and company_currency:
1380 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1381 return {'value': {'base_amount':base}}
1383 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1384 cur_obj = self.pool.get('res.currency')
1385 company_obj = self.pool.get('res.company')
1386 company_currency=False
1388 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1389 if currency_id and company_currency:
1390 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1391 return {'value': {'tax_amount':amount}}
1395 'manual': lambda *a: 1,
1396 'base_amount': lambda *a: 0.0,
1397 'tax_amount': lambda *a: 0.0,
1399 def compute(self, cr, uid, invoice_id, context={}):
1401 tax_obj = self.pool.get('account.tax')
1402 cur_obj = self.pool.get('res.currency')
1403 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1404 cur = inv.currency_id
1405 company_currency = inv.company_id.currency_id.id
1407 for line in inv.invoice_line:
1408 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):
1410 val['invoice_id'] = inv.id
1411 val['name'] = tax['name']
1412 val['amount'] = tax['amount']
1413 val['manual'] = False
1414 val['sequence'] = tax['sequence']
1415 val['base'] = tax['price_unit'] * line['quantity']
1417 if inv.type in ('out_invoice','in_invoice'):
1418 val['base_code_id'] = tax['base_code_id']
1419 val['tax_code_id'] = tax['tax_code_id']
1420 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)
1421 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)
1422 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1424 val['base_code_id'] = tax['ref_base_code_id']
1425 val['tax_code_id'] = tax['ref_tax_code_id']
1426 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)
1427 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)
1428 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1430 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1431 if not key in tax_grouped:
1432 tax_grouped[key] = val
1434 tax_grouped[key]['amount'] += val['amount']
1435 tax_grouped[key]['base'] += val['base']
1436 tax_grouped[key]['base_amount'] += val['base_amount']
1437 tax_grouped[key]['tax_amount'] += val['tax_amount']
1439 for t in tax_grouped.values():
1440 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1441 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1442 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1445 def move_line_get(self, cr, uid, invoice_id):
1447 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1448 for t in cr.dictfetchall():
1449 if not t['amount'] \
1450 and not t['tax_code_id'] \
1451 and not t['tax_amount']:
1456 'price_unit': t['amount'],
1458 'price': t['amount'] or 0.0,
1459 'account_id': t['account_id'],
1460 'tax_code_id': t['tax_code_id'],
1461 'tax_amount': t['tax_amount']
1464 account_invoice_tax()
1466 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: