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):
106 data_inv = self.browse(cr, uid, ids)
107 cur_obj = self.pool.get('res.currency')
110 context.update({'date':inv.date_invoice})
111 context_unreconciled=context.copy()
112 for lines in inv.move_lines:
113 debit_tmp = lines.debit
114 credit_tmp = lines.credit
115 # If currency conversion needed
116 if inv.company_id.currency_id.id <> inv.currency_id.id:
117 # If invoice paid, compute currency amount according to invoice date
118 # otherwise, take the line date
119 if not inv.reconciled:
120 context.update({'date':lines.date})
121 context_unreconciled.update({'date':lines.date})
122 # If amount currency setted, compute for debit and credit in company currency
123 if lines.amount_currency < 0:
124 credit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False,context=context_unreconciled))
125 elif lines.amount_currency > 0:
126 debit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False,context=context_unreconciled))
127 # Then, recomput into invoice currency to avoid rounding trouble !
128 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
129 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
134 if not inv.amount_total:
136 elif inv.type in ('out_invoice','in_refund'):
137 amount = credit-debit
138 result = inv.amount_total - amount
140 amount = debit-credit
141 result = inv.amount_total - amount
142 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
143 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
147 def _get_lines(self, cr, uid, ids, name, arg, context=None):
150 move_lines = self.move_line_id_payment_get(cr,uid,[id])
155 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
156 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
157 for line in data_lines:
159 if line.reconcile_id:
160 ids_line = line.reconcile_id.line_id
161 elif line.reconcile_partial_id:
162 ids_line = line.reconcile_partial_id.line_partial_ids
163 l = map(lambda x: x.id, ids_line)
164 partial_ids.append(line.id)
165 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
168 def _get_invoice_line(self, cr, uid, ids, context=None):
170 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
171 result[line.invoice_id.id] = True
174 def _get_invoice_tax(self, cr, uid, ids, context=None):
176 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
177 result[tax.invoice_id.id] = True
180 def _compute_lines(self, cr, uid, ids, name, args, context=None):
182 for invoice in self.browse(cr, uid, ids, context):
183 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
186 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
187 temp_lines = []#Added temp list to avoid duplicate records
189 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
190 elif m.reconcile_partial_id:
191 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
192 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):
478 ait_obj = self.pool.get('account.invoice.tax')
480 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
481 partner = self.browse(cr, uid, id,context=context).partner_id
483 context.update({'lang': partner.lang})
484 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
485 ait_obj.create(cr, uid, taxe)
486 # Update the stored value (fields.function), so we write to trigger recompute
487 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
488 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
491 def button_compute(self, cr, uid, ids, context=None, set_total=False):
492 self.button_reset_taxes(cr, uid, ids, context)
493 for inv in self.browse(cr, uid, ids):
495 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
498 def _convert_ref(self, cr, uid, ref):
499 return (ref or '').replace('/','')
501 def _get_analytic_lines(self, cr, uid, id):
502 inv = self.browse(cr, uid, [id])[0]
503 cur_obj = self.pool.get('res.currency')
505 company_currency = inv.company_id.currency_id.id
506 if inv.type in ('out_invoice', 'in_refund'):
511 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
513 if il['account_analytic_id']:
514 if inv.type in ('in_invoice', 'in_refund'):
517 ref = self._convert_ref(cr, uid, inv.number)
518 il['analytic_lines'] = [(0,0, {
520 'date': inv['date_invoice'],
521 'account_id': il['account_analytic_id'],
522 'unit_amount': il['quantity'],
523 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
524 'product_id': il['product_id'],
525 'product_uom_id': il['uos_id'],
526 'general_account_id': il['account_id'],
527 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
532 def action_date_assign(self, cr, uid, ids, *args):
533 for inv in self.browse(cr, uid, ids):
534 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
535 if res and res['value']:
536 self.write(cr, uid, [inv.id], res['value'])
539 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
540 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
541 Hook method to be overridden in additional modules to verify and possibly alter the
542 move lines to be created by an invoice, for special cases.
543 :param invoice_browse: browsable record of the invoice that is generating the move lines
544 :param move_lines: list of dictionaries with the account.move.lines (as for create())
545 :return: the (possibly updated) final move_lines to create for this invoice
549 def action_move_create(self, cr, uid, ids, *args):
550 ait_obj = self.pool.get('account.invoice.tax')
551 cur_obj = self.pool.get('res.currency')
553 for inv in self.browse(cr, uid, ids):
557 if not inv.date_invoice:
558 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
559 company_currency = inv.company_id.currency_id.id
560 # create the analytical lines
561 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
562 # one move line per invoice line
563 iml = self._get_analytic_lines(cr, uid, inv.id)
564 # check if taxes are all computed
566 context.update({'lang': inv.partner_id.lang})
567 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
569 for tax in compute_taxes.values():
570 ait_obj.create(cr, uid, tax)
573 for tax in inv.tax_line:
576 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
578 if not key in compute_taxes:
579 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
580 base = compute_taxes[key]['base']
581 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
582 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
583 for key in compute_taxes:
584 if not key in tax_key:
585 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
587 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
588 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
590 # one move line per tax line
591 iml += ait_obj.move_line_get(cr, uid, inv.id)
593 if inv.type in ('in_invoice', 'in_refund'):
596 ref = self._convert_ref(cr, uid, inv.number)
598 diff_currency_p = inv.currency_id.id <> company_currency
599 # create one move line for the total and possibly adjust the other lines amount
603 if inv.currency_id.id != company_currency:
604 i['currency_id'] = inv.currency_id.id
605 i['amount_currency'] = i['price']
606 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
607 company_currency, i['price'],
608 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
610 i['amount_currency'] = False
611 i['currency_id'] = False
613 if inv.type in ('out_invoice','in_refund'):
615 total_currency += i['amount_currency'] or i['price']
616 i['price'] = - i['price']
619 total_currency -= i['amount_currency'] or i['price']
620 acc_id = inv.account_id.id
622 name = inv['name'] or '/'
625 totlines = self.pool.get('account.payment.term').compute(cr,
626 uid, inv.payment_term.id, total, inv.date_invoice or False)
628 res_amount_currency = total_currency
631 if inv.currency_id.id != company_currency:
632 amount_currency = cur_obj.compute(cr, uid,
633 company_currency, inv.currency_id.id, t[1])
635 amount_currency = False
637 # last line add the diff
638 res_amount_currency -= amount_currency or 0
640 if i == len(totlines):
641 amount_currency += res_amount_currency
647 'account_id': acc_id,
648 'date_maturity': t[0],
649 'amount_currency': diff_currency_p \
650 and amount_currency or False,
651 'currency_id': diff_currency_p \
652 and inv.currency_id.id or False,
660 'account_id': acc_id,
661 'date_maturity' : inv.date_due or False,
662 'amount_currency': diff_currency_p \
663 and total_currency or False,
664 'currency_id': diff_currency_p \
665 and inv.currency_id.id or False,
669 date = inv.date_invoice or time.strftime('%Y-%m-%d')
670 part = inv.partner_id.id
672 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
674 if inv.journal_id.group_invoice_lines:
677 tmp = str(l['account_id'])
678 tmp += '-'+str(l.get('tax_code_id',"False"))
679 tmp += '-'+str(l.get('product_id',"False"))
680 tmp += '-'+str(l.get('analytic_account_id',"False"))
681 tmp += '-'+str(l.get('date_maturity',"False"))
684 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
685 line2[tmp]['debit'] = (am > 0) and am or 0.0
686 line2[tmp]['credit'] = (am < 0) and -am or 0.0
687 line2[tmp]['tax_amount'] += l['tax_amount']
688 line2[tmp]['analytic_lines'] += l['analytic_lines']
692 for key, val in line2.items():
693 line.append((0,0,val))
695 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
696 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
697 if journal.centralisation:
698 raise osv.except_osv(_('UserError'),
699 _('Cannot create invoice move on centralised journal'))
701 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
703 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
704 period_id=inv.period_id and inv.period_id.id or False
706 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'))])
708 period_id=period_ids[0]
710 move['period_id'] = period_id
712 i[2]['period_id'] = period_id
714 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
715 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
716 # make the invoice point to that move
717 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
718 self.pool.get('account.move').post(cr, uid, [move_id])
719 self._log_event(cr, uid, ids)
722 def line_get_convert(self, cr, uid, x, part, date, context=None):
724 'date_maturity': x.get('date_maturity', False),
726 'name':x['name'][:64],
728 'debit':x['price']>0 and x['price'],
729 'credit':x['price']<0 and -x['price'],
730 'account_id':x['account_id'],
731 'analytic_lines':x.get('analytic_lines', []),
732 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
733 'currency_id':x.get('currency_id', False),
734 'tax_code_id': x.get('tax_code_id', False),
735 'tax_amount': x.get('tax_amount', False),
736 'ref':x.get('ref',False),
737 'quantity':x.get('quantity',1.00),
738 'product_id':x.get('product_id', False),
739 'product_uom_id':x.get('uos_id',False),
740 'analytic_account_id':x.get('account_analytic_id',False),
743 def action_number(self, cr, uid, ids, *args):
744 cr.execute('SELECT id, type, number, move_id, reference ' \
745 'FROM account_invoice ' \
748 obj_inv = self.browse(cr, uid, ids)[0]
749 for (id, invtype, number, move_id, reference) in cr.fetchall():
752 'fiscalyear_id' : obj_inv.period_id.fiscalyear_id.id,
754 if obj_inv.journal_id.invoice_sequence_id:
755 sid = obj_inv.journal_id.invoice_sequence_id.id
756 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
758 number = self.pool.get('ir.sequence').get_id(cr, uid,
759 'account.invoice.' + invtype,
762 if invtype in ('in_invoice', 'in_refund'):
765 ref = self._convert_ref(cr, uid, number)
766 cr.execute('UPDATE account_invoice SET number=%s ' \
767 'WHERE id=%s', (number, id))
768 cr.execute('UPDATE account_move SET ref=%s ' \
769 'WHERE id=%s AND (ref is null OR ref = \'\')',
771 cr.execute('UPDATE account_move_line SET ref=%s ' \
772 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
774 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
775 'FROM account_move_line ' \
776 'WHERE account_move_line.move_id = %s ' \
777 'AND account_analytic_line.move_id = account_move_line.id',
781 def action_cancel(self, cr, uid, ids, *args):
782 account_move_obj = self.pool.get('account.move')
783 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
786 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
787 # delete the move this invoice was pointing to
788 # Note that the corresponding move_lines and move_reconciles
789 # will be automatically deleted too
790 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
792 account_move_line_obj = self.pool.get('account.move.line')
793 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
794 for move_line in pay_ids:
795 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
796 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
798 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
799 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
804 def list_distinct_taxes(self, cr, uid, ids):
805 invoices = self.browse(cr, uid, ids)
808 for tax in inv.tax_line:
809 if not tax['name'] in taxes:
810 taxes[tax['name']] = {'name': tax['name']}
811 return taxes.values()
813 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
814 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
816 part=inv['partner_id'] and inv['partner_id'][0]
818 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
819 total = inv['amount_untaxed']
820 if inv['type'] in ('in_invoice','in_refund'):
821 partnertype='supplier'
822 eventtype = 'purchase'
825 partnertype = 'customer'
828 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
829 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})
832 def name_get(self, cr, uid, ids, context=None):
836 'out_invoice': 'CI: ',
837 'in_invoice': 'SI: ',
838 'out_refund': 'OR: ',
841 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')]
843 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
850 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
852 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
853 return self.name_get(cr, user, ids, context)
855 def _refund_cleanup_lines(self, cr, uid, lines):
858 del line['invoice_id']
859 if 'account_id' in line:
860 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
861 if 'product_id' in line:
862 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
864 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
865 if 'invoice_line_tax_id' in line:
866 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
867 if 'account_analytic_id' in line:
868 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
869 if 'tax_code_id' in line :
870 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
871 line['tax_code_id'] = line['tax_code_id'][0]
872 if 'base_code_id' in line :
873 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
874 line['base_code_id'] = line['base_code_id'][0]
875 return map(lambda x: (0,0,x), lines)
877 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
878 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'])
881 for invoice in invoices:
885 'out_invoice': 'out_refund', # Customer Invoice
886 'in_invoice': 'in_refund', # Supplier Invoice
887 'out_refund': 'out_invoice', # Customer Refund
888 'in_refund': 'in_invoice', # Supplier Refund
892 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
893 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
895 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
896 tax_lines = filter(lambda l: l['manual'], tax_lines)
897 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
899 date = time.strftime('%Y-%m-%d')
901 'type': type_dict[invoice['type']],
902 'date_invoice': date,
905 'invoice_line': invoice_lines,
906 'tax_line': tax_lines
910 'period_id': period_id,
916 # take the id part of the tuple returned for many2one fields
917 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
918 'account_id', 'currency_id', 'payment_term', 'journal_id'):
919 invoice[field] = invoice[field] and invoice[field][0]
920 # create the new invoice
921 new_ids.append(self.create(cr, uid, invoice))
924 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=''):
927 #TODO check if we can use different period for payment and the writeoff line
928 assert len(ids)==1, "Can only pay one invoice at a time"
929 invoice = self.browse(cr, uid, ids[0])
930 src_account_id = invoice.account_id.id
931 # Take the seq as name for move
932 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
933 direction = types[invoice.type]
934 #take the choosen date
935 if 'date_p' in context and context['date_p']:
936 date=context['date_p']
938 date=time.strftime('%Y-%m-%d')
940 # Take the amount in currency and the currency of the payment
941 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
942 amount_currency = context['amount_currency']
943 currency_id = context['currency_id']
945 amount_currency = False
948 if invoice.type in ('in_invoice', 'in_refund'):
949 ref = invoice.reference
951 ref = self._convert_ref(cr, uid, invoice.number)
952 # Pay attention to the sign for both debit/credit AND amount_currency
954 'debit': direction * pay_amount>0 and direction * pay_amount,
955 'credit': direction * pay_amount<0 and - direction * pay_amount,
956 'account_id': src_account_id,
957 'partner_id': invoice.partner_id.id,
960 'currency_id':currency_id,
961 'amount_currency':amount_currency and direction * amount_currency or 0.0,
964 'debit': direction * pay_amount<0 and - direction * pay_amount,
965 'credit': direction * pay_amount>0 and direction * pay_amount,
966 'account_id': pay_account_id,
967 'partner_id': invoice.partner_id.id,
970 'currency_id':currency_id,
971 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
975 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
979 lines = [(0, 0, l1), (0, 0, l2)]
980 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
981 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
985 line = self.pool.get('account.move.line')
986 cr.execute('SELECT id FROM account_move_line '\
987 'WHERE move_id in %s',
988 ((move_id, invoice.move_id.id),))
989 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
990 for l in lines+invoice.payment_ids:
991 if l.account_id.id==src_account_id:
992 line_ids.append(l.id)
993 total += (l.debit or 0.0) - (l.credit or 0.0)
994 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
995 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
997 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
999 # Update the stored value (fields.function), so we write to trigger recompute
1000 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1004 class account_invoice_line(osv.osv):
1005 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1007 cur_obj=self.pool.get('res.currency')
1008 for line in self.browse(cr, uid, ids):
1010 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1011 cur = line.invoice_id.currency_id
1012 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1014 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1018 def _price_unit_default(self, cr, uid, context=None):
1021 if 'check_total' in context:
1022 t = context['check_total']
1023 for l in context.get('invoice_line', {}):
1024 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1025 tax_obj = self.pool.get('account.tax')
1026 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1027 t = t - (p * l[2].get('quantity'))
1028 taxes = l[2].get('invoice_line_tax_id')
1029 if len(taxes[0]) >= 3 and taxes[0][2]:
1030 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1031 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)):
1032 t = t - tax['amount']
1036 _name = "account.invoice.line"
1037 _description = "Invoice line"
1039 'name': fields.char('Description', size=256, required=True),
1040 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1041 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1042 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1043 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1044 '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."),
1045 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1046 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1047 'quantity': fields.float('Quantity', required=True),
1048 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1049 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1050 'note': fields.text('Notes'),
1051 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1054 'quantity': lambda *a: 1,
1055 'discount': lambda *a: 0.0,
1056 'price_unit': _price_unit_default,
1059 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1060 tax_obj = self.pool.get('account.tax')
1062 taxes = tax_obj.browse(cr, uid, tax_id)
1063 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1064 price_unit = price_unit - tax['amount']
1065 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1067 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):
1071 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1073 if type in ('in_invoice', 'in_refund'):
1074 return {'value':{}, 'domain':{'product_uom':[]}}
1076 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1077 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1078 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1081 context.update({'lang': part.lang})
1083 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1085 if type in ('out_invoice','out_refund'):
1086 a = res.product_tmpl_id.property_account_income.id
1088 a = res.categ_id.property_account_income_categ.id
1090 a = res.product_tmpl_id.property_account_expense.id
1092 a = res.categ_id.property_account_expense_categ.id
1094 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1096 result['account_id'] = a
1099 tax_obj = self.pool.get('account.tax')
1100 if type in ('out_invoice', 'out_refund'):
1101 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1102 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1104 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)
1105 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1106 if type in ('in_invoice', 'in_refund'):
1107 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)
1108 result.update(to_update)
1110 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1113 result['name'] = res.partner_ref
1116 result['uos_id'] = uom or res.uom_id.id or False
1117 if result['uos_id']:
1118 res2 = res.uom_id.category_id.id
1120 domain = {'uos_id':[('category_id','=',res2 )]}
1121 return {'value':result, 'domain':domain}
1123 def move_line_get(self, cr, uid, invoice_id, context=None):
1126 tax_obj = self.pool.get('account.tax')
1127 cur_obj = self.pool.get('res.currency')
1128 ait_obj = self.pool.get('account.invoice.tax')
1129 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1130 company_currency = inv.company_id.currency_id.id
1131 cur = inv.currency_id
1133 for line in inv.invoice_line:
1134 mres = self.move_line_get_item(cr, uid, line, context)
1138 tax_code_found= False
1139 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1140 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1141 line.quantity, inv.address_invoice_id.id, line.product_id,
1144 if inv.type in ('out_invoice', 'in_invoice'):
1145 tax_code_id = tax['base_code_id']
1146 tax_amount = line.price_subtotal * tax['base_sign']
1148 tax_code_id = tax['ref_base_code_id']
1149 tax_amount = line.price_subtotal * tax['ref_base_sign']
1154 res.append(self.move_line_get_item(cr, uid, line, context))
1155 res[-1]['price'] = 0.0
1156 res[-1]['account_analytic_id'] = False
1157 elif not tax_code_id:
1159 tax_code_found = True
1161 res[-1]['tax_code_id'] = tax_code_id
1162 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1165 def move_line_get_item(self, cr, uid, line, context=None):
1168 'name': line.name[:64],
1169 'price_unit':line.price_unit,
1170 'quantity':line.quantity,
1171 'price':line.price_subtotal,
1172 'account_id':line.account_id.id,
1173 'product_id':line.product_id.id,
1174 'uos_id':line.uos_id.id,
1175 'account_analytic_id':line.account_analytic_id.id,
1176 'taxes':line.invoice_line_tax_id,
1179 # Set the tax field according to the account and the fiscal position
1181 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1184 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1185 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1186 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1187 r = {'value':{'invoice_line_tax_id': res}}
1189 account_invoice_line()
1191 class account_invoice_tax(osv.osv):
1192 _name = "account.invoice.tax"
1193 _description = "Invoice Tax"
1195 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1196 'name': fields.char('Tax Description', size=64, required=True),
1197 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1198 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1199 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1200 'manual': fields.boolean('Manual'),
1201 'sequence': fields.integer('Sequence'),
1203 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1204 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1205 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1206 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1209 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1210 cur_obj = self.pool.get('res.currency')
1211 company_obj = self.pool.get('res.company')
1212 company_currency=False
1214 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1215 if currency_id and company_currency:
1216 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1217 return {'value': {'base_amount':base}}
1219 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1220 cur_obj = self.pool.get('res.currency')
1221 company_obj = self.pool.get('res.company')
1222 company_currency=False
1224 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1225 if currency_id and company_currency:
1226 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1227 return {'value': {'tax_amount':amount}}
1231 'manual': lambda *a: 1,
1232 'base_amount': lambda *a: 0.0,
1233 'tax_amount': lambda *a: 0.0,
1235 def compute(self, cr, uid, invoice_id, context={}):
1237 tax_obj = self.pool.get('account.tax')
1238 cur_obj = self.pool.get('res.currency')
1239 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1240 cur = inv.currency_id
1241 company_currency = inv.company_id.currency_id.id
1243 for line in inv.invoice_line:
1244 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):
1246 val['invoice_id'] = inv.id
1247 val['name'] = tax['name']
1248 val['amount'] = tax['amount']
1249 val['manual'] = False
1250 val['sequence'] = tax['sequence']
1251 val['base'] = tax['price_unit'] * line['quantity']
1253 if inv.type in ('out_invoice','in_invoice'):
1254 val['base_code_id'] = tax['base_code_id']
1255 val['tax_code_id'] = tax['tax_code_id']
1256 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)
1257 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)
1258 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1260 val['base_code_id'] = tax['ref_base_code_id']
1261 val['tax_code_id'] = tax['ref_tax_code_id']
1262 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)
1263 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)
1264 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1266 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1267 if not key in tax_grouped:
1268 tax_grouped[key] = val
1270 tax_grouped[key]['amount'] += val['amount']
1271 tax_grouped[key]['base'] += val['base']
1272 tax_grouped[key]['base_amount'] += val['base_amount']
1273 tax_grouped[key]['tax_amount'] += val['tax_amount']
1275 for t in tax_grouped.values():
1276 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1277 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1278 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1281 def move_line_get(self, cr, uid, invoice_id):
1283 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1284 for t in cr.dictfetchall():
1285 if not t['amount'] \
1286 and not t['tax_code_id'] \
1287 and not t['tax_amount']:
1292 'price_unit': t['amount'],
1294 'price': t['amount'] or 0.0,
1295 'account_id': t['account_id'],
1296 'tax_code_id': t['tax_code_id'],
1297 'tax_amount': t['tax_amount']
1300 account_invoice_tax()
1302 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: