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 obj_journal = self.pool.get('account.journal')
449 if company_id and part_id and type:
451 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
452 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
453 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
454 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)])
455 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)])
457 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
459 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
460 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
461 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
462 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
463 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
464 if not rec_res_id and not rec_res_id:
465 raise osv.except_osv(_('Configration Error !'),
466 _('Can not find account chart for this company, Please Create account.'))
467 if type in ('out_invoice', 'out_refund'):
471 val= {'account_id': acc_id}
474 inv_obj = self.browse(cr,uid,ids)
475 for line in inv_obj[0].invoice_line:
477 if line.account_id.company_id.id != company_id:
478 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
480 raise osv.except_osv(_('Configration Error !'),
481 _('Can not find account chart for this company in invoice line account, Please Create account.'))
482 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
485 for inv_line in invoice_line:
486 obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
487 if obj_l.company_id.id != company_id:
488 raise osv.except_osv(_('Configration Error !'),
489 _('invoice line account company is not match with invoice company.'))
492 if company_id and type:
493 if type in ('out_invoice', 'out_refund'):
494 journal_type = 'sale'
496 journal_type = 'purchase'
497 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
499 val['journal_id'] = journal_ids[0]
501 raise osv.except_osv(_('Configration Error !'),
502 _('Can not find account journal for this company in invoice, Please Create journal.'))
503 dom = {'journal_id': [('id', 'in', journal_ids)]}
505 journal_ids = obj_journal.search(cr, uid, [])
507 if currency_id and company_id:
508 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
509 if currency.company_id.id != company_id:
510 val['currency_id'] = False
512 val['currency_id'] = currency.id
515 company = self.pool.get('res.company').browse(cr, uid, company_id)
516 if company.currency_id.company_id.id != company_id:
517 val['currency_id'] = False
519 val['currency_id'] = company.currency_id.id
521 return {'value' : val, 'domain': dom }
523 # go from canceled state to draft state
524 def action_cancel_draft(self, cr, uid, ids, *args):
525 self.write(cr, uid, ids, {'state':'draft'})
526 wf_service = netsvc.LocalService("workflow")
528 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
534 # return the ids of the move lines which has the same account than the invoice
536 def move_line_id_payment_get(self, cr, uid, ids, *args):
538 if not ids: return res
541 from account_move_line l \
542 left join account_invoice i on (i.move_id=l.move_id) \
543 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
544 res = map(lambda x: x[0], cr.fetchall())
547 def copy(self, cr, uid, id, default=None, context=None):
550 default = default.copy()
551 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
552 if 'date_invoice' not in default:
553 default['date_invoice'] = False
554 if 'date_due' not in default:
555 default['date_due'] = False
556 return super(account_invoice, self).copy(cr, uid, id, default, context)
558 def test_paid(self, cr, uid, ids, *args):
559 res = self.move_line_id_payment_get(cr, uid, ids)
564 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
565 ok = ok and bool(cr.fetchone()[0])
568 def button_reset_taxes(self, cr, uid, ids, context=None):
571 ait_obj = self.pool.get('account.invoice.tax')
573 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
574 partner = self.browse(cr, uid, id,context=context).partner_id
576 context.update({'lang': partner.lang})
577 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
578 ait_obj.create(cr, uid, taxe)
579 # Update the stored value (fields.function), so we write to trigger recompute
580 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
581 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
584 def button_compute(self, cr, uid, ids, context=None, set_total=False):
585 self.button_reset_taxes(cr, uid, ids, context)
586 for inv in self.browse(cr, uid, ids):
588 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
591 def _convert_ref(self, cr, uid, ref):
592 return (ref or '').replace('/','')
594 def _get_analytic_lines(self, cr, uid, id):
595 inv = self.browse(cr, uid, [id])[0]
596 cur_obj = self.pool.get('res.currency')
598 company_currency = inv.company_id.currency_id.id
599 if inv.type in ('out_invoice', 'in_refund'):
604 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
606 if il['account_analytic_id']:
607 if inv.type in ('in_invoice', 'in_refund'):
610 ref = self._convert_ref(cr, uid, inv.number)
611 il['analytic_lines'] = [(0,0, {
613 'date': inv['date_invoice'],
614 'account_id': il['account_analytic_id'],
615 'unit_amount': il['quantity'],
616 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
617 'product_id': il['product_id'],
618 'product_uom_id': il['uos_id'],
619 'general_account_id': il['account_id'],
620 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
625 def action_date_assign(self, cr, uid, ids, *args):
626 for inv in self.browse(cr, uid, ids):
627 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
628 if res and res['value']:
629 self.write(cr, uid, [inv.id], res['value'])
632 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
634 for tax in compute_taxes.values():
635 ait_obj.create(cr, uid, tax)
638 for tax in inv.tax_line:
641 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
643 if not key in compute_taxes:
644 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
645 base = compute_taxes[key]['base']
646 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
647 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
648 for key in compute_taxes:
649 if not key in tax_key:
650 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
652 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
655 cur_obj = self.pool.get('res.currency')
656 for i in invoice_move_lines:
657 if inv.currency_id.id != company_currency:
658 i['currency_id'] = inv.currency_id.id
659 i['amount_currency'] = i['price']
660 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
661 company_currency, i['price'],
662 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
664 i['amount_currency'] = False
665 i['currency_id'] = False
667 if inv.type in ('out_invoice','in_refund'):
669 total_currency += i['amount_currency'] or i['price']
670 i['price'] = - i['price']
673 total_currency -= i['amount_currency'] or i['price']
674 return total, total_currency, invoice_move_lines
676 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
677 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
678 will be grouped together if the journal has the 'group line' option. Of course a module
679 can add fields to invoice lines that would need to be tested too before merging lines
681 return "%s-%s-%s-%s-%s"%(
682 invoice_line['account_id'],
683 invoice_line.get('tax_code_id',"False"),
684 invoice_line.get('product_id',"False"),
685 invoice_line.get('analytic_account_id',"False"),
686 invoice_line.get('date_maturity',"False"))
688 def group_lines(self, cr, uid, iml, line, inv):
689 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
690 if inv.journal_id.group_invoice_lines:
693 tmp = self.inv_line_characteristic_hashcode(inv, l)
696 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
697 line2[tmp]['debit'] = (am > 0) and am or 0.0
698 line2[tmp]['credit'] = (am < 0) and -am or 0.0
699 line2[tmp]['tax_amount'] += l['tax_amount']
700 line2[tmp]['analytic_lines'] += l['analytic_lines']
704 for key, val in line2.items():
705 line.append((0,0,val))
709 def action_move_create(self, cr, uid, ids, *args):
710 """Creates invoice related analytics and financial move lines"""
711 ait_obj = self.pool.get('account.invoice.tax')
712 cur_obj = self.pool.get('res.currency')
714 for inv in self.browse(cr, uid, ids):
718 if not inv.date_invoice:
719 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
720 company_currency = inv.company_id.currency_id.id
721 # create the analytical lines
722 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
723 # one move line per invoice line
724 iml = self._get_analytic_lines(cr, uid, inv.id)
725 # check if taxes are all computed
727 context.update({'lang': inv.partner_id.lang})
728 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
729 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
731 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
732 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
734 # one move line per tax line
735 iml += ait_obj.move_line_get(cr, uid, inv.id)
737 if inv.type in ('in_invoice', 'in_refund'):
740 ref = self._convert_ref(cr, uid, inv.number)
742 diff_currency_p = inv.currency_id.id <> company_currency
743 # create one move line for the total and possibly adjust the other lines amount
746 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
747 acc_id = inv.account_id.id
749 name = inv['name'] or '/'
752 totlines = self.pool.get('account.payment.term').compute(cr,
753 uid, inv.payment_term.id, total, inv.date_invoice or False)
755 res_amount_currency = total_currency
758 if inv.currency_id.id != company_currency:
759 amount_currency = cur_obj.compute(cr, uid,
760 company_currency, inv.currency_id.id, t[1])
762 amount_currency = False
764 # last line add the diff
765 res_amount_currency -= amount_currency or 0
767 if i == len(totlines):
768 amount_currency += res_amount_currency
774 'account_id': acc_id,
775 'date_maturity': t[0],
776 'amount_currency': diff_currency_p \
777 and amount_currency or False,
778 'currency_id': diff_currency_p \
779 and inv.currency_id.id or False,
787 'account_id': acc_id,
788 'date_maturity' : inv.date_due or False,
789 'amount_currency': diff_currency_p \
790 and total_currency or False,
791 'currency_id': diff_currency_p \
792 and inv.currency_id.id or False,
796 date = inv.date_invoice or time.strftime('%Y-%m-%d')
797 part = inv.partner_id.id
799 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
801 line = self.group_lines(cr, uid, iml, line, inv)
803 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
804 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
805 if journal.centralisation:
806 raise osv.except_osv(_('UserError'),
807 _('Cannot create invoice move on centralised journal'))
808 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
809 period_id=inv.period_id and inv.period_id.id or False
811 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'))])
813 period_id=period_ids[0]
815 move['period_id'] = period_id
817 i[2]['period_id'] = period_id
819 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
820 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
821 # make the invoice point to that move
822 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
823 self.pool.get('account.move').post(cr, uid, [move_id])
824 self._log_event(cr, uid, ids)
827 def line_get_convert(self, cr, uid, x, part, date, context=None):
829 'date_maturity': x.get('date_maturity', False),
831 'name':x['name'][:64],
833 'debit':x['price']>0 and x['price'],
834 'credit':x['price']<0 and -x['price'],
835 'account_id':x['account_id'],
836 'analytic_lines':x.get('analytic_lines', []),
837 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
838 'currency_id':x.get('currency_id', False),
839 'tax_code_id': x.get('tax_code_id', False),
840 'tax_amount': x.get('tax_amount', False),
841 'ref':x.get('ref',False),
842 'quantity':x.get('quantity',1.00),
843 'product_id':x.get('product_id', False),
844 'product_uom_id':x.get('uos_id',False),
845 'analytic_account_id':x.get('account_analytic_id',False),
848 def action_number(self, cr, uid, ids, *args):
849 cr.execute('SELECT id, type, number, move_id, reference ' \
850 'FROM account_invoice ' \
851 'WHERE id IN ('+','.join(map(str, ids))+')')
852 obj_inv = self.browse(cr, uid, ids)[0]
853 for (id, invtype, number, move_id, reference) in cr.fetchall():
855 if obj_inv.journal_id.invoice_sequence_id:
856 sid = obj_inv.journal_id.invoice_sequence_id.id
857 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
859 number = self.pool.get('ir.sequence').get(cr, uid,
860 'account.invoice.' + invtype)
861 if invtype in ('in_invoice', 'in_refund'):
864 ref = self._convert_ref(cr, uid, number)
865 cr.execute('UPDATE account_invoice SET number=%s ' \
866 'WHERE id=%s', (number, id))
867 cr.execute('UPDATE account_move SET ref=%s ' \
868 'WHERE id=%s AND (ref is null OR ref = \'\')',
870 cr.execute('UPDATE account_move_line SET ref=%s ' \
871 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
873 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
874 'FROM account_move_line ' \
875 'WHERE account_move_line.move_id = %s ' \
876 'AND account_analytic_line.move_id = account_move_line.id',
880 def action_cancel(self, cr, uid, ids, *args):
881 account_move_obj = self.pool.get('account.move')
882 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
885 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
886 # delete the move this invoice was pointing to
887 # Note that the corresponding move_lines and move_reconciles
888 # will be automatically deleted too
889 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
891 self.pool.get('account.move.line').write(cr, uid, i['payment_ids'], {'reconcile_partial_id': False})
892 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
893 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
898 def list_distinct_taxes(self, cr, uid, ids):
899 invoices = self.browse(cr, uid, ids)
902 for tax in inv.tax_line:
903 if not tax['name'] in taxes:
904 taxes[tax['name']] = {'name': tax['name']}
905 return taxes.values()
907 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
908 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
910 part=inv['partner_id'] and inv['partner_id'][0]
912 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
913 total = inv['amount_untaxed']
914 if inv['type'] in ('in_invoice','in_refund'):
915 partnertype='supplier'
916 eventtype = 'purchase'
919 partnertype = 'customer'
922 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
923 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})
926 def name_get(self, cr, uid, ids, context=None):
930 'out_invoice': 'CI: ',
931 'in_invoice': 'SI: ',
932 'out_refund': 'OR: ',
935 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')]
937 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
944 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
946 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
947 return self.name_get(cr, user, ids, context)
949 def _refund_cleanup_lines(self, cr, uid, lines):
952 del line['invoice_id']
953 if 'account_id' in line:
954 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
955 if 'product_id' in line:
956 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
958 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
959 if 'invoice_line_tax_id' in line:
960 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
961 if 'account_analytic_id' in line:
962 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
963 if 'tax_code_id' in line :
964 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
965 line['tax_code_id'] = line['tax_code_id'][0]
966 if 'base_code_id' in line :
967 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
968 line['base_code_id'] = line['base_code_id'][0]
969 return map(lambda x: (0,0,x), lines)
971 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
972 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'])
975 for invoice in invoices:
979 'out_invoice': 'out_refund', # Customer Invoice
980 'in_invoice': 'in_refund', # Supplier Invoice
981 'out_refund': 'out_invoice', # Customer Refund
982 'in_refund': 'in_invoice', # Supplier Refund
986 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
987 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
989 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
990 tax_lines = filter(lambda l: l['manual'], tax_lines)
991 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
993 date = time.strftime('%Y-%m-%d')
995 'type': type_dict[invoice['type']],
996 'date_invoice': date,
999 'invoice_line': invoice_lines,
1000 'tax_line': tax_lines
1004 'period_id': period_id,
1008 'name': description,
1010 # take the id part of the tuple returned for many2one fields
1011 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1012 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1013 invoice[field] = invoice[field] and invoice[field][0]
1014 # create the new invoice
1015 new_ids.append(self.create(cr, uid, invoice))
1018 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=''):
1021 #TODO check if we can use different period for payment and the writeoff line
1022 assert len(ids)==1, "Can only pay one invoice at a time"
1023 invoice = self.browse(cr, uid, ids[0])
1024 src_account_id = invoice.account_id.id
1025 # Take the seq as name for move
1026 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1027 direction = types[invoice.type]
1028 #take the choosen date
1029 if 'date_p' in context and context['date_p']:
1030 date=context['date_p']
1032 date=time.strftime('%Y-%m-%d')
1034 # Take the amount in currency and the currency of the payment
1035 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1036 amount_currency = context['amount_currency']
1037 currency_id = context['currency_id']
1039 amount_currency = False
1041 if invoice.type in ('in_invoice', 'in_refund'):
1042 ref = invoice.reference
1044 ref = self._convert_ref(cr, uid, invoice.number)
1045 # Pay attention to the sign for both debit/credit AND amount_currency
1047 'debit': direction * pay_amount>0 and direction * pay_amount,
1048 'credit': direction * pay_amount<0 and - direction * pay_amount,
1049 'account_id': src_account_id,
1050 'partner_id': invoice.partner_id.id,
1053 'currency_id':currency_id,
1054 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1055 'company_id': invoice.company_id.id,
1058 'debit': direction * pay_amount<0 and - direction * pay_amount,
1059 'credit': direction * pay_amount>0 and direction * pay_amount,
1060 'account_id': pay_account_id,
1061 'partner_id': invoice.partner_id.id,
1064 'currency_id':currency_id,
1065 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1066 'company_id': invoice.company_id.id,
1070 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1074 lines = [(0, 0, l1), (0, 0, l2)]
1075 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1076 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1080 line = self.pool.get('account.move.line')
1081 move_ids = [move_id,]
1083 move_ids.append(invoice.move_id.id)
1084 cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1085 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1086 for l in lines+invoice.payment_ids:
1087 if l.account_id.id==src_account_id:
1088 line_ids.append(l.id)
1089 total += (l.debit or 0.0) - (l.credit or 0.0)
1090 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1091 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1093 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1095 # Update the stored value (fields.function), so we write to trigger recompute
1096 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1100 class account_invoice_line(osv.osv):
1101 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1103 cur_obj=self.pool.get('res.currency')
1104 for line in self.browse(cr, uid, ids):
1106 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1107 cur = line.invoice_id.currency_id
1108 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1110 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'))
1114 def _price_unit_default(self, cr, uid, context=None):
1117 if 'check_total' in context:
1118 t = context['check_total']
1119 for l in context.get('invoice_line', {}):
1120 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1121 tax_obj = self.pool.get('account.tax')
1122 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1123 t = t - (p * l[2].get('quantity'))
1124 taxes = l[2].get('invoice_line_tax_id')
1125 if len(taxes[0]) >= 3 and taxes[0][2]:
1126 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1127 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)):
1128 t = t - tax['amount']
1132 _name = "account.invoice.line"
1133 _description = "Invoice line"
1135 'name': fields.char('Description', size=256, required=True),
1136 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1137 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1138 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1139 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1140 '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."),
1141 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1142 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits_compute= dp.get_precision('Account')),
1143 'quantity': fields.float('Quantity', required=True),
1144 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1145 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1146 'note': fields.text('Notes', translate=True),
1147 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1148 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1149 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1152 'quantity': lambda *a: 1,
1153 'discount': lambda *a: 0.0,
1154 'price_unit': _price_unit_default,
1157 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1158 tax_obj = self.pool.get('account.tax')
1160 taxes = tax_obj.browse(cr, uid, tax_id)
1161 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1162 price_unit = price_unit - tax['amount']
1163 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1165 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):
1168 company_id = context.get('company_id',False)
1170 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1172 if type in ('in_invoice', 'in_refund'):
1173 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1175 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1176 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1177 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1180 context.update({'lang': lang})
1182 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1185 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)])
1187 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)])
1188 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)])
1190 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)])
1193 in_acc = res.product_tmpl_id.property_account_income
1194 in_acc_cate = res.categ_id.property_account_income_categ
1198 app_acc_in = in_acc_cate
1200 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1202 ex_acc = res.product_tmpl_id.property_account_expense
1203 ex_acc_cate = res.categ_id.property_account_expense_categ
1205 app_acc_exp = ex_acc
1207 app_acc_exp = ex_acc_cate
1209 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1210 if not in_pro_id and not exp_pro_id:
1211 in_acc = res.product_tmpl_id.property_account_income
1212 in_acc_cate = res.categ_id.property_account_income_categ
1213 ex_acc = res.product_tmpl_id.property_account_expense
1214 ex_acc_cate = res.categ_id.property_account_expense_categ
1215 if in_acc or ex_acc:
1217 app_acc_exp = ex_acc
1219 app_acc_in = in_acc_cate
1220 app_acc_exp = ex_acc_cate
1222 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1223 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1224 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1225 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1226 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1227 if not in_res_id and not exp_res_id:
1228 raise osv.except_osv(_('Configration Error !'),
1229 _('Can not find account chart for this company, Please Create account.'))
1230 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1231 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1232 if in_acc or ex_acc:
1233 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1234 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1236 res.categ_id.property_account_income_categ = in_obj_acc[0]
1237 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1239 if type in ('out_invoice','out_refund'):
1240 a = res.product_tmpl_id.property_account_income.id
1242 a = res.categ_id.property_account_income_categ.id
1244 a = res.product_tmpl_id.property_account_expense.id
1246 a = res.categ_id.property_account_expense_categ.id
1248 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1250 result['account_id'] = a
1253 tax_obj = self.pool.get('account.tax')
1254 if type in ('out_invoice', 'out_refund'):
1255 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1256 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1258 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)
1259 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1260 if type in ('in_invoice', 'in_refund'):
1261 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)
1262 result.update(to_update)
1264 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1267 result['name'] = res.partner_ref
1270 result['uos_id'] = uom or res.uom_id.id or False
1271 if result['uos_id']:
1272 res2 = res.uom_id.category_id.id
1274 domain = {'uos_id':[('category_id','=',res2 )]}
1276 prod_pool=self.pool.get('product.product')
1277 result['categ_id'] = res.categ_id.id
1278 res_final = {'value':result, 'domain':domain}
1280 if not company_id and not currency_id:
1283 company = self.pool.get('res.company').browse(cr, uid, company_id)
1284 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1286 if not currency.company_id.id == company.id:
1287 raise osv.except_osv(_('Configration Error !'),
1288 _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1290 if company.currency_id.id != currency.id:
1291 new_price = res_final['value']['price_unit'] * currency.rate
1292 res_final['value']['price_unit'] = new_price
1295 def move_line_get(self, cr, uid, invoice_id, context=None):
1298 tax_obj = self.pool.get('account.tax')
1299 cur_obj = self.pool.get('res.currency')
1300 ait_obj = self.pool.get('account.invoice.tax')
1301 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1302 company_currency = inv.company_id.currency_id.id
1303 cur = inv.currency_id
1305 for line in inv.invoice_line:
1306 mres = self.move_line_get_item(cr, uid, line, context)
1310 tax_code_found= False
1311 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1312 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1313 line.quantity, inv.address_invoice_id.id, line.product_id,
1316 if inv.type in ('out_invoice', 'in_invoice'):
1317 tax_code_id = tax['base_code_id']
1318 tax_amount = line.price_subtotal * tax['base_sign']
1320 tax_code_id = tax['ref_base_code_id']
1321 tax_amount = line.price_subtotal * tax['ref_base_sign']
1326 res.append(self.move_line_get_item(cr, uid, line, context))
1327 res[-1]['price'] = 0.0
1328 res[-1]['account_analytic_id'] = False
1329 elif not tax_code_id:
1331 tax_code_found = True
1333 res[-1]['tax_code_id'] = tax_code_id
1334 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1337 def move_line_get_item(self, cr, uid, line, context=None):
1340 'name': line.name[:64],
1341 'price_unit':line.price_unit,
1342 'quantity':line.quantity,
1343 'price':line.price_subtotal,
1344 'account_id':line.account_id.id,
1345 'product_id':line.product_id.id,
1346 'uos_id':line.uos_id.id,
1347 'account_analytic_id':line.account_analytic_id.id,
1348 'taxes':line.invoice_line_tax_id,
1351 # Set the tax field according to the account and the fiscal position
1353 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1356 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1357 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1358 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1359 r = {'value':{'invoice_line_tax_id': res}}
1361 account_invoice_line()
1363 class account_invoice_tax(osv.osv):
1364 _name = "account.invoice.tax"
1365 _description = "Invoice Tax"
1367 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1368 'name': fields.char('Tax Description', size=64, required=True),
1369 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1370 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1371 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1372 'manual': fields.boolean('Manual'),
1373 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1375 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1376 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1377 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1378 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1379 'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1382 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1383 cur_obj = self.pool.get('res.currency')
1384 company_obj = self.pool.get('res.company')
1385 company_currency=False
1387 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1388 if currency_id and company_currency:
1389 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1390 return {'value': {'base_amount':base}}
1392 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1393 cur_obj = self.pool.get('res.currency')
1394 company_obj = self.pool.get('res.company')
1395 company_currency=False
1397 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1398 if currency_id and company_currency:
1399 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1400 return {'value': {'tax_amount':amount}}
1404 'manual': lambda *a: 1,
1405 'base_amount': lambda *a: 0.0,
1406 'tax_amount': lambda *a: 0.0,
1408 def compute(self, cr, uid, invoice_id, context={}):
1410 tax_obj = self.pool.get('account.tax')
1411 cur_obj = self.pool.get('res.currency')
1412 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1413 cur = inv.currency_id
1414 company_currency = inv.company_id.currency_id.id
1416 for line in inv.invoice_line:
1417 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):
1419 val['invoice_id'] = inv.id
1420 val['name'] = tax['name']
1421 val['amount'] = tax['amount']
1422 val['manual'] = False
1423 val['sequence'] = tax['sequence']
1424 val['base'] = tax['price_unit'] * line['quantity']
1426 if inv.type in ('out_invoice','in_invoice'):
1427 val['base_code_id'] = tax['base_code_id']
1428 val['tax_code_id'] = tax['tax_code_id']
1429 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)
1430 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)
1431 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1433 val['base_code_id'] = tax['ref_base_code_id']
1434 val['tax_code_id'] = tax['ref_tax_code_id']
1435 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)
1436 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)
1437 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1439 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1440 if not key in tax_grouped:
1441 tax_grouped[key] = val
1443 tax_grouped[key]['amount'] += val['amount']
1444 tax_grouped[key]['base'] += val['base']
1445 tax_grouped[key]['base_amount'] += val['base_amount']
1446 tax_grouped[key]['tax_amount'] += val['tax_amount']
1448 for t in tax_grouped.values():
1449 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1450 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1451 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1454 def move_line_get(self, cr, uid, invoice_id):
1456 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1457 for t in cr.dictfetchall():
1458 if not t['amount'] \
1459 and not t['tax_code_id'] \
1460 and not t['tax_amount']:
1465 'price_unit': t['amount'],
1467 'price': t['amount'] or 0.0,
1468 'account_id': t['account_id'],
1469 'tax_code_id': t['tax_code_id'],
1470 'tax_amount': t['tax_amount']
1473 account_invoice_tax()
1475 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: