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 lxml import etree
24 import decimal_precision as dp
28 from osv import fields, osv, orm
29 from tools.translate import _
31 class account_invoice(osv.osv):
32 def _amount_all(self, cr, uid, ids, name, args, context=None):
34 for invoice in self.browse(cr, uid, ids, context=context):
36 'amount_untaxed': 0.0,
40 for line in invoice.invoice_line:
41 res[invoice.id]['amount_untaxed'] += line.price_subtotal
42 for line in invoice.tax_line:
43 res[invoice.id]['amount_tax'] += line.amount
44 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
47 def _get_journal(self, cr, uid, context=None):
50 type_inv = context.get('type', 'out_invoice')
51 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
52 company_id = context.get('company_id', user.company_id.id)
53 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
54 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
55 journal_obj = self.pool.get('account.journal')
56 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
57 ('company_id', '=', company_id),
58 ('refund_journal', '=', refund_journal.get(type_inv, False))],
60 return res and res[0] or False
62 def _get_currency(self, cr, uid, context=None):
63 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
65 return user.company_id.currency_id.id
66 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=', 1.0)])[0]
68 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
69 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
70 tt = type2journal.get(type_inv, 'sale')
71 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
73 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
76 def _get_type(self, cr, uid, context=None):
79 return context.get('type', 'out_invoice')
81 def _reconciled(self, cr, uid, ids, name, args, context=None):
84 res[id] = self.test_paid(cr, uid, [id])
87 def _get_reference_type(self, cr, uid, context=None):
88 return [('none', _('Free Reference'))]
90 def _amount_residual(self, cr, uid, ids, name, args, context=None):
92 cur_obj = self.pool.get('res.currency')
93 data_inv = self.browse(cr, uid, ids)
99 context.update({'date':inv.date_invoice})
100 context_unreconciled=context.copy()
101 for lines in inv.move_lines:
102 debit_tmp = lines.debit
103 credit_tmp = lines.credit
104 # If currency conversion needed
105 if inv.company_id.currency_id.id <> inv.currency_id.id:
106 # If invoice paid, compute currency amount according to invoice date
107 # otherwise, take the line date
108 if not inv.reconciled:
109 context.update({'date':lines.date})
110 context_unreconciled.update({'date':lines.date})
111 # If amount currency setted, compute for debit and credit in company currency
112 if lines.amount_currency < 0:
113 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))
114 elif lines.amount_currency > 0:
115 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))
116 # Then, recomput into invoice currency to avoid rounding trouble !
117 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False, context=context)
118 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False, context=context)
123 if not inv.amount_total:
125 elif inv.type in ('out_invoice','in_refund'):
126 amount = credit-debit
127 result = inv.amount_total - amount
129 amount = debit-credit
130 result = inv.amount_total - amount
131 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
132 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id, result) and result or 0.0
135 # Give Journal Items related to the payment reconciled to this invoice
136 # Return ids of partial and total payments related to the selected invoices
137 def _get_lines(self, cr, uid, ids, name, arg, context=None):
139 for invoice in self.browse(cr, uid, ids, context=context):
142 if not invoice.move_id:
144 data_lines = [x for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
146 for line in data_lines:
148 if line.reconcile_id:
149 ids_line = line.reconcile_id.line_id
150 elif line.reconcile_partial_id:
151 ids_line = line.reconcile_partial_id.line_partial_ids
152 l = map(lambda x: x.id, ids_line)
153 partial_ids.append(line.id)
154 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
157 def _get_invoice_line(self, cr, uid, ids, context=None):
159 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
160 result[line.invoice_id.id] = True
163 def _get_invoice_tax(self, cr, uid, ids, context=None):
165 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
166 result[tax.invoice_id.id] = True
169 def _compute_lines(self, cr, uid, ids, name, args, context=None):
171 for invoice in self.browse(cr, uid, ids, context):
175 for m in invoice.move_id.line_id:
178 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
179 elif m.reconcile_partial_id:
180 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
181 lines += [x for x in temp_lines if x not in lines]
184 lines = filter(lambda x: x not in src, lines)
185 result[invoice.id] = lines
188 def _get_invoice_from_line(self, cr, uid, ids, context=None):
190 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
191 if line.reconcile_partial_id:
192 for line2 in line.reconcile_partial_id.line_partial_ids:
193 move[line2.move_id.id] = True
194 if line.reconcile_id:
195 for line2 in line.reconcile_id.line_id:
196 move[line2.move_id.id] = True
199 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
202 def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
204 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
205 for line in r.line_partial_ids:
206 move[line.move_id.id] = True
207 for line in r.line_id:
208 move[line.move_id.id] = True
212 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
215 _name = "account.invoice"
216 _description = 'Invoice'
220 'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
221 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
222 'type': fields.selection([
223 ('out_invoice','Customer Invoice'),
224 ('in_invoice','Supplier Invoice'),
225 ('out_refund','Customer Refund'),
226 ('in_refund','Supplier Refund'),
227 ],'Type', readonly=True, select=True, change_default=True),
229 'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
230 'internal_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',
233 required=True, readonly=True, states={'draft':[('readonly',False)]}),
234 'comment': fields.text('Additional Information'),
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 \'Paid\' state is set automatically when invoice is paid.\
248 \n* The \'Cancelled\' state is used when user cancel invoice.'),
249 'date_invoice': fields.date('Invoice Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, help="Keep empty to use the current date"),
250 'date_due': fields.date('Due Date', states={'paid':[('readonly',True)], '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', 'Journal Entry', readonly=True, help="Link to the automatically generated Journal Items."),
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, readonly=True, states={'draft':[('readonly',False)]}),
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 Journal Entry of the invoice have been totally reconciled with one or several Journal Entries of payment."),
298 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
299 help='Bank Account Number, Company bank account if Invoice is customer or supplier refund, otherwise Partner bank account number.', readonly=True, states={'draft':[('readonly',False)]}),
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('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
312 'user_id': fields.many2one('res.users', 'Salesman', readonly=True, states={'draft':[('readonly',False)]}),
313 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
318 'journal_id': _get_journal,
319 'currency_id': _get_currency,
320 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
321 'reference_type': 'none',
323 'internal_number': False,
324 'user_id': lambda s, cr, u, c: u,
327 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
328 journal_obj = self.pool.get('account.journal')
329 if context.get('active_model','') in ['res.partner']:
330 partner = self.pool.get(context['active_model']).read(cr,uid,context['active_ids'],['supplier','customer'])[0]
332 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.tree')])[0]
334 if view_type == 'form':
335 if partner['supplier'] and not partner['customer']:
336 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.supplier.form')])[0]
338 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name','=','account.invoice.form')])[0]
339 res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
340 type = context.get('journal_type', 'sale')
341 for field in res['fields']:
342 if field == 'journal_id':
343 journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
344 res['fields'][field]['selection'] = journal_select
346 if view_type == 'tree':
347 doc = etree.XML(res['arch'])
348 nodes = doc.xpath("//field[@name='partner_id']")
349 partner_string = _('Customer')
350 if context.get('type', 'out_invoice') in ('in_invoice', 'in_refund'):
351 partner_string = _('Supplier')
353 node.set('string', partner_string)
354 res['arch'] = etree.tostring(doc)
357 def get_log_context(self, cr, uid, context=None):
360 mob_obj = self.pool.get('ir.model.data')
361 res = mob_obj.get_object_reference(cr, uid, 'account', 'invoice_form') or False
362 view_id = res and res[1] or False
363 context.update({'view_id': view_id})
366 def create(self, cr, uid, vals, context=None):
370 res = super(account_invoice, self).create(cr, uid, vals, context)
371 for inv_id, name in self.name_get(cr, uid, [res], context=context):
373 if vals.get('type', 'in_invoice') in ('out_invoice', 'out_refund'):
374 ctx = self.get_log_context(cr, uid, context=ctx)
375 message = _("Invoice '%s' is waiting for validation.") % name
376 self.log(cr, uid, inv_id, message, context=ctx)
379 if '"journal_id" viol' in e.args[0]:
380 raise orm.except_orm(_('Configuration Error!'),
381 _('There is no Accounting Journal of type Sale/Purchase defined!'))
383 raise orm.except_orm(_('UnknownError'), str(e))
385 def confirm_paid(self, cr, uid, ids, context=None):
386 self.write(cr, uid, ids, {'state':'paid'}, context=context)
387 for inv_id, name in self.name_get(cr, uid, ids, context=context):
388 message = _("Invoice '%s' is paid.") % name
389 self.log(cr, uid, inv_id, message)
392 def unlink(self, cr, uid, ids, context=None):
393 invoices = self.read(cr, uid, ids, ['state'])
396 if t['state'] in ('draft', 'cancel'):
397 unlink_ids.append(t['id'])
399 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
400 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
403 def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
404 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
405 invoice_addr_id = False
406 contact_addr_id = False
407 partner_payment_term = False
410 fiscal_position = False
412 opt = [('uid', str(uid))]
415 opt.insert(0, ('id', partner_id))
416 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
417 contact_addr_id = res['contact']
418 invoice_addr_id = res['invoice']
419 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
421 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
422 property_obj = self.pool.get('ir.property')
423 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
424 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
426 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
428 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
429 rec_line_data = property_obj.read(cr,uid,rec_pro_id,['name','value_reference','res_id'])
430 pay_line_data = property_obj.read(cr,uid,pay_pro_id,['name','value_reference','res_id'])
431 rec_res_id = rec_line_data and rec_line_data[0].get('value_reference',False) and int(rec_line_data[0]['value_reference'].split(',')[1]) or False
432 pay_res_id = pay_line_data and pay_line_data[0].get('value_reference',False) and int(pay_line_data[0]['value_reference'].split(',')[1]) or False
433 if not rec_res_id and not pay_res_id:
434 raise osv.except_osv(_('Configuration Error !'),
435 _('Can not find account chart for this company, Please Create account.'))
436 account_obj = self.pool.get('account.account')
437 rec_obj_acc = account_obj.browse(cr, uid, [rec_res_id])
438 pay_obj_acc = account_obj.browse(cr, uid, [pay_res_id])
439 p.property_account_receivable = rec_obj_acc[0]
440 p.property_account_payable = pay_obj_acc[0]
442 if type in ('out_invoice', 'out_refund'):
443 acc_id = p.property_account_receivable.id
445 acc_id = p.property_account_payable.id
446 fiscal_position = p.property_account_position and p.property_account_position.id or False
447 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
449 bank_id = p.bank_ids[0].id
452 'address_contact_id': contact_addr_id,
453 'address_invoice_id': invoice_addr_id,
454 'account_id': acc_id,
455 'payment_term': partner_payment_term,
456 'fiscal_position': fiscal_position
460 if type in ('in_invoice', 'in_refund'):
461 result['value']['partner_bank_id'] = bank_id
463 if payment_term != partner_payment_term:
464 if partner_payment_term:
465 to_update = self.onchange_payment_term_date_invoice(
466 cr, uid, ids, partner_payment_term, date_invoice)
467 result['value'].update(to_update['value'])
469 result['value']['date_due'] = False
471 if partner_bank_id != bank_id:
472 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
473 result['value'].update(to_update['value'])
476 def onchange_journal_id(self, cr, uid, ids, journal_id=False):
479 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
480 currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id
482 'currency_id': currency_id,
487 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
488 if not payment_term_id:
491 pt_obj = self.pool.get('account.payment.term')
493 date_invoice = time.strftime('%Y-%m-%d')
495 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
498 pterm_list = [line[0] for line in pterm_list]
500 res = {'value':{'date_due': pterm_list[-1]}}
502 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
505 def onchange_invoice_line(self, cr, uid, ids, lines):
508 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id=False):
511 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
514 obj_journal = self.pool.get('account.journal')
515 account_obj = self.pool.get('account.account')
516 inv_line_obj = self.pool.get('account.invoice.line')
517 if company_id and part_id and type:
519 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
520 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
521 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
522 property_obj = self.pool.get('ir.property')
523 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
524 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
526 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
528 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
529 rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value_reference','res_id'])
530 pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value_reference','res_id'])
531 rec_res_id = rec_line_data and rec_line_data[0].get('value_reference',False) and int(rec_line_data[0]['value_reference'].split(',')[1]) or False
532 pay_res_id = pay_line_data and pay_line_data[0].get('value_reference',False) and int(pay_line_data[0]['value_reference'].split(',')[1]) or False
533 if not rec_res_id and not pay_res_id:
534 raise osv.except_osv(_('Configuration Error !'),
535 _('Can not find account chart for this company, Please Create account.'))
536 if type in ('out_invoice', 'out_refund'):
540 val= {'account_id': acc_id}
543 inv_obj = self.browse(cr,uid,ids)
544 for line in inv_obj[0].invoice_line:
546 if line.account_id.company_id.id != company_id:
547 result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
549 raise osv.except_osv(_('Configuration Error !'),
550 _('Can not find account chart for this company in invoice line account, Please Create account.'))
551 inv_line_obj.write(cr, uid, [line.id], {'account_id': result_id[0]})
554 for inv_line in invoice_line:
555 obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
556 if obj_l.company_id.id != company_id:
557 raise osv.except_osv(_('Configuration Error !'),
558 _('Invoice line account company does not match with invoice company.'))
561 if company_id and type:
562 if type in ('out_invoice'):
563 journal_type = 'sale'
564 elif type in ('out_refund'):
565 journal_type = 'sale_refund'
566 elif type in ('in_refund'):
567 journal_type = 'purchase_refund'
569 journal_type = 'purchase'
570 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
572 val['journal_id'] = journal_ids[0]
574 raise osv.except_osv(_('Configuration Error !'), _('Can\'t find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Financial Accounting\Accounts\Journals.' % (journal_type)))
575 dom = {'journal_id': [('id', 'in', journal_ids)]}
577 journal_ids = obj_journal.search(cr, uid, [])
579 if currency_id and company_id:
580 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
581 if currency.company_id and currency.company_id.id != company_id:
582 val['currency_id'] = False
584 val['currency_id'] = currency.id
586 company = self.pool.get('res.company').browse(cr, uid, company_id)
587 if company.currency_id.company_id and company.currency_id.company_id.id != company_id:
588 val['currency_id'] = False
590 val['currency_id'] = company.currency_id.id
592 return {'value': val, 'domain': dom}
594 # go from canceled state to draft state
595 def action_cancel_draft(self, cr, uid, ids, *args):
596 self.write(cr, uid, ids, {'state':'draft'})
597 wf_service = netsvc.LocalService("workflow")
599 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
605 # return the ids of the move lines which has the same account than the invoice
607 def move_line_id_payment_get(self, cr, uid, ids, *args):
608 if not ids: return []
609 result = self.move_line_id_payment_gets(cr, uid, ids, *args)
610 return result.get(ids[0], [])
612 def move_line_id_payment_gets(self, cr, uid, ids, *args):
614 if not ids: return res
615 cr.execute('SELECT i.id, l.id '\
616 'FROM account_move_line l '\
617 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
619 'AND l.account_id=i.account_id',
621 for r in cr.fetchall():
622 res.setdefault(r[0], [])
623 res[r[0]].append( r[1] )
626 def copy(self, cr, uid, id, default={}, context=None):
632 'internal_number': False,
634 if 'date_invoice' not in default:
638 if 'date_due' not in default:
642 return super(account_invoice, self).copy(cr, uid, id, default, context)
644 def test_paid(self, cr, uid, ids, *args):
645 res = self.move_line_id_payment_get(cr, uid, ids)
650 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
651 ok = ok and bool(cr.fetchone()[0])
654 def button_reset_taxes(self, cr, uid, ids, context=None):
658 ait_obj = self.pool.get('account.invoice.tax')
660 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
661 partner = self.browse(cr, uid, id, context=ctx).partner_id
663 ctx.update({'lang': partner.lang})
664 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
665 ait_obj.create(cr, uid, taxe)
666 # Update the stored value (fields.function), so we write to trigger recompute
667 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
670 def button_compute(self, cr, uid, ids, context=None, set_total=False):
671 self.button_reset_taxes(cr, uid, ids, context)
672 for inv in self.browse(cr, uid, ids):
674 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
677 def _convert_ref(self, cr, uid, ref):
678 return (ref or '').replace('/','')
680 def _get_analytic_lines(self, cr, uid, id):
681 inv = self.browse(cr, uid, id)
682 cur_obj = self.pool.get('res.currency')
684 company_currency = inv.company_id.currency_id.id
685 if inv.type in ('out_invoice', 'in_refund'):
690 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
692 if il['account_analytic_id']:
693 if inv.type in ('in_invoice', 'in_refund'):
696 ref = self._convert_ref(cr, uid, inv.number)
697 if not inv.journal_id.analytic_journal_id:
698 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (inv.journal_id.name,))
699 il['analytic_lines'] = [(0,0, {
701 'date': inv['date_invoice'],
702 'account_id': il['account_analytic_id'],
703 'unit_amount': il['quantity'],
704 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
705 'product_id': il['product_id'],
706 'product_uom_id': il['uos_id'],
707 'general_account_id': il['account_id'],
708 'journal_id': inv.journal_id.analytic_journal_id.id,
713 def action_date_assign(self, cr, uid, ids, *args):
714 for inv in self.browse(cr, uid, ids):
715 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
716 if res and res['value']:
717 self.write(cr, uid, [inv.id], res['value'])
720 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
721 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
722 Hook method to be overridden in additional modules to verify and possibly alter the
723 move lines to be created by an invoice, for special cases.
724 :param invoice_browse: browsable record of the invoice that is generating the move lines
725 :param move_lines: list of dictionaries with the account.move.lines (as for create())
726 :return: the (possibly updated) final move_lines to create for this invoice
730 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
732 for tax in compute_taxes.values():
733 ait_obj.create(cr, uid, tax)
736 for tax in inv.tax_line:
739 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
741 if not key in compute_taxes:
742 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
743 base = compute_taxes[key]['base']
744 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
745 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
746 for key in compute_taxes:
747 if not key in tax_key:
748 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
750 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
753 cur_obj = self.pool.get('res.currency')
754 for i in invoice_move_lines:
755 if inv.currency_id.id != company_currency:
756 i['currency_id'] = inv.currency_id.id
757 i['amount_currency'] = i['price']
758 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
759 company_currency, i['price'],
760 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
762 i['amount_currency'] = False
763 i['currency_id'] = False
765 if inv.type in ('out_invoice','in_refund'):
767 total_currency += i['amount_currency'] or i['price']
768 i['price'] = - i['price']
771 total_currency -= i['amount_currency'] or i['price']
772 return total, total_currency, invoice_move_lines
774 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
775 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
776 will be grouped together if the journal has the 'group line' option. Of course a module
777 can add fields to invoice lines that would need to be tested too before merging lines
779 return "%s-%s-%s-%s-%s"%(
780 invoice_line['account_id'],
781 invoice_line.get('tax_code_id',"False"),
782 invoice_line.get('product_id',"False"),
783 invoice_line.get('analytic_account_id',"False"),
784 invoice_line.get('date_maturity',"False"))
786 def group_lines(self, cr, uid, iml, line, inv):
787 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
788 if inv.journal_id.group_invoice_lines:
791 tmp = self.inv_line_characteristic_hashcode(inv, l)
794 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
795 line2[tmp]['debit'] = (am > 0) and am or 0.0
796 line2[tmp]['credit'] = (am < 0) and -am or 0.0
797 line2[tmp]['tax_amount'] += l['tax_amount']
798 line2[tmp]['analytic_lines'] += l['analytic_lines']
802 for key, val in line2.items():
803 line.append((0,0,val))
806 def action_move_create(self, cr, uid, ids, *args):
807 """Creates invoice related analytics and financial move lines"""
808 ait_obj = self.pool.get('account.invoice.tax')
809 cur_obj = self.pool.get('res.currency')
811 for inv in self.browse(cr, uid, ids):
812 if not inv.journal_id.sequence_id:
813 raise osv.except_osv(_('Error !'), _('Please define sequence on invoice journal'))
814 if not inv.invoice_line:
815 raise osv.except_osv(_('No Invoice Lines !'), _('Please create some invoice lines.'))
819 if not inv.date_invoice:
820 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
821 company_currency = inv.company_id.currency_id.id
822 # create the analytical lines
823 # one move line per invoice line
824 iml = self._get_analytic_lines(cr, uid, inv.id)
825 # check if taxes are all computed
827 ctx.update({'lang': inv.partner_id.lang})
828 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
829 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
831 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
832 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
835 total_fixed = total_percent = 0
836 for line in inv.payment_term.line_ids:
837 if line.value == 'fixed':
838 total_fixed += line.value_amount
839 if line.value == 'procent':
840 total_percent += line.value_amount
841 total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
842 if (total_fixed + total_percent) > 100:
843 raise osv.except_osv(_('Error !'), _("Cannot create the invoice !\nThe payment term defined gives a computed amount greater than the total invoiced amount."))
845 # one move line per tax line
846 iml += ait_obj.move_line_get(cr, uid, inv.id)
849 if inv.type in ('in_invoice', 'in_refund'):
851 entry_type = 'journal_pur_voucher'
852 if inv.type == 'in_refund':
853 entry_type = 'cont_voucher'
855 ref = self._convert_ref(cr, uid, inv.number)
856 entry_type = 'journal_sale_vou'
857 if inv.type == 'out_refund':
858 entry_type = 'cont_voucher'
860 diff_currency_p = inv.currency_id.id <> company_currency
861 # create one move line for the total and possibly adjust the other lines amount
864 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
865 acc_id = inv.account_id.id
867 name = inv['name'] or '/'
870 totlines = self.pool.get('account.payment.term').compute(cr,
871 uid, inv.payment_term.id, total, inv.date_invoice or False)
873 res_amount_currency = total_currency
876 if inv.currency_id.id != company_currency:
877 amount_currency = cur_obj.compute(cr, uid,
878 company_currency, inv.currency_id.id, t[1])
880 amount_currency = False
882 # last line add the diff
883 res_amount_currency -= amount_currency or 0
885 if i == len(totlines):
886 amount_currency += res_amount_currency
892 'account_id': acc_id,
893 'date_maturity': t[0],
894 'amount_currency': diff_currency_p \
895 and amount_currency or False,
896 'currency_id': diff_currency_p \
897 and inv.currency_id.id or False,
905 'account_id': acc_id,
906 'date_maturity': inv.date_due or False,
907 'amount_currency': diff_currency_p \
908 and total_currency or False,
909 'currency_id': diff_currency_p \
910 and inv.currency_id.id or False,
914 date = inv.date_invoice or time.strftime('%Y-%m-%d')
915 part = inv.partner_id.id
917 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})),iml)
919 line = self.group_lines(cr, uid, iml, line, inv)
921 journal_id = inv.journal_id.id
922 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
923 if journal.centralisation:
924 raise osv.except_osv(_('UserError'),
925 _('Cannot create invoice move on centralised journal'))
927 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
930 'ref': inv.reference and inv.reference or inv.name,
932 'journal_id': journal_id,
935 'narration':inv.comment
937 period_id = inv.period_id and inv.period_id.id or False
939 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'))])
941 period_id = period_ids[0]
943 move['period_id'] = period_id
945 i[2]['period_id'] = period_id
947 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
948 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
949 # make the invoice point to that move
950 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
951 # Pass invoice in context in method post: used if you want to get the same
952 # account move reference when creating the same invoice after a cancelled one:
953 self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
954 self._log_event(cr, uid, ids)
957 def line_get_convert(self, cr, uid, x, part, date, context=None):
959 'date_maturity': x.get('date_maturity', False),
961 'name':x['name'][:64],
963 'debit':x['price']>0 and x['price'],
964 'credit':x['price']<0 and -x['price'],
965 'account_id':x['account_id'],
966 'analytic_lines':x.get('analytic_lines', []),
967 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
968 'currency_id':x.get('currency_id', False),
969 'tax_code_id': x.get('tax_code_id', False),
970 'tax_amount': x.get('tax_amount', False),
971 'ref':x.get('ref',False),
972 'quantity':x.get('quantity',1.00),
973 'product_id':x.get('product_id', False),
974 'product_uom_id':x.get('uos_id',False),
975 'analytic_account_id':x.get('account_analytic_id',False),
978 def action_number(self, cr, uid, ids, context=None):
981 #TODO: not correct fix but required a frech values before reading it.
982 self.write(cr, uid, ids, {})
984 for obj_inv in self.browse(cr, uid, ids):
986 invtype = obj_inv.type
987 number = obj_inv.number
988 move_id = obj_inv.move_id and obj_inv.move_id.id or False
989 reference = obj_inv.reference or ''
991 self.write(cr, uid, ids, {'internal_number':number})
993 if invtype in ('in_invoice', 'in_refund'):
995 ref = self._convert_ref(cr, uid, number)
999 ref = self._convert_ref(cr, uid, number)
1001 cr.execute('UPDATE account_move SET ref=%s ' \
1002 'WHERE id=%s AND (ref is null OR ref = \'\')',
1004 cr.execute('UPDATE account_move_line SET ref=%s ' \
1005 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
1007 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
1008 'FROM account_move_line ' \
1009 'WHERE account_move_line.move_id = %s ' \
1010 'AND account_analytic_line.move_id = account_move_line.id',
1013 for inv_id, name in self.name_get(cr, uid, [id]):
1014 ctx = context.copy()
1015 if obj_inv.type in ('out_invoice', 'out_refund'):
1016 ctx = self.get_log_context(cr, uid, context=ctx)
1017 message = _('Invoice ') + " '" + name + "' "+ _("is validated.")
1018 self.log(cr, uid, inv_id, message, context=ctx)
1021 def action_cancel(self, cr, uid, ids, *args):
1022 account_move_obj = self.pool.get('account.move')
1023 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
1026 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
1027 # delete the move this invoice was pointing to
1028 # Note that the corresponding move_lines and move_reconciles
1029 # will be automatically deleted too
1030 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
1031 if i['payment_ids']:
1032 account_move_line_obj = self.pool.get('account.move.line')
1033 pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
1034 for move_line in pay_ids:
1035 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
1036 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
1038 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
1039 self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
1044 def list_distinct_taxes(self, cr, uid, ids):
1045 invoices = self.browse(cr, uid, ids)
1047 for inv in invoices:
1048 for tax in inv.tax_line:
1049 if not tax['name'] in taxes:
1050 taxes[tax['name']] = {'name': tax['name']}
1051 return taxes.values()
1053 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
1054 #TODO: implement messages system
1057 def name_get(self, cr, uid, ids, context=None):
1061 'out_invoice': 'CI: ',
1062 'in_invoice': 'SI: ',
1063 'out_refund': 'OR: ',
1064 'in_refund': 'SR: ',
1066 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')]
1068 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1075 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
1077 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
1078 return self.name_get(cr, user, ids, context)
1080 def _refund_cleanup_lines(self, cr, uid, lines):
1083 del line['invoice_id']
1084 for field in ('company_id', 'partner_id', 'account_id', 'product_id',
1085 'uos_id', 'account_analytic_id', 'tax_code_id', 'base_code_id'):
1086 line[field] = line.get(field, False) and line[field][0]
1087 if 'invoice_line_tax_id' in line:
1088 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1089 return map(lambda x: (0,0,x), lines)
1091 def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
1092 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'])
1093 obj_invoice_line = self.pool.get('account.invoice.line')
1094 obj_invoice_tax = self.pool.get('account.invoice.tax')
1095 obj_journal = self.pool.get('account.journal')
1097 for invoice in invoices:
1101 'out_invoice': 'out_refund', # Customer Invoice
1102 'in_invoice': 'in_refund', # Supplier Invoice
1103 'out_refund': 'out_invoice', # Customer Refund
1104 'in_refund': 'in_invoice', # Supplier Refund
1107 invoice_lines = obj_invoice_line.read(cr, uid, invoice['invoice_line'])
1108 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1110 tax_lines = obj_invoice_tax.read(cr, uid, invoice['tax_line'])
1111 tax_lines = filter(lambda l: l['manual'], tax_lines)
1112 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1114 refund_journal_ids = [journal_id]
1115 elif invoice['type'] == 'in_invoice':
1116 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')])
1118 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')])
1121 date = time.strftime('%Y-%m-%d')
1123 'type': type_dict[invoice['type']],
1124 'date_invoice': date,
1127 'invoice_line': invoice_lines,
1128 'tax_line': tax_lines,
1129 'journal_id': refund_journal_ids
1133 'period_id': period_id,
1137 'name': description,
1139 # take the id part of the tuple returned for many2one fields
1140 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1141 'account_id', 'currency_id', 'payment_term', 'journal_id'):
1142 invoice[field] = invoice[field] and invoice[field][0]
1143 # create the new invoice
1144 new_ids.append(self.create(cr, uid, invoice))
1147 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=''):
1150 #TODO check if we can use different period for payment and the writeoff line
1151 assert len(ids)==1, "Can only pay one invoice at a time"
1152 invoice = self.browse(cr, uid, ids[0])
1153 src_account_id = invoice.account_id.id
1154 # Take the seq as name for move
1155 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1156 direction = types[invoice.type]
1157 #take the choosen date
1158 if 'date_p' in context and context['date_p']:
1159 date=context['date_p']
1161 date=time.strftime('%Y-%m-%d')
1163 # Take the amount in currency and the currency of the payment
1164 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1165 amount_currency = context['amount_currency']
1166 currency_id = context['currency_id']
1168 amount_currency = False
1171 pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1172 if invoice.type in ('in_invoice', 'out_invoice'):
1173 if pay_journal['type'] == 'bank':
1174 entry_type = 'bank_pay_voucher' # Bank payment
1176 entry_type = 'pay_voucher' # Cash payment
1178 entry_type = 'cont_voucher'
1179 if invoice.type in ('in_invoice', 'in_refund'):
1180 ref = invoice.reference
1182 ref = self._convert_ref(cr, uid, invoice.number)
1183 # Pay attention to the sign for both debit/credit AND amount_currency
1185 'debit': direction * pay_amount>0 and direction * pay_amount,
1186 'credit': direction * pay_amount<0 and - direction * pay_amount,
1187 'account_id': src_account_id,
1188 'partner_id': invoice.partner_id.id,
1191 'currency_id':currency_id,
1192 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1193 'company_id': invoice.company_id.id,
1196 'debit': direction * pay_amount<0 and - direction * pay_amount,
1197 'credit': direction * pay_amount>0 and direction * pay_amount,
1198 'account_id': pay_account_id,
1199 'partner_id': invoice.partner_id.id,
1202 'currency_id':currency_id,
1203 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1204 'company_id': invoice.company_id.id,
1208 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1212 lines = [(0, 0, l1), (0, 0, l2)]
1213 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date, 'type': entry_type}
1214 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1218 line = self.pool.get('account.move.line')
1219 move_ids = [move_id,]
1221 move_ids.append(invoice.move_id.id)
1222 cr.execute('SELECT id FROM account_move_line '\
1223 'WHERE move_id IN %s',
1224 ((move_id, invoice.move_id.id),))
1225 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1226 for l in lines+invoice.payment_ids:
1227 if l.account_id.id == src_account_id:
1228 line_ids.append(l.id)
1229 total += (l.debit or 0.0) - (l.credit or 0.0)
1231 inv_id, name = self.name_get(cr, uid, [invoice.id], context=context)[0]
1232 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1233 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1235 code = invoice.currency_id.code
1236 # TODO: use currency's formatting function
1237 msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining)") % \
1238 (name, pay_amount, code, invoice.amount_total, code, total, code)
1239 self.log(cr, uid, inv_id, msg)
1240 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1242 # Update the stored value (fields.function), so we write to trigger recompute
1243 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1247 class account_invoice_line(osv.osv):
1248 def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1250 tax_obj = self.pool.get('account.tax')
1251 cur_obj = self.pool.get('res.currency')
1252 for line in self.browse(cr, uid, ids):
1253 price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1254 taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity, product=line.product_id, address_id=line.invoice_id.address_invoice_id, partner=line.invoice_id.partner_id)
1255 res[line.id] = taxes['total']
1257 cur = line.invoice_id.currency_id
1258 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1261 def _price_unit_default(self, cr, uid, context=None):
1264 if 'check_total' in context:
1265 t = context['check_total']
1266 for l in context.get('invoice_line', {}):
1267 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1268 tax_obj = self.pool.get('account.tax')
1269 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1270 t = t - (p * l[2].get('quantity'))
1271 taxes = l[2].get('invoice_line_tax_id')
1272 if len(taxes[0]) >= 3 and taxes[0][2]:
1273 taxes = tax_obj.browse(cr, uid, taxes[0][2])
1274 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']:
1275 t = t - tax['amount']
1279 _name = "account.invoice.line"
1280 _description = "Invoice Line"
1282 'name': fields.char('Description', size=256, required=True),
1283 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1284 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1285 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1286 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1287 '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."),
1288 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1289 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', type="float",
1290 digits_compute= dp.get_precision('Account'), store=True),
1291 'quantity': fields.float('Quantity', required=True),
1292 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1293 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1294 'note': fields.text('Notes'),
1295 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1296 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1297 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1302 'price_unit': _price_unit_default,
1305 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):
1308 company_id = context.get('company_id',False)
1310 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1312 if type in ('in_invoice', 'in_refund'):
1313 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1315 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1316 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1317 fpos_obj = self.pool.get('account.fiscal.position')
1318 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id) or False
1321 context.update({'lang': part.lang})
1323 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1326 property_obj = self.pool.get('ir.property')
1327 account_obj = self.pool.get('account.account')
1328 in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1330 in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1331 exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1333 exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1336 in_acc = res.product_tmpl_id.property_account_income
1337 in_acc_cate = res.categ_id.property_account_income_categ
1341 app_acc_in = in_acc_cate
1343 # Get the fields from the ir.property record
1344 my_value = property_obj.read(cr,uid,in_pro_id,['name','value_reference','res_id'])
1345 # Parse the value_reference field to get the ID of the account.account record
1346 account_id = int (my_value[0]["value_reference"].split(",")[1])
1347 # Use the ID of the account.account record in the browse for the account.account record
1348 app_acc_in = account_obj.browse(cr, uid, [account_id])[0]
1350 ex_acc = res.product_tmpl_id.property_account_expense
1351 ex_acc_cate = res.categ_id.property_account_expense_categ
1353 app_acc_exp = ex_acc
1355 app_acc_exp = ex_acc_cate
1357 app_acc_exp = account_obj.browse(cr, uid, exp_pro_id)[0]
1358 if not in_pro_id and not exp_pro_id:
1359 in_acc = res.product_tmpl_id.property_account_income
1360 in_acc_cate = res.categ_id.property_account_income_categ
1361 ex_acc = res.product_tmpl_id.property_account_expense
1362 ex_acc_cate = res.categ_id.property_account_expense_categ
1363 if in_acc or ex_acc:
1365 app_acc_exp = ex_acc
1367 app_acc_in = in_acc_cate
1368 app_acc_exp = ex_acc_cate
1369 if app_acc_in and app_acc_in.company_id.id != company_id and app_acc_exp and app_acc_exp.company_id.id != company_id:
1370 in_res_id = account_obj.search(cr, uid, [('name','=',app_acc_in.name),('company_id','=',company_id)])
1371 exp_res_id = account_obj.search(cr, uid, [('name','=',app_acc_exp.name),('company_id','=',company_id)])
1372 if not in_res_id and not exp_res_id:
1373 raise osv.except_osv(_('Configuration Error !'),
1374 _('Can not find account chart for this company, Please Create account.'))
1375 in_obj_acc = account_obj.browse(cr, uid, in_res_id)
1376 exp_obj_acc = account_obj.browse(cr, uid, exp_res_id)
1377 if in_acc or ex_acc:
1378 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1379 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1381 res.categ_id.property_account_income_categ = in_obj_acc[0]
1382 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1384 if type in ('out_invoice','out_refund'):
1385 a = res.product_tmpl_id.property_account_income.id
1387 a = res.categ_id.property_account_income_categ.id
1389 a = res.product_tmpl_id.property_account_expense.id
1391 a = res.categ_id.property_account_expense_categ.id
1393 a = fpos_obj.map_account(cr, uid, fpos, a)
1395 result['account_id'] = a
1397 if type in ('out_invoice', 'out_refund'):
1398 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1399 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1401 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)
1402 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1403 if type in ('in_invoice', 'in_refund'):
1404 result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
1406 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1409 result['name'] = res.partner_ref
1412 result['uos_id'] = res.uom_id.id or uom or False
1413 if result['uos_id']:
1414 res2 = res.uom_id.category_id.id
1416 domain = {'uos_id':[('category_id','=',res2 )]}
1418 result['categ_id'] = res.categ_id.id
1419 res_final = {'value':result, 'domain':domain}
1421 if not company_id or not currency_id:
1424 company = self.pool.get('res.company').browse(cr, uid, company_id)
1425 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1427 if company.currency_id.id != currency.id:
1428 new_price = res_final['value']['price_unit'] * currency.rate
1429 res_final['value']['price_unit'] = new_price
1432 uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1433 if res.uom_id.category_id.id == uom.category_id.id:
1434 new_price = res_final['value']['price_unit'] * uom.factor_inv
1435 res_final['value']['price_unit'] = new_price
1438 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):
1439 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)
1440 if 'uos_id' in res['value']:
1441 del res['value']['uos_id']
1443 res['value']['price_unit'] = 0.0
1446 def move_line_get(self, cr, uid, invoice_id, context=None):
1448 tax_obj = self.pool.get('account.tax')
1449 cur_obj = self.pool.get('res.currency')
1450 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1451 company_currency = inv.company_id.currency_id.id
1453 for line in inv.invoice_line:
1454 mres = self.move_line_get_item(cr, uid, line, context)
1458 tax_code_found= False
1459 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1460 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1461 line.quantity, inv.address_invoice_id.id, line.product_id,
1462 inv.partner_id)['taxes']:
1464 if inv.type in ('out_invoice', 'in_invoice'):
1465 tax_code_id = tax['base_code_id']
1466 tax_amount = line.price_subtotal * tax['base_sign']
1468 tax_code_id = tax['ref_base_code_id']
1469 tax_amount = line.price_subtotal * tax['ref_base_sign']
1474 res.append(self.move_line_get_item(cr, uid, line, context))
1475 res[-1]['price'] = 0.0
1476 res[-1]['account_analytic_id'] = False
1477 elif not tax_code_id:
1479 tax_code_found = True
1481 res[-1]['tax_code_id'] = tax_code_id
1482 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1485 def move_line_get_item(self, cr, uid, line, context=None):
1488 'name': line.name[:64],
1489 'price_unit':line.price_unit,
1490 'quantity':line.quantity,
1491 'price':line.price_subtotal,
1492 'account_id':line.account_id.id,
1493 'product_id':line.product_id.id,
1494 'uos_id':line.uos_id.id,
1495 'account_analytic_id':line.account_analytic_id.id,
1496 'taxes':line.invoice_line_tax_id,
1499 # Set the tax field according to the account and the fiscal position
1501 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1504 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1505 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1506 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1507 return {'value':{'invoice_line_tax_id': res}}
1509 account_invoice_line()
1511 class account_invoice_tax(osv.osv):
1512 _name = "account.invoice.tax"
1513 _description = "Invoice Tax"
1515 def _count_factor(self, cr, uid, ids, name, args, context=None):
1517 for invoice_tax in self.browse(cr, uid, ids, context=context):
1518 res[invoice_tax.id] = {
1522 if invoice_tax.amount <> 0.0:
1523 factor_tax = invoice_tax.tax_amount / invoice_tax.amount
1524 res[invoice_tax.id]['factor_tax'] = factor_tax
1526 if invoice_tax.base <> 0.0:
1527 factor_base = invoice_tax.base_amount / invoice_tax.base
1528 res[invoice_tax.id]['factor_base'] = factor_base
1533 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1534 'name': fields.char('Tax Description', size=64, required=True),
1535 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1536 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1537 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1538 'manual': fields.boolean('Manual'),
1539 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1540 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1541 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1542 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1543 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1544 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True),
1545 'factor_base': fields.function(_count_factor, method=True, string='Multipication factor for Base code', type='float', multi="all"),
1546 'factor_tax': fields.function(_count_factor, method=True, string='Multipication factor Tax code', type='float', multi="all")
1549 def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1550 cur_obj = self.pool.get('res.currency')
1551 company_obj = self.pool.get('res.company')
1552 company_currency = False
1555 factor = self.read(cr, uid, ids[0], ['factor_base'])['factor_base']
1557 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1558 if currency_id and company_currency:
1559 base = cur_obj.compute(cr, uid, currency_id, company_currency, base*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1560 return {'value': {'base_amount':base}}
1562 def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1563 cur_obj = self.pool.get('res.currency')
1564 company_obj = self.pool.get('res.company')
1565 company_currency = False
1568 factor = self.read(cr, uid, ids[0], ['factor_tax'])['factor_tax']
1570 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1571 if currency_id and company_currency:
1572 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1573 return {'value': {'tax_amount': amount}}
1581 def compute(self, cr, uid, invoice_id, context={}):
1583 tax_obj = self.pool.get('account.tax')
1584 cur_obj = self.pool.get('res.currency')
1585 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1586 cur = inv.currency_id
1587 company_currency = inv.company_id.currency_id.id
1589 for line in inv.invoice_line:
1590 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']:
1592 val['invoice_id'] = inv.id
1593 val['name'] = tax['name']
1594 val['amount'] = tax['amount']
1595 val['manual'] = False
1596 val['sequence'] = tax['sequence']
1597 val['base'] = tax['price_unit'] * line['quantity']
1599 if inv.type in ('out_invoice','in_invoice'):
1600 val['base_code_id'] = tax['base_code_id']
1601 val['tax_code_id'] = tax['tax_code_id']
1602 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)
1603 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)
1604 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1606 val['base_code_id'] = tax['ref_base_code_id']
1607 val['tax_code_id'] = tax['ref_tax_code_id']
1608 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)
1609 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)
1610 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1612 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1613 if not key in tax_grouped:
1614 tax_grouped[key] = val
1616 tax_grouped[key]['amount'] += val['amount']
1617 tax_grouped[key]['base'] += val['base']
1618 tax_grouped[key]['base_amount'] += val['base_amount']
1619 tax_grouped[key]['tax_amount'] += val['tax_amount']
1621 for t in tax_grouped.values():
1622 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1623 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1624 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1625 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1628 def move_line_get(self, cr, uid, invoice_id):
1630 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1631 for t in cr.dictfetchall():
1632 if not t['amount'] \
1633 and not t['tax_code_id'] \
1634 and not t['tax_amount']:
1639 'price_unit': t['amount'],
1641 'price': t['amount'] or 0.0,
1642 'account_id': t['account_id'],
1643 'tax_code_id': t['tax_code_id'],
1644 'tax_amount': t['tax_amount']
1647 account_invoice_tax()
1650 class res_partner(osv.osv):
1651 """ Inherits partner and adds invoice information in the partner form """
1652 _inherit = 'res.partner'
1654 'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1659 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: