1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 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 ##############################################################################
24 from osv import fields, osv
28 from mx.DateTime import RelativeDateTime
29 from tools import config
30 from tools.translate import _
32 class fiscalyear_seq(osv.osv):
33 _name = "fiscalyear.seq"
34 _description = "Maintains Invoice sequences with Fiscal Year"
35 _rec_name = 'fiscalyear_id'
37 'journal_id': fields.many2one('account.journal', 'Journal'),
38 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
39 'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
44 class account_invoice(osv.osv):
45 def _amount_all(self, cr, uid, ids, name, args, context=None):
47 for invoice in self.browse(cr,uid,ids, context=context):
49 'amount_untaxed': 0.0,
53 for line in invoice.invoice_line:
54 res[invoice.id]['amount_untaxed'] += line.price_subtotal
55 for line in invoice.tax_line:
56 res[invoice.id]['amount_tax'] += line.amount
57 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
60 def _get_journal(self, cr, uid, context):
63 type_inv = context.get('type', 'out_invoice')
64 user = self.pool.get('res.users').browse(cr, uid, uid)
65 company_id = context.get('company_id', user.company_id.id)
66 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
67 journal_obj = self.pool.get('account.journal')
68 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
69 ('company_id', '=', company_id)],
76 def _get_currency(self, cr, uid, context):
77 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
79 return user.company_id.currency_id.id
81 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
83 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
84 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
85 tt = type2journal.get(type_inv, 'sale')
86 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
88 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
91 def _get_type(self, cr, uid, context=None):
94 type = context.get('type', 'out_invoice')
97 def _reconciled(self, cr, uid, ids, name, args, context):
100 res[id] = self.test_paid(cr, uid, [id])
103 def _get_reference_type(self, cr, uid, context=None):
104 return [('none', _('Free Reference'))]
106 def _amount_residual(self, cr, uid, ids, name, args, context=None):
108 data_inv = self.browse(cr, uid, ids)
109 cur_obj = self.pool.get('res.currency')
112 context.update({'date':inv.date_invoice})
113 context_unreconciled=context.copy()
114 for lines in inv.move_lines:
115 debit_tmp = lines.debit
116 credit_tmp = lines.credit
117 # If currency conversion needed
118 if inv.company_id.currency_id.id <> inv.currency_id.id:
119 # If invoice paid, compute currency amount according to invoice date
120 # otherwise, take the line date
121 if not inv.reconciled:
122 context.update({'date':lines.date})
123 context_unreconciled.update({'date':lines.date})
124 # If amount currency setted, compute for debit and credit in company currency
125 if lines.amount_currency < 0:
126 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))
127 elif lines.amount_currency > 0:
128 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))
129 # Then, recomput into invoice currency to avoid rounding trouble !
130 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
131 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
136 if not inv.amount_total:
138 elif inv.type in ('out_invoice','in_refund'):
139 amount = credit-debit
140 result = inv.amount_total - amount
142 amount = debit-credit
143 result = inv.amount_total - amount
144 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
145 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
149 def _get_lines(self, cr, uid, ids, name, arg, context=None):
152 move_lines = self.move_line_id_payment_get(cr,uid,[id])
156 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
157 for line in data_lines:
159 if line.reconcile_id:
160 ids_line = line.reconcile_id.line_id
161 elif line.reconcile_partial_id:
162 ids_line = line.reconcile_partial_id.line_partial_ids
163 l = map(lambda x: x.id, ids_line)
164 res[id]=[x for x in l if x <> line.id]
167 def _get_invoice_line(self, cr, uid, ids, context=None):
169 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
170 result[line.invoice_id.id] = True
173 def _get_invoice_tax(self, cr, uid, ids, context=None):
175 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
176 result[tax.invoice_id.id] = True
179 def _compute_lines(self, cr, uid, ids, name, args, context=None):
181 for invoice in self.browse(cr, uid, ids, context):
182 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
185 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
187 lines += map(lambda x: x.id, m.reconcile_id.line_id)
188 elif m.reconcile_partial_id:
189 lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
191 lines = filter(lambda x: x not in src, lines)
192 result[invoice.id] = lines
195 def _get_invoice_from_line(self, cr, uid, ids, context={}):
197 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
198 if line.reconcile_partial_id:
199 for line2 in line.reconcile_partial_id.line_partial_ids:
200 move[line2.move_id.id] = True
201 if line.reconcile_id:
202 for line2 in line.reconcile_id.line_id:
203 move[line2.move_id.id] = True
206 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
209 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
211 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
212 for line in r.line_partial_ids:
213 move[line.move_id.id] = True
214 for line in r.line_id:
215 move[line.move_id.id] = True
219 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
222 _name = "account.invoice"
223 _description = 'Invoice'
226 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
227 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
228 'type': fields.selection([
229 ('out_invoice','Customer Invoice'),
230 ('in_invoice','Supplier Invoice'),
231 ('out_refund','Customer Refund'),
232 ('in_refund','Supplier Refund'),
233 ],'Type', readonly=True, select=True, change_default=True),
235 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
236 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
237 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
239 'comment': fields.text('Additional Information'),
241 'state': fields.selection([
243 ('proforma','Pro-forma'),
244 ('proforma2','Pro-forma'),
247 ('cancel','Cancelled')
248 ],'State', select=True, readonly=True),
250 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
251 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
252 help="If you use payment terms, the due date will be computed automatically at the generation "\
253 "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."),
254 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
255 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
256 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
257 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
258 help="If you use payment terms, the due date will be computed automatically at the generation "\
259 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
260 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
261 '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)]}),
263 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
264 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
265 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
267 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated Ledger Postings."),
268 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
270 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
271 'account.invoice.tax': (_get_invoice_tax, None, 20),
272 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
275 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
277 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
278 'account.invoice.tax': (_get_invoice_tax, None, 20),
279 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
282 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
284 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
285 'account.invoice.tax': (_get_invoice_tax, None, 20),
286 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
289 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
290 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
291 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True),
292 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
293 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
295 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
296 'account.move.line': (_get_invoice_from_line, None, 50),
297 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
298 }, help="The Ledger Postings of the invoice have been reconciled with Ledger Postings of the payment(s)."),
299 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
300 help='The bank account to pay to or to be paid from'),
301 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Entry Lines'),
302 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
304 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
305 'account.invoice.tax': (_get_invoice_tax, None, 50),
306 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
307 'account.move.line': (_get_invoice_from_line, None, 50),
308 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
310 help="Remaining amount due."),
311 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
312 'move_name': fields.char('Ledger Posting', size=64),
313 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
317 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
318 'state': lambda *a: 'draft',
319 'journal_id': _get_journal,
320 'currency_id': _get_currency,
321 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', c),
322 'reference_type': lambda *a: 'none',
323 'check_total': lambda *a: 0.0,
326 def unlink(self, cr, uid, ids, context=None):
327 invoices = self.read(cr, uid, ids, ['state'])
330 if t['state'] in ('draft', 'cancel'):
331 unlink_ids.append(t['id'])
333 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
334 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
337 # def get_invoice_address(self, cr, uid, ids):
338 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
340 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
341 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
342 invoice_addr_id = False
343 contact_addr_id = False
344 partner_payment_term = False
347 fiscal_position = False
349 opt = [('uid', str(uid))]
352 opt.insert(0, ('id', partner_id))
353 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
354 contact_addr_id = res['contact']
355 invoice_addr_id = res['invoice']
356 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
358 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
359 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)])
360 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)])
362 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
364 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
365 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
366 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
367 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
368 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
369 if not rec_res_id and not pay_res_id:
370 raise osv.except_osv(_('Configration Error !'),
371 _('Can not find account chart for this company, Please Create account.'))
372 rec_obj_acc=self.pool.get('account.account').browse(cr,uid,[rec_res_id])
373 pay_obj_acc=self.pool.get('account.account').browse(cr,uid,[pay_res_id])
374 p.property_account_receivable = rec_obj_acc[0]
375 p.property_account_payable = pay_obj_acc[0]
377 if type in ('out_invoice', 'out_refund'):
378 acc_id = p.property_account_receivable.id
380 acc_id = p.property_account_payable.id
381 fiscal_position = p.property_account_position and p.property_account_position.id or False
382 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
384 bank_id = p.bank_ids[0].id
387 'address_contact_id': contact_addr_id,
388 'address_invoice_id': invoice_addr_id,
389 'account_id': acc_id,
390 'payment_term': partner_payment_term,
391 'fiscal_position': fiscal_position
395 if type in ('in_invoice', 'in_refund'):
396 result['value']['partner_bank'] = bank_id
398 if payment_term != partner_payment_term:
399 if partner_payment_term:
400 to_update = self.onchange_payment_term_date_invoice(
401 cr,uid,ids,partner_payment_term,date_invoice)
402 result['value'].update(to_update['value'])
404 result['value']['date_due'] = False
406 if partner_bank_id != bank_id:
407 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
408 result['value'].update(to_update['value'])
411 def onchange_currency_id(self, cr, uid, ids, curr_id):
414 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
415 if not payment_term_id:
418 pt_obj= self.pool.get('account.payment.term')
419 if not date_invoice :
420 date_invoice = time.strftime('%Y-%m-%d')
422 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
425 pterm_list = [line[0] for line in pterm_list]
427 res= {'value':{'date_due': pterm_list[-1]}}
429 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
433 def onchange_invoice_line(self, cr, uid, ids, lines):
436 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
439 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line):
442 if company_id and part_id and type:
444 partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
445 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
446 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
447 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)])
448 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)])
450 rec_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
452 pay_pro_id = self.pool.get('ir.property').search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
453 rec_line_data = self.pool.get('ir.property').read(cr,uid,rec_pro_id,['name','value','res_id'])
454 pay_line_data = self.pool.get('ir.property').read(cr,uid,pay_pro_id,['name','value','res_id'])
455 rec_res_id = rec_line_data and int(rec_line_data[0]['value'].split(',')[1]) or False
456 pay_res_id = pay_line_data and int(pay_line_data[0]['value'].split(',')[1]) or False
457 if not rec_res_id and not rec_res_id:
458 raise osv.except_osv(_('Configration Error !'),
459 _('Can not find account chart for this company, Please Create account.'))
460 if type in ('out_invoice', 'out_refund'):
464 val= {'account_id': acc_id}
467 inv_obj = self.browse(cr,uid,ids)
468 for line in inv_obj[0].invoice_line:
470 if line.account_id.company_id.id != company_id:
471 result_id = self.pool.get('account.account').search(cr,uid,[('name','=',line.account_id.name),('company_id','=',company_id)])
473 raise osv.except_osv(_('Configration Error !'),
474 _('Can not find account chart for this company in invoice line account, Please Create account.'))
475 r_id = self.pool.get('account.invoice.line').write(cr,uid,[line.id],{'account_id': result_id[0]})
478 for inv_line in invoice_line:
479 obj_l = self.pool.get('account.account').browse(cr,uid,inv_line[2]['account_id'])
480 if obj_l.company_id.id != company_id:
481 raise osv.except_osv(_('Configration Error !'),
482 _('invoice line account company is not match with invoice company.'))
486 val['journal_id']=False
487 journal_ids=self.pool.get('account.journal').search(cr,uid,[('company_id','=',company_id)])
488 dom={'journal_id': [('id','in',journal_ids)]}
490 journal_ids=self.pool.get('account.journal').search(cr,uid,[])
491 dom={'journal_id': [('id','in',journal_ids)]}
492 return {'value' : val, 'domain': dom }
494 # go from canceled state to draft state
495 def action_cancel_draft(self, cr, uid, ids, *args):
496 self.write(cr, uid, ids, {'state':'draft'})
497 wf_service = netsvc.LocalService("workflow")
499 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
505 # return the ids of the move lines which has the same account than the invoice
507 def move_line_id_payment_get(self, cr, uid, ids, *args):
509 if not ids: return res
512 from account_move_line l \
513 left join account_invoice i on (i.move_id=l.move_id) \
514 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
515 res = map(lambda x: x[0], cr.fetchall())
518 def copy(self, cr, uid, id, default=None, context=None):
521 default = default.copy()
522 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
523 if 'date_invoice' not in default:
524 default['date_invoice'] = False
525 if 'date_due' not in default:
526 default['date_due'] = False
527 return super(account_invoice, self).copy(cr, uid, id, default, context)
529 def test_paid(self, cr, uid, ids, *args):
530 res = self.move_line_id_payment_get(cr, uid, ids)
535 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
536 ok = ok and bool(cr.fetchone()[0])
539 def button_reset_taxes(self, cr, uid, ids, context=None):
542 ait_obj = self.pool.get('account.invoice.tax')
544 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
545 partner = self.browse(cr, uid, id,context=context).partner_id
547 context.update({'lang': partner.lang})
548 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
549 ait_obj.create(cr, uid, taxe)
550 # Update the stored value (fields.function), so we write to trigger recompute
551 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
552 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
555 def button_compute(self, cr, uid, ids, context=None, set_total=False):
556 self.button_reset_taxes(cr, uid, ids, context)
557 for inv in self.browse(cr, uid, ids):
559 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
562 def _convert_ref(self, cr, uid, ref):
563 return (ref or '').replace('/','')
565 def _get_analytic_lines(self, cr, uid, id):
566 inv = self.browse(cr, uid, [id])[0]
567 cur_obj = self.pool.get('res.currency')
569 company_currency = inv.company_id.currency_id.id
570 if inv.type in ('out_invoice', 'in_refund'):
575 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
577 if il['account_analytic_id']:
578 if inv.type in ('in_invoice', 'in_refund'):
581 ref = self._convert_ref(cr, uid, inv.number)
582 il['analytic_lines'] = [(0,0, {
584 'date': inv['date_invoice'],
585 'account_id': il['account_analytic_id'],
586 'unit_amount': il['quantity'],
587 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
588 'product_id': il['product_id'],
589 'product_uom_id': il['uos_id'],
590 'general_account_id': il['account_id'],
591 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
596 def action_date_assign(self, cr, uid, ids, *args):
597 for inv in self.browse(cr, uid, ids):
598 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
599 if res and res['value']:
600 self.write(cr, uid, [inv.id], res['value'])
603 def action_move_create(self, cr, uid, ids, *args):
604 ait_obj = self.pool.get('account.invoice.tax')
605 cur_obj = self.pool.get('res.currency')
607 for inv in self.browse(cr, uid, ids):
611 if not inv.date_invoice:
612 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
613 company_currency = inv.company_id.currency_id.id
614 # create the analytical lines
615 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
616 # one move line per invoice line
617 iml = self._get_analytic_lines(cr, uid, inv.id)
618 # check if taxes are all computed
620 context.update({'lang': inv.partner_id.lang})
621 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
623 for tax in compute_taxes.values():
624 ait_obj.create(cr, uid, tax)
627 for tax in inv.tax_line:
630 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
632 if not key in compute_taxes:
633 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
634 base = compute_taxes[key]['base']
635 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
636 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
637 for key in compute_taxes:
638 if not key in tax_key:
639 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
641 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
642 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
644 # one move line per tax line
645 iml += ait_obj.move_line_get(cr, uid, inv.id)
647 if inv.type in ('in_invoice', 'in_refund'):
650 ref = self._convert_ref(cr, uid, inv.number)
652 diff_currency_p = inv.currency_id.id <> company_currency
653 # create one move line for the total and possibly adjust the other lines amount
657 if inv.currency_id.id != company_currency:
658 i['currency_id'] = inv.currency_id.id
659 i['amount_currency'] = i['price']
660 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
661 company_currency, i['price'],
662 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
664 i['amount_currency'] = False
665 i['currency_id'] = False
667 if inv.type in ('out_invoice','in_refund'):
669 total_currency += i['amount_currency'] or i['price']
670 i['price'] = - i['price']
673 total_currency -= i['amount_currency'] or i['price']
674 acc_id = inv.account_id.id
676 name = inv['name'] or '/'
679 totlines = self.pool.get('account.payment.term').compute(cr,
680 uid, inv.payment_term.id, total, inv.date_invoice or False)
682 res_amount_currency = total_currency
685 if inv.currency_id.id != company_currency:
686 amount_currency = cur_obj.compute(cr, uid,
687 company_currency, inv.currency_id.id, t[1])
689 amount_currency = False
691 # last line add the diff
692 res_amount_currency -= amount_currency or 0
694 if i == len(totlines):
695 amount_currency += res_amount_currency
701 'account_id': acc_id,
702 'date_maturity': t[0],
703 'amount_currency': diff_currency_p \
704 and amount_currency or False,
705 'currency_id': diff_currency_p \
706 and inv.currency_id.id or False,
714 'account_id': acc_id,
715 'date_maturity' : inv.date_due or False,
716 'amount_currency': diff_currency_p \
717 and total_currency or False,
718 'currency_id': diff_currency_p \
719 and inv.currency_id.id or False,
723 date = inv.date_invoice or time.strftime('%Y-%m-%d')
724 part = inv.partner_id.id
726 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
728 if inv.journal_id.group_invoice_lines:
731 tmp = str(l['account_id'])
732 tmp += '-'+str(l.get('tax_code_id',"False"))
733 tmp += '-'+str(l.get('product_id',"False"))
734 tmp += '-'+str(l.get('analytic_account_id',"False"))
735 tmp += '-'+str(l.get('date_maturity',"False"))
738 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
739 line2[tmp]['debit'] = (am > 0) and am or 0.0
740 line2[tmp]['credit'] = (am < 0) and -am or 0.0
741 line2[tmp]['tax_amount'] += l['tax_amount']
742 line2[tmp]['analytic_lines'] += l['analytic_lines']
746 for key, val in line2.items():
747 line.append((0,0,val))
749 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
750 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
751 if journal.centralisation:
752 raise osv.except_osv(_('UserError'),
753 _('Cannot create invoice move on centralised journal'))
754 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
755 period_id=inv.period_id and inv.period_id.id or False
757 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'))])
759 period_id=period_ids[0]
761 move['period_id'] = period_id
763 i[2]['period_id'] = period_id
765 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
766 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
767 # make the invoice point to that move
768 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
769 self.pool.get('account.move').post(cr, uid, [move_id])
770 self._log_event(cr, uid, ids)
773 def line_get_convert(self, cr, uid, x, part, date, context=None):
775 'date_maturity': x.get('date_maturity', False),
777 'name':x['name'][:64],
779 'debit':x['price']>0 and x['price'],
780 'credit':x['price']<0 and -x['price'],
781 'account_id':x['account_id'],
782 'analytic_lines':x.get('analytic_lines', []),
783 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
784 'currency_id':x.get('currency_id', False),
785 'tax_code_id': x.get('tax_code_id', False),
786 'tax_amount': x.get('tax_amount', False),
787 'ref':x.get('ref',False),
788 'quantity':x.get('quantity',1.00),
789 'product_id':x.get('product_id', False),
790 'product_uom_id':x.get('uos_id',False),
791 'analytic_account_id':x.get('account_analytic_id',False),
794 def action_number(self, cr, uid, ids, *args):
795 cr.execute('SELECT id, type, number, move_id, reference ' \
796 'FROM account_invoice ' \
797 'WHERE id IN ('+','.join(map(str, ids))+')')
798 obj_inv = self.browse(cr, uid, ids)[0]
799 for (id, invtype, number, move_id, reference) in cr.fetchall():
801 if obj_inv.journal_id.invoice_sequence_id:
802 sid = obj_inv.journal_id.invoice_sequence_id.id
803 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
805 number = self.pool.get('ir.sequence').get(cr, uid,
806 'account.invoice.' + invtype)
807 if invtype in ('in_invoice', 'in_refund'):
810 ref = self._convert_ref(cr, uid, number)
811 cr.execute('UPDATE account_invoice SET number=%s ' \
812 'WHERE id=%s', (number, id))
813 cr.execute('UPDATE account_move SET ref=%s ' \
814 'WHERE id=%s AND (ref is null OR ref = \'\')',
816 cr.execute('UPDATE account_move_line SET ref=%s ' \
817 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
819 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
820 'FROM account_move_line ' \
821 'WHERE account_move_line.move_id = %s ' \
822 'AND account_analytic_line.move_id = account_move_line.id',
826 def action_cancel(self, cr, uid, ids, *args):
827 account_move_obj = self.pool.get('account.move')
828 invoices = self.read(cr, uid, ids, ['move_id'])
831 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
832 # delete the move this invoice was pointing to
833 # Note that the corresponding move_lines and move_reconciles
834 # will be automatically deleted too
835 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
836 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
837 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
842 def list_distinct_taxes(self, cr, uid, ids):
843 invoices = self.browse(cr, uid, ids)
846 for tax in inv.tax_line:
847 if not tax['name'] in taxes:
848 taxes[tax['name']] = {'name': tax['name']}
849 return taxes.values()
851 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
852 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
854 part=inv['partner_id'] and inv['partner_id'][0]
856 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
857 total = inv['amount_untaxed']
858 if inv['type'] in ('in_invoice','in_refund'):
859 partnertype='supplier'
860 eventtype = 'purchase'
863 partnertype = 'customer'
866 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
867 self.pool.get('res.partner.event').create(cr, uid, {'name':'Invoice: '+name, 'som':False, 'description':name+' '+str(inv['id']), 'document':name, 'partner_id':part, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'canal_id':False, 'user_id':uid, 'partner_type':partnertype, 'probability':1.0, 'planned_revenue':pr, 'planned_cost':pc, 'type':eventtype})
870 def name_get(self, cr, uid, ids, context=None):
874 'out_invoice': 'CI: ',
875 'in_invoice': 'SI: ',
876 'out_refund': 'OR: ',
879 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')]
881 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
888 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
890 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
891 return self.name_get(cr, user, ids, context)
893 def _refund_cleanup_lines(self, cr, uid, lines):
896 del line['invoice_id']
897 if 'account_id' in line:
898 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
899 if 'product_id' in line:
900 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
902 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
903 if 'invoice_line_tax_id' in line:
904 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
905 if 'account_analytic_id' in line:
906 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
907 if 'tax_code_id' in line :
908 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
909 line['tax_code_id'] = line['tax_code_id'][0]
910 if 'base_code_id' in line :
911 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
912 line['base_code_id'] = line['base_code_id'][0]
913 return map(lambda x: (0,0,x), lines)
915 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
916 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'])
919 for invoice in invoices:
923 'out_invoice': 'out_refund', # Customer Invoice
924 'in_invoice': 'in_refund', # Supplier Invoice
925 'out_refund': 'out_invoice', # Customer Refund
926 'in_refund': 'in_invoice', # Supplier Refund
930 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
931 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
933 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
934 tax_lines = filter(lambda l: l['manual'], tax_lines)
935 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
937 date = time.strftime('%Y-%m-%d')
939 'type': type_dict[invoice['type']],
940 'date_invoice': date,
943 'invoice_line': invoice_lines,
944 'tax_line': tax_lines
948 'period_id': period_id,
954 # take the id part of the tuple returned for many2one fields
955 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
956 'account_id', 'currency_id', 'payment_term', 'journal_id'):
957 invoice[field] = invoice[field] and invoice[field][0]
958 # create the new invoice
959 new_ids.append(self.create(cr, uid, invoice))
962 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=''):
965 #TODO check if we can use different period for payment and the writeoff line
966 assert len(ids)==1, "Can only pay one invoice at a time"
967 invoice = self.browse(cr, uid, ids[0])
968 src_account_id = invoice.account_id.id
969 # Take the seq as name for move
970 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
971 direction = types[invoice.type]
972 #take the choosen date
973 if 'date_p' in context and context['date_p']:
974 date=context['date_p']
976 date=time.strftime('%Y-%m-%d')
978 # Take the amount in currency and the currency of the payment
979 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
980 amount_currency = context['amount_currency']
981 currency_id = context['currency_id']
983 amount_currency = False
986 # Pay attention to the sign for both debit/credit AND amount_currency
988 'debit': direction * pay_amount>0 and direction * pay_amount,
989 'credit': direction * pay_amount<0 and - direction * pay_amount,
990 'account_id': src_account_id,
991 'partner_id': invoice.partner_id.id,
992 'ref':invoice.number,
994 'currency_id':currency_id,
995 'amount_currency':amount_currency and direction * amount_currency or 0.0,
996 'company_id': invoice.company_id.id,
999 'debit': direction * pay_amount<0 and - direction * pay_amount,
1000 'credit': direction * pay_amount>0 and direction * pay_amount,
1001 'account_id': pay_account_id,
1002 'partner_id': invoice.partner_id.id,
1003 'ref':invoice.number,
1005 'currency_id':currency_id,
1006 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1007 'company_id': invoice.company_id.id,
1011 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1015 lines = [(0, 0, l1), (0, 0, l2)]
1016 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1017 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1021 line = self.pool.get('account.move.line')
1022 move_ids = [move_id,]
1024 move_ids.append(invoice.move_id.id)
1025 cr.execute('SELECT id FROM account_move_line WHERE move_id = ANY(%s)',(move_ids,))
1026 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1027 for l in lines+invoice.payment_ids:
1028 if l.account_id.id==src_account_id:
1029 line_ids.append(l.id)
1030 total += (l.debit or 0.0) - (l.credit or 0.0)
1031 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
1032 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1034 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1036 # Update the stored value (fields.function), so we write to trigger recompute
1037 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1041 class account_invoice_line(osv.osv):
1042 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1044 cur_obj=self.pool.get('res.currency')
1045 for line in self.browse(cr, uid, ids):
1047 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1048 cur = line.invoice_id.currency_id
1049 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1051 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1055 def _price_unit_default(self, cr, uid, context=None):
1058 if 'check_total' in context:
1059 t = context['check_total']
1060 for l in context.get('invoice_line', {}):
1061 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1062 tax_obj = self.pool.get('account.tax')
1063 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1064 t = t - (p * l[2].get('quantity'))
1065 taxes = l[2].get('invoice_line_tax_id')
1066 if len(taxes[0]) >= 3 and taxes[0][2]:
1067 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1068 for tax in tax_obj.compute(cr, uid, taxes, p,l[2].get('quantity'), context.get('address_invoice_id', False), l[2].get('product_id', False), context.get('partner_id', False)):
1069 t = t - tax['amount']
1073 _name = "account.invoice.line"
1074 _description = "Invoice line"
1076 'name': fields.char('Description', size=256, required=True),
1077 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1078 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1079 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1080 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1081 '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."),
1082 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1083 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1084 'quantity': fields.float('Quantity', required=True),
1085 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1086 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1087 'note': fields.text('Notes'),
1088 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1089 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
1092 'quantity': lambda *a: 1,
1093 'discount': lambda *a: 0.0,
1094 'price_unit': _price_unit_default,
1097 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1098 tax_obj = self.pool.get('account.tax')
1100 taxes = tax_obj.browse(cr, uid, tax_id)
1101 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1102 price_unit = price_unit - tax['amount']
1103 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1105 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):
1108 company_id = context.get('company_id',False)
1110 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1112 if type in ('in_invoice', 'in_refund'):
1113 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1115 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1116 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1117 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1120 context.update({'lang': lang})
1122 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1125 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)])
1127 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)])
1128 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)])
1130 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)])
1133 in_acc = res.product_tmpl_id.property_account_income
1134 in_acc_cate = res.categ_id.property_account_income_categ
1138 app_acc_in = in_acc_cate
1140 app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1142 ex_acc = res.product_tmpl_id.property_account_expense
1143 ex_acc_cate = res.categ_id.property_account_expense_categ
1145 app_acc_exp = ex_acc
1147 app_acc_exp = ex_acc_cate
1149 app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1150 if not in_pro_id and not exp_pro_id:
1151 in_acc = res.product_tmpl_id.property_account_income
1152 in_acc_cate = res.categ_id.property_account_income_categ
1153 ex_acc = res.product_tmpl_id.property_account_expense
1154 ex_acc_cate = res.categ_id.property_account_expense_categ
1155 if in_acc or ex_acc:
1157 app_acc_exp = ex_acc
1159 app_acc_in = in_acc_cate
1160 app_acc_exp = ex_acc_cate
1162 # app_acc_in = self.pool.get('account.account').browse(cr,uid,in_pro_id)[0]
1163 # app_acc_exp = self.pool.get('account.account').browse(cr,uid,exp_pro_id)[0]
1164 if app_acc_in.company_id.id != company_id and app_acc_exp.company_id.id != company_id:
1165 in_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_in.name),('company_id','=',company_id)])
1166 exp_res_id=self.pool.get('account.account').search(cr,uid,[('name','=',app_acc_exp.name),('company_id','=',company_id)])
1167 if not in_res_id and not exp_res_id:
1168 raise osv.except_osv(_('Configration Error !'),
1169 _('Can not find account chart for this company, Please Create account.'))
1170 in_obj_acc=self.pool.get('account.account').browse(cr,uid,in_res_id)
1171 exp_obj_acc=self.pool.get('account.account').browse(cr,uid,exp_res_id)
1172 if in_acc or ex_acc:
1173 res.product_tmpl_id.property_account_income = in_obj_acc[0]
1174 res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1176 res.categ_id.property_account_income_categ = in_obj_acc[0]
1177 res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1179 if type in ('out_invoice','out_refund'):
1180 a = res.product_tmpl_id.property_account_income.id
1182 a = res.categ_id.property_account_income_categ.id
1184 a = res.product_tmpl_id.property_account_expense.id
1186 a = res.categ_id.property_account_expense_categ.id
1188 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1190 result['account_id'] = a
1193 tax_obj = self.pool.get('account.tax')
1194 if type in ('out_invoice', 'out_refund'):
1195 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1196 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1198 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)
1199 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1200 if type in ('in_invoice', 'in_refund'):
1201 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
1202 result.update(to_update)
1204 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1207 result['name'] = res.name
1210 result['uos_id'] = uom or res.uom_id.id or False
1211 if result['uos_id']:
1212 res2 = res.uom_id.category_id.id
1214 domain = {'uos_id':[('category_id','=',res2 )]}
1216 prod_pool=self.pool.get('product.product')
1217 result['categ_id'] = res.categ_id.id
1218 res_final = {'value':result, 'domain':domain}
1220 if not company_id and not currency_id:
1223 company = self.pool.get('res.company').browse(cr, uid, company_id)
1224 currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
1226 if company.currency_id.id != currency.id and currency.company_id.id == company.id:
1227 new_price = res_final['value']['price_unit'] * currency.rate
1228 res_final['value']['price_unit'] = new_price
1231 def move_line_get(self, cr, uid, invoice_id, context=None):
1234 tax_obj = self.pool.get('account.tax')
1235 cur_obj = self.pool.get('res.currency')
1236 ait_obj = self.pool.get('account.invoice.tax')
1237 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1238 company_currency = inv.company_id.currency_id.id
1239 cur = inv.currency_id
1241 for line in inv.invoice_line:
1242 mres = self.move_line_get_item(cr, uid, line, context)
1246 tax_code_found= False
1247 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1248 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1249 line.quantity, inv.address_invoice_id.id, line.product_id,
1252 if inv.type in ('out_invoice', 'in_invoice'):
1253 tax_code_id = tax['base_code_id']
1254 tax_amount = line.price_subtotal * tax['base_sign']
1256 tax_code_id = tax['ref_base_code_id']
1257 tax_amount = line.price_subtotal * tax['ref_base_sign']
1262 res.append(self.move_line_get_item(cr, uid, line, context))
1263 res[-1]['price'] = 0.0
1264 res[-1]['account_analytic_id'] = False
1265 elif not tax_code_id:
1267 tax_code_found = True
1269 res[-1]['tax_code_id'] = tax_code_id
1270 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1273 def move_line_get_item(self, cr, uid, line, context=None):
1276 'name': line.name[:64],
1277 'price_unit':line.price_unit,
1278 'quantity':line.quantity,
1279 'price':line.price_subtotal,
1280 'account_id':line.account_id.id,
1281 'product_id':line.product_id.id,
1282 'uos_id':line.uos_id.id,
1283 'account_analytic_id':line.account_analytic_id.id,
1284 'taxes':line.invoice_line_tax_id,
1287 # Set the tax field according to the account and the fiscal position
1289 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1292 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1293 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1294 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1295 r = {'value':{'invoice_line_tax_id': res}}
1297 account_invoice_line()
1299 class account_invoice_tax(osv.osv):
1300 _name = "account.invoice.tax"
1301 _description = "Invoice Tax"
1303 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1304 'name': fields.char('Tax Description', size=64, required=True),
1305 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1306 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1307 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1308 'manual': fields.boolean('Manual'),
1309 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1311 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1312 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1313 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1314 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1315 'company_id': fields.related('account_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1318 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1319 cur_obj = self.pool.get('res.currency')
1320 company_obj = self.pool.get('res.company')
1321 company_currency=False
1323 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1324 if currency_id and company_currency:
1325 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1326 return {'value': {'base_amount':base}}
1328 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1329 cur_obj = self.pool.get('res.currency')
1330 company_obj = self.pool.get('res.company')
1331 company_currency=False
1333 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1334 if currency_id and company_currency:
1335 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1336 return {'value': {'tax_amount':amount}}
1340 'manual': lambda *a: 1,
1341 'base_amount': lambda *a: 0.0,
1342 'tax_amount': lambda *a: 0.0,
1344 def compute(self, cr, uid, invoice_id, context={}):
1346 tax_obj = self.pool.get('account.tax')
1347 cur_obj = self.pool.get('res.currency')
1348 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1349 cur = inv.currency_id
1350 company_currency = inv.company_id.currency_id.id
1352 for line in inv.invoice_line:
1353 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, inv.address_invoice_id.id, line.product_id, inv.partner_id):
1355 val['invoice_id'] = inv.id
1356 val['name'] = tax['name']
1357 val['amount'] = tax['amount']
1358 val['manual'] = False
1359 val['sequence'] = tax['sequence']
1360 val['base'] = tax['price_unit'] * line['quantity']
1362 if inv.type in ('out_invoice','in_invoice'):
1363 val['base_code_id'] = tax['base_code_id']
1364 val['tax_code_id'] = tax['tax_code_id']
1365 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)
1366 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)
1367 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1369 val['base_code_id'] = tax['ref_base_code_id']
1370 val['tax_code_id'] = tax['ref_tax_code_id']
1371 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)
1372 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)
1373 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1375 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1376 if not key in tax_grouped:
1377 tax_grouped[key] = val
1379 tax_grouped[key]['amount'] += val['amount']
1380 tax_grouped[key]['base'] += val['base']
1381 tax_grouped[key]['base_amount'] += val['base_amount']
1382 tax_grouped[key]['tax_amount'] += val['tax_amount']
1384 for t in tax_grouped.values():
1385 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1386 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1387 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1390 def move_line_get(self, cr, uid, invoice_id):
1392 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1393 for t in cr.dictfetchall():
1394 if not t['amount'] \
1395 and not t['tax_code_id'] \
1396 and not t['tax_amount']:
1401 'price_unit': t['amount'],
1403 'price': t['amount'] or 0.0,
1404 'account_id': t['account_id'],
1405 'tax_code_id': t['tax_code_id'],
1406 'tax_amount': t['tax_amount']
1409 account_invoice_tax()
1411 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: