1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from osv import fields, osv
28 from mx.DateTime import RelativeDateTime
29 from tools import config
30 from tools.translate import _
32 class fiscalyear_seq(osv.osv):
33 _name = "fiscalyear.seq"
34 _description = "Maintains Invoice sequences with Fiscal Year"
35 _rec_name = 'fiscalyear_id'
37 'journal_id': fields.many2one('account.journal', 'Journal'),
38 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
39 'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
44 class account_invoice(osv.osv):
45 def _amount_all(self, cr, uid, ids, name, args, context=None):
47 for invoice in self.browse(cr,uid,ids, context=context):
49 'amount_untaxed': 0.0,
53 for line in invoice.invoice_line:
54 res[invoice.id]['amount_untaxed'] += line.price_subtotal
55 for line in invoice.tax_line:
56 res[invoice.id]['amount_tax'] += line.amount
57 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
60 def _get_journal(self, cr, uid, context):
63 type_inv = context.get('type', 'out_invoice')
64 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
65 journal_obj = self.pool.get('account.journal')
66 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
72 def _get_currency(self, cr, uid, context):
73 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
75 return user.company_id.currency_id.id
77 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
79 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
80 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
81 tt = type2journal.get(type_inv, 'sale')
82 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
84 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
87 def _get_type(self, cr, uid, context=None):
90 type = context.get('type', 'out_invoice')
93 def _reconciled(self, cr, uid, ids, name, args, context):
96 res[id] = self.test_paid(cr, uid, [id])
99 def _get_reference_type(self, cr, uid, context=None):
100 return [('none', _('Free Reference'))]
102 def _amount_residual(self, cr, uid, ids, name, args, context=None):
104 data_inv = self.browse(cr, uid, ids)
105 cur_obj = self.pool.get('res.currency')
108 for lines in inv.move_lines:
109 if lines.account_id.company_currency_id.id <> inv.currency_id.id:
111 debit += cur_obj.compute(cr, uid, lines.account_id.company_currency_id.id, inv.currency_id.id, lines.debit)
113 credit += cur_obj.compute(cr, uid, lines.account_id.company_currency_id.id, inv.currency_id.id, lines.credit)
116 credit += lines.credit
118 if not inv.amount_total:
120 elif inv.type in ('out_invoice','in_refund'):
121 result = inv.amount_total * (1.0 - credit / (debit + inv.amount_total))
123 result = inv.amount_total * (1.0 - debit / (credit + inv.amount_total))
124 res[inv.id] = round(result,int(config['price_accuracy']))
127 def _get_lines(self, cr, uid, ids, name, arg, context=None):
130 move_lines = self.move_line_id_payment_get(cr,uid,[id])
134 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
135 for line in data_lines:
137 if line.reconcile_id:
138 ids_line = line.reconcile_id.line_id
139 elif line.reconcile_partial_id:
140 ids_line = line.reconcile_partial_id.line_partial_ids
141 l = map(lambda x: x.id, ids_line)
142 res[id]=[x for x in l if x <> line.id]
145 def _get_invoice_line(self, cr, uid, ids, context=None):
147 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
148 result[line.invoice_id.id] = True
151 def _get_invoice_tax(self, cr, uid, ids, context=None):
153 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
154 result[tax.invoice_id.id] = True
157 def _compute_lines(self, cr, uid, ids, name, args, context=None):
159 for invoice in self.browse(cr, uid, ids, context):
160 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
163 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
165 lines += map(lambda x: x.id, m.reconcile_id.line_id)
166 elif m.reconcile_partial_id:
167 lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
169 lines = filter(lambda x: x not in src, lines)
170 result[invoice.id] = lines
173 def _get_invoice_from_line(self, cr, uid, ids, context={}):
175 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
176 if line.reconcile_partial_id:
177 for line2 in line.reconcile_partial_id.line_partial_ids:
178 move[line2.move_id.id] = True
179 if line.reconcile_id:
180 for line2 in line.reconcile_id.line_id:
181 move[line2.move_id.id] = True
184 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
187 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
189 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
190 for line in r.line_partial_ids:
191 move[line.move_id.id] = True
192 for line in r.line_id:
193 move[line.move_id.id] = True
197 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
200 _name = "account.invoice"
201 _description = 'Invoice'
204 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
205 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
206 'type': fields.selection([
207 ('out_invoice','Customer Invoice'),
208 ('in_invoice','Supplier Invoice'),
209 ('out_refund','Customer Refund'),
210 ('in_refund','Supplier Refund'),
211 ],'Type', readonly=True, select=True),
213 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
214 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
215 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
217 'comment': fields.text('Additional Information'),
219 'state': fields.selection([
221 ('proforma','Pro-forma'),
222 ('proforma2','Pro-forma'),
225 ('cancel','Cancelled')
226 ],'State', select=True, readonly=True),
228 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
229 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
230 help="If you use payment terms, the due date will be computed automatically at the generation "\
231 "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."),
232 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
233 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
234 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
235 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
236 help="If you use payment terms, the due date will be computed automatically at the generation "\
237 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
238 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
239 '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)]}),
241 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
242 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
243 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
245 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
246 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
248 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
249 'account.invoice.tax': (_get_invoice_tax, None, 20),
250 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
253 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
255 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
256 'account.invoice.tax': (_get_invoice_tax, None, 20),
257 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
260 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
262 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
263 'account.invoice.tax': (_get_invoice_tax, None, 20),
264 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
267 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
268 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
269 'company_id': fields.many2one('res.company', 'Company', required=True),
270 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
271 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
273 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
274 'account.move.line': (_get_invoice_from_line, None, 50),
275 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
276 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
277 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
278 help='The bank account to pay to or to be paid from'),
279 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
280 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
282 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
283 'account.invoice.tax': (_get_invoice_tax, None, 50),
284 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
285 'account.move.line': (_get_invoice_from_line, None, 50),
286 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
288 help="Remaining amount due."),
289 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
290 'move_name': fields.char('Account Move', size=64),
291 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
295 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
296 'state': lambda *a: 'draft',
297 'journal_id': _get_journal,
298 'currency_id': _get_currency,
299 'company_id': lambda self, cr, uid, context: \
300 self.pool.get('res.users').browse(cr, uid, uid,
301 context=context).company_id.id,
302 'reference_type': lambda *a: 'none',
305 def unlink(self, cr, uid, ids, context=None):
306 invoices = self.read(cr, uid, ids, ['state'])
309 if t['state'] in ('draft', 'cancel'):
310 unlink_ids.append(t['id'])
312 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
313 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
316 # def get_invoice_address(self, cr, uid, ids):
317 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
320 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
321 date_invoice=False, payment_term=False, partner_bank_id=False):
322 invoice_addr_id = False
323 contact_addr_id = False
324 partner_payment_term = False
327 fiscal_position = False
329 opt = [('uid', str(uid))]
332 opt.insert(0, ('id', partner_id))
333 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
334 contact_addr_id = res['contact']
335 invoice_addr_id = res['invoice']
336 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
337 if type in ('out_invoice', 'out_refund'):
338 acc_id = p.property_account_receivable.id
340 acc_id = p.property_account_payable.id
341 fiscal_position = p.property_account_position and p.property_account_position.id or False
342 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
344 bank_id = p.bank_ids[0].id
347 'address_contact_id': contact_addr_id,
348 'address_invoice_id': invoice_addr_id,
349 'account_id': acc_id,
350 'payment_term': partner_payment_term,
351 'fiscal_position': fiscal_position
355 if type in ('in_invoice', 'in_refund'):
356 result['value']['partner_bank'] = bank_id
358 if partner_bank_id != bank_id:
359 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
360 result['value'].update(to_update['value'])
363 def onchange_currency_id(self, cr, uid, ids, curr_id):
366 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
367 if not payment_term_id:
370 pt_obj= self.pool.get('account.payment.term')
371 if not date_invoice :
372 date_invoice = time.strftime('%Y-%m-%d')
374 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
377 pterm_list = [line[0] for line in pterm_list]
379 res= {'value':{'date_due': pterm_list[-1]}}
381 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
385 def onchange_invoice_line(self, cr, uid, ids, lines):
388 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
391 # go from canceled state to draft state
392 def action_cancel_draft(self, cr, uid, ids, *args):
393 self.write(cr, uid, ids, {'state':'draft'})
394 wf_service = netsvc.LocalService("workflow")
396 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
402 # return the ids of the move lines which has the same account than the invoice
404 def move_line_id_payment_get(self, cr, uid, ids, *args):
406 if not ids: return res
409 from account_move_line l \
410 left join account_invoice i on (i.move_id=l.move_id) \
411 where i.id in ('+','.join(map(str,map(int, ids)))+') and l.account_id=i.account_id')
412 res = map(lambda x: x[0], cr.fetchall())
415 def copy(self, cr, uid, id, default=None, context=None):
418 default = default.copy()
419 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
420 if 'date_invoice' not in default:
421 default['date_invoice'] = False
422 if 'date_due' not in default:
423 default['date_due'] = False
424 return super(account_invoice, self).copy(cr, uid, id, default, context)
426 def test_paid(self, cr, uid, ids, *args):
427 res = self.move_line_id_payment_get(cr, uid, ids)
432 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
433 ok = ok and bool(cr.fetchone()[0])
436 def button_reset_taxes(self, cr, uid, ids, context=None):
439 ait_obj = self.pool.get('account.invoice.tax')
441 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
442 partner = self.browse(cr, uid, id,context=context).partner_id
444 context.update({'lang': partner.lang})
445 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
446 ait_obj.create(cr, uid, taxe)
447 # Update the stored value (fields.function), so we write to trigger recompute
448 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
449 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
452 def button_compute(self, cr, uid, ids, context=None, set_total=False):
453 self.button_reset_taxes(cr, uid, ids, context)
454 for inv in self.browse(cr, uid, ids):
456 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
459 def _convert_ref(self, cr, uid, ref):
460 return (ref or '').replace('/','')
462 def _get_analytic_lines(self, cr, uid, id):
463 inv = self.browse(cr, uid, [id])[0]
464 cur_obj = self.pool.get('res.currency')
466 company_currency = inv.company_id.currency_id.id
467 if inv.type in ('out_invoice', 'in_refund'):
472 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
474 if il['account_analytic_id']:
475 if inv.type in ('in_invoice', 'in_refund'):
478 ref = self._convert_ref(cr, uid, inv.number)
479 il['analytic_lines'] = [(0,0, {
481 'date': inv['date_invoice'],
482 'account_id': il['account_analytic_id'],
483 'unit_amount': il['quantity'],
484 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
485 'product_id': il['product_id'],
486 'product_uom_id': il['uos_id'],
487 'general_account_id': il['account_id'],
488 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
493 def action_date_assign(self, cr, uid, ids, *args):
494 for inv in self.browse(cr, uid, ids):
495 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
496 if res and res['value']:
497 self.write(cr, uid, [inv.id], res['value'])
500 def action_move_create(self, cr, uid, ids, *args):
501 ait_obj = self.pool.get('account.invoice.tax')
502 cur_obj = self.pool.get('res.currency')
504 for inv in self.browse(cr, uid, ids):
508 if not inv.date_invoice:
509 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
510 company_currency = inv.company_id.currency_id.id
511 # create the analytical lines
512 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
513 # one move line per invoice line
514 iml = self._get_analytic_lines(cr, uid, inv.id)
515 # check if taxes are all computed
517 context.update({'lang': inv.partner_id.lang})
518 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
520 for tax in compute_taxes.values():
521 ait_obj.create(cr, uid, tax)
524 for tax in inv.tax_line:
527 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
529 if not key in compute_taxes:
530 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
531 base = compute_taxes[key]['base']
532 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
533 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
534 for key in compute_taxes:
535 if not key in tax_key:
536 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
538 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
539 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
541 # one move line per tax line
542 iml += ait_obj.move_line_get(cr, uid, inv.id)
544 if inv.type in ('in_invoice', 'in_refund'):
547 ref = self._convert_ref(cr, uid, inv.number)
549 diff_currency_p = inv.currency_id.id <> company_currency
550 # create one move line for the total and possibly adjust the other lines amount
554 if inv.currency_id.id != company_currency:
555 i['currency_id'] = inv.currency_id.id
556 i['amount_currency'] = i['price']
557 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
558 company_currency, i['price'],
559 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
561 i['amount_currency'] = False
562 i['currency_id'] = False
564 if inv.type in ('out_invoice','in_refund'):
566 total_currency += i['amount_currency'] or i['price']
567 i['price'] = - i['price']
570 total_currency -= i['amount_currency'] or i['price']
571 acc_id = inv.account_id.id
573 name = inv['name'] or '/'
576 totlines = self.pool.get('account.payment.term').compute(cr,
577 uid, inv.payment_term.id, total, inv.date_invoice or False)
579 res_amount_currency = total_currency
582 if inv.currency_id.id != company_currency:
583 amount_currency = cur_obj.compute(cr, uid,
584 company_currency, inv.currency_id.id, t[1])
586 amount_currency = False
588 # last line add the diff
589 res_amount_currency -= amount_currency or 0
591 if i == len(totlines):
592 amount_currency += res_amount_currency
598 'account_id': acc_id,
599 'date_maturity': t[0],
600 'amount_currency': diff_currency_p \
601 and amount_currency or False,
602 'currency_id': diff_currency_p \
603 and inv.currency_id.id or False,
611 'account_id': acc_id,
612 'date_maturity' : inv.date_due or False,
613 'amount_currency': diff_currency_p \
614 and total_currency or False,
615 'currency_id': diff_currency_p \
616 and inv.currency_id.id or False,
620 date = inv.date_invoice or time.strftime('%Y-%m-%d')
621 part = inv.partner_id.id
623 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
625 if inv.journal_id.group_invoice_lines:
628 tmp = str(l['account_id'])
629 tmp += '-'+str(l.get('tax_code_id',"False"))
630 tmp += '-'+str(l.get('product_id',"False"))
631 tmp += '-'+str(l.get('analytic_account_id',"False"))
632 tmp += '-'+str(l.get('date_maturity',"False"))
635 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
636 line2[tmp]['debit'] = (am > 0) and am or 0.0
637 line2[tmp]['credit'] = (am < 0) and -am or 0.0
638 line2[tmp]['tax_amount'] += l['tax_amount']
639 line2[tmp]['analytic_lines'] += l['analytic_lines']
643 for key, val in line2.items():
644 line.append((0,0,val))
646 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
647 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
648 if journal.centralisation:
649 raise osv.except_osv(_('UserError'),
650 _('Cannot create invoice move on centralised journal'))
651 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
652 period_id=inv.period_id and inv.period_id.id or False
654 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'))])
656 period_id=period_ids[0]
658 move['period_id'] = period_id
660 i[2]['period_id'] = period_id
662 move_id = self.pool.get('account.move').create(cr, uid, move)
663 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
664 # make the invoice point to that move
665 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
666 self.pool.get('account.move').post(cr, uid, [move_id])
667 self._log_event(cr, uid, ids)
670 def line_get_convert(self, cr, uid, x, part, date, context=None):
672 'date_maturity': x.get('date_maturity', False),
674 'name':x['name'][:64],
676 'debit':x['price']>0 and x['price'],
677 'credit':x['price']<0 and -x['price'],
678 'account_id':x['account_id'],
679 'analytic_lines':x.get('analytic_lines', []),
680 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
681 'currency_id':x.get('currency_id', False),
682 'tax_code_id': x.get('tax_code_id', False),
683 'tax_amount': x.get('tax_amount', False),
684 'ref':x.get('ref',False),
685 'quantity':x.get('quantity',1.00),
686 'product_id':x.get('product_id', False),
687 'product_uom_id':x.get('uos_id',False),
688 'analytic_account_id':x.get('account_analytic_id',False),
691 def action_number(self, cr, uid, ids, *args):
692 cr.execute('SELECT id, type, number, move_id, reference ' \
693 'FROM account_invoice ' \
694 'WHERE id IN ('+','.join(map(str, ids))+')')
695 obj_inv = self.browse(cr, uid, ids)[0]
696 for (id, invtype, number, move_id, reference) in cr.fetchall():
698 if obj_inv.journal_id.invoice_sequence_id:
699 sid = obj_inv.journal_id.invoice_sequence_id.id
700 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
702 number = self.pool.get('ir.sequence').get(cr, uid,
703 'account.invoice.' + invtype)
704 if invtype in ('in_invoice', 'in_refund'):
707 ref = self._convert_ref(cr, uid, number)
708 cr.execute('UPDATE account_invoice SET number=%s ' \
709 'WHERE id=%s', (number, id))
710 cr.execute('UPDATE account_move SET ref=%s ' \
711 'WHERE id=%s AND (ref is null OR ref = \'\')',
713 cr.execute('UPDATE account_move_line SET ref=%s ' \
714 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
716 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
717 'FROM account_move_line ' \
718 'WHERE account_move_line.move_id = %s ' \
719 'AND account_analytic_line.move_id = account_move_line.id',
723 def action_cancel(self, cr, uid, ids, *args):
724 account_move_obj = self.pool.get('account.move')
725 invoices = self.read(cr, uid, ids, ['move_id'])
728 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
729 # delete the move this invoice was pointing to
730 # Note that the corresponding move_lines and move_reconciles
731 # will be automatically deleted too
732 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
733 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
734 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
739 def list_distinct_taxes(self, cr, uid, ids):
740 invoices = self.browse(cr, uid, ids)
743 for tax in inv.tax_line:
744 if not tax['name'] in taxes:
745 taxes[tax['name']] = {'name': tax['name']}
746 return taxes.values()
748 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
749 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
751 part=inv['partner_id'] and inv['partner_id'][0]
753 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
754 total = inv['amount_untaxed']
755 if inv['type'] in ('in_invoice','in_refund'):
756 partnertype='supplier'
757 eventtype = 'purchase'
760 partnertype = 'customer'
763 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
764 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})
767 def name_get(self, cr, uid, ids, context=None):
771 'out_invoice': 'CI: ',
772 'in_invoice': 'SI: ',
773 'out_refund': 'OR: ',
776 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')]
778 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
785 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
787 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
788 return self.name_get(cr, user, ids, context)
790 def _refund_cleanup_lines(self, cr, uid, lines):
793 del line['invoice_id']
794 if 'account_id' in line:
795 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
796 if 'product_id' in line:
797 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
799 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
800 if 'invoice_line_tax_id' in line:
801 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
802 if 'account_analytic_id' in line:
803 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
804 if 'tax_code_id' in line :
805 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
806 line['tax_code_id'] = line['tax_code_id'][0]
807 if 'base_code_id' in line :
808 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
809 line['base_code_id'] = line['base_code_id'][0]
810 return map(lambda x: (0,0,x), lines)
812 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
813 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'])
816 for invoice in invoices:
820 'out_invoice': 'out_refund', # Customer Invoice
821 'in_invoice': 'in_refund', # Supplier Invoice
822 'out_refund': 'out_invoice', # Customer Refund
823 'in_refund': 'in_invoice', # Supplier Refund
827 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
828 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
830 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
831 tax_lines = filter(lambda l: l['manual'], tax_lines)
832 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
834 date = time.strftime('%Y-%m-%d')
836 'type': type_dict[invoice['type']],
837 'date_invoice': date,
840 'invoice_line': invoice_lines,
841 'tax_line': tax_lines
845 'period_id': period_id,
851 # take the id part of the tuple returned for many2one fields
852 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
853 'account_id', 'currency_id', 'payment_term', 'journal_id'):
854 invoice[field] = invoice[field] and invoice[field][0]
855 # create the new invoice
856 new_ids.append(self.create(cr, uid, invoice))
859 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=''):
862 #TODO check if we can use different period for payment and the writeoff line
863 assert len(ids)==1, "Can only pay one invoice at a time"
864 invoice = self.browse(cr, uid, ids[0])
865 src_account_id = invoice.account_id.id
866 # Take the seq as name for move
867 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
868 direction = types[invoice.type]
869 #take the choosen date
870 if 'date_p' in context and context['date_p']:
871 date=context['date_p']
873 date=time.strftime('%Y-%m-%d')
875 'debit': direction * pay_amount>0 and direction * pay_amount,
876 'credit': direction * pay_amount<0 and - direction * pay_amount,
877 'account_id': src_account_id,
878 'partner_id': invoice.partner_id.id,
879 'ref':invoice.number,
883 'debit': direction * pay_amount<0 and - direction * pay_amount,
884 'credit': direction * pay_amount>0 and direction * pay_amount,
885 'account_id': pay_account_id,
886 'partner_id': invoice.partner_id.id,
887 'ref':invoice.number,
892 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
896 lines = [(0, 0, l1), (0, 0, l2)]
897 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
898 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
902 line = self.pool.get('account.move.line')
903 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
904 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
905 for l in lines+invoice.payment_ids:
906 if l.account_id.id==src_account_id:
907 line_ids.append(l.id)
908 total += (l.debit or 0.0) - (l.credit or 0.0)
909 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
910 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
912 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
914 # Update the stored value (fields.function), so we write to trigger recompute
915 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
919 class account_invoice_line(osv.osv):
920 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
922 cur_obj=self.pool.get('res.currency')
923 for line in self.browse(cr, uid, ids):
925 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
926 cur = line.invoice_id.currency_id
927 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
929 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
933 def _price_unit_default(self, cr, uid, context=None):
936 if 'check_total' in context:
937 t = context['check_total']
938 for l in context.get('invoice_line', {}):
939 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
940 tax_obj = self.pool.get('account.tax')
941 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
942 t = t - (p * l[2].get('quantity'))
943 taxes = l[2].get('invoice_line_tax_id')
944 if len(taxes[0]) >= 3 and taxes[0][2]:
945 taxes=tax_obj.browse(cr, uid, taxes[0][2])
946 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)):
947 t = t - tax['amount']
951 _name = "account.invoice.line"
952 _description = "Invoice line"
954 'name': fields.char('Description', size=256, required=True),
955 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
956 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
957 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
958 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
959 '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."),
960 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
961 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
962 'quantity': fields.float('Quantity', required=True),
963 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
964 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
965 'note': fields.text('Notes'),
966 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
969 'quantity': lambda *a: 1,
970 'discount': lambda *a: 0.0,
971 'price_unit': _price_unit_default,
974 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
975 tax_obj = self.pool.get('account.tax')
977 taxes = tax_obj.browse(cr, uid, tax_id)
978 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
979 price_unit = price_unit - tax['amount']
980 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
982 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):
986 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
988 if type in ('in_invoice', 'in_refund'):
989 return {'domain':{'product_uom':[]}}
991 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
992 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
993 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
996 context.update({'lang': lang})
998 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1000 if type in ('out_invoice','out_refund'):
1001 a = res.product_tmpl_id.property_account_income.id
1003 a = res.categ_id.property_account_income_categ.id
1005 a = res.product_tmpl_id.property_account_expense.id
1007 a = res.categ_id.property_account_expense_categ.id
1009 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1011 result['account_id'] = a
1014 tax_obj = self.pool.get('account.tax')
1015 if type in ('out_invoice', 'out_refund'):
1016 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1017 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1019 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)
1020 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1021 if type in ('in_invoice', 'in_refund'):
1022 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
1023 result.update(to_update)
1025 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1028 result['name'] = res.name
1031 result['uos_id'] = uom or res.uom_id.id or False
1032 if result['uos_id']:
1033 res2 = res.uom_id.category_id.id
1035 domain = {'uos_id':[('category_id','=',res2 )]}
1036 return {'value':result, 'domain':domain}
1038 def move_line_get(self, cr, uid, invoice_id, context=None):
1041 tax_obj = self.pool.get('account.tax')
1042 cur_obj = self.pool.get('res.currency')
1043 ait_obj = self.pool.get('account.invoice.tax')
1044 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1045 company_currency = inv.company_id.currency_id.id
1046 cur = inv.currency_id
1048 for line in inv.invoice_line:
1049 mres = self.move_line_get_item(cr, uid, line, context)
1053 tax_code_found= False
1054 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1055 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1056 line.quantity, inv.address_invoice_id.id, line.product_id,
1059 if inv.type in ('out_invoice', 'in_invoice'):
1060 tax_code_id = tax['base_code_id']
1061 tax_amount = line.price_subtotal * tax['base_sign']
1063 tax_code_id = tax['ref_base_code_id']
1064 tax_amount = line.price_subtotal * tax['ref_base_sign']
1069 res.append(self.move_line_get_item(cr, uid, line, context))
1070 res[-1]['price'] = 0.0
1071 res[-1]['account_analytic_id'] = False
1072 elif not tax_code_id:
1074 tax_code_found = True
1076 res[-1]['tax_code_id'] = tax_code_id
1077 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1080 def move_line_get_item(self, cr, uid, line, context=None):
1083 'name': line.name[:64],
1084 'price_unit':line.price_unit,
1085 'quantity':line.quantity,
1086 'price':line.price_subtotal,
1087 'account_id':line.account_id.id,
1088 'product_id':line.product_id.id,
1089 'uos_id':line.uos_id.id,
1090 'account_analytic_id':line.account_analytic_id.id,
1091 'taxes':line.invoice_line_tax_id,
1094 # Set the tax field according to the account and the fiscal position
1096 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1099 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1100 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1101 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1102 r = {'value':{'invoice_line_tax_id': res}}
1104 account_invoice_line()
1106 class account_invoice_tax(osv.osv):
1107 _name = "account.invoice.tax"
1108 _description = "Invoice Tax"
1110 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1111 'name': fields.char('Tax Description', size=64, required=True),
1112 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1113 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1114 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1115 'manual': fields.boolean('Manual'),
1116 'sequence': fields.integer('Sequence'),
1118 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1119 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1120 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1121 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1124 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1125 cur_obj = self.pool.get('res.currency')
1126 company_obj = self.pool.get('res.company')
1127 company_currency=False
1129 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1130 if currency_id and company_currency:
1131 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1132 return {'value': {'base_amount':base}}
1134 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1135 cur_obj = self.pool.get('res.currency')
1136 company_obj = self.pool.get('res.company')
1137 company_currency=False
1139 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1140 if currency_id and company_currency:
1141 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1142 return {'value': {'tax_amount':amount}}
1146 'manual': lambda *a: 1,
1147 'base_amount': lambda *a: 0.0,
1148 'tax_amount': lambda *a: 0.0,
1150 def compute(self, cr, uid, invoice_id, context={}):
1152 tax_obj = self.pool.get('account.tax')
1153 cur_obj = self.pool.get('res.currency')
1154 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1155 cur = inv.currency_id
1156 company_currency = inv.company_id.currency_id.id
1158 for line in inv.invoice_line:
1159 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):
1161 val['invoice_id'] = inv.id
1162 val['name'] = tax['name']
1163 val['amount'] = tax['amount']
1164 val['manual'] = False
1165 val['sequence'] = tax['sequence']
1166 val['base'] = tax['price_unit'] * line['quantity']
1168 if inv.type in ('out_invoice','in_invoice'):
1169 val['base_code_id'] = tax['base_code_id']
1170 val['tax_code_id'] = tax['tax_code_id']
1171 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)
1172 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)
1173 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1175 val['base_code_id'] = tax['ref_base_code_id']
1176 val['tax_code_id'] = tax['ref_tax_code_id']
1177 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)
1178 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)
1179 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1181 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1182 if not key in tax_grouped:
1183 tax_grouped[key] = val
1185 tax_grouped[key]['amount'] += val['amount']
1186 tax_grouped[key]['base'] += val['base']
1187 tax_grouped[key]['base_amount'] += val['base_amount']
1188 tax_grouped[key]['tax_amount'] += val['tax_amount']
1190 for t in tax_grouped.values():
1191 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1192 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1193 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1196 def move_line_get(self, cr, uid, invoice_id):
1198 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1199 for t in cr.dictfetchall():
1200 if not t['amount'] \
1201 and not t['tax_code_id'] \
1202 and not t['tax_amount']:
1207 'price_unit': t['amount'],
1209 'price': t['amount'] or 0.0,
1210 'account_id': t['account_id'],
1211 'tax_code_id': t['tax_code_id'],
1212 'tax_amount': t['tax_amount']
1215 account_invoice_tax()
1217 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: