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
28 from osv.orm import except_orm
30 from tools import config
31 from tools.translate import _
33 class fiscalyear_seq(osv.osv):
34 _name = "fiscalyear.seq"
35 _description = "Maintains Invoice sequences with Fiscal Year"
36 _rec_name = 'fiscalyear_id'
38 'journal_id': fields.many2one('account.journal', 'Journal'),
39 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
40 'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
45 class account_invoice(osv.osv):
46 def _amount_all(self, cr, uid, ids, name, args, context=None):
48 for invoice in self.browse(cr,uid,ids, context=context):
50 'amount_untaxed': 0.0,
54 for line in invoice.invoice_line:
55 res[invoice.id]['amount_untaxed'] += line.price_subtotal
56 for line in invoice.tax_line:
57 res[invoice.id]['amount_tax'] += line.amount
58 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
61 def _get_journal(self, cr, uid, context):
64 type_inv = context.get('type', 'out_invoice')
65 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
66 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
67 journal_obj = self.pool.get('account.journal')
68 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')), ('refund_journal', '=', refund_journal.get(type_inv, False))], limit=1)
74 def _get_currency(self, cr, uid, context):
75 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
77 return user.company_id.currency_id.id
79 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
81 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
82 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
83 tt = type2journal.get(type_inv, 'sale')
84 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
86 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
89 def _get_type(self, cr, uid, context=None):
92 type = context.get('type', 'out_invoice')
95 def _reconciled(self, cr, uid, ids, name, args, context):
98 res[id] = self.test_paid(cr, uid, [id])
101 def _get_reference_type(self, cr, uid, context=None):
102 return [('none', _('Free Reference'))]
104 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')
114 inv_total = inv.amount_total
115 context_unreconciled = context.copy()
116 for lines in inv.move_lines:
117 if lines.currency_id and lines.currency_id.id == inv.currency_id.id:
118 if inv.type in ('out_invoice','in_refund'):
119 inv_total += lines.amount_currency
121 inv_total -= lines.amount_currency
123 context_unreconciled.update({'date': lines.date})
124 amount_in_invoice_currency = cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id,abs(lines.debit-lines.credit),round=False,context=context_unreconciled)
125 inv_total -= amount_in_invoice_currency
128 res[inv.id] = self.pool.get('res.currency').round(cr, uid, inv.currency_id, result)
131 def _get_lines(self, cr, uid, ids, name, arg, context=None):
134 move_lines = self.move_line_id_payment_get(cr,uid,[id])
139 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
140 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
141 for line in data_lines:
143 if line.reconcile_id:
144 ids_line = line.reconcile_id.line_id
145 elif line.reconcile_partial_id:
146 ids_line = line.reconcile_partial_id.line_partial_ids
147 l = map(lambda x: x.id, ids_line)
148 partial_ids.append(line.id)
149 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
152 def _get_invoice_line(self, cr, uid, ids, context=None):
154 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
155 result[line.invoice_id.id] = True
158 def _get_invoice_tax(self, cr, uid, ids, context=None):
160 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
161 result[tax.invoice_id.id] = True
164 def _compute_lines(self, cr, uid, ids, name, args, context=None):
166 for invoice in self.browse(cr, uid, ids, context):
167 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
170 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
171 temp_lines = []#Added temp list to avoid duplicate records
173 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
174 elif m.reconcile_partial_id:
175 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
176 lines += [x for x in temp_lines if x not in lines]
178 lines = filter(lambda x: x not in src, lines)
179 result[invoice.id] = lines
182 def _get_invoice_from_line(self, cr, uid, ids, context={}):
184 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
185 if line.reconcile_partial_id:
186 for line2 in line.reconcile_partial_id.line_partial_ids:
187 move[line2.move_id.id] = True
188 if line.reconcile_id:
189 for line2 in line.reconcile_id.line_id:
190 move[line2.move_id.id] = True
193 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
196 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
198 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
199 for line in r.line_partial_ids:
200 move[line.move_id.id] = True
201 for line in r.line_id:
202 move[line.move_id.id] = True
206 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
209 _name = "account.invoice"
210 _description = 'Invoice'
213 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
214 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
215 'type': fields.selection([
216 ('out_invoice','Customer Invoice'),
217 ('in_invoice','Supplier Invoice'),
218 ('out_refund','Customer Refund'),
219 ('in_refund','Supplier Refund'),
220 ],'Type', readonly=True, select=True),
222 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
223 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
224 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
226 'comment': fields.text('Additional Information'),
228 'state': fields.selection([
230 ('proforma','Pro-forma'),
231 ('proforma2','Pro-forma'),
234 ('cancel','Cancelled')
235 ],'State', select=True, readonly=True),
237 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
238 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
239 help="If you use payment terms, the due date will be computed automatically at the generation "\
240 "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."),
241 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
242 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
243 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
244 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
245 help="If you use payment terms, the due date will be computed automatically at the generation "\
246 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
247 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
248 '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)]}),
250 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
251 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
252 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
254 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
255 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
257 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
258 'account.invoice.tax': (_get_invoice_tax, None, 20),
259 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
262 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
264 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
265 'account.invoice.tax': (_get_invoice_tax, None, 20),
266 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
269 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
271 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
272 'account.invoice.tax': (_get_invoice_tax, None, 20),
273 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
276 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
277 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
278 'company_id': fields.many2one('res.company', 'Company', required=True),
279 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
280 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
282 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
283 'account.move.line': (_get_invoice_from_line, None, 50),
284 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
285 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
286 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
287 help='The bank account to pay to or to be paid from'),
288 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
289 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
291 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
292 'account.invoice.tax': (_get_invoice_tax, None, 50),
293 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
294 'account.move.line': (_get_invoice_from_line, None, 50),
295 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
297 help="Remaining amount due."),
298 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
299 'move_name': fields.char('Account Move', size=64),
300 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
304 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
305 'state': lambda *a: 'draft',
306 'journal_id': _get_journal,
307 'currency_id': _get_currency,
308 'company_id': lambda self, cr, uid, context: \
309 self.pool.get('res.users').browse(cr, uid, uid,
310 context=context).company_id.id,
311 'reference_type': lambda *a: 'none',
312 'check_total': lambda *a: 0.0,
315 def create(self, cr, uid, vals, context={}):
317 res = super(account_invoice, self).create(cr, uid, vals, context)
320 if '"journal_id" viol' in e.args[0]:
321 raise except_orm(_('Configuration Error!'),
322 _('There is no Accounting Journal of type Sale/Purchase defined!'))
324 raise except_orm(_('UnknownError'), str(e))
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'])
341 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
342 date_invoice=False, payment_term=False, partner_bank=False):
343 invoice_addr_id = False
344 contact_addr_id = False
345 partner_payment_term = False
348 fiscal_position = False
350 opt = [('uid', str(uid))]
353 opt.insert(0, ('id', partner_id))
354 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
355 contact_addr_id = res['contact']
356 invoice_addr_id = res['invoice']
357 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
358 if type in ('out_invoice', 'out_refund'):
359 acc_id = p.property_account_receivable.id
361 acc_id = p.property_account_payable.id
362 fiscal_position = p.property_account_position and p.property_account_position.id or False
363 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
365 bank_id = p.bank_ids[0].id
368 'address_contact_id': contact_addr_id,
369 'address_invoice_id': invoice_addr_id,
370 'account_id': acc_id,
371 'payment_term': partner_payment_term,
372 'fiscal_position': fiscal_position
376 if type in ('in_invoice', 'in_refund'):
377 result['value']['partner_bank'] = bank_id
379 if partner_bank != bank_id:
380 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
381 result['value'].update(to_update['value'])
384 def onchange_currency_id(self, cr, uid, ids, curr_id):
387 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
388 if not payment_term_id:
391 pt_obj= self.pool.get('account.payment.term')
392 if not date_invoice :
393 date_invoice = time.strftime('%Y-%m-%d')
395 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
398 pterm_list = [line[0] for line in pterm_list]
400 res= {'value':{'date_due': pterm_list[-1]}}
402 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
406 def onchange_invoice_line(self, cr, uid, ids, lines):
409 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
412 # go from canceled state to draft state
413 def action_cancel_draft(self, cr, uid, ids, *args):
414 self.write(cr, uid, ids, {'state':'draft'})
415 wf_service = netsvc.LocalService("workflow")
417 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
423 # return the ids of the move lines which has the same account than the invoice
425 def move_line_id_payment_get(self, cr, uid, ids, *args):
427 if not ids: return res
428 cr.execute('SELECT l.id '\
429 'FROM account_move_line l '\
430 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
432 'AND l.account_id=i.account_id',
434 res = map(itemgetter(0), cr.fetchall())
437 def copy(self, cr, uid, id, default=None, context=None):
440 default = default.copy()
441 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
442 if 'date_invoice' not in default:
443 default['date_invoice'] = False
444 if 'date_due' not in default:
445 default['date_due'] = False
446 return super(account_invoice, self).copy(cr, uid, id, default, context)
448 def test_paid(self, cr, uid, ids, *args):
449 res = self.move_line_id_payment_get(cr, uid, ids)
454 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
455 ok = ok and bool(cr.fetchone()[0])
458 def button_reset_taxes(self, cr, uid, ids, context=None):
462 ait_obj = self.pool.get('account.invoice.tax')
464 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
465 partner = self.browse(cr, uid, id,context=ctx).partner_id
467 ctx.update({'lang': partner.lang})
468 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
469 ait_obj.create(cr, uid, taxe)
470 # Update the stored value (fields.function), so we write to trigger recompute
471 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
472 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
475 def button_compute(self, cr, uid, ids, context=None, set_total=False):
476 self.button_reset_taxes(cr, uid, ids, context)
477 for inv in self.browse(cr, uid, ids):
479 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
482 def _convert_ref(self, cr, uid, ref):
483 return (ref or '').replace('/','')
485 def _get_analytic_lines(self, cr, uid, id):
486 inv = self.browse(cr, uid, [id])[0]
487 cur_obj = self.pool.get('res.currency')
489 company_currency = inv.company_id.currency_id.id
490 if inv.type in ('out_invoice', 'in_refund'):
495 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
497 if il['account_analytic_id']:
498 if inv.type in ('in_invoice', 'in_refund'):
501 ref = self._convert_ref(cr, uid, inv.number)
502 il['analytic_lines'] = [(0,0, {
504 'date': inv['date_invoice'],
505 'account_id': il['account_analytic_id'],
506 'unit_amount': il['quantity'],
507 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
508 'product_id': il['product_id'],
509 'product_uom_id': il['uos_id'],
510 'general_account_id': il['account_id'],
511 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
516 def action_date_assign(self, cr, uid, ids, *args):
517 for inv in self.browse(cr, uid, ids):
518 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
519 if res and res['value']:
520 self.write(cr, uid, [inv.id], res['value'])
523 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
524 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
525 Hook method to be overridden in additional modules to verify and possibly alter the
526 move lines to be created by an invoice, for special cases.
527 :param invoice_browse: browsable record of the invoice that is generating the move lines
528 :param move_lines: list of dictionaries with the account.move.lines (as for create())
529 :return: the (possibly updated) final move_lines to create for this invoice
533 def action_move_create(self, cr, uid, ids, *args):
534 ait_obj = self.pool.get('account.invoice.tax')
535 cur_obj = self.pool.get('res.currency')
537 for inv in self.browse(cr, uid, ids):
541 if not inv.date_invoice:
542 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
543 company_currency = inv.company_id.currency_id.id
544 # create the analytical lines
545 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
546 # one move line per invoice line
547 iml = self._get_analytic_lines(cr, uid, inv.id)
548 # check if taxes are all computed
550 ctx.update({'lang': inv.partner_id.lang})
551 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
553 for tax in compute_taxes.values():
554 ait_obj.create(cr, uid, tax)
557 for tax in inv.tax_line:
560 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
562 if not key in compute_taxes:
563 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
564 base = compute_taxes[key]['base']
565 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
566 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
567 for key in compute_taxes:
568 if not key in tax_key:
569 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
571 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
572 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
574 # one move line per tax line
575 iml += ait_obj.move_line_get(cr, uid, inv.id)
577 if inv.type in ('in_invoice', 'in_refund'):
580 ref = self._convert_ref(cr, uid, inv.number)
582 diff_currency_p = inv.currency_id.id <> company_currency
583 # create one move line for the total and possibly adjust the other lines amount
587 if inv.currency_id.id != company_currency:
588 i['currency_id'] = inv.currency_id.id
589 i['amount_currency'] = i['price']
590 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
591 company_currency, i['price'],
592 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
594 i['amount_currency'] = False
595 i['currency_id'] = False
597 if inv.type in ('out_invoice','in_refund'):
599 total_currency += i['amount_currency'] or i['price']
600 i['price'] = - i['price']
603 total_currency -= i['amount_currency'] or i['price']
604 acc_id = inv.account_id.id
606 name = inv['name'] or '/'
609 totlines = self.pool.get('account.payment.term').compute(cr,
610 uid, inv.payment_term.id, total, inv.date_invoice or False)
612 res_amount_currency = total_currency
615 if inv.currency_id.id != company_currency:
616 amount_currency = cur_obj.compute(cr, uid,
617 company_currency, inv.currency_id.id, t[1])
619 amount_currency = False
621 # last line add the diff
622 res_amount_currency -= amount_currency or 0
624 if i == len(totlines):
625 amount_currency += res_amount_currency
631 'account_id': acc_id,
632 'date_maturity': t[0],
633 'amount_currency': diff_currency_p \
634 and amount_currency or False,
635 'currency_id': diff_currency_p \
636 and inv.currency_id.id or False,
644 'account_id': acc_id,
645 'date_maturity' : inv.date_due or False,
646 'amount_currency': diff_currency_p \
647 and total_currency or False,
648 'currency_id': diff_currency_p \
649 and inv.currency_id.id or False,
653 date = inv.date_invoice or time.strftime('%Y-%m-%d')
654 part = inv.partner_id.id
656 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
658 if inv.journal_id.group_invoice_lines:
661 tmp = str(l['account_id'])
662 tmp += '-'+str(l.get('tax_code_id',"False"))
663 tmp += '-'+str(l.get('product_id',"False"))
664 tmp += '-'+str(l.get('analytic_account_id',"False"))
665 tmp += '-'+str(l.get('date_maturity',"False"))
668 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
669 line2[tmp]['debit'] = (am > 0) and am or 0.0
670 line2[tmp]['credit'] = (am < 0) and -am or 0.0
671 line2[tmp]['tax_amount'] += l['tax_amount']
672 line2[tmp]['analytic_lines'] += l['analytic_lines']
676 for key, val in line2.items():
677 line.append((0,0,val))
679 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
680 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
681 if journal.centralisation:
682 raise osv.except_osv(_('UserError'),
683 _('Cannot create invoice move on centralised journal'))
685 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
687 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
688 period_id=inv.period_id and inv.period_id.id or False
690 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'))])
692 period_id=period_ids[0]
694 move['period_id'] = period_id
696 i[2]['period_id'] = period_id
698 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
699 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
700 # make the invoice point to that move
701 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
702 # Pass invoice in context in method post: used if you want to get the same
703 # account move reference when creating the same invoice after a cancelled one:
704 self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
705 self._log_event(cr, uid, ids)
708 def line_get_convert(self, cr, uid, x, part, date, context=None):
710 'date_maturity': x.get('date_maturity', False),
712 'name':x['name'][:64],
714 'debit':x['price']>0 and x['price'],
715 'credit':x['price']<0 and -x['price'],
716 'account_id':x['account_id'],
717 'analytic_lines':x.get('analytic_lines', []),
718 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
719 'currency_id':x.get('currency_id', False),
720 'tax_code_id': x.get('tax_code_id', False),
721 'tax_amount': x.get('tax_amount', False),
722 'ref':x.get('ref',False),
723 'quantity':x.get('quantity',1.00),
724 'product_id':x.get('product_id', False),
725 'product_uom_id':x.get('uos_id',False),
726 'analytic_account_id':x.get('account_analytic_id',False),
729 def action_number(self, cr, uid, ids, *args):
730 cr.execute('SELECT id, type, number, move_id, reference ' \
731 'FROM account_invoice ' \
734 obj_inv = self.browse(cr, uid, ids)[0]
735 for (id, invtype, number, move_id, reference) in cr.fetchall():
738 'fiscalyear_id' : obj_inv.period_id.fiscalyear_id.id,
740 if obj_inv.journal_id.invoice_sequence_id:
741 sid = obj_inv.journal_id.invoice_sequence_id.id
742 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
744 number = self.pool.get('ir.sequence').get_id(cr, uid,
745 'account.invoice.' + invtype,
749 raise osv.except_osv(_('Warning !'), _('There is no active invoice sequence defined for the journal !'))
751 if invtype in ('in_invoice', 'in_refund'):
754 ref = self._convert_ref(cr, uid, number)
755 cr.execute('UPDATE account_invoice SET number=%s ' \
756 'WHERE id=%s', (number, id))
757 cr.execute('UPDATE account_move SET ref=%s ' \
758 'WHERE id=%s AND (ref is null OR ref = \'\')',
760 cr.execute('UPDATE account_move_line SET ref=%s ' \
761 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
763 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
764 'FROM account_move_line ' \
765 'WHERE account_move_line.move_id = %s ' \
766 'AND account_analytic_line.move_id = account_move_line.id',
770 def action_cancel(self, cr, uid, ids, *args):
771 account_move_obj = self.pool.get('account.move')
772 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
775 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
776 # delete the move this invoice was pointing to
777 # Note that the corresponding move_lines and move_reconciles
778 # will be automatically deleted too
779 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
781 account_move_line_obj = self.pool.get('account.move.line')
782 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
783 for move_line in pay_ids:
784 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
785 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
787 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
788 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
793 def list_distinct_taxes(self, cr, uid, ids):
794 invoices = self.browse(cr, uid, ids)
797 for tax in inv.tax_line:
798 if not tax['name'] in taxes:
799 taxes[tax['name']] = {'name': tax['name']}
800 return taxes.values()
802 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
803 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
805 part=inv['partner_id'] and inv['partner_id'][0]
807 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
808 total = inv['amount_untaxed']
809 if inv['type'] in ('in_invoice','in_refund'):
810 partnertype='supplier'
811 eventtype = 'purchase'
814 partnertype = 'customer'
817 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
818 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})
821 def name_get(self, cr, uid, ids, context=None):
825 'out_invoice': 'CI: ',
826 'in_invoice': 'SI: ',
827 'out_refund': 'OR: ',
830 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')]
832 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
839 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
841 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
842 return self.name_get(cr, user, ids, context)
844 def _refund_cleanup_lines(self, cr, uid, lines):
847 del line['invoice_id']
848 if 'account_id' in line:
849 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
850 if 'product_id' in line:
851 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
853 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
854 if 'invoice_line_tax_id' in line:
855 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
856 if 'account_analytic_id' in line:
857 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
858 if 'tax_code_id' in line :
859 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
860 line['tax_code_id'] = line['tax_code_id'][0]
861 if 'base_code_id' in line :
862 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
863 line['base_code_id'] = line['base_code_id'][0]
864 return map(lambda x: (0,0,x), lines)
866 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
867 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'])
870 for invoice in invoices:
874 'out_invoice': 'out_refund', # Customer Invoice
875 'in_invoice': 'in_refund', # Supplier Invoice
876 'out_refund': 'out_invoice', # Customer Refund
877 'in_refund': 'in_invoice', # Supplier Refund
881 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
882 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
884 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
885 tax_lines = filter(lambda l: l['manual'], tax_lines)
886 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
888 date = time.strftime('%Y-%m-%d')
890 'type': type_dict[invoice['type']],
891 'date_invoice': date,
894 'invoice_line': invoice_lines,
895 'tax_line': tax_lines
899 'period_id': period_id,
905 # take the id part of the tuple returned for many2one fields
906 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
907 'account_id', 'currency_id', 'payment_term', 'journal_id'):
908 invoice[field] = invoice[field] and invoice[field][0]
909 # create the new invoice
910 new_ids.append(self.create(cr, uid, invoice))
913 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=''):
916 #TODO check if we can use different period for payment and the writeoff line
917 assert len(ids)==1, "Can only pay one invoice at a time"
918 invoice = self.browse(cr, uid, ids[0])
919 src_account_id = invoice.account_id.id
920 # Take the seq as name for move
921 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
922 direction = types[invoice.type]
923 #take the choosen date
924 if 'date_p' in context and context['date_p']:
925 date=context['date_p']
927 date=time.strftime('%Y-%m-%d')
929 # Take the amount in currency and the currency of the payment
930 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
931 amount_currency = context['amount_currency']
932 currency_id = context['currency_id']
934 amount_currency = False
937 if invoice.type in ('in_invoice', 'in_refund'):
938 ref = invoice.reference
940 ref = self._convert_ref(cr, uid, invoice.number)
941 # Pay attention to the sign for both debit/credit AND amount_currency
943 'debit': direction * pay_amount>0 and direction * pay_amount,
944 'credit': direction * pay_amount<0 and - direction * pay_amount,
945 'account_id': src_account_id,
946 'partner_id': invoice.partner_id.id,
949 'currency_id':currency_id,
950 'amount_currency':amount_currency and direction * amount_currency or 0.0,
953 'debit': direction * pay_amount<0 and - direction * pay_amount,
954 'credit': direction * pay_amount>0 and direction * pay_amount,
955 'account_id': pay_account_id,
956 'partner_id': invoice.partner_id.id,
959 'currency_id':currency_id,
960 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
964 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
968 lines = [(0, 0, l1), (0, 0, l2)]
969 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
970 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
974 line = self.pool.get('account.move.line')
975 cr.execute('SELECT id FROM account_move_line '\
976 'WHERE move_id in %s',
977 ((move_id, invoice.move_id.id),))
978 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
980 for l in lines+invoice.payment_ids:
981 if l.account_id.id==src_account_id:
982 line_ids.append(l.id)
983 total += (l.debit or 0.0) - (l.credit or 0.0)
985 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
986 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
988 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
990 # Update the stored value (fields.function), so we write to trigger recompute
991 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
995 class account_invoice_line(osv.osv):
996 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
998 cur_obj=self.pool.get('res.currency')
999 for line in self.browse(cr, uid, ids):
1001 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1002 cur = line.invoice_id.currency_id
1003 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1005 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1009 def _price_unit_default(self, cr, uid, context=None):
1012 if 'check_total' in context:
1013 t = context['check_total']
1014 for l in context.get('invoice_line', {}):
1015 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1016 tax_obj = self.pool.get('account.tax')
1017 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1018 t = t - (p * l[2].get('quantity'))
1019 taxes = l[2].get('invoice_line_tax_id')
1020 if len(taxes[0]) >= 3 and taxes[0][2]:
1021 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1022 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)):
1023 t = t - tax['amount']
1027 _name = "account.invoice.line"
1028 _description = "Invoice line"
1030 'name': fields.char('Description', size=256, required=True),
1031 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1032 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1033 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1034 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1035 '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."),
1036 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1037 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1038 'quantity': fields.float('Quantity', required=True),
1039 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1040 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1041 'note': fields.text('Notes'),
1042 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1045 'quantity': lambda *a: 1,
1046 'discount': lambda *a: 0.0,
1047 'price_unit': _price_unit_default,
1050 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1051 tax_obj = self.pool.get('account.tax')
1053 taxes = tax_obj.browse(cr, uid, tax_id)
1054 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1055 price_unit = price_unit - tax['amount']
1056 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1058 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):
1062 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1064 if type in ('in_invoice', 'in_refund'):
1065 return {'value':{}, 'domain':{'product_uom':[]}}
1067 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1068 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1069 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1072 context.update({'lang': part.lang})
1074 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1076 if type in ('out_invoice','out_refund'):
1077 a = res.product_tmpl_id.property_account_income.id
1079 a = res.categ_id.property_account_income_categ.id
1081 a = res.product_tmpl_id.property_account_expense.id
1083 a = res.categ_id.property_account_expense_categ.id
1085 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1087 result['account_id'] = a
1090 tax_obj = self.pool.get('account.tax')
1091 if type in ('out_invoice', 'out_refund'):
1092 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1093 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1095 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)
1096 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1097 if type in ('in_invoice', 'in_refund'):
1098 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)
1099 result.update(to_update)
1101 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1104 result['name'] = res.partner_ref
1107 result['uos_id'] = uom or res.uom_id.id or False
1108 result['note'] = res.description
1109 if result['uos_id']:
1110 res2 = res.uom_id.category_id.id
1112 domain = {'uos_id':[('category_id','=',res2 )]}
1113 return {'value':result, 'domain':domain}
1115 def move_line_get(self, cr, uid, invoice_id, context=None):
1118 tax_obj = self.pool.get('account.tax')
1119 cur_obj = self.pool.get('res.currency')
1120 ait_obj = self.pool.get('account.invoice.tax')
1121 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1122 company_currency = inv.company_id.currency_id.id
1123 cur = inv.currency_id
1125 for line in inv.invoice_line:
1126 mres = self.move_line_get_item(cr, uid, line, context)
1130 tax_code_found= False
1131 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1132 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1133 line.quantity, inv.address_invoice_id.id, line.product_id,
1136 if inv.type in ('out_invoice', 'in_invoice'):
1137 tax_code_id = tax['base_code_id']
1138 tax_amount = line.price_subtotal * tax['base_sign']
1140 tax_code_id = tax['ref_base_code_id']
1141 tax_amount = line.price_subtotal * tax['ref_base_sign']
1146 res.append(self.move_line_get_item(cr, uid, line, context))
1147 res[-1]['price'] = 0.0
1148 res[-1]['account_analytic_id'] = False
1149 elif not tax_code_id:
1151 tax_code_found = True
1153 res[-1]['tax_code_id'] = tax_code_id
1154 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1157 def move_line_get_item(self, cr, uid, line, context=None):
1160 'name': line.name[:64],
1161 'price_unit':line.price_unit,
1162 'quantity':line.quantity,
1163 'price':line.price_subtotal,
1164 'account_id':line.account_id.id,
1165 'product_id':line.product_id.id,
1166 'uos_id':line.uos_id.id,
1167 'account_analytic_id':line.account_analytic_id.id,
1168 'taxes':line.invoice_line_tax_id,
1171 # Set the tax field according to the account and the fiscal position
1173 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1176 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1177 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1178 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1179 r = {'value':{'invoice_line_tax_id': res}}
1181 account_invoice_line()
1183 class account_invoice_tax(osv.osv):
1184 _name = "account.invoice.tax"
1185 _description = "Invoice Tax"
1187 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1188 'name': fields.char('Tax Description', size=64, required=True),
1189 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1190 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1191 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1192 'manual': fields.boolean('Manual'),
1193 'sequence': fields.integer('Sequence'),
1195 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1196 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1197 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1198 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1201 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1202 cur_obj = self.pool.get('res.currency')
1203 company_obj = self.pool.get('res.company')
1204 company_currency=False
1206 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1207 if currency_id and company_currency:
1208 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1209 return {'value': {'base_amount':base}}
1211 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1212 cur_obj = self.pool.get('res.currency')
1213 company_obj = self.pool.get('res.company')
1214 company_currency=False
1216 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1217 if currency_id and company_currency:
1218 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1219 return {'value': {'tax_amount':amount}}
1223 'manual': lambda *a: 1,
1224 'base_amount': lambda *a: 0.0,
1225 'tax_amount': lambda *a: 0.0,
1227 def compute(self, cr, uid, invoice_id, context={}):
1229 tax_obj = self.pool.get('account.tax')
1230 cur_obj = self.pool.get('res.currency')
1231 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1232 cur = inv.currency_id
1233 company_currency = inv.company_id.currency_id.id
1235 for line in inv.invoice_line:
1236 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):
1238 val['invoice_id'] = inv.id
1239 val['name'] = tax['name']
1240 val['amount'] = tax['amount']
1241 val['manual'] = False
1242 val['sequence'] = tax['sequence']
1243 val['base'] = tax['price_unit'] * line['quantity']
1245 if inv.type in ('out_invoice','in_invoice'):
1246 val['base_code_id'] = tax['base_code_id']
1247 val['tax_code_id'] = tax['tax_code_id']
1248 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)
1249 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)
1250 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1252 val['base_code_id'] = tax['ref_base_code_id']
1253 val['tax_code_id'] = tax['ref_tax_code_id']
1254 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)
1255 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)
1256 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1258 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1259 if not key in tax_grouped:
1260 tax_grouped[key] = val
1262 tax_grouped[key]['amount'] += val['amount']
1263 tax_grouped[key]['base'] += val['base']
1264 tax_grouped[key]['base_amount'] += val['base_amount']
1265 tax_grouped[key]['tax_amount'] += val['tax_amount']
1267 for t in tax_grouped.values():
1268 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1269 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1270 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1271 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1274 def move_line_get(self, cr, uid, invoice_id):
1276 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1277 for t in cr.dictfetchall():
1278 if not t['amount'] \
1279 and not t['tax_code_id'] \
1280 and not t['tax_amount']:
1285 'price_unit': t['amount'],
1287 'price': t['amount'] or 0.0,
1288 'account_id': t['account_id'],
1289 'tax_code_id': t['tax_code_id'],
1290 'tax_amount': t['tax_amount']
1293 account_invoice_tax()
1295 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: