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 from operator import itemgetter
24 import decimal_precision as dp
27 from osv import fields, osv, orm
29 from tools import config
30 from tools.translate import _
32 class account_invoice(osv.osv):
33 def _amount_all(self, cr, uid, ids, name, args, context=None):
35 for invoice in self.browse(cr, uid, ids, context=context):
37 'amount_untaxed': 0.0,
41 for line in invoice.invoice_line:
42 res[invoice.id]['amount_untaxed'] += line.price_subtotal
43 for line in invoice.tax_line:
44 res[invoice.id]['amount_tax'] += line.amount
45 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
48 def _get_journal(self, cr, uid, context=None):
51 type_inv = context.get('type', 'out_invoice')
52 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
53 company_id = context.get('company_id', user.company_id.id)
54 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
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),
59 ('refund_journal', '=', refund_journal.get(type_inv, False))],
66 def _get_currency(self, cr, uid, context=None):
67 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
69 return user.company_id.currency_id.id
71 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
73 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
74 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
75 tt = type2journal.get(type_inv, 'sale')
76 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
78 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
81 def _get_type(self, cr, uid, context=None):
84 return context.get('type', 'out_invoice')
86 def _reconciled(self, cr, uid, ids, name, args, context=None):
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 cur_obj = self.pool.get('res.currency')
98 data_inv = self.browse(cr, uid, ids)
104 context.update({'date':inv.date_invoice})
105 context_unreconciled=context.copy()
106 for lines in inv.move_lines:
107 debit_tmp = lines.debit
108 credit_tmp = lines.credit
109 # If currency conversion needed
110 if inv.company_id.currency_id.id <> inv.currency_id.id:
111 # If invoice paid, compute currency amount according to invoice date
112 # otherwise, take the line date
113 if not inv.reconciled:
114 context.update({'date':lines.date})
115 context_unreconciled.update({'date':lines.date})
116 # If amount currency setted, compute for debit and credit in company currency
117 if lines.amount_currency < 0:
118 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))
119 elif lines.amount_currency > 0:
120 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))
121 # Then, recomput into invoice currency to avoid rounding trouble !
122 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False, context=context)
123 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False, context=context)
128 if not inv.amount_total:
130 elif inv.type in ('out_invoice','in_refund'):
131 amount = credit-debit
132 result = inv.amount_total - amount
134 amount = debit-credit
135 result = inv.amount_total - amount
136 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
137 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id, result) and result or 0.0
141 def _get_lines(self, cr, uid, ids, name, arg, context=None):
144 move_lines = self.move_line_id_payment_get(cr, uid, [id])
149 data_lines = self.pool.get('account.move.line').browse(cr, uid, move_lines)
150 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
151 for line in data_lines:
153 if line.reconcile_id:
154 ids_line = line.reconcile_id.line_id
155 elif line.reconcile_partial_id:
156 ids_line = line.reconcile_partial_id.line_partial_ids
157 l = map(lambda x: x.id, ids_line)
158 partial_ids.append(line.id)
159 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
162 def _get_invoice_line(self, cr, uid, ids, context=None):
164 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
165 result[line.invoice_id.id] = True
168 def _get_invoice_tax(self, cr, uid, ids, context=None):
170 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
171 result[tax.invoice_id.id] = True
174 def _compute_lines(self, cr, uid, ids, name, args, context=None):
176 for invoice in self.browse(cr, uid, ids, context):
177 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
180 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
181 temp_lines = []#Added temp list to avoid duplicate records
183 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
184 elif m.reconcile_partial_id:
185 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
186 lines += [x for x in temp_lines if x not in lines]
189 lines = filter(lambda x: x not in src, lines)
190 result[invoice.id] = lines
193 def _get_invoice_from_line(self, cr, uid, ids, context=None):
195 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
196 if line.reconcile_partial_id:
197 for line2 in line.reconcile_partial_id.line_partial_ids:
198 move[line2.move_id.id] = True
199 if line.reconcile_id:
200 for line2 in line.reconcile_id.line_id:
201 move[line2.move_id.id] = True
204 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
207 def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
209 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
210 for line in r.line_partial_ids:
211 move[line.move_id.id] = True
212 for line in r.line_id:
213 move[line.move_id.id] = True
217 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
220 _name = "account.invoice"
221 _description = 'Invoice'
225 'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
226 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
227 'type': fields.selection([
228 ('out_invoice','Customer Invoice'),
229 ('in_invoice','Supplier Invoice'),
230 ('out_refund','Customer Refund'),
231 ('in_refund','Supplier Refund'),
232 ],'Type', readonly=True, select=True, change_default=True),
234 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
235 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
236 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
237 required=True, readonly=True, states={'draft':[('readonly',False)]}),
238 'comment': fields.text('Additional Information', translate=True),
240 'state': fields.selection([
242 ('proforma','Pro-forma'),
243 ('proforma2','Pro-forma'),
246 ('cancel','Cancelled')
247 ],'State', select=True, readonly=True,
248 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
249 \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
250 \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. \
251 \n* The \'Done\' state is set automatically when invoice is paid.\
252 \n* The \'Cancelled\' state is used when user cancel invoice.'),
253 'date_invoice': fields.date('Date Invoiced', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, help="Keep empty to use the current date"),
254 'date_due': fields.date('Due Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]},
255 help="If you use payment terms, the due date will be computed automatically at the generation "\
256 "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."),
257 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
258 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
259 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
260 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
261 help="If you use payment terms, the due date will be computed automatically at the generation "\
262 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
263 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
264 '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)]}),
266 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
267 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
268 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
270 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Links to the automatically generated Ledger Postings."),
271 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Untaxed',
273 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
274 'account.invoice.tax': (_get_invoice_tax, None, 20),
275 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
278 'amount_tax': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Tax',
280 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
281 'account.invoice.tax': (_get_invoice_tax, None, 20),
282 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
285 'amount_total': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Total',
287 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
288 'account.invoice.tax': (_get_invoice_tax, None, 20),
289 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
292 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
293 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
295 'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
296 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
298 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
299 'account.move.line': (_get_invoice_from_line, None, 50),
300 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
301 }, help="The Ledger Postings of the invoice have been reconciled with Ledger Postings of the payment(s)."),
302 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
303 help='The bank account to pay to or to be paid from', readonly=True, states={'draft':[('readonly',False)]}),
304 'move_lines':fields.function(_get_lines , method=True, type='many2many', relation='account.move.line', string='Entry Lines'),
305 'residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual',
307 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
308 'account.invoice.tax': (_get_invoice_tax, None, 50),
309 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
310 'account.move.line': (_get_invoice_from_line, None, 50),
311 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
313 help="Remaining amount due."),
314 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
315 'move_name': fields.char('Ledger Posting', size=64, readonly=True, states={'draft':[('readonly',False)]}),
316 'user_id': fields.many2one('res.users', 'Salesman', readonly=True, states={'draft':[('readonly',False)]}),
317 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
321 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
323 'journal_id': _get_journal,
324 'currency_id': _get_currency,
325 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
326 'reference_type': 'none',
328 'user_id': lambda s, cr, u, c: u,
331 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
332 if context.get('active_model','') in ['res.partner']:
333 partner = self.pool.get(context['active_model']).read(cr,uid,context['active_ids'],['supplier','customer'])[0]
335 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.tree')])[0]
337 if view_type == 'form':
338 if partner['supplier'] and not partner['customer']:
339 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.supplier.form')])[0]
341 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.form')])[0]
342 return super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
344 def create(self, cr, uid, vals, context=None):
346 res = super(account_invoice, self).create(cr, uid, vals, context)
349 if '"journal_id" viol' in e.args[0]:
350 raise orm.except_orm(_('Configuration Error!'),
351 _('There is no Accounting Journal of type Sale/Purchase defined!'))
353 raise orm.except_orm(_('UnknownError'), str(e))
355 def confirm_paid(self, cr, uid, ids, context=None):
356 self.write(cr, uid, ids, {'state':'paid'}, context=context)
357 for (id, name) in self.name_get(cr, uid, ids):
358 message = _('Document ') + " '" + name + "' "+ _("has been paid.")
359 self.log(cr, uid, id, message)
362 def unlink(self, cr, uid, ids, context=None):
363 invoices = self.read(cr, uid, ids, ['state'])
366 if t['state'] in ('draft', 'cancel'):
367 unlink_ids.append(t['id'])
369 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
370 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
373 # def get_invoice_address(self, cr, uid, ids):
374 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
376 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
377 date_invoice=False, payment_term=False, partner_bank=False, company_id=False):
378 invoice_addr_id = False
379 contact_addr_id = False
380 partner_payment_term = False
383 fiscal_position = False
385 opt = [('uid', str(uid))]
388 opt.insert(0, ('id', partner_id))
389 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
390 contact_addr_id = res['contact']
391 invoice_addr_id = res['invoice']
392 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
394 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
395 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)])
396 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)])
398 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
400 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
401 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
402 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
403 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
404 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
405 if not rec_res_id and not pay_res_id:
406 raise osv.except_osv(_('Configration Error !'),
407 _('Can not find account chart for this company, Please Create account.'))
408 rec_obj_acc=self.pool.get('account.account').browse(cr, uid, [rec_res_id])
409 pay_obj_acc=self.pool.get('account.account').browse(cr, uid, [pay_res_id])
410 p.property_account_receivable = rec_obj_acc[0]
411 p.property_account_payable = pay_obj_acc[0]
413 if type in ('out_invoice', 'out_refund'):
414 acc_id = p.property_account_receivable.id
416 acc_id = p.property_account_payable.id
417 fiscal_position = p.property_account_position and p.property_account_position.id or False
418 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
420 bank_id = p.bank_ids[0].id
423 'address_contact_id': contact_addr_id,
424 'address_invoice_id': invoice_addr_id,
425 'account_id': acc_id,
426 'payment_term': partner_payment_term,
427 'fiscal_position': fiscal_position
431 if type in ('in_invoice', 'in_refund'):
432 result['value']['partner_bank'] = bank_id
434 if payment_term != partner_payment_term:
435 if partner_payment_term:
436 to_update = self.onchange_payment_term_date_invoice(
437 cr, uid, ids, partner_payment_term, date_invoice)
438 result['value'].update(to_update['value'])
440 result['value']['date_due'] = False
442 if partner_bank != bank_id:
443 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
444 result['value'].update(to_update['value'])
447 def onchange_currency_id(self, cr, uid, ids, curr_id, company_id):
448 if curr_id and company_id:
449 currency = self.pool.get('res.currency').browse(cr, uid, curr_id)
450 if currency.company_id.id != company_id:
451 raise osv.except_osv(_('Configration Error !'),
452 _('Can not select currency that is not related to current company.\nPlease select accordingly !.'))
455 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
456 if not payment_term_id:
459 pt_obj= self.pool.get('account.payment.term')
460 if not date_invoice :
461 date_invoice = time.strftime('%Y-%m-%d')
463 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
466 pterm_list = [line[0] for line in pterm_list]
468 res= {'value':{'date_due': pterm_list[-1]}}
470 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
474 def onchange_invoice_line(self, cr, uid, ids, lines):
477 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
480 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
483 obj_journal = self.pool.get('account.journal')
484 if company_id and part_id and type:
486 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
487 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
488 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
489 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)])
490 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)])
492 rec_pro_id = self.pool.get('ir.property').search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
494 pay_pro_id = self.pool.get('ir.property').search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
495 rec_line_data = self.pool.get('ir.property').read(cr, uid, rec_pro_id, ['name','value','res_id'])
496 pay_line_data = self.pool.get('ir.property').read(cr, uid, pay_pro_id, ['name','value','res_id'])
497 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
498 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
499 if not rec_res_id and not rec_res_id:
500 raise osv.except_osv(_('Configration Error !'),
501 _('Can not find account chart for this company, Please Create account.'))
502 if type in ('out_invoice', 'out_refund'):
506 val= {'account_id': acc_id}
509 inv_obj = self.browse(cr,uid,ids)
510 for line in inv_obj[0].invoice_line:
512 if line.account_id.company_id.id != company_id:
513 result_id = self.pool.get('account.account').search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
515 raise osv.except_osv(_('Configration Error !'),
516 _('Can not find account chart for this company in invoice line account, Please Create account.'))
517 r_id = self.pool.get('account.invoice.line').write(cr, uid, [line.id], {'account_id': result_id[0]})
520 for inv_line in invoice_line:
521 obj_l = self.pool.get('account.account').browse(cr, uid, inv_line[2]['account_id'])
522 if obj_l.company_id.id != company_id:
523 raise osv.except_osv(_('Configration Error !'),
524 _('invoice line account company is not match with invoice company.'))
527 if company_id and type:
528 if type in ('out_invoice', 'out_refund'):
529 journal_type = 'sale'
531 journal_type = 'purchase'
532 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
534 val['journal_id'] = journal_ids[0]
536 raise osv.except_osv(_('Configration Error !'),
537 _('Can not find account journal for this company in invoice, Please Create journal.'))
538 dom = {'journal_id': [('id', 'in', journal_ids)]}
540 journal_ids = obj_journal.search(cr, uid, [])
542 if currency_id and company_id:
543 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
544 if currency.company_id.id != company_id:
545 val['currency_id'] = False
547 val['currency_id'] = currency.id
550 company = self.pool.get('res.company').browse(cr, uid, company_id)
551 if company.currency_id.company_id.id != company_id:
552 val['currency_id'] = False
554 val['currency_id'] = company.currency_id.id
556 return {'value' : val, 'domain': dom }
558 # go from canceled state to draft state
559 def action_cancel_draft(self, cr, uid, ids, *args):
560 self.write(cr, uid, ids, {'state':'draft'})
561 wf_service = netsvc.LocalService("workflow")
563 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
566 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
567 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
568 Hook method to be overridden in additional modules to verify and possibly alter the
569 move lines to be created by an invoice, for special cases.
570 :param invoice_browse: browsable record of the invoice that is generating the move lines
571 :param move_lines: list of dictionaries with the account.move.lines (as for create())
572 :return: the (possibly updated) final move_lines to create for this invoice
579 # return the ids of the move lines which has the same account than the invoice
581 def move_line_id_payment_get(self, cr, uid, ids, *args):
583 if not ids: return res
584 cr.execute('SELECT l.id '\
585 'FROM account_move_line l '\
586 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
588 'AND l.account_id=i.account_id',
590 res = map(itemgetter(0), cr.fetchall())
593 def copy(self, cr, uid, id, default=None, context=None):
596 default = default.copy()
597 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
598 if 'date_invoice' not in default:
599 default['date_invoice'] = False
600 if 'date_due' not in default:
601 default['date_due'] = False
602 return super(account_invoice, self).copy(cr, uid, id, default, context)
604 def test_paid(self, cr, uid, ids, *args):
605 res = self.move_line_id_payment_get(cr, uid, ids)
610 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
611 ok = ok and bool(cr.fetchone()[0])
614 def button_reset_taxes(self, cr, uid, ids, context=None):
617 ait_obj = self.pool.get('account.invoice.tax')
619 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
620 partner = self.browse(cr, uid, id, context=context).partner_id
622 context.update({'lang': partner.lang})
623 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
624 ait_obj.create(cr, uid, taxe)
625 # Update the stored value (fields.function), so we write to trigger recompute
626 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
627 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
630 def button_compute(self, cr, uid, ids, context=None, set_total=False):
631 self.button_reset_taxes(cr, uid, ids, context)
632 for inv in self.browse(cr, uid, ids):
634 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
637 def _convert_ref(self, cr, uid, ref):
638 return (ref or '').replace('/','')
640 def _get_analytic_lines(self, cr, uid, id):
641 inv = self.browse(cr, uid, [id])[0]
642 cur_obj = self.pool.get('res.currency')
644 company_currency = inv.company_id.currency_id.id
645 if inv.type in ('out_invoice', 'in_refund'):
650 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
652 if il['account_analytic_id']:
653 if inv.type in ('in_invoice', 'in_refund'):
656 ref = self._convert_ref(cr, uid, inv.number)
657 il['analytic_lines'] = [(0,0, {
659 'date': inv['date_invoice'],
660 'account_id': il['account_analytic_id'],
661 'unit_amount': il['quantity'],
662 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
663 'product_id': il['product_id'],
664 'product_uom_id': il['uos_id'],
665 'general_account_id': il['account_id'],
666 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
671 def action_date_assign(self, cr, uid, ids, *args):
672 for inv in self.browse(cr, uid, ids):
673 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
674 if res and res['value']:
675 self.write(cr, uid, [inv.id], res['value'])
678 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
679 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
680 Hook method to be overridden in additional modules to verify and possibly alter the
681 move lines to be created by an invoice, for special cases.
682 :param invoice_browse: browsable record of the invoice that is generating the move lines
683 :param move_lines: list of dictionaries with the account.move.lines (as for create())
684 :return: the (possibly updated) final move_lines to create for this invoice
688 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
690 for tax in compute_taxes.values():
691 ait_obj.create(cr, uid, tax)
694 for tax in inv.tax_line:
697 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
699 if not key in compute_taxes:
700 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
701 base = compute_taxes[key]['base']
702 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
703 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
704 for key in compute_taxes:
705 if not key in tax_key:
706 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
708 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
711 cur_obj = self.pool.get('res.currency')
712 for i in invoice_move_lines:
713 if inv.currency_id.id != company_currency:
714 i['currency_id'] = inv.currency_id.id
715 i['amount_currency'] = i['price']
716 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
717 company_currency, i['price'],
718 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
720 i['amount_currency'] = False
721 i['currency_id'] = False
723 if inv.type in ('out_invoice','in_refund'):
725 total_currency += i['amount_currency'] or i['price']
726 i['price'] = - i['price']
729 total_currency -= i['amount_currency'] or i['price']
730 return total, total_currency, invoice_move_lines
732 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
733 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
734 will be grouped together if the journal has the 'group line' option. Of course a module
735 can add fields to invoice lines that would need to be tested too before merging lines
737 return "%s-%s-%s-%s-%s"%(
738 invoice_line['account_id'],
739 invoice_line.get('tax_code_id',"False"),
740 invoice_line.get('product_id',"False"),
741 invoice_line.get('analytic_account_id',"False"),
742 invoice_line.get('date_maturity',"False"))
744 def group_lines(self, cr, uid, iml, line, inv):
745 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
746 if inv.journal_id.group_invoice_lines:
749 tmp = self.inv_line_characteristic_hashcode(inv, l)
752 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
753 line2[tmp]['debit'] = (am > 0) and am or 0.0
754 line2[tmp]['credit'] = (am < 0) and -am or 0.0
755 line2[tmp]['tax_amount'] += l['tax_amount']
756 line2[tmp]['analytic_lines'] += l['analytic_lines']
760 for key, val in line2.items():
761 line.append((0,0,val))
765 def action_move_create(self, cr, uid, ids, *args):
766 """Creates invoice related analytics and financial move lines"""
767 ait_obj = self.pool.get('account.invoice.tax')
768 cur_obj = self.pool.get('res.currency')
770 for inv in self.browse(cr, uid, ids):
771 if not inv.journal_id.invoice_sequence_id:
772 raise osv.except_osv(_('Error !'), _('Please define invoice sequence on invoice journal'))
773 if not inv.invoice_line:
774 raise osv.except_osv(_('No Invoice Lines !'), _('Please create some invoice lines.'))
778 if not inv.date_invoice:
779 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
780 company_currency = inv.company_id.currency_id.id
781 # create the analytical lines
782 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
783 # one move line per invoice line
784 iml = self._get_analytic_lines(cr, uid, inv.id)
785 # check if taxes are all computed
787 context.update({'lang': inv.partner_id.lang})
788 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
789 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
791 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
792 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
794 # one move line per tax line
795 iml += ait_obj.move_line_get(cr, uid, inv.id)
798 if inv.type in ('in_invoice', 'in_refund'):
800 entry_type = 'journal_pur_voucher'
801 if inv.type == 'in_refund':
802 entry_type = 'cont_voucher'
804 ref = self._convert_ref(cr, uid, inv.number)
805 entry_type = 'journal_sale_vou'
806 if inv.type == 'out_refund':
807 entry_type = 'cont_voucher'
809 diff_currency_p = inv.currency_id.id <> company_currency
810 # create one move line for the total and possibly adjust the other lines amount
813 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
814 acc_id = inv.account_id.id
816 name = inv['name'] or '/'
819 totlines = self.pool.get('account.payment.term').compute(cr,
820 uid, inv.payment_term.id, total, inv.date_invoice or False)
822 res_amount_currency = total_currency
825 if inv.currency_id.id != company_currency:
826 amount_currency = cur_obj.compute(cr, uid,
827 company_currency, inv.currency_id.id, t[1])
829 amount_currency = False
831 # last line add the diff
832 res_amount_currency -= amount_currency or 0
834 if i == len(totlines):
835 amount_currency += res_amount_currency
841 'account_id': acc_id,
842 'date_maturity': t[0],
843 'amount_currency': diff_currency_p \
844 and amount_currency or False,
845 'currency_id': diff_currency_p \
846 and inv.currency_id.id or False,
854 'account_id': acc_id,
855 'date_maturity' : inv.date_due or False,
856 'amount_currency': diff_currency_p \
857 and total_currency or False,
858 'currency_id': diff_currency_p \
859 and inv.currency_id.id or False,
863 date = inv.date_invoice or time.strftime('%Y-%m-%d')
864 part = inv.partner_id.id
866 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
868 line = self.group_lines(cr, uid, iml, line, inv)
870 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
871 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
872 if journal.centralisation:
873 raise osv.except_osv(_('UserError'),
874 _('Cannot create invoice move on centralised journal'))
876 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
878 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date, 'type': entry_type}
879 period_id=inv.period_id and inv.period_id.id or False
881 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'))])
883 period_id=period_ids[0]
885 move['period_id'] = period_id
887 i[2]['period_id'] = period_id
889 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
890 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
891 # make the invoice point to that move
892 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
893 self.pool.get('account.move').post(cr, uid, [move_id])
894 self._log_event(cr, uid, ids)
897 def line_get_convert(self, cr, uid, x, part, date, context=None):
899 'date_maturity': x.get('date_maturity', False),
901 'name':x['name'][:64],
903 'debit':x['price']>0 and x['price'],
904 'credit':x['price']<0 and -x['price'],
905 'account_id':x['account_id'],
906 'analytic_lines':x.get('analytic_lines', []),
907 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
908 'currency_id':x.get('currency_id', False),
909 'tax_code_id': x.get('tax_code_id', False),
910 'tax_amount': x.get('tax_amount', False),
911 'ref':x.get('ref',False),
912 'quantity':x.get('quantity',1.00),
913 'product_id':x.get('product_id', False),
914 'product_uom_id':x.get('uos_id',False),
915 'analytic_account_id':x.get('account_analytic_id',False),
918 def action_number(self, cr, uid, ids, *args):
919 cr.execute('SELECT id, type, number, move_id, reference ' \
920 'FROM account_invoice ' \
923 obj_inv = self.browse(cr, uid, ids)[0]
924 for (id, invtype, number, move_id, reference) in cr.fetchall():
926 if obj_inv.journal_id.invoice_sequence_id:
927 sid = obj_inv.journal_id.invoice_sequence_id.id
928 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
930 number = self.pool.get('ir.sequence').get(cr, uid,
931 'account.invoice.' + invtype)
932 if invtype in ('in_invoice', 'in_refund'):
935 ref = self._convert_ref(cr, uid, number)
936 cr.execute('UPDATE account_invoice SET number=%s ' \
937 'WHERE id=%s', (number, id))
938 cr.execute('UPDATE account_move SET ref=%s ' \
939 'WHERE id=%s AND (ref is null OR ref = \'\')',
941 cr.execute('UPDATE account_move_line SET ref=%s ' \
942 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
944 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
945 'FROM account_move_line ' \
946 'WHERE account_move_line.move_id = %s ' \
947 'AND account_analytic_line.move_id = account_move_line.id',
951 def action_cancel(self, cr, uid, ids, *args):
952 account_move_obj = self.pool.get('account.move')
953 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
956 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
957 # delete the move this invoice was pointing to
958 # Note that the corresponding move_lines and move_reconciles
959 # will be automatically deleted too
960 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
962 account_move_line_obj = self.pool.get('account.move.line')
963 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
964 for move_line in pay_ids:
965 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
966 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
968 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
969 self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
974 def list_distinct_taxes(self, cr, uid, ids):
975 invoices = self.browse(cr, uid, ids)
978 for tax in inv.tax_line:
979 if not tax['name'] in taxes:
980 taxes[tax['name']] = {'name': tax['name']}
981 return taxes.values()
983 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
984 #TODO: implement messages system
987 def name_get(self, cr, uid, ids, context=None):
991 'out_invoice': 'CI: ',
992 'in_invoice': 'SI: ',
993 'out_refund': 'OR: ',
996 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')]
998 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1005 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
1007 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
1008 return self.name_get(cr, user, ids, context)
1010 def _refund_cleanup_lines(self, cr, uid, lines):
1013 del line['invoice_id']
1014 if 'account_id' in line:
1015 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
1016 if 'product_id' in line:
1017 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
1018 if 'uos_id' in line:
1019 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
1020 if 'invoice_line_tax_id' in line:
1021 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1022 if 'account_analytic_id' in line:
1023 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
1024 if 'tax_code_id' in line :
1025 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
1026 line['tax_code_id'] = line['tax_code_id'][0]
1027 if 'base_code_id' in line :
1028 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
1029 line['base_code_id'] = line['base_code_id'][0]
1030 return map(lambda x: (0,0,x), lines)
1032 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
1033 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'])
1036 for invoice in invoices:
1040 'out_invoice': 'out_refund', # Customer Invoice
1041 'in_invoice': 'in_refund', # Supplier Invoice
1042 'out_refund': 'out_invoice', # Customer Refund
1043 'in_refund': 'in_invoice', # Supplier Refund
1047 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
1048 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1050 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
1051 tax_lines = filter(lambda l: l['manual'], tax_lines)
1052 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1053 if invoice['type'] == 'in_invoice':
1054 refund_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase_refund')])
1056 refund_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','sale_refund')])
1058 date = time.strftime('%Y-%m-%d')
1060 'type': type_dict[invoice['type']],
1061 'date_invoice': date,
1064 'invoice_line': invoice_lines,
1065 'tax_line': tax_lines,
1066 'journal_id': refund_journal_ids
1070 'period_id': period_id,
1074 'name': description,
1076 # take the id part of the tuple returned for many2one fields
1077 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1078 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1079 invoice[field] = invoice[field] and invoice[field][0]
1080 # create the new invoice
1081 new_ids.append(self.create(cr, uid, invoice))
1084 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=''):
1087 #TODO check if we can use different period for payment and the writeoff line
1088 assert len(ids)==1, "Can only pay one invoice at a time"
1089 invoice = self.browse(cr, uid, ids[0])
1090 src_account_id = invoice.account_id.id
1091 # Take the seq as name for move
1092 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1093 direction = types[invoice.type]
1094 #take the choosen date
1095 if 'date_p' in context and context['date_p']:
1096 date=context['date_p']
1098 date=time.strftime('%Y-%m-%d')
1100 # Take the amount in currency and the currency of the payment
1101 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1102 amount_currency = context['amount_currency']
1103 currency_id = context['currency_id']
1105 amount_currency = False
1108 pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1109 if invoice.type in ('in_invoice', 'out_invoice'):
1110 if pay_journal['type'] == 'bank':
1111 entry_type = 'bank_pay_voucher' # Bank payment
1113 entry_type = 'pay_voucher' # Cash payment
1115 entry_type = 'cont_voucher'
1116 if invoice.type in ('in_invoice', 'in_refund'):
1117 ref = invoice.reference
1119 ref = self._convert_ref(cr, uid, invoice.number)
1120 # Pay attention to the sign for both debit/credit AND amount_currency
1122 'debit': direction * pay_amount>0 and direction * pay_amount,
1123 'credit': direction * pay_amount<0 and - direction * pay_amount,
1124 'account_id': src_account_id,
1125 'partner_id': invoice.partner_id.id,
1128 'currency_id':currency_id,
1129 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1130 'company_id': invoice.company_id.id,
1133 'debit': direction * pay_amount<0 and - direction * pay_amount,
1134 'credit': direction * pay_amount>0 and direction * pay_amount,
1135 'account_id': pay_account_id,
1136 'partner_id': invoice.partner_id.id,
1139 'currency_id':currency_id,
1140 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1141 'company_id': invoice.company_id.id,
1145 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1149 lines = [(0, 0, l1), (0, 0, l2)]
1150 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date, 'type': entry_type}
1151 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1155 line = self.pool.get('account.move.line')
1156 move_ids = [move_id,]
1158 move_ids.append(invoice.move_id.id)
1159 cr.execute('SELECT id FROM account_move_line '\
1160 'WHERE move_id IN %s',
1161 ((move_id, invoice.move_id.id),))
1162 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1163 for l in lines+invoice.payment_ids:
1164 if l.account_id.id==src_account_id:
1165 line_ids.append(l.id)
1166 total += (l.debit or 0.0) - (l.credit or 0.0)
1167 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1168 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1170 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1172 # Update the stored value (fields.function), so we write to trigger recompute
1173 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1177 class account_invoice_line(osv.osv):
1178 def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1180 tax_obj = self.pool.get('account.tax')
1181 cur_obj = self.pool.get('res.currency')
1182 for line in self.browse(cr, uid, ids):
1183 price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1184 taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity)
1185 res[line.id] = taxes['total']
1187 cur = line.invoice_id.currency_id
1188 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1191 def _price_unit_default(self, cr, uid, context=None):
1194 if 'check_total' in context:
1195 t = context['check_total']
1196 for l in context.get('invoice_line', {}):
1197 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1198 tax_obj = self.pool.get('account.tax')
1199 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1200 t = t - (p * l[2].get('quantity'))
1201 taxes = l[2].get('invoice_line_tax_id')
1202 if len(taxes[0]) >= 3 and taxes[0][2]:
1203 taxes = tax_obj.browse(cr, uid, taxes[0][2])
1204 for tax in tax_obj.compute_all(cr, uid, taxes, p,l[2].get('quantity'), context.get('address_invoice_id', False), l[2].get('product_id', False), context.get('partner_id', False))['taxes']:
1205 t = t - tax['amount']
1209 _name = "account.invoice.line"
1210 _description = "Invoice Line"
1212 'name': fields.char('Description', size=256, required=True),
1213 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1214 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1215 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1216 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1217 '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."),
1218 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1219 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', type="float",
1220 digits_compute= dp.get_precision('Account'), store=True),
1221 'quantity': fields.float('Quantity', required=True),
1222 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1223 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1224 'note': fields.text('Notes', translate=True),
1225 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1226 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1227 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1232 'price_unit': _price_unit_default,
1235 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1236 tax_obj = self.pool.get('account.tax')
1238 taxes = tax_obj.browse(cr, uid, tax_id)
1239 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1240 price_unit = price_unit - tax['amount']
1241 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1243 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):
1244 print "2222*", uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, currency_id, context
1247 company_id = context.get('company_id',False)
1249 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1251 if type in ('in_invoice', 'in_refund'):
1252 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1254 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1255 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1256 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1259 context.update({'lang': part.lang})
1261 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1264 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)])
1266 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)])
1267 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)])
1269 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)])
1272 in_acc = res.product_tmpl_id.property_account_income
1273 in_acc_cate = res.categ_id.property_account_income_categ
1277 app_acc_in = in_acc_cate
1279 app_acc_in = self.pool.get('account.account').browse(cr, uid, in_pro_id)[0]
1281 ex_acc = res.product_tmpl_id.property_account_expense
1282 ex_acc_cate = res.categ_id.property_account_expense_categ
1284 app_acc_exp = ex_acc
1286 app_acc_exp = ex_acc_cate
1288 app_acc_exp = self.pool.get('account.account').browse(cr, uid, exp_pro_id)[0]
1289 if not in_pro_id and not exp_pro_id:
1290 in_acc = res.product_tmpl_id.property_account_income
1291 in_acc_cate = res.categ_id.property_account_income_categ
1292 ex_acc = res.product_tmpl_id.property_account_expense
1293 ex_acc_cate = res.categ_id.property_account_expense_categ
1294 if in_acc or ex_acc:
1296 app_acc_exp = ex_acc
1298 app_acc_in = in_acc_cate
1299 app_acc_exp = ex_acc_cate
1301 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1302 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1303 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1304 in_res_id=self.pool.get('account.account').search(cr, uid, [('name','=',app_acc_in.name),('company_id','=',company_id)])
1305 exp_res_id=self.pool.get('account.account').search(cr, uid, [('name','=',app_acc_exp.name),('company_id','=',company_id)])
1306 if not in_res_id and not exp_res_id:
1307 raise osv.except_osv(_('Configration Error !'),
1308 _('Can not find account chart for this company, Please Create account.'))
1309 in_obj_acc=self.pool.get('account.account').browse(cr, uid, in_res_id)
1310 exp_obj_acc=self.pool.get('account.account').browse(cr, uid, exp_res_id)
1311 if in_acc or ex_acc:
1312 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1313 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1315 res.categ_id.property_account_income_categ = in_obj_acc[0]
1316 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1318 if type in ('out_invoice','out_refund'):
1319 a = res.product_tmpl_id.property_account_income.id
1321 a = res.categ_id.property_account_income_categ.id
1323 a = res.product_tmpl_id.property_account_expense.id
1325 a = res.categ_id.property_account_expense_categ.id
1327 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1329 result['account_id'] = a
1332 tax_obj = self.pool.get('account.tax')
1333 if type in ('out_invoice', 'out_refund'):
1334 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1335 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1337 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)
1338 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1339 if type in ('in_invoice', 'in_refund'):
1340 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)
1341 result.update(to_update)
1343 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1346 result['name'] = res.partner_ref
1349 result['uos_id'] = res.uom_id.id or uom or False
1350 if result['uos_id']:
1351 res2 = res.uom_id.category_id.id
1353 domain = {'uos_id':[('category_id','=',res2 )]}
1355 prod_pool=self.pool.get('product.product')
1356 result['categ_id'] = res.categ_id.id
1357 res_final = {'value':result, 'domain':domain}
1359 if not company_id and not currency_id:
1362 company = self.pool.get('res.company').browse(cr, uid, company_id)
1363 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1365 if not currency.company_id.id == company.id:
1366 raise osv.except_osv(_('Configration Error !'),
1367 _('Can not select currency that is not related to any company.\nPlease select accordingly !.'))
1369 if company.currency_id.id != currency.id:
1370 new_price = res_final['value']['price_unit'] * currency.rate
1371 res_final['value']['price_unit'] = new_price
1374 uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1375 if res.uom_id.category_id.id == uom.category_id.id:
1376 new_price = res_final['value']['price_unit'] * uom.factor_inv
1377 res_final['value']['price_unit'] = new_price
1380 def uos_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):
1381 res = self.product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, currency_id, context)
1382 if 'uos_id' in res['value']:
1383 del res['value']['uos_id']
1385 res['value']['price_unit'] = 0.0
1388 def move_line_get(self, cr, uid, invoice_id, context=None):
1391 tax_obj = self.pool.get('account.tax')
1392 cur_obj = self.pool.get('res.currency')
1393 ait_obj = self.pool.get('account.invoice.tax')
1394 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1395 company_currency = inv.company_id.currency_id.id
1396 cur = inv.currency_id
1398 for line in inv.invoice_line:
1399 mres = self.move_line_get_item(cr, uid, line, context)
1403 tax_code_found= False
1404 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1405 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1406 line.quantity, inv.address_invoice_id.id, line.product_id,
1407 inv.partner_id)['taxes']:
1409 if inv.type in ('out_invoice', 'in_invoice'):
1410 tax_code_id = tax['base_code_id']
1411 tax_amount = line.price_subtotal * tax['base_sign']
1413 tax_code_id = tax['ref_base_code_id']
1414 tax_amount = line.price_subtotal * tax['ref_base_sign']
1419 res.append(self.move_line_get_item(cr, uid, line, context))
1420 res[-1]['price'] = 0.0
1421 res[-1]['account_analytic_id'] = False
1422 elif not tax_code_id:
1424 tax_code_found = True
1426 res[-1]['tax_code_id'] = tax_code_id
1427 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1430 def move_line_get_item(self, cr, uid, line, context=None):
1433 'name': line.name[:64],
1434 'price_unit':line.price_unit,
1435 'quantity':line.quantity,
1436 'price':line.price_subtotal,
1437 'account_id':line.account_id.id,
1438 'product_id':line.product_id.id,
1439 'uos_id':line.uos_id.id,
1440 'account_analytic_id':line.account_analytic_id.id,
1441 'taxes':line.invoice_line_tax_id,
1444 # Set the tax field according to the account and the fiscal position
1446 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1449 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1450 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1451 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1452 r = {'value':{'invoice_line_tax_id': res}}
1454 account_invoice_line()
1456 class account_invoice_tax(osv.osv):
1457 _name = "account.invoice.tax"
1458 _description = "Invoice Tax"
1460 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1461 'name': fields.char('Tax Description', size=64, required=True),
1462 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1463 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1464 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1465 'manual': fields.boolean('Manual'),
1466 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1468 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1469 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1470 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1471 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1472 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True),
1475 def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1476 cur_obj = self.pool.get('res.currency')
1477 company_obj = self.pool.get('res.company')
1478 company_currency=False
1480 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1481 if currency_id and company_currency:
1482 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1483 return {'value': {'base_amount':base}}
1485 def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1486 cur_obj = self.pool.get('res.currency')
1487 company_obj = self.pool.get('res.company')
1488 company_currency=False
1490 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1491 if currency_id and company_currency:
1492 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1493 return {'value': {'tax_amount':amount}}
1497 'manual': lambda *a: 1,
1498 'base_amount': lambda *a: 0.0,
1499 'tax_amount': lambda *a: 0.0,
1501 def compute(self, cr, uid, invoice_id, context={}):
1503 tax_obj = self.pool.get('account.tax')
1504 cur_obj = self.pool.get('res.currency')
1505 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1506 cur = inv.currency_id
1507 company_currency = inv.company_id.currency_id.id
1509 for line in inv.invoice_line:
1510 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, inv.address_invoice_id.id, line.product_id, inv.partner_id)['taxes']:
1512 val['invoice_id'] = inv.id
1513 val['name'] = tax['name']
1514 val['amount'] = tax['amount']
1515 val['manual'] = False
1516 val['sequence'] = tax['sequence']
1517 val['base'] = tax['price_unit'] * line['quantity']
1519 if inv.type in ('out_invoice','in_invoice'):
1520 val['base_code_id'] = tax['base_code_id']
1521 val['tax_code_id'] = tax['tax_code_id']
1522 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)
1523 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)
1524 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1526 val['base_code_id'] = tax['ref_base_code_id']
1527 val['tax_code_id'] = tax['ref_tax_code_id']
1528 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)
1529 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)
1530 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1532 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1533 if not key in tax_grouped:
1534 tax_grouped[key] = val
1536 tax_grouped[key]['amount'] += val['amount']
1537 tax_grouped[key]['base'] += val['base']
1538 tax_grouped[key]['base_amount'] += val['base_amount']
1539 tax_grouped[key]['tax_amount'] += val['tax_amount']
1541 for t in tax_grouped.values():
1542 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1543 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1544 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1547 def move_line_get(self, cr, uid, invoice_id):
1549 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1550 for t in cr.dictfetchall():
1551 if not t['amount'] \
1552 and not t['tax_code_id'] \
1553 and not t['tax_amount']:
1558 'price_unit': t['amount'],
1560 'price': t['amount'] or 0.0,
1561 'account_id': t['account_id'],
1562 'tax_code_id': t['tax_code_id'],
1563 'tax_amount': t['tax_amount']
1566 account_invoice_tax()
1569 class res_partner(osv.osv):
1570 """ Inherits partner and adds invoice information in the partner form """
1571 _inherit = 'res.partner'
1573 'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1578 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: