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')
112 context_unreconciled = context.copy()
113 # If one of the invoice line is not in the currency of the invoice,
114 # we use the currency of the company to compute the residual amount.
115 # All the lines must use the same currency source (company or invoice)
118 for lines in inv.move_lines:
119 if lines.currency_id and lines.currency_id.id <> inv.currency_id.id:
122 for lines in inv.move_lines:
123 # If currency conversion needed
125 context_unreconciled.update({'date':lines.date})
126 # If amount currency setted, compute for debit and credit in company currency
128 credit += abs(cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, lines.credit, round=False,context=context_unreconciled))
130 debit += abs(cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, lines.debit, round=False,context=context_unreconciled))
132 if lines.amount_currency:
133 debit += lines.debit > 0 and abs(lines.amount_currency) or 0.00
134 credit += lines.credit > 0 and abs(lines.amount_currency) or 0.00
136 debit += lines.debit or 0.00
137 credit += lines.credit or 0.00
139 if inv.type in ('out_invoice','in_refund'):
140 amount = credit-debit
142 amount = debit-credit
144 result = inv.amount_total - amount
145 res[inv.id] = self.pool.get('res.currency').round(cr, uid, inv.currency_id, result)
148 def _get_lines(self, cr, uid, ids, name, arg, context=None):
151 move_lines = self.move_line_id_payment_get(cr,uid,[id])
156 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
157 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
158 for line in data_lines:
160 if line.reconcile_id:
161 ids_line = line.reconcile_id.line_id
162 elif line.reconcile_partial_id:
163 ids_line = line.reconcile_partial_id.line_partial_ids
164 l = map(lambda x: x.id, ids_line)
165 partial_ids.append(line.id)
166 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
169 def _get_invoice_line(self, cr, uid, ids, context=None):
171 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
172 result[line.invoice_id.id] = True
175 def _get_invoice_tax(self, cr, uid, ids, context=None):
177 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
178 result[tax.invoice_id.id] = True
181 def _compute_lines(self, cr, uid, ids, name, args, context=None):
183 for invoice in self.browse(cr, uid, ids, context):
184 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
187 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
188 temp_lines = []#Added temp list to avoid duplicate records
190 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
191 elif m.reconcile_partial_id:
192 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
193 lines += [x for x in temp_lines if x not in lines]
195 lines = filter(lambda x: x not in src, lines)
196 result[invoice.id] = lines
199 def _get_invoice_from_line(self, cr, uid, ids, context={}):
201 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
202 if line.reconcile_partial_id:
203 for line2 in line.reconcile_partial_id.line_partial_ids:
204 move[line2.move_id.id] = True
205 if line.reconcile_id:
206 for line2 in line.reconcile_id.line_id:
207 move[line2.move_id.id] = True
210 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
213 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
215 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
216 for line in r.line_partial_ids:
217 move[line.move_id.id] = True
218 for line in r.line_id:
219 move[line.move_id.id] = True
223 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
226 _name = "account.invoice"
227 _description = 'Invoice'
230 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
231 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
232 'type': fields.selection([
233 ('out_invoice','Customer Invoice'),
234 ('in_invoice','Supplier Invoice'),
235 ('out_refund','Customer Refund'),
236 ('in_refund','Supplier Refund'),
237 ],'Type', readonly=True, select=True),
239 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
240 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
241 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
243 'comment': fields.text('Additional Information'),
245 'state': fields.selection([
247 ('proforma','Pro-forma'),
248 ('proforma2','Pro-forma'),
251 ('cancel','Cancelled')
252 ],'State', select=True, readonly=True),
254 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
255 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
256 help="If you use payment terms, the due date will be computed automatically at the generation "\
257 "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."),
258 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
259 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
260 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
261 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
262 help="If you use payment terms, the due date will be computed automatically at the generation "\
263 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
264 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
265 '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)]}),
267 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
268 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
269 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
271 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
272 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
274 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
275 'account.invoice.tax': (_get_invoice_tax, None, 20),
276 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
279 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
281 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
282 'account.invoice.tax': (_get_invoice_tax, None, 20),
283 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
286 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
288 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
289 'account.invoice.tax': (_get_invoice_tax, None, 20),
290 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
293 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
295 'company_id': fields.many2one('res.company', 'Company', required=True),
296 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
297 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
299 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
300 'account.move.line': (_get_invoice_from_line, None, 50),
301 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
302 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
303 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
304 help='The bank account to pay to or to be paid from'),
305 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
306 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
308 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
309 'account.invoice.tax': (_get_invoice_tax, None, 50),
310 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
311 'account.move.line': (_get_invoice_from_line, None, 50),
312 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
314 help="Remaining amount due."),
315 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
316 'move_name': fields.char('Account Move', size=64),
317 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
321 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
322 'state': lambda *a: 'draft',
323 'journal_id': _get_journal,
324 'currency_id': _get_currency,
325 'company_id': lambda self, cr, uid, context: \
326 self.pool.get('res.users').browse(cr, uid, uid,
327 context=context).company_id.id,
328 'reference_type': lambda *a: 'none',
329 'check_total': lambda *a: 0.0,
332 def create(self, cr, uid, vals, context={}):
334 res = super(account_invoice, self).create(cr, uid, vals, context)
337 if '"journal_id" viol' in e.args[0]:
338 raise except_orm(_('Configuration Error!'),
339 _('There is no Accounting Journal of type Sale/Purchase defined!'))
341 raise except_orm(_('UnknownError'), str(e))
343 def unlink(self, cr, uid, ids, context=None):
344 invoices = self.read(cr, uid, ids, ['state'])
347 if t['state'] in ('draft', 'cancel'):
348 unlink_ids.append(t['id'])
350 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
351 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
354 # def get_invoice_address(self, cr, uid, ids):
355 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
358 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
359 date_invoice=False, payment_term=False, partner_bank=False):
360 invoice_addr_id = False
361 contact_addr_id = False
362 partner_payment_term = False
365 fiscal_position = False
367 opt = [('uid', str(uid))]
370 opt.insert(0, ('id', partner_id))
371 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
372 contact_addr_id = res['contact']
373 invoice_addr_id = res['invoice']
374 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
375 if type in ('out_invoice', 'out_refund'):
376 acc_id = p.property_account_receivable.id
378 acc_id = p.property_account_payable.id
379 fiscal_position = p.property_account_position and p.property_account_position.id or False
380 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
382 bank_id = p.bank_ids[0].id
385 'address_contact_id': contact_addr_id,
386 'address_invoice_id': invoice_addr_id,
387 'account_id': acc_id,
388 'payment_term': partner_payment_term,
389 'fiscal_position': fiscal_position
393 if type in ('in_invoice', 'in_refund'):
394 result['value']['partner_bank'] = bank_id
396 if partner_bank != bank_id:
397 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
398 result['value'].update(to_update['value'])
401 def onchange_currency_id(self, cr, uid, ids, curr_id):
404 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
405 if not payment_term_id:
408 pt_obj= self.pool.get('account.payment.term')
409 if not date_invoice :
410 date_invoice = time.strftime('%Y-%m-%d')
412 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
415 pterm_list = [line[0] for line in pterm_list]
417 res= {'value':{'date_due': pterm_list[-1]}}
419 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
423 def onchange_invoice_line(self, cr, uid, ids, lines):
426 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
429 # go from canceled state to draft state
430 def action_cancel_draft(self, cr, uid, ids, *args):
431 self.write(cr, uid, ids, {'state':'draft'})
432 wf_service = netsvc.LocalService("workflow")
434 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
440 # return the ids of the move lines which has the same account than the invoice
442 def move_line_id_payment_get(self, cr, uid, ids, *args):
444 if not ids: return res
445 cr.execute('SELECT l.id '\
446 'FROM account_move_line l '\
447 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
449 'AND l.account_id=i.account_id',
451 res = map(itemgetter(0), cr.fetchall())
454 def copy(self, cr, uid, id, default=None, context=None):
457 default = default.copy()
458 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
459 if 'date_invoice' not in default:
460 default['date_invoice'] = False
461 if 'date_due' not in default:
462 default['date_due'] = False
463 return super(account_invoice, self).copy(cr, uid, id, default, context)
465 def test_paid(self, cr, uid, ids, *args):
466 res = self.move_line_id_payment_get(cr, uid, ids)
471 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
472 ok = ok and bool(cr.fetchone()[0])
475 def button_reset_taxes(self, cr, uid, ids, context=None):
479 ait_obj = self.pool.get('account.invoice.tax')
481 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
482 partner = self.browse(cr, uid, id,context=ctx).partner_id
484 ctx.update({'lang': partner.lang})
485 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
486 ait_obj.create(cr, uid, taxe)
487 # Update the stored value (fields.function), so we write to trigger recompute
488 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
489 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
492 def button_compute(self, cr, uid, ids, context=None, set_total=False):
493 self.button_reset_taxes(cr, uid, ids, context)
494 for inv in self.browse(cr, uid, ids):
496 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
499 def _convert_ref(self, cr, uid, ref):
500 return (ref or '').replace('/','')
502 def _get_analytic_lines(self, cr, uid, id):
503 inv = self.browse(cr, uid, [id])[0]
504 cur_obj = self.pool.get('res.currency')
506 company_currency = inv.company_id.currency_id.id
507 if inv.type in ('out_invoice', 'in_refund'):
512 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
514 if il['account_analytic_id']:
515 if inv.type in ('in_invoice', 'in_refund'):
518 ref = self._convert_ref(cr, uid, inv.number)
519 il['analytic_lines'] = [(0,0, {
521 'date': inv['date_invoice'],
522 'account_id': il['account_analytic_id'],
523 'unit_amount': il['quantity'],
524 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
525 'product_id': il['product_id'],
526 'product_uom_id': il['uos_id'],
527 'general_account_id': il['account_id'],
528 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
533 def action_date_assign(self, cr, uid, ids, *args):
534 for inv in self.browse(cr, uid, ids):
535 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
536 if res and res['value']:
537 self.write(cr, uid, [inv.id], res['value'])
540 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
541 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
542 Hook method to be overridden in additional modules to verify and possibly alter the
543 move lines to be created by an invoice, for special cases.
544 :param invoice_browse: browsable record of the invoice that is generating the move lines
545 :param move_lines: list of dictionaries with the account.move.lines (as for create())
546 :return: the (possibly updated) final move_lines to create for this invoice
550 def action_move_create(self, cr, uid, ids, *args):
551 ait_obj = self.pool.get('account.invoice.tax')
552 cur_obj = self.pool.get('res.currency')
554 for inv in self.browse(cr, uid, ids):
558 if not inv.date_invoice:
559 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
560 company_currency = inv.company_id.currency_id.id
561 # create the analytical lines
562 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
563 # one move line per invoice line
564 iml = self._get_analytic_lines(cr, uid, inv.id)
565 # check if taxes are all computed
567 context.update({'lang': inv.partner_id.lang})
568 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
570 for tax in compute_taxes.values():
571 ait_obj.create(cr, uid, tax)
574 for tax in inv.tax_line:
577 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
579 if not key in compute_taxes:
580 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
581 base = compute_taxes[key]['base']
582 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
583 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
584 for key in compute_taxes:
585 if not key in tax_key:
586 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
588 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
589 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
591 # one move line per tax line
592 iml += ait_obj.move_line_get(cr, uid, inv.id)
594 if inv.type in ('in_invoice', 'in_refund'):
597 ref = self._convert_ref(cr, uid, inv.number)
599 diff_currency_p = inv.currency_id.id <> company_currency
600 # create one move line for the total and possibly adjust the other lines amount
604 if inv.currency_id.id != company_currency:
605 i['currency_id'] = inv.currency_id.id
606 i['amount_currency'] = i['price']
607 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
608 company_currency, i['price'],
609 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
611 i['amount_currency'] = False
612 i['currency_id'] = False
614 if inv.type in ('out_invoice','in_refund'):
616 total_currency += i['amount_currency'] or i['price']
617 i['price'] = - i['price']
620 total_currency -= i['amount_currency'] or i['price']
621 acc_id = inv.account_id.id
623 name = inv['name'] or '/'
626 totlines = self.pool.get('account.payment.term').compute(cr,
627 uid, inv.payment_term.id, total, inv.date_invoice or False)
629 res_amount_currency = total_currency
632 if inv.currency_id.id != company_currency:
633 amount_currency = cur_obj.compute(cr, uid,
634 company_currency, inv.currency_id.id, t[1])
636 amount_currency = False
638 # last line add the diff
639 res_amount_currency -= amount_currency or 0
641 if i == len(totlines):
642 amount_currency += res_amount_currency
648 'account_id': acc_id,
649 'date_maturity': t[0],
650 'amount_currency': diff_currency_p \
651 and amount_currency or False,
652 'currency_id': diff_currency_p \
653 and inv.currency_id.id or False,
661 'account_id': acc_id,
662 'date_maturity' : inv.date_due or False,
663 'amount_currency': diff_currency_p \
664 and total_currency or False,
665 'currency_id': diff_currency_p \
666 and inv.currency_id.id or False,
670 date = inv.date_invoice or time.strftime('%Y-%m-%d')
671 part = inv.partner_id.id
673 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
675 if inv.journal_id.group_invoice_lines:
678 tmp = str(l['account_id'])
679 tmp += '-'+str(l.get('tax_code_id',"False"))
680 tmp += '-'+str(l.get('product_id',"False"))
681 tmp += '-'+str(l.get('analytic_account_id',"False"))
682 tmp += '-'+str(l.get('date_maturity',"False"))
685 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
686 line2[tmp]['debit'] = (am > 0) and am or 0.0
687 line2[tmp]['credit'] = (am < 0) and -am or 0.0
688 line2[tmp]['tax_amount'] += l['tax_amount']
689 line2[tmp]['analytic_lines'] += l['analytic_lines']
693 for key, val in line2.items():
694 line.append((0,0,val))
696 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
697 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
698 if journal.centralisation:
699 raise osv.except_osv(_('UserError'),
700 _('Cannot create invoice move on centralised journal'))
702 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
704 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
705 period_id=inv.period_id and inv.period_id.id or False
707 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'))])
709 period_id=period_ids[0]
711 move['period_id'] = period_id
713 i[2]['period_id'] = period_id
715 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
716 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
717 # make the invoice point to that move
718 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
719 # Pass invoice in context in method post: used if you want to get the same
720 # account move reference when creating the same invoice after a cancelled one:
721 self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
722 self._log_event(cr, uid, ids)
725 def line_get_convert(self, cr, uid, x, part, date, context=None):
727 'date_maturity': x.get('date_maturity', False),
729 'name':x['name'][:64],
731 'debit':x['price']>0 and x['price'],
732 'credit':x['price']<0 and -x['price'],
733 'account_id':x['account_id'],
734 'analytic_lines':x.get('analytic_lines', []),
735 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
736 'currency_id':x.get('currency_id', False),
737 'tax_code_id': x.get('tax_code_id', False),
738 'tax_amount': x.get('tax_amount', False),
739 'ref':x.get('ref',False),
740 'quantity':x.get('quantity',1.00),
741 'product_id':x.get('product_id', False),
742 'product_uom_id':x.get('uos_id',False),
743 'analytic_account_id':x.get('account_analytic_id',False),
746 def action_number(self, cr, uid, ids, *args):
747 cr.execute('SELECT id, type, number, move_id, reference ' \
748 'FROM account_invoice ' \
751 obj_inv = self.browse(cr, uid, ids)[0]
752 for (id, invtype, number, move_id, reference) in cr.fetchall():
755 'fiscalyear_id' : obj_inv.period_id.fiscalyear_id.id,
757 if obj_inv.journal_id.invoice_sequence_id:
758 sid = obj_inv.journal_id.invoice_sequence_id.id
759 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
761 number = self.pool.get('ir.sequence').get_id(cr, uid,
762 'account.invoice.' + invtype,
766 raise osv.except_osv(_('Warning !'), _('There is no active invoice sequence defined for the journal !'))
768 if invtype in ('in_invoice', 'in_refund'):
771 ref = self._convert_ref(cr, uid, number)
772 cr.execute('UPDATE account_invoice SET number=%s ' \
773 'WHERE id=%s', (number, id))
774 cr.execute('UPDATE account_move SET ref=%s ' \
775 'WHERE id=%s AND (ref is null OR ref = \'\')',
777 cr.execute('UPDATE account_move_line SET ref=%s ' \
778 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
780 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
781 'FROM account_move_line ' \
782 'WHERE account_move_line.move_id = %s ' \
783 'AND account_analytic_line.move_id = account_move_line.id',
787 def action_cancel(self, cr, uid, ids, *args):
788 account_move_obj = self.pool.get('account.move')
789 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
792 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
793 # delete the move this invoice was pointing to
794 # Note that the corresponding move_lines and move_reconciles
795 # will be automatically deleted too
796 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
798 account_move_line_obj = self.pool.get('account.move.line')
799 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
800 for move_line in pay_ids:
801 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
802 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
804 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
805 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
810 def list_distinct_taxes(self, cr, uid, ids):
811 invoices = self.browse(cr, uid, ids)
814 for tax in inv.tax_line:
815 if not tax['name'] in taxes:
816 taxes[tax['name']] = {'name': tax['name']}
817 return taxes.values()
819 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
820 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
822 part=inv['partner_id'] and inv['partner_id'][0]
824 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
825 total = inv['amount_untaxed']
826 if inv['type'] in ('in_invoice','in_refund'):
827 partnertype='supplier'
828 eventtype = 'purchase'
831 partnertype = 'customer'
834 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
835 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})
838 def name_get(self, cr, uid, ids, context=None):
842 'out_invoice': 'CI: ',
843 'in_invoice': 'SI: ',
844 'out_refund': 'OR: ',
847 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')]
849 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
856 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
858 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
859 return self.name_get(cr, user, ids, context)
861 def _refund_cleanup_lines(self, cr, uid, lines):
864 del line['invoice_id']
865 if 'account_id' in line:
866 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
867 if 'product_id' in line:
868 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
870 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
871 if 'invoice_line_tax_id' in line:
872 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
873 if 'account_analytic_id' in line:
874 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
875 if 'tax_code_id' in line :
876 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
877 line['tax_code_id'] = line['tax_code_id'][0]
878 if 'base_code_id' in line :
879 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
880 line['base_code_id'] = line['base_code_id'][0]
881 return map(lambda x: (0,0,x), lines)
883 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
884 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'])
887 for invoice in invoices:
891 'out_invoice': 'out_refund', # Customer Invoice
892 'in_invoice': 'in_refund', # Supplier Invoice
893 'out_refund': 'out_invoice', # Customer Refund
894 'in_refund': 'in_invoice', # Supplier Refund
898 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
899 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
901 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
902 tax_lines = filter(lambda l: l['manual'], tax_lines)
903 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
905 date = time.strftime('%Y-%m-%d')
907 'type': type_dict[invoice['type']],
908 'date_invoice': date,
911 'invoice_line': invoice_lines,
912 'tax_line': tax_lines
916 'period_id': period_id,
922 # take the id part of the tuple returned for many2one fields
923 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
924 'account_id', 'currency_id', 'payment_term', 'journal_id'):
925 invoice[field] = invoice[field] and invoice[field][0]
926 # create the new invoice
927 new_ids.append(self.create(cr, uid, invoice))
930 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=''):
933 #TODO check if we can use different period for payment and the writeoff line
934 assert len(ids)==1, "Can only pay one invoice at a time"
935 invoice = self.browse(cr, uid, ids[0])
936 src_account_id = invoice.account_id.id
937 # Take the seq as name for move
938 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
939 direction = types[invoice.type]
940 #take the choosen date
941 if 'date_p' in context and context['date_p']:
942 date=context['date_p']
944 date=time.strftime('%Y-%m-%d')
946 # Take the amount in currency and the currency of the payment
947 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
948 amount_currency = context['amount_currency']
949 currency_id = context['currency_id']
951 amount_currency = False
954 if invoice.type in ('in_invoice', 'in_refund'):
955 ref = invoice.reference
957 ref = self._convert_ref(cr, uid, invoice.number)
958 # Pay attention to the sign for both debit/credit AND amount_currency
960 'debit': direction * pay_amount>0 and direction * pay_amount,
961 'credit': direction * pay_amount<0 and - direction * pay_amount,
962 'account_id': src_account_id,
963 'partner_id': invoice.partner_id.id,
966 'currency_id':currency_id,
967 'amount_currency':amount_currency and direction * amount_currency or 0.0,
970 'debit': direction * pay_amount<0 and - direction * pay_amount,
971 'credit': direction * pay_amount>0 and direction * pay_amount,
972 'account_id': pay_account_id,
973 'partner_id': invoice.partner_id.id,
976 'currency_id':currency_id,
977 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
981 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
985 lines = [(0, 0, l1), (0, 0, l2)]
986 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
987 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
991 line = self.pool.get('account.move.line')
992 cr.execute('SELECT id FROM account_move_line '\
993 'WHERE move_id in %s',
994 ((move_id, invoice.move_id.id),))
995 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
997 for l in lines+invoice.payment_ids:
998 if l.account_id.id==src_account_id:
999 line_ids.append(l.id)
1000 total += (l.debit or 0.0) - (l.credit or 0.0)
1002 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
1003 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1005 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1007 # Update the stored value (fields.function), so we write to trigger recompute
1008 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1012 class account_invoice_line(osv.osv):
1013 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1015 cur_obj=self.pool.get('res.currency')
1016 for line in self.browse(cr, uid, ids):
1018 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1019 cur = line.invoice_id.currency_id
1020 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1022 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1026 def _price_unit_default(self, cr, uid, context=None):
1029 if 'check_total' in context:
1030 t = context['check_total']
1031 for l in context.get('invoice_line', {}):
1032 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1033 tax_obj = self.pool.get('account.tax')
1034 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1035 t = t - (p * l[2].get('quantity'))
1036 taxes = l[2].get('invoice_line_tax_id')
1037 if len(taxes[0]) >= 3 and taxes[0][2]:
1038 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1039 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)):
1040 t = t - tax['amount']
1044 _name = "account.invoice.line"
1045 _description = "Invoice line"
1047 'name': fields.char('Description', size=256, required=True),
1048 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1049 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1050 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1051 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1052 '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."),
1053 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1054 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1055 'quantity': fields.float('Quantity', required=True),
1056 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1057 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1058 'note': fields.text('Notes'),
1059 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1062 'quantity': lambda *a: 1,
1063 'discount': lambda *a: 0.0,
1064 'price_unit': _price_unit_default,
1067 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1068 tax_obj = self.pool.get('account.tax')
1070 taxes = tax_obj.browse(cr, uid, tax_id)
1071 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1072 price_unit = price_unit - tax['amount']
1073 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1075 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):
1079 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1081 if type in ('in_invoice', 'in_refund'):
1082 return {'value':{}, 'domain':{'product_uom':[]}}
1084 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1085 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1086 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1089 context.update({'lang': part.lang})
1091 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1093 if type in ('out_invoice','out_refund'):
1094 a = res.product_tmpl_id.property_account_income.id
1096 a = res.categ_id.property_account_income_categ.id
1098 a = res.product_tmpl_id.property_account_expense.id
1100 a = res.categ_id.property_account_expense_categ.id
1102 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1104 result['account_id'] = a
1107 tax_obj = self.pool.get('account.tax')
1108 if type in ('out_invoice', 'out_refund'):
1109 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1110 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1112 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)
1113 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1114 if type in ('in_invoice', 'in_refund'):
1115 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)
1116 result.update(to_update)
1118 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1121 result['name'] = res.partner_ref
1124 result['uos_id'] = uom or res.uom_id.id or False
1125 if result['uos_id']:
1126 res2 = res.uom_id.category_id.id
1128 domain = {'uos_id':[('category_id','=',res2 )]}
1129 return {'value':result, 'domain':domain}
1131 def move_line_get(self, cr, uid, invoice_id, context=None):
1134 tax_obj = self.pool.get('account.tax')
1135 cur_obj = self.pool.get('res.currency')
1136 ait_obj = self.pool.get('account.invoice.tax')
1137 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1138 company_currency = inv.company_id.currency_id.id
1139 cur = inv.currency_id
1141 for line in inv.invoice_line:
1142 mres = self.move_line_get_item(cr, uid, line, context)
1146 tax_code_found= False
1147 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1148 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1149 line.quantity, inv.address_invoice_id.id, line.product_id,
1152 if inv.type in ('out_invoice', 'in_invoice'):
1153 tax_code_id = tax['base_code_id']
1154 tax_amount = line.price_subtotal * tax['base_sign']
1156 tax_code_id = tax['ref_base_code_id']
1157 tax_amount = line.price_subtotal * tax['ref_base_sign']
1162 res.append(self.move_line_get_item(cr, uid, line, context))
1163 res[-1]['price'] = 0.0
1164 res[-1]['account_analytic_id'] = False
1165 elif not tax_code_id:
1167 tax_code_found = True
1169 res[-1]['tax_code_id'] = tax_code_id
1170 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1173 def move_line_get_item(self, cr, uid, line, context=None):
1176 'name': line.name[:64],
1177 'price_unit':line.price_unit,
1178 'quantity':line.quantity,
1179 'price':line.price_subtotal,
1180 'account_id':line.account_id.id,
1181 'product_id':line.product_id.id,
1182 'uos_id':line.uos_id.id,
1183 'account_analytic_id':line.account_analytic_id.id,
1184 'taxes':line.invoice_line_tax_id,
1187 # Set the tax field according to the account and the fiscal position
1189 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1192 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1193 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1194 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1195 r = {'value':{'invoice_line_tax_id': res}}
1197 account_invoice_line()
1199 class account_invoice_tax(osv.osv):
1200 _name = "account.invoice.tax"
1201 _description = "Invoice Tax"
1203 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1204 'name': fields.char('Tax Description', size=64, required=True),
1205 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1206 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1207 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1208 'manual': fields.boolean('Manual'),
1209 'sequence': fields.integer('Sequence'),
1211 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1212 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1213 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1214 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1217 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1218 cur_obj = self.pool.get('res.currency')
1219 company_obj = self.pool.get('res.company')
1220 company_currency=False
1222 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1223 if currency_id and company_currency:
1224 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1225 return {'value': {'base_amount':base}}
1227 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1228 cur_obj = self.pool.get('res.currency')
1229 company_obj = self.pool.get('res.company')
1230 company_currency=False
1232 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1233 if currency_id and company_currency:
1234 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1235 return {'value': {'tax_amount':amount}}
1239 'manual': lambda *a: 1,
1240 'base_amount': lambda *a: 0.0,
1241 'tax_amount': lambda *a: 0.0,
1243 def compute(self, cr, uid, invoice_id, context={}):
1245 tax_obj = self.pool.get('account.tax')
1246 cur_obj = self.pool.get('res.currency')
1247 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1248 cur = inv.currency_id
1249 company_currency = inv.company_id.currency_id.id
1251 for line in inv.invoice_line:
1252 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):
1254 val['invoice_id'] = inv.id
1255 val['name'] = tax['name']
1256 val['amount'] = tax['amount']
1257 val['manual'] = False
1258 val['sequence'] = tax['sequence']
1259 val['base'] = tax['price_unit'] * line['quantity']
1261 if inv.type in ('out_invoice','in_invoice'):
1262 val['base_code_id'] = tax['base_code_id']
1263 val['tax_code_id'] = tax['tax_code_id']
1264 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)
1265 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)
1266 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1268 val['base_code_id'] = tax['ref_base_code_id']
1269 val['tax_code_id'] = tax['ref_tax_code_id']
1270 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)
1271 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)
1272 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1274 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1275 if not key in tax_grouped:
1276 tax_grouped[key] = val
1278 tax_grouped[key]['amount'] += val['amount']
1279 tax_grouped[key]['base'] += val['base']
1280 tax_grouped[key]['base_amount'] += val['base_amount']
1281 tax_grouped[key]['tax_amount'] += val['tax_amount']
1283 for t in tax_grouped.values():
1284 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1285 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1286 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1287 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1290 def move_line_get(self, cr, uid, invoice_id):
1292 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1293 for t in cr.dictfetchall():
1294 if not t['amount'] \
1295 and not t['tax_code_id'] \
1296 and not t['tax_amount']:
1301 'price_unit': t['amount'],
1303 'price': t['amount'] or 0.0,
1304 'account_id': t['account_id'],
1305 'tax_code_id': t['tax_code_id'],
1306 'tax_amount': t['tax_amount']
1309 account_invoice_tax()
1311 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: