1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
24 from operator import itemgetter
27 from osv import fields, osv
28 from osv.orm import except_orm
30 from tools import config
31 from tools.translate import _
33 class fiscalyear_seq(osv.osv):
34 _name = "fiscalyear.seq"
35 _description = "Maintains Invoice sequences with Fiscal Year"
36 _rec_name = 'fiscalyear_id'
38 'journal_id': fields.many2one('account.journal', 'Journal'),
39 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
40 'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
45 class account_invoice(osv.osv):
46 def _amount_all(self, cr, uid, ids, name, args, context=None):
48 for invoice in self.browse(cr,uid,ids, context=context):
50 'amount_untaxed': 0.0,
54 for line in invoice.invoice_line:
55 res[invoice.id]['amount_untaxed'] += line.price_subtotal
56 for line in invoice.tax_line:
57 res[invoice.id]['amount_tax'] += line.amount
58 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
61 def _get_journal(self, cr, uid, context):
64 type_inv = context.get('type', 'out_invoice')
65 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
66 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
67 journal_obj = self.pool.get('account.journal')
68 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')), ('refund_journal', '=', refund_journal.get(type_inv, False))], limit=1)
74 def _get_currency(self, cr, uid, context):
75 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
77 return user.company_id.currency_id.id
79 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
81 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
82 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
83 tt = type2journal.get(type_inv, 'sale')
84 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
86 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
89 def _get_type(self, cr, uid, context=None):
92 type = context.get('type', 'out_invoice')
95 def _reconciled(self, cr, uid, ids, name, args, context):
98 res[id] = self.test_paid(cr, uid, [id])
101 def _get_reference_type(self, cr, uid, context=None):
102 return [('none', _('Free Reference'))]
104 def _amount_residual(self, cr, uid, ids, name, args, context=None):
108 data_inv = self.browse(cr, uid, ids)
109 cur_obj = self.pool.get('res.currency')
114 inv_total = inv.amount_total
115 context_unreconciled = context.copy()
116 for lines in inv.payment_ids:
117 if lines.amount_currency and lines.currency_id.id == inv.currency_id.id:
118 if inv.type in ('out_invoice','in_refund'):
119 inv_total += lines.amount_currency
121 inv_total -= lines.amount_currency
123 context_unreconciled.update({'date': lines.date})
124 amount_in_invoice_currency = cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id,abs(lines.debit-lines.credit),round=False,context=context_unreconciled)
125 inv_total -= amount_in_invoice_currency
128 res[inv.id] = self.pool.get('res.currency').round(cr, uid, inv.currency_id, result)
131 #This function is called by the fields.function move_lines, which is probably unused now.
132 #This function is also wrongly computed: you should use the one on the field payment_ids instead
133 def _get_lines(self, cr, uid, ids, name, arg, context=None):
136 move_lines = self.move_line_id_payment_get(cr,uid,[id])
141 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
142 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
143 for line in data_lines:
145 if line.reconcile_id:
146 ids_line = line.reconcile_id.line_id
147 elif line.reconcile_partial_id:
148 ids_line = line.reconcile_partial_id.line_partial_ids
149 l = map(lambda x: x.id, ids_line)
150 partial_ids.append(line.id)
151 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
154 def _get_invoice_line(self, cr, uid, ids, context=None):
156 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
157 result[line.invoice_id.id] = True
160 def _get_invoice_tax(self, cr, uid, ids, context=None):
162 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
163 result[tax.invoice_id.id] = True
166 def _compute_lines(self, cr, uid, ids, name, args, context=None):
168 for invoice in self.browse(cr, uid, ids, context):
169 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
172 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
173 temp_lines = []#Added temp list to avoid duplicate records
175 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
176 elif m.reconcile_partial_id:
177 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
178 lines += [x for x in temp_lines if x not in lines]
180 lines = filter(lambda x: x not in src, lines)
181 result[invoice.id] = lines
184 def _get_invoice_from_line(self, cr, uid, ids, context={}):
186 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
187 if line.reconcile_partial_id:
188 for line2 in line.reconcile_partial_id.line_partial_ids:
189 move[line2.move_id.id] = True
190 if line.reconcile_id:
191 for line2 in line.reconcile_id.line_id:
192 move[line2.move_id.id] = True
195 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
198 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
200 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
201 for line in r.line_partial_ids:
202 move[line.move_id.id] = True
203 for line in r.line_id:
204 move[line.move_id.id] = True
208 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
211 _name = "account.invoice"
212 _description = 'Invoice'
215 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
216 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
217 'type': fields.selection([
218 ('out_invoice','Customer Invoice'),
219 ('in_invoice','Supplier Invoice'),
220 ('out_refund','Customer Refund'),
221 ('in_refund','Supplier Refund'),
222 ],'Type', readonly=True, select=True),
224 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
225 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
226 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
228 'comment': fields.text('Additional Information'),
230 'state': fields.selection([
232 ('proforma','Pro-forma'),
233 ('proforma2','Pro-forma'),
236 ('cancel','Cancelled')
237 ],'State', select=True, readonly=True),
239 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
240 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
241 help="If you use payment terms, the due date will be computed automatically at the generation "\
242 "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."),
243 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
244 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
245 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
246 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
247 help="If you use payment terms, the due date will be computed automatically at the generation "\
248 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
249 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
250 '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)]}),
252 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
253 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
254 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
256 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
257 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
259 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
260 'account.invoice.tax': (_get_invoice_tax, None, 20),
261 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
264 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
266 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
267 'account.invoice.tax': (_get_invoice_tax, None, 20),
268 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
271 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
273 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
274 'account.invoice.tax': (_get_invoice_tax, None, 20),
275 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
278 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
279 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
280 'company_id': fields.many2one('res.company', 'Company', required=True),
281 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
282 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
284 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
285 'account.move.line': (_get_invoice_from_line, None, 50),
286 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
287 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
288 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
289 help='The bank account to pay to or to be paid from'),
290 #this field is probably unused, and wrongly computed. Use payment_ids instead.
291 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
292 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
294 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
295 'account.invoice.tax': (_get_invoice_tax, None, 50),
296 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
297 'account.move.line': (_get_invoice_from_line, None, 50),
298 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
300 help="Remaining amount due."),
301 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
302 'move_name': fields.char('Account Move', size=64),
303 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
307 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
308 'state': lambda *a: 'draft',
309 'journal_id': _get_journal,
310 'currency_id': _get_currency,
311 'company_id': lambda self, cr, uid, context: \
312 self.pool.get('res.users').browse(cr, uid, uid,
313 context=context).company_id.id,
314 'reference_type': lambda *a: 'none',
315 'check_total': lambda *a: 0.0,
318 def create(self, cr, uid, vals, context={}):
320 res = super(account_invoice, self).create(cr, uid, vals, context)
323 if '"journal_id" viol' in e.args[0]:
324 raise except_orm(_('Configuration Error!'),
325 _('There is no Accounting Journal of type Sale/Purchase defined!'))
327 raise except_orm(_('UnknownError'), str(e))
329 def unlink(self, cr, uid, ids, context=None):
330 invoices = self.read(cr, uid, ids, ['state'])
333 if t['state'] in ('draft', 'cancel'):
334 unlink_ids.append(t['id'])
336 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
337 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
340 # def get_invoice_address(self, cr, uid, ids):
341 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
344 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
345 date_invoice=False, payment_term=False, partner_bank=False):
346 invoice_addr_id = False
347 contact_addr_id = False
348 partner_payment_term = False
351 fiscal_position = False
353 opt = [('uid', str(uid))]
356 opt.insert(0, ('id', partner_id))
357 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
358 contact_addr_id = res['contact']
359 invoice_addr_id = res['invoice']
360 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
361 if type in ('out_invoice', 'out_refund'):
362 acc_id = p.property_account_receivable.id
364 acc_id = p.property_account_payable.id
365 fiscal_position = p.property_account_position and p.property_account_position.id or False
366 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
368 bank_id = p.bank_ids[0].id
371 'address_contact_id': contact_addr_id,
372 'address_invoice_id': invoice_addr_id,
373 'account_id': acc_id,
374 'payment_term': partner_payment_term,
375 'fiscal_position': fiscal_position
379 if type in ('in_invoice', 'in_refund'):
380 result['value']['partner_bank'] = bank_id
382 if partner_bank != bank_id:
383 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
384 result['value'].update(to_update['value'])
387 def onchange_currency_id(self, cr, uid, ids, curr_id):
390 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
391 if not payment_term_id:
394 pt_obj= self.pool.get('account.payment.term')
395 if not date_invoice :
396 date_invoice = time.strftime('%Y-%m-%d')
398 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
401 pterm_list = [line[0] for line in pterm_list]
403 res= {'value':{'date_due': pterm_list[-1]}}
405 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
409 def onchange_invoice_line(self, cr, uid, ids, lines):
412 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
415 # go from canceled state to draft state
416 def action_cancel_draft(self, cr, uid, ids, *args):
417 self.write(cr, uid, ids, {'state':'draft'})
418 wf_service = netsvc.LocalService("workflow")
420 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
426 # return the ids of the move lines which has the same account than the invoice
428 def move_line_id_payment_get(self, cr, uid, ids, *args):
430 if not ids: return res
431 cr.execute('SELECT l.id '\
432 'FROM account_move_line l '\
433 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
435 'AND l.account_id=i.account_id',
437 res = map(itemgetter(0), cr.fetchall())
440 def copy(self, cr, uid, id, default=None, context=None):
443 default = default.copy()
444 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
445 if 'date_invoice' not in default:
446 default['date_invoice'] = False
447 if 'date_due' not in default:
448 default['date_due'] = False
449 return super(account_invoice, self).copy(cr, uid, id, default, context)
451 def test_paid(self, cr, uid, ids, *args):
452 res = self.move_line_id_payment_get(cr, uid, ids)
457 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
458 ok = ok and bool(cr.fetchone()[0])
461 def button_reset_taxes(self, cr, uid, ids, context=None):
465 ait_obj = self.pool.get('account.invoice.tax')
467 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
468 partner = self.browse(cr, uid, id,context=ctx).partner_id
470 ctx.update({'lang': partner.lang})
471 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
472 ait_obj.create(cr, uid, taxe)
473 # Update the stored value (fields.function), so we write to trigger recompute
474 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
475 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
478 def button_compute(self, cr, uid, ids, context=None, set_total=False):
479 self.button_reset_taxes(cr, uid, ids, context)
480 for inv in self.browse(cr, uid, ids):
482 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
485 def _convert_ref(self, cr, uid, ref):
486 return (ref or '').replace('/','')
488 def _get_analytic_lines(self, cr, uid, id):
489 inv = self.browse(cr, uid, [id])[0]
490 cur_obj = self.pool.get('res.currency')
492 company_currency = inv.company_id.currency_id.id
493 if inv.type in ('out_invoice', 'in_refund'):
498 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
500 if il['account_analytic_id']:
501 if inv.type in ('in_invoice', 'in_refund'):
504 ref = self._convert_ref(cr, uid, inv.number)
505 il['analytic_lines'] = [(0,0, {
507 'date': inv['date_invoice'],
508 'account_id': il['account_analytic_id'],
509 'unit_amount': il['quantity'],
510 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
511 'product_id': il['product_id'],
512 'product_uom_id': il['uos_id'],
513 'general_account_id': il['account_id'],
514 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
519 def action_date_assign(self, cr, uid, ids, *args):
520 for inv in self.browse(cr, uid, ids):
521 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
522 if res and res['value']:
523 self.write(cr, uid, [inv.id], res['value'])
526 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
527 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
528 Hook method to be overridden in additional modules to verify and possibly alter the
529 move lines to be created by an invoice, for special cases.
530 :param invoice_browse: browsable record of the invoice that is generating the move lines
531 :param move_lines: list of dictionaries with the account.move.lines (as for create())
532 :return: the (possibly updated) final move_lines to create for this invoice
536 def action_move_create(self, cr, uid, ids, *args):
537 ait_obj = self.pool.get('account.invoice.tax')
538 cur_obj = self.pool.get('res.currency')
540 for inv in self.browse(cr, uid, ids):
544 if not inv.date_invoice:
545 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
546 company_currency = inv.company_id.currency_id.id
547 # create the analytical lines
548 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
549 # one move line per invoice line
550 iml = self._get_analytic_lines(cr, uid, inv.id)
551 # check if taxes are all computed
553 ctx.update({'lang': inv.partner_id.lang})
554 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
556 for tax in compute_taxes.values():
557 ait_obj.create(cr, uid, tax)
560 for tax in inv.tax_line:
563 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
565 if not key in compute_taxes:
566 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
567 base = compute_taxes[key]['base']
568 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
569 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
570 for key in compute_taxes:
571 if not key in tax_key:
572 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
574 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
575 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
577 # one move line per tax line
578 iml += ait_obj.move_line_get(cr, uid, inv.id)
580 if inv.type in ('in_invoice', 'in_refund'):
583 ref = self._convert_ref(cr, uid, inv.number)
585 diff_currency_p = inv.currency_id.id <> company_currency
586 # create one move line for the total and possibly adjust the other lines amount
590 if inv.currency_id.id != company_currency:
591 i['currency_id'] = inv.currency_id.id
592 i['amount_currency'] = i['price']
593 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
594 company_currency, i['price'],
595 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
597 i['amount_currency'] = False
598 i['currency_id'] = False
600 if inv.type in ('out_invoice','in_refund'):
602 total_currency += i['amount_currency'] or i['price']
603 i['price'] = - i['price']
606 total_currency -= i['amount_currency'] or i['price']
607 acc_id = inv.account_id.id
609 name = inv['name'] or '/'
612 totlines = self.pool.get('account.payment.term').compute(cr,
613 uid, inv.payment_term.id, total, inv.date_invoice or False)
615 res_amount_currency = total_currency
618 if inv.currency_id.id != company_currency:
619 amount_currency = cur_obj.compute(cr, uid,
620 company_currency, inv.currency_id.id, t[1])
622 amount_currency = False
624 # last line add the diff
625 res_amount_currency -= amount_currency or 0
627 if i == len(totlines):
628 amount_currency += res_amount_currency
634 'account_id': acc_id,
635 'date_maturity': t[0],
636 'amount_currency': diff_currency_p \
637 and amount_currency or False,
638 'currency_id': diff_currency_p \
639 and inv.currency_id.id or False,
647 'account_id': acc_id,
648 'date_maturity' : inv.date_due or False,
649 'amount_currency': diff_currency_p \
650 and total_currency or False,
651 'currency_id': diff_currency_p \
652 and inv.currency_id.id or False,
656 date = inv.date_invoice or time.strftime('%Y-%m-%d')
657 part = inv.partner_id.id
659 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
661 if inv.journal_id.group_invoice_lines:
664 tmp = str(l['account_id'])
665 tmp += '-'+str(l.get('tax_code_id',"False"))
666 tmp += '-'+str(l.get('product_id',"False"))
667 tmp += '-'+str(l.get('analytic_account_id',"False"))
668 tmp += '-'+str(l.get('date_maturity',"False"))
671 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
672 line2[tmp]['debit'] = (am > 0) and am or 0.0
673 line2[tmp]['credit'] = (am < 0) and -am or 0.0
674 line2[tmp]['tax_amount'] += l['tax_amount']
675 line2[tmp]['analytic_lines'] += l['analytic_lines']
679 for key, val in line2.items():
680 line.append((0,0,val))
682 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
683 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
684 if journal.centralisation:
685 raise osv.except_osv(_('UserError'),
686 _('Cannot create invoice move on centralised journal'))
688 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
690 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
691 period_id=inv.period_id and inv.period_id.id or False
693 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'))])
695 period_id=period_ids[0]
697 move['period_id'] = period_id
699 i[2]['period_id'] = period_id
701 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
702 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
703 # make the invoice point to that move
704 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
705 # Pass invoice in context in method post: used if you want to get the same
706 # account move reference when creating the same invoice after a cancelled one:
707 self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
708 self._log_event(cr, uid, ids)
711 def line_get_convert(self, cr, uid, x, part, date, context=None):
713 'date_maturity': x.get('date_maturity', False),
715 'name':x['name'][:64],
717 'debit':x['price']>0 and x['price'],
718 'credit':x['price']<0 and -x['price'],
719 'account_id':x['account_id'],
720 'analytic_lines':x.get('analytic_lines', []),
721 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
722 'currency_id':x.get('currency_id', False),
723 'tax_code_id': x.get('tax_code_id', False),
724 'tax_amount': x.get('tax_amount', False),
725 'ref':x.get('ref',False),
726 'quantity':x.get('quantity',1.00),
727 'product_id':x.get('product_id', False),
728 'product_uom_id':x.get('uos_id',False),
729 'analytic_account_id':x.get('account_analytic_id',False),
732 def action_number(self, cr, uid, ids, *args):
733 cr.execute('SELECT id, type, number, move_id, reference ' \
734 'FROM account_invoice ' \
737 obj_inv = self.browse(cr, uid, ids)[0]
738 for (id, invtype, number, move_id, reference) in cr.fetchall():
741 'fiscalyear_id' : obj_inv.period_id.fiscalyear_id.id,
743 if obj_inv.journal_id.invoice_sequence_id:
744 sid = obj_inv.journal_id.invoice_sequence_id.id
745 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
747 number = self.pool.get('ir.sequence').get_id(cr, uid,
748 'account.invoice.' + invtype,
752 raise osv.except_osv(_('Warning !'), _('There is no active invoice sequence defined for the journal !'))
754 if invtype in ('in_invoice', 'in_refund'):
757 ref = self._convert_ref(cr, uid, number)
758 cr.execute('UPDATE account_invoice SET number=%s ' \
759 'WHERE id=%s', (number, id))
760 cr.execute('UPDATE account_move SET ref=%s ' \
761 'WHERE id=%s AND (ref is null OR ref = \'\')',
763 cr.execute('UPDATE account_move_line SET ref=%s ' \
764 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
766 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
767 'FROM account_move_line ' \
768 'WHERE account_move_line.move_id = %s ' \
769 'AND account_analytic_line.move_id = account_move_line.id',
773 def action_cancel(self, cr, uid, ids, *args):
774 account_move_obj = self.pool.get('account.move')
775 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
778 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
779 # delete the move this invoice was pointing to
780 # Note that the corresponding move_lines and move_reconciles
781 # will be automatically deleted too
782 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
784 account_move_line_obj = self.pool.get('account.move.line')
785 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
786 for move_line in pay_ids:
787 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
788 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
790 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
791 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
796 def list_distinct_taxes(self, cr, uid, ids):
797 invoices = self.browse(cr, uid, ids)
800 for tax in inv.tax_line:
801 if not tax['name'] in taxes:
802 taxes[tax['name']] = {'name': tax['name']}
803 return taxes.values()
805 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
806 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
808 part=inv['partner_id'] and inv['partner_id'][0]
810 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
811 total = inv['amount_untaxed']
812 if inv['type'] in ('in_invoice','in_refund'):
813 partnertype='supplier'
814 eventtype = 'purchase'
817 partnertype = 'customer'
820 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
821 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})
824 def name_get(self, cr, uid, ids, context=None):
828 'out_invoice': 'CI: ',
829 'in_invoice': 'SI: ',
830 'out_refund': 'OR: ',
833 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')]
835 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
842 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
844 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
845 return self.name_get(cr, user, ids, context)
847 def _refund_cleanup_lines(self, cr, uid, lines):
850 del line['invoice_id']
851 if 'account_id' in line:
852 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
853 if 'product_id' in line:
854 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
856 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
857 if 'invoice_line_tax_id' in line:
858 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
859 if 'account_analytic_id' in line:
860 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
861 if 'tax_code_id' in line :
862 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
863 line['tax_code_id'] = line['tax_code_id'][0]
864 if 'base_code_id' in line :
865 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
866 line['base_code_id'] = line['base_code_id'][0]
867 return map(lambda x: (0,0,x), lines)
869 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
870 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'])
873 for invoice in invoices:
877 'out_invoice': 'out_refund', # Customer Invoice
878 'in_invoice': 'in_refund', # Supplier Invoice
879 'out_refund': 'out_invoice', # Customer Refund
880 'in_refund': 'in_invoice', # Supplier Refund
884 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
885 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
887 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
888 tax_lines = filter(lambda l: l['manual'], tax_lines)
889 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
891 date = time.strftime('%Y-%m-%d')
893 'type': type_dict[invoice['type']],
894 'date_invoice': date,
897 'invoice_line': invoice_lines,
898 'tax_line': tax_lines
902 'period_id': period_id,
908 # take the id part of the tuple returned for many2one fields
909 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
910 'account_id', 'currency_id', 'payment_term', 'journal_id'):
911 invoice[field] = invoice[field] and invoice[field][0]
912 # create the new invoice
913 new_ids.append(self.create(cr, uid, invoice))
916 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=''):
919 #TODO check if we can use different period for payment and the writeoff line
920 assert len(ids)==1, "Can only pay one invoice at a time"
921 invoice = self.browse(cr, uid, ids[0])
922 src_account_id = invoice.account_id.id
923 # Take the seq as name for move
924 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
925 direction = types[invoice.type]
926 #take the choosen date
927 if 'date_p' in context and context['date_p']:
928 date=context['date_p']
930 date=time.strftime('%Y-%m-%d')
932 # Take the amount in currency and the currency of the payment
933 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
934 amount_currency = context['amount_currency']
935 currency_id = context['currency_id']
937 amount_currency = False
940 if invoice.type in ('in_invoice', 'in_refund'):
941 ref = invoice.reference
943 ref = self._convert_ref(cr, uid, invoice.number)
944 # Pay attention to the sign for both debit/credit AND amount_currency
946 'debit': direction * pay_amount>0 and direction * pay_amount,
947 'credit': direction * pay_amount<0 and - direction * pay_amount,
948 'account_id': src_account_id,
949 'partner_id': invoice.partner_id.id,
952 'currency_id':currency_id,
953 'amount_currency':amount_currency and direction * amount_currency or 0.0,
956 'debit': direction * pay_amount<0 and - direction * pay_amount,
957 'credit': direction * pay_amount>0 and direction * pay_amount,
958 'account_id': pay_account_id,
959 'partner_id': invoice.partner_id.id,
962 'currency_id':currency_id,
963 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
967 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
971 lines = [(0, 0, l1), (0, 0, l2)]
972 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
973 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
977 line = self.pool.get('account.move.line')
978 cr.execute('SELECT id FROM account_move_line '\
979 'WHERE move_id in %s',
980 ((move_id, invoice.move_id.id),))
981 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
983 for l in lines+invoice.payment_ids:
984 if l.account_id.id==src_account_id:
985 line_ids.append(l.id)
986 total += (l.debit or 0.0) - (l.credit or 0.0)
988 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
989 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
991 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
993 # Update the stored value (fields.function), so we write to trigger recompute
994 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
998 class account_invoice_line(osv.osv):
999 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
1001 cur_obj=self.pool.get('res.currency')
1002 for line in self.browse(cr, uid, ids):
1004 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1005 cur = line.invoice_id.currency_id
1006 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1008 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1012 def _price_unit_default(self, cr, uid, context=None):
1015 if 'check_total' in context:
1016 t = context['check_total']
1017 for l in context.get('invoice_line', {}):
1018 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1019 tax_obj = self.pool.get('account.tax')
1020 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1021 t = t - (p * l[2].get('quantity'))
1022 taxes = l[2].get('invoice_line_tax_id')
1023 if len(taxes[0]) >= 3 and taxes[0][2]:
1024 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1025 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)):
1026 t = t - tax['amount']
1030 _name = "account.invoice.line"
1031 _description = "Invoice line"
1033 'name': fields.char('Description', size=256, required=True),
1034 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1035 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1036 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1037 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1038 '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."),
1039 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1040 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1041 'quantity': fields.float('Quantity', required=True),
1042 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1043 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1044 'note': fields.text('Notes'),
1045 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1048 'quantity': lambda *a: 1,
1049 'discount': lambda *a: 0.0,
1050 'price_unit': _price_unit_default,
1053 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1054 tax_obj = self.pool.get('account.tax')
1056 taxes = tax_obj.browse(cr, uid, tax_id)
1057 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1058 price_unit = price_unit - tax['amount']
1059 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1061 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):
1065 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1067 if type in ('in_invoice', 'in_refund'):
1068 return {'value':{}, 'domain':{'product_uom':[]}}
1070 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1071 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1072 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1075 context.update({'lang': part.lang})
1077 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1079 if type in ('out_invoice','out_refund'):
1080 a = res.product_tmpl_id.property_account_income.id
1082 a = res.categ_id.property_account_income_categ.id
1084 a = res.product_tmpl_id.property_account_expense.id
1086 a = res.categ_id.property_account_expense_categ.id
1088 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1090 result['account_id'] = a
1093 tax_obj = self.pool.get('account.tax')
1094 if type in ('out_invoice', 'out_refund'):
1095 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1096 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1098 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)
1099 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1100 if type in ('in_invoice', 'in_refund'):
1101 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)
1102 result.update(to_update)
1104 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1107 result['name'] = res.partner_ref
1110 result['uos_id'] = uom or res.uom_id.id or False
1111 result['note'] = res.description
1112 if result['uos_id']:
1113 res2 = res.uom_id.category_id.id
1115 domain = {'uos_id':[('category_id','=',res2 )]}
1116 return {'value':result, 'domain':domain}
1118 def move_line_get(self, cr, uid, invoice_id, context=None):
1121 tax_obj = self.pool.get('account.tax')
1122 cur_obj = self.pool.get('res.currency')
1123 ait_obj = self.pool.get('account.invoice.tax')
1124 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1125 company_currency = inv.company_id.currency_id.id
1126 cur = inv.currency_id
1128 for line in inv.invoice_line:
1129 mres = self.move_line_get_item(cr, uid, line, context)
1133 tax_code_found= False
1134 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1135 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1136 line.quantity, inv.address_invoice_id.id, line.product_id,
1139 if inv.type in ('out_invoice', 'in_invoice'):
1140 tax_code_id = tax['base_code_id']
1141 tax_amount = line.price_subtotal * tax['base_sign']
1143 tax_code_id = tax['ref_base_code_id']
1144 tax_amount = line.price_subtotal * tax['ref_base_sign']
1149 res.append(self.move_line_get_item(cr, uid, line, context))
1150 res[-1]['price'] = 0.0
1151 res[-1]['account_analytic_id'] = False
1152 elif not tax_code_id:
1154 tax_code_found = True
1156 res[-1]['tax_code_id'] = tax_code_id
1157 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1160 def move_line_get_item(self, cr, uid, line, context=None):
1163 'name': line.name[:64],
1164 'price_unit':line.price_unit,
1165 'quantity':line.quantity,
1166 'price':line.price_subtotal,
1167 'account_id':line.account_id.id,
1168 'product_id':line.product_id.id,
1169 'uos_id':line.uos_id.id,
1170 'account_analytic_id':line.account_analytic_id.id,
1171 'taxes':line.invoice_line_tax_id,
1174 # Set the tax field according to the account and the fiscal position
1176 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1179 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1180 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1181 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1182 r = {'value':{'invoice_line_tax_id': res}}
1184 account_invoice_line()
1186 class account_invoice_tax(osv.osv):
1187 _name = "account.invoice.tax"
1188 _description = "Invoice Tax"
1190 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1191 'name': fields.char('Tax Description', size=64, required=True),
1192 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1193 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1194 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1195 'manual': fields.boolean('Manual'),
1196 'sequence': fields.integer('Sequence'),
1198 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1199 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1200 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1201 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1204 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1205 cur_obj = self.pool.get('res.currency')
1206 company_obj = self.pool.get('res.company')
1207 company_currency=False
1209 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1210 if currency_id and company_currency:
1211 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1212 return {'value': {'base_amount':base}}
1214 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1215 cur_obj = self.pool.get('res.currency')
1216 company_obj = self.pool.get('res.company')
1217 company_currency=False
1219 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1220 if currency_id and company_currency:
1221 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1222 return {'value': {'tax_amount':amount}}
1226 'manual': lambda *a: 1,
1227 'base_amount': lambda *a: 0.0,
1228 'tax_amount': lambda *a: 0.0,
1230 def compute(self, cr, uid, invoice_id, context={}):
1232 tax_obj = self.pool.get('account.tax')
1233 cur_obj = self.pool.get('res.currency')
1234 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1235 cur = inv.currency_id
1236 company_currency = inv.company_id.currency_id.id
1238 for line in inv.invoice_line:
1239 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):
1241 val['invoice_id'] = inv.id
1242 val['name'] = tax['name']
1243 val['amount'] = tax['amount']
1244 val['manual'] = False
1245 val['sequence'] = tax['sequence']
1246 val['base'] = tax['price_unit'] * line['quantity']
1248 if inv.type in ('out_invoice','in_invoice'):
1249 val['base_code_id'] = tax['base_code_id']
1250 val['tax_code_id'] = tax['tax_code_id']
1251 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)
1252 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)
1253 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1255 val['base_code_id'] = tax['ref_base_code_id']
1256 val['tax_code_id'] = tax['ref_tax_code_id']
1257 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)
1258 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)
1259 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1261 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1262 if not key in tax_grouped:
1263 tax_grouped[key] = val
1265 tax_grouped[key]['amount'] += val['amount']
1266 tax_grouped[key]['base'] += val['base']
1267 tax_grouped[key]['base_amount'] += val['base_amount']
1268 tax_grouped[key]['tax_amount'] += val['tax_amount']
1270 for t in tax_grouped.values():
1271 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1272 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1273 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1274 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1277 def move_line_get(self, cr, uid, invoice_id):
1279 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1280 for t in cr.dictfetchall():
1281 if not t['amount'] \
1282 and not t['tax_code_id'] \
1283 and not t['tax_amount']:
1288 'price_unit': t['amount'],
1290 'price': t['amount'] or 0.0,
1291 'account_id': t['account_id'],
1292 'tax_code_id': t['tax_code_id'],
1293 'tax_amount': t['tax_amount']
1296 account_invoice_tax()
1298 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: