1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
24 from operator import itemgetter
27 from osv import fields, osv
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 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
65 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
66 journal_obj = self.pool.get('account.journal')
67 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')), ('refund_journal', '=', refund_journal.get(type_inv, False))], limit=1)
73 def _get_currency(self, cr, uid, context):
74 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
76 return user.company_id.currency_id.id
78 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
80 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
81 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
82 tt = type2journal.get(type_inv, 'sale')
83 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
85 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
88 def _get_type(self, cr, uid, context=None):
91 type = context.get('type', 'out_invoice')
94 def _reconciled(self, cr, uid, ids, name, args, context):
97 res[id] = self.test_paid(cr, uid, [id])
100 def _get_reference_type(self, cr, uid, context=None):
101 return [('none', _('Free Reference'))]
103 def _amount_residual(self, cr, uid, ids, name, args, context=None):
105 data_inv = self.browse(cr, uid, ids)
106 cur_obj = self.pool.get('res.currency')
109 context.update({'date':inv.date_invoice})
110 context_unreconciled=context.copy()
111 for lines in inv.move_lines:
112 debit_tmp = lines.debit
113 credit_tmp = lines.credit
114 # If currency conversion needed
115 if inv.company_id.currency_id.id <> inv.currency_id.id:
116 # If invoice paid, compute currency amount according to invoice date
117 # otherwise, take the line date
118 if not inv.reconciled:
119 context.update({'date':lines.date})
120 context_unreconciled.update({'date':lines.date})
121 # If amount currency setted, compute for debit and credit in company currency
122 if lines.amount_currency < 0:
123 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))
124 elif lines.amount_currency > 0:
125 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))
126 # Then, recomput into invoice currency to avoid rounding trouble !
127 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
128 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
133 if not inv.amount_total:
135 elif inv.type in ('out_invoice','in_refund'):
136 amount = credit-debit
137 result = inv.amount_total - amount
139 amount = debit-credit
140 result = inv.amount_total - amount
141 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
142 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
146 def _get_lines(self, cr, uid, ids, name, arg, context=None):
149 move_lines = self.move_line_id_payment_get(cr,uid,[id])
154 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
155 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
156 for line in data_lines:
158 if line.reconcile_id:
159 ids_line = line.reconcile_id.line_id
160 elif line.reconcile_partial_id:
161 ids_line = line.reconcile_partial_id.line_partial_ids
162 l = map(lambda x: x.id, ids_line)
163 partial_ids.append(line.id)
164 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
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):
186 temp_lines = []#Added temp list to avoid duplicate records
188 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
189 elif m.reconcile_partial_id:
190 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
191 lines += [x for x in temp_lines if x not in lines]
194 lines = filter(lambda x: x not in src, lines)
195 result[invoice.id] = lines
198 def _get_invoice_from_line(self, cr, uid, ids, context={}):
200 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
201 if line.reconcile_partial_id:
202 for line2 in line.reconcile_partial_id.line_partial_ids:
203 move[line2.move_id.id] = True
204 if line.reconcile_id:
205 for line2 in line.reconcile_id.line_id:
206 move[line2.move_id.id] = True
209 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
212 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
214 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
215 for line in r.line_partial_ids:
216 move[line.move_id.id] = True
217 for line in r.line_id:
218 move[line.move_id.id] = True
222 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
225 _name = "account.invoice"
226 _description = 'Invoice'
229 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
230 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
231 'type': fields.selection([
232 ('out_invoice','Customer Invoice'),
233 ('in_invoice','Supplier Invoice'),
234 ('out_refund','Customer Refund'),
235 ('in_refund','Supplier Refund'),
236 ],'Type', readonly=True, select=True),
238 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
239 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
240 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
242 'comment': fields.text('Additional Information'),
244 'state': fields.selection([
246 ('proforma','Pro-forma'),
247 ('proforma2','Pro-forma'),
250 ('cancel','Cancelled')
251 ],'State', select=True, readonly=True),
253 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
254 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
255 help="If you use payment terms, the due date will be computed automatically at the generation "\
256 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."),
257 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
258 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
259 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
260 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
261 help="If you use payment terms, the due date will be computed automatically at the generation "\
262 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
263 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
264 'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
266 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
267 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
268 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
270 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
271 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
273 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
274 'account.invoice.tax': (_get_invoice_tax, None, 20),
275 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
278 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
280 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
281 'account.invoice.tax': (_get_invoice_tax, None, 20),
282 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
285 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
287 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
288 'account.invoice.tax': (_get_invoice_tax, None, 20),
289 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
292 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
293 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
294 'company_id': fields.many2one('res.company', 'Company', required=True),
295 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
296 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
298 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
299 'account.move.line': (_get_invoice_from_line, None, 50),
300 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
301 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
302 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
303 help='The bank account to pay to or to be paid from'),
304 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
305 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
307 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
308 'account.invoice.tax': (_get_invoice_tax, None, 50),
309 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
310 'account.move.line': (_get_invoice_from_line, None, 50),
311 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
313 help="Remaining amount due."),
314 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
315 'move_name': fields.char('Account Move', size=64),
316 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
320 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
321 'state': lambda *a: 'draft',
322 'journal_id': _get_journal,
323 'currency_id': _get_currency,
324 'company_id': lambda self, cr, uid, context: \
325 self.pool.get('res.users').browse(cr, uid, uid,
326 context=context).company_id.id,
327 'reference_type': lambda *a: 'none',
328 'check_total': lambda *a: 0.0,
331 def unlink(self, cr, uid, ids, context=None):
332 invoices = self.read(cr, uid, ids, ['state'])
335 if t['state'] in ('draft', 'cancel'):
336 unlink_ids.append(t['id'])
338 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
339 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
342 # def get_invoice_address(self, cr, uid, ids):
343 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
346 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
347 date_invoice=False, payment_term=False, partner_bank_id=False):
348 invoice_addr_id = False
349 contact_addr_id = False
350 partner_payment_term = False
353 fiscal_position = False
355 opt = [('uid', str(uid))]
358 opt.insert(0, ('id', partner_id))
359 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
360 contact_addr_id = res['contact']
361 invoice_addr_id = res['invoice']
362 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
363 if type in ('out_invoice', 'out_refund'):
364 acc_id = p.property_account_receivable.id
366 acc_id = p.property_account_payable.id
367 fiscal_position = p.property_account_position and p.property_account_position.id or False
368 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
370 bank_id = p.bank_ids[0].id
373 'address_contact_id': contact_addr_id,
374 'address_invoice_id': invoice_addr_id,
375 'account_id': acc_id,
376 'payment_term': partner_payment_term,
377 'fiscal_position': fiscal_position
381 if type in ('in_invoice', 'in_refund'):
382 result['value']['partner_bank'] = bank_id
384 if partner_bank_id != bank_id:
385 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
386 result['value'].update(to_update['value'])
389 def onchange_currency_id(self, cr, uid, ids, curr_id):
392 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
393 if not payment_term_id:
396 pt_obj= self.pool.get('account.payment.term')
397 if not date_invoice :
398 date_invoice = time.strftime('%Y-%m-%d')
400 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
403 pterm_list = [line[0] for line in pterm_list]
405 res= {'value':{'date_due': pterm_list[-1]}}
407 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
411 def onchange_invoice_line(self, cr, uid, ids, lines):
414 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
417 # go from canceled state to draft state
418 def action_cancel_draft(self, cr, uid, ids, *args):
419 self.write(cr, uid, ids, {'state':'draft'})
420 wf_service = netsvc.LocalService("workflow")
422 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
428 # return the ids of the move lines which has the same account than the invoice
430 def move_line_id_payment_get(self, cr, uid, ids, *args):
432 if not ids: return res
433 cr.execute('SELECT l.id '\
434 'FROM account_move_line l '\
435 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
437 'AND l.account_id=i.account_id',
439 res = map(itemgetter(0), cr.fetchall())
442 def copy(self, cr, uid, id, default=None, context=None):
445 default = default.copy()
446 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
447 if 'date_invoice' not in default:
448 default['date_invoice'] = False
449 if 'date_due' not in default:
450 default['date_due'] = False
451 return super(account_invoice, self).copy(cr, uid, id, default, context)
453 def test_paid(self, cr, uid, ids, *args):
454 res = self.move_line_id_payment_get(cr, uid, ids)
459 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
460 ok = ok and bool(cr.fetchone()[0])
463 def button_reset_taxes(self, cr, uid, ids, context=None):
466 ait_obj = self.pool.get('account.invoice.tax')
468 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
469 partner = self.browse(cr, uid, id,context=context).partner_id
471 context.update({'lang': partner.lang})
472 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
473 ait_obj.create(cr, uid, taxe)
474 # Update the stored value (fields.function), so we write to trigger recompute
475 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
476 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
479 def button_compute(self, cr, uid, ids, context=None, set_total=False):
480 self.button_reset_taxes(cr, uid, ids, context)
481 for inv in self.browse(cr, uid, ids):
483 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
486 def _convert_ref(self, cr, uid, ref):
487 return (ref or '').replace('/','')
489 def _get_analytic_lines(self, cr, uid, id):
490 inv = self.browse(cr, uid, [id])[0]
491 cur_obj = self.pool.get('res.currency')
493 company_currency = inv.company_id.currency_id.id
494 if inv.type in ('out_invoice', 'in_refund'):
499 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
501 if il['account_analytic_id']:
502 if inv.type in ('in_invoice', 'in_refund'):
505 ref = self._convert_ref(cr, uid, inv.number)
506 il['analytic_lines'] = [(0,0, {
508 'date': inv['date_invoice'],
509 'account_id': il['account_analytic_id'],
510 'unit_amount': il['quantity'],
511 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
512 'product_id': il['product_id'],
513 'product_uom_id': il['uos_id'],
514 'general_account_id': il['account_id'],
515 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
520 def action_date_assign(self, cr, uid, ids, *args):
521 for inv in self.browse(cr, uid, ids):
522 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
523 if res and res['value']:
524 self.write(cr, uid, [inv.id], res['value'])
527 def action_move_create(self, cr, uid, ids, *args):
528 ait_obj = self.pool.get('account.invoice.tax')
529 cur_obj = self.pool.get('res.currency')
531 for inv in self.browse(cr, uid, ids):
535 if not inv.date_invoice:
536 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
537 company_currency = inv.company_id.currency_id.id
538 # create the analytical lines
539 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
540 # one move line per invoice line
541 iml = self._get_analytic_lines(cr, uid, inv.id)
542 # check if taxes are all computed
544 context.update({'lang': inv.partner_id.lang})
545 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
547 for tax in compute_taxes.values():
548 ait_obj.create(cr, uid, tax)
551 for tax in inv.tax_line:
554 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
556 if not key in compute_taxes:
557 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
558 base = compute_taxes[key]['base']
559 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
560 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
561 for key in compute_taxes:
562 if not key in tax_key:
563 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
565 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
566 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
568 # one move line per tax line
569 iml += ait_obj.move_line_get(cr, uid, inv.id)
571 if inv.type in ('in_invoice', 'in_refund'):
574 ref = self._convert_ref(cr, uid, inv.number)
576 diff_currency_p = inv.currency_id.id <> company_currency
577 # create one move line for the total and possibly adjust the other lines amount
581 if inv.currency_id.id != company_currency:
582 i['currency_id'] = inv.currency_id.id
583 i['amount_currency'] = i['price']
584 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
585 company_currency, i['price'],
586 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
588 i['amount_currency'] = False
589 i['currency_id'] = False
591 if inv.type in ('out_invoice','in_refund'):
593 total_currency += i['amount_currency'] or i['price']
594 i['price'] = - i['price']
597 total_currency -= i['amount_currency'] or i['price']
598 acc_id = inv.account_id.id
600 name = inv['name'] or '/'
603 totlines = self.pool.get('account.payment.term').compute(cr,
604 uid, inv.payment_term.id, total, inv.date_invoice or False)
606 res_amount_currency = total_currency
609 if inv.currency_id.id != company_currency:
610 amount_currency = cur_obj.compute(cr, uid,
611 company_currency, inv.currency_id.id, t[1])
613 amount_currency = False
615 # last line add the diff
616 res_amount_currency -= amount_currency or 0
618 if i == len(totlines):
619 amount_currency += res_amount_currency
625 'account_id': acc_id,
626 'date_maturity': t[0],
627 'amount_currency': diff_currency_p \
628 and amount_currency or False,
629 'currency_id': diff_currency_p \
630 and inv.currency_id.id or False,
638 'account_id': acc_id,
639 'date_maturity' : inv.date_due or False,
640 'amount_currency': diff_currency_p \
641 and total_currency or False,
642 'currency_id': diff_currency_p \
643 and inv.currency_id.id or False,
647 date = inv.date_invoice or time.strftime('%Y-%m-%d')
648 part = inv.partner_id.id
650 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
652 if inv.journal_id.group_invoice_lines:
655 tmp = str(l['account_id'])
656 tmp += '-'+str(l.get('tax_code_id',"False"))
657 tmp += '-'+str(l.get('product_id',"False"))
658 tmp += '-'+str(l.get('analytic_account_id',"False"))
659 tmp += '-'+str(l.get('date_maturity',"False"))
662 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
663 line2[tmp]['debit'] = (am > 0) and am or 0.0
664 line2[tmp]['credit'] = (am < 0) and -am or 0.0
665 line2[tmp]['tax_amount'] += l['tax_amount']
666 line2[tmp]['analytic_lines'] += l['analytic_lines']
670 for key, val in line2.items():
671 line.append((0,0,val))
673 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
674 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
675 if journal.centralisation:
676 raise osv.except_osv(_('UserError'),
677 _('Cannot create invoice move on centralised journal'))
678 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
679 period_id=inv.period_id and inv.period_id.id or False
681 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'))])
683 period_id=period_ids[0]
685 move['period_id'] = period_id
687 i[2]['period_id'] = period_id
689 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
690 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
691 # make the invoice point to that move
692 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
693 self.pool.get('account.move').post(cr, uid, [move_id])
694 self._log_event(cr, uid, ids)
697 def line_get_convert(self, cr, uid, x, part, date, context=None):
699 'date_maturity': x.get('date_maturity', False),
701 'name':x['name'][:64],
703 'debit':x['price']>0 and x['price'],
704 'credit':x['price']<0 and -x['price'],
705 'account_id':x['account_id'],
706 'analytic_lines':x.get('analytic_lines', []),
707 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
708 'currency_id':x.get('currency_id', False),
709 'tax_code_id': x.get('tax_code_id', False),
710 'tax_amount': x.get('tax_amount', False),
711 'ref':x.get('ref',False),
712 'quantity':x.get('quantity',1.00),
713 'product_id':x.get('product_id', False),
714 'product_uom_id':x.get('uos_id',False),
715 'analytic_account_id':x.get('account_analytic_id',False),
718 def action_number(self, cr, uid, ids, *args):
719 cr.execute('SELECT id, type, number, move_id, reference ' \
720 'FROM account_invoice ' \
723 obj_inv = self.browse(cr, uid, ids)[0]
724 for (id, invtype, number, move_id, reference) in cr.fetchall():
726 if obj_inv.journal_id.invoice_sequence_id:
727 sid = obj_inv.journal_id.invoice_sequence_id.id
728 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
730 number = self.pool.get('ir.sequence').get(cr, uid,
731 'account.invoice.' + invtype)
732 if invtype in ('in_invoice', 'in_refund'):
735 ref = self._convert_ref(cr, uid, number)
736 cr.execute('UPDATE account_invoice SET number=%s ' \
737 'WHERE id=%s', (number, id))
738 cr.execute('UPDATE account_move SET ref=%s ' \
739 'WHERE id=%s AND (ref is null OR ref = \'\')',
741 cr.execute('UPDATE account_move_line SET ref=%s ' \
742 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
744 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
745 'FROM account_move_line ' \
746 'WHERE account_move_line.move_id = %s ' \
747 'AND account_analytic_line.move_id = account_move_line.id',
751 def action_cancel(self, cr, uid, ids, *args):
752 account_move_obj = self.pool.get('account.move')
753 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
756 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
757 # delete the move this invoice was pointing to
758 # Note that the corresponding move_lines and move_reconciles
759 # will be automatically deleted too
760 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
762 self.pool.get('account.move.line').write(cr, uid, i['payment_ids'], {'reconcile_partial_id': False})
763 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
764 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
769 def list_distinct_taxes(self, cr, uid, ids):
770 invoices = self.browse(cr, uid, ids)
773 for tax in inv.tax_line:
774 if not tax['name'] in taxes:
775 taxes[tax['name']] = {'name': tax['name']}
776 return taxes.values()
778 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
779 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
781 part=inv['partner_id'] and inv['partner_id'][0]
783 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
784 total = inv['amount_untaxed']
785 if inv['type'] in ('in_invoice','in_refund'):
786 partnertype='supplier'
787 eventtype = 'purchase'
790 partnertype = 'customer'
793 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
794 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})
797 def name_get(self, cr, uid, ids, context=None):
801 'out_invoice': 'CI: ',
802 'in_invoice': 'SI: ',
803 'out_refund': 'OR: ',
806 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')]
808 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
815 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
817 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
818 return self.name_get(cr, user, ids, context)
820 def _refund_cleanup_lines(self, cr, uid, lines):
823 del line['invoice_id']
824 if 'account_id' in line:
825 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
826 if 'product_id' in line:
827 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
829 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
830 if 'invoice_line_tax_id' in line:
831 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
832 if 'account_analytic_id' in line:
833 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
834 if 'tax_code_id' in line :
835 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
836 line['tax_code_id'] = line['tax_code_id'][0]
837 if 'base_code_id' in line :
838 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
839 line['base_code_id'] = line['base_code_id'][0]
840 return map(lambda x: (0,0,x), lines)
842 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
843 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'])
846 for invoice in invoices:
850 'out_invoice': 'out_refund', # Customer Invoice
851 'in_invoice': 'in_refund', # Supplier Invoice
852 'out_refund': 'out_invoice', # Customer Refund
853 'in_refund': 'in_invoice', # Supplier Refund
857 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
858 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
860 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
861 tax_lines = filter(lambda l: l['manual'], tax_lines)
862 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
864 date = time.strftime('%Y-%m-%d')
866 'type': type_dict[invoice['type']],
867 'date_invoice': date,
870 'invoice_line': invoice_lines,
871 'tax_line': tax_lines
875 'period_id': period_id,
881 # take the id part of the tuple returned for many2one fields
882 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
883 'account_id', 'currency_id', 'payment_term', 'journal_id'):
884 invoice[field] = invoice[field] and invoice[field][0]
885 # create the new invoice
886 new_ids.append(self.create(cr, uid, invoice))
889 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=''):
892 #TODO check if we can use different period for payment and the writeoff line
893 assert len(ids)==1, "Can only pay one invoice at a time"
894 invoice = self.browse(cr, uid, ids[0])
895 src_account_id = invoice.account_id.id
896 # Take the seq as name for move
897 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
898 direction = types[invoice.type]
899 #take the choosen date
900 if 'date_p' in context and context['date_p']:
901 date=context['date_p']
903 date=time.strftime('%Y-%m-%d')
905 # Take the amount in currency and the currency of the payment
906 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
907 amount_currency = context['amount_currency']
908 currency_id = context['currency_id']
910 amount_currency = False
913 if invoice.type in ('in_invoice', 'in_refund'):
914 ref = invoice.reference
916 ref = self._convert_ref(cr, uid, invoice.number)
917 # Pay attention to the sign for both debit/credit AND amount_currency
919 'debit': direction * pay_amount>0 and direction * pay_amount,
920 'credit': direction * pay_amount<0 and - direction * pay_amount,
921 'account_id': src_account_id,
922 'partner_id': invoice.partner_id.id,
925 'currency_id':currency_id,
926 'amount_currency':amount_currency and direction * amount_currency or 0.0,
929 'debit': direction * pay_amount<0 and - direction * pay_amount,
930 'credit': direction * pay_amount>0 and direction * pay_amount,
931 'account_id': pay_account_id,
932 'partner_id': invoice.partner_id.id,
935 'currency_id':currency_id,
936 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
940 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
944 lines = [(0, 0, l1), (0, 0, l2)]
945 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
946 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
950 line = self.pool.get('account.move.line')
951 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
952 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
953 for l in lines+invoice.payment_ids:
954 if l.account_id.id==src_account_id:
955 line_ids.append(l.id)
956 total += (l.debit or 0.0) - (l.credit or 0.0)
957 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
958 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
960 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
962 # Update the stored value (fields.function), so we write to trigger recompute
963 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
967 class account_invoice_line(osv.osv):
968 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
970 cur_obj=self.pool.get('res.currency')
971 for line in self.browse(cr, uid, ids):
973 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
974 cur = line.invoice_id.currency_id
975 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
977 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
981 def _price_unit_default(self, cr, uid, context=None):
984 if 'check_total' in context:
985 t = context['check_total']
986 for l in context.get('invoice_line', {}):
987 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
988 tax_obj = self.pool.get('account.tax')
989 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
990 t = t - (p * l[2].get('quantity'))
991 taxes = l[2].get('invoice_line_tax_id')
992 if len(taxes[0]) >= 3 and taxes[0][2]:
993 taxes=tax_obj.browse(cr, uid, taxes[0][2])
994 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)):
995 t = t - tax['amount']
999 _name = "account.invoice.line"
1000 _description = "Invoice line"
1002 'name': fields.char('Description', size=256, required=True),
1003 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1004 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1005 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1006 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1007 '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."),
1008 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1009 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1010 'quantity': fields.float('Quantity', required=True),
1011 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1012 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1013 'note': fields.text('Notes'),
1014 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1017 'quantity': lambda *a: 1,
1018 'discount': lambda *a: 0.0,
1019 'price_unit': _price_unit_default,
1022 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1023 tax_obj = self.pool.get('account.tax')
1025 taxes = tax_obj.browse(cr, uid, tax_id)
1026 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1027 price_unit = price_unit - tax['amount']
1028 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1030 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, context=None):
1034 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1036 if type in ('in_invoice', 'in_refund'):
1037 return {'domain':{'product_uom':[]}}
1039 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1040 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1041 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1044 context.update({'lang': part.lang})
1046 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1048 if type in ('out_invoice','out_refund'):
1049 a = res.product_tmpl_id.property_account_income.id
1051 a = res.categ_id.property_account_income_categ.id
1053 a = res.product_tmpl_id.property_account_expense.id
1055 a = res.categ_id.property_account_expense_categ.id
1057 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1059 result['account_id'] = a
1062 tax_obj = self.pool.get('account.tax')
1063 if type in ('out_invoice', 'out_refund'):
1064 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1065 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1067 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)
1068 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1069 if type in ('in_invoice', 'in_refund'):
1070 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit or res.standard_price, qty, address_invoice_id, product, partner_id, context=context)
1071 result.update(to_update)
1073 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1076 result['name'] = res.partner_ref
1079 result['uos_id'] = uom or res.uom_id.id or False
1080 if result['uos_id']:
1081 res2 = res.uom_id.category_id.id
1083 domain = {'uos_id':[('category_id','=',res2 )]}
1084 return {'value':result, 'domain':domain}
1086 def move_line_get(self, cr, uid, invoice_id, context=None):
1089 tax_obj = self.pool.get('account.tax')
1090 cur_obj = self.pool.get('res.currency')
1091 ait_obj = self.pool.get('account.invoice.tax')
1092 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1093 company_currency = inv.company_id.currency_id.id
1094 cur = inv.currency_id
1096 for line in inv.invoice_line:
1097 mres = self.move_line_get_item(cr, uid, line, context)
1101 tax_code_found= False
1102 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1103 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1104 line.quantity, inv.address_invoice_id.id, line.product_id,
1107 if inv.type in ('out_invoice', 'in_invoice'):
1108 tax_code_id = tax['base_code_id']
1109 tax_amount = line.price_subtotal * tax['base_sign']
1111 tax_code_id = tax['ref_base_code_id']
1112 tax_amount = line.price_subtotal * tax['ref_base_sign']
1117 res.append(self.move_line_get_item(cr, uid, line, context))
1118 res[-1]['price'] = 0.0
1119 res[-1]['account_analytic_id'] = False
1120 elif not tax_code_id:
1122 tax_code_found = True
1124 res[-1]['tax_code_id'] = tax_code_id
1125 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1128 def move_line_get_item(self, cr, uid, line, context=None):
1131 'name': line.name[:64],
1132 'price_unit':line.price_unit,
1133 'quantity':line.quantity,
1134 'price':line.price_subtotal,
1135 'account_id':line.account_id.id,
1136 'product_id':line.product_id.id,
1137 'uos_id':line.uos_id.id,
1138 'account_analytic_id':line.account_analytic_id.id,
1139 'taxes':line.invoice_line_tax_id,
1142 # Set the tax field according to the account and the fiscal position
1144 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1147 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1148 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1149 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1150 r = {'value':{'invoice_line_tax_id': res}}
1152 account_invoice_line()
1154 class account_invoice_tax(osv.osv):
1155 _name = "account.invoice.tax"
1156 _description = "Invoice Tax"
1158 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1159 'name': fields.char('Tax Description', size=64, required=True),
1160 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1161 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1162 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1163 'manual': fields.boolean('Manual'),
1164 'sequence': fields.integer('Sequence'),
1166 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1167 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1168 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1169 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1172 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1173 cur_obj = self.pool.get('res.currency')
1174 company_obj = self.pool.get('res.company')
1175 company_currency=False
1177 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1178 if currency_id and company_currency:
1179 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1180 return {'value': {'base_amount':base}}
1182 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1183 cur_obj = self.pool.get('res.currency')
1184 company_obj = self.pool.get('res.company')
1185 company_currency=False
1187 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1188 if currency_id and company_currency:
1189 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1190 return {'value': {'tax_amount':amount}}
1194 'manual': lambda *a: 1,
1195 'base_amount': lambda *a: 0.0,
1196 'tax_amount': lambda *a: 0.0,
1198 def compute(self, cr, uid, invoice_id, context={}):
1200 tax_obj = self.pool.get('account.tax')
1201 cur_obj = self.pool.get('res.currency')
1202 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1203 cur = inv.currency_id
1204 company_currency = inv.company_id.currency_id.id
1206 for line in inv.invoice_line:
1207 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):
1209 val['invoice_id'] = inv.id
1210 val['name'] = tax['name']
1211 val['amount'] = tax['amount']
1212 val['manual'] = False
1213 val['sequence'] = tax['sequence']
1214 val['base'] = tax['price_unit'] * line['quantity']
1216 if inv.type in ('out_invoice','in_invoice'):
1217 val['base_code_id'] = tax['base_code_id']
1218 val['tax_code_id'] = tax['tax_code_id']
1219 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)
1220 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)
1221 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1223 val['base_code_id'] = tax['ref_base_code_id']
1224 val['tax_code_id'] = tax['ref_tax_code_id']
1225 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)
1226 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)
1227 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1229 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1230 if not key in tax_grouped:
1231 tax_grouped[key] = val
1233 tax_grouped[key]['amount'] += val['amount']
1234 tax_grouped[key]['base'] += val['base']
1235 tax_grouped[key]['base_amount'] += val['base_amount']
1236 tax_grouped[key]['tax_amount'] += val['tax_amount']
1238 for t in tax_grouped.values():
1239 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1240 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1241 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1244 def move_line_get(self, cr, uid, invoice_id):
1246 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1247 for t in cr.dictfetchall():
1248 if not t['amount'] \
1249 and not t['tax_code_id'] \
1250 and not t['tax_amount']:
1255 'price_unit': t['amount'],
1257 'price': t['amount'] or 0.0,
1258 'account_id': t['account_id'],
1259 'tax_code_id': t['tax_code_id'],
1260 'tax_amount': t['tax_amount']
1263 account_invoice_tax()
1265 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: