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 ##############################################################################
25 from osv import fields, osv
26 from osv.orm import except_orm
28 from tools import config
29 from tools.translate import _
31 class fiscalyear_seq(osv.osv):
32 _name = "fiscalyear.seq"
33 _description = "Maintains Invoice sequences with Fiscal Year"
34 _rec_name = 'fiscalyear_id'
36 'journal_id': fields.many2one('account.journal', 'Journal'),
37 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
38 'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
43 class account_invoice(osv.osv):
44 def _amount_all(self, cr, uid, ids, name, args, context=None):
46 for invoice in self.browse(cr,uid,ids, context=context):
48 'amount_untaxed': 0.0,
52 for line in invoice.invoice_line:
53 res[invoice.id]['amount_untaxed'] += line.price_subtotal
54 for line in invoice.tax_line:
55 res[invoice.id]['amount_tax'] += line.amount
56 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
59 def _get_journal(self, cr, uid, context):
62 type_inv = context.get('type', 'out_invoice')
63 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
64 refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
65 journal_obj = self.pool.get('account.journal')
66 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')), ('refund_journal', '=', refund_journal.get(type_inv, False))], limit=1)
72 def _get_currency(self, cr, uid, context):
73 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
75 return user.company_id.currency_id.id
77 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
79 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
80 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
81 tt = type2journal.get(type_inv, 'sale')
82 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
84 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
87 def _get_type(self, cr, uid, context=None):
90 type = context.get('type', 'out_invoice')
93 def _reconciled(self, cr, uid, ids, name, args, context):
96 res[id] = self.test_paid(cr, uid, [id])
99 def _get_reference_type(self, cr, uid, context=None):
100 return [('none', _('Free Reference'))]
102 def _amount_residual(self, cr, uid, ids, name, args, context=None):
104 data_inv = self.browse(cr, uid, ids)
105 cur_obj = self.pool.get('res.currency')
108 context.update({'date':inv.date_invoice})
109 context_unreconciled=context.copy()
110 for lines in inv.move_lines:
111 debit_tmp = lines.debit
112 credit_tmp = lines.credit
113 # If currency conversion needed
114 if inv.company_id.currency_id.id <> inv.currency_id.id:
115 # If invoice paid, compute currency amount according to invoice date
116 # otherwise, take the line date
117 if not inv.reconciled:
118 context.update({'date':lines.date})
119 context_unreconciled.update({'date':lines.date})
120 # If amount currency setted, compute for debit and credit in company currency
121 if lines.amount_currency < 0:
122 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))
123 elif lines.amount_currency > 0:
124 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))
125 # Then, recomput into invoice currency to avoid rounding trouble !
126 debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False,context=context)
127 credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False,context=context)
132 if not inv.amount_total:
134 elif inv.type in ('out_invoice','in_refund'):
135 amount = credit-debit
136 result = inv.amount_total - amount
138 amount = debit-credit
139 result = inv.amount_total - amount
140 # Use is_zero function to avoid rounding trouble => should be fixed into ORM
141 res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id,result) and result or 0.0
145 def _get_lines(self, cr, uid, ids, name, arg, context=None):
148 move_lines = self.move_line_id_payment_get(cr,uid,[id])
153 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
154 partial_ids = []# Keeps the track of ids where partial payments are done with payment terms
155 for line in data_lines:
157 if line.reconcile_id:
158 ids_line = line.reconcile_id.line_id
159 elif line.reconcile_partial_id:
160 ids_line = line.reconcile_partial_id.line_partial_ids
161 l = map(lambda x: x.id, ids_line)
162 partial_ids.append(line.id)
163 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
166 def _get_invoice_line(self, cr, uid, ids, context=None):
168 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
169 result[line.invoice_id.id] = True
172 def _get_invoice_tax(self, cr, uid, ids, context=None):
174 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
175 result[tax.invoice_id.id] = True
178 def _compute_lines(self, cr, uid, ids, name, args, context=None):
180 for invoice in self.browse(cr, uid, ids, context):
181 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
184 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
185 temp_lines = []#Added temp list to avoid duplicate records
187 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
188 elif m.reconcile_partial_id:
189 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
190 lines += [x for x in temp_lines if x not in lines]
193 lines = filter(lambda x: x not in src, lines)
194 result[invoice.id] = lines
197 def _get_invoice_from_line(self, cr, uid, ids, context={}):
199 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
200 if line.reconcile_partial_id:
201 for line2 in line.reconcile_partial_id.line_partial_ids:
202 move[line2.move_id.id] = True
203 if line.reconcile_id:
204 for line2 in line.reconcile_id.line_id:
205 move[line2.move_id.id] = True
208 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
211 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
213 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
214 for line in r.line_partial_ids:
215 move[line.move_id.id] = True
216 for line in r.line_id:
217 move[line.move_id.id] = True
221 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
224 _name = "account.invoice"
225 _description = 'Invoice'
228 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
229 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
230 'type': fields.selection([
231 ('out_invoice','Customer Invoice'),
232 ('in_invoice','Supplier Invoice'),
233 ('out_refund','Customer Refund'),
234 ('in_refund','Supplier Refund'),
235 ],'Type', readonly=True, select=True),
237 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
238 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
239 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
241 'comment': fields.text('Additional Information'),
243 'state': fields.selection([
245 ('proforma','Pro-forma'),
246 ('proforma2','Pro-forma'),
249 ('cancel','Cancelled')
250 ],'State', select=True, readonly=True),
252 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
253 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
254 help="If you use payment terms, the due date will be computed automatically at the generation "\
255 "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."),
256 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
257 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
258 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
259 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
260 help="If you use payment terms, the due date will be computed automatically at the generation "\
261 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
262 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
263 '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)]}),
265 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
266 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
267 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
269 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
270 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])),string='Untaxed',
272 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
273 'account.invoice.tax': (_get_invoice_tax, None, 20),
274 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
277 'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Tax',
279 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
280 'account.invoice.tax': (_get_invoice_tax, None, 20),
281 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
284 'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
286 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
287 'account.invoice.tax': (_get_invoice_tax, None, 20),
288 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
291 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
292 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
293 'company_id': fields.many2one('res.company', 'Company', required=True),
294 'check_total': fields.float('Total', digits=(16, int(config['price_accuracy'])), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
295 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
297 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
298 'account.move.line': (_get_invoice_from_line, None, 50),
299 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
300 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
301 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
302 help='The bank account to pay to or to be paid from'),
303 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
304 'residual': fields.function(_amount_residual, method=True, digits=(16, int(config['price_accuracy'])),string='Residual',
306 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
307 'account.invoice.tax': (_get_invoice_tax, None, 50),
308 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
309 'account.move.line': (_get_invoice_from_line, None, 50),
310 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
312 help="Remaining amount due."),
313 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
314 'move_name': fields.char('Account Move', size=64),
315 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
319 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
320 'state': lambda *a: 'draft',
321 'journal_id': _get_journal,
322 'currency_id': _get_currency,
323 'company_id': lambda self, cr, uid, context: \
324 self.pool.get('res.users').browse(cr, uid, uid,
325 context=context).company_id.id,
326 'reference_type': lambda *a: 'none',
327 'check_total': lambda *a: 0.0,
330 def create(self, cr, uid, vals, context={}):
332 res = super(account_invoice, self).create(cr, uid, vals, context)
335 if '"journal_id" viol' in e.args[0]:
336 raise except_orm(_('Configuration Error!'),
337 _('There is no Accounting Journal of type Sale/Purchase defined!'))
339 raise except_orm(_('UnknownError'), str(e))
341 def unlink(self, cr, uid, ids, context=None):
342 invoices = self.read(cr, uid, ids, ['state'])
345 if t['state'] in ('draft', 'cancel'):
346 unlink_ids.append(t['id'])
348 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
349 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
352 # def get_invoice_address(self, cr, uid, ids):
353 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
356 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
357 date_invoice=False, payment_term=False, partner_bank=False):
358 invoice_addr_id = False
359 contact_addr_id = False
360 partner_payment_term = False
363 fiscal_position = False
365 opt = [('uid', str(uid))]
368 opt.insert(0, ('id', partner_id))
369 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
370 contact_addr_id = res['contact']
371 invoice_addr_id = res['invoice']
372 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
373 if type in ('out_invoice', 'out_refund'):
374 acc_id = p.property_account_receivable.id
376 acc_id = p.property_account_payable.id
377 fiscal_position = p.property_account_position and p.property_account_position.id or False
378 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
380 bank_id = p.bank_ids[0].id
383 'address_contact_id': contact_addr_id,
384 'address_invoice_id': invoice_addr_id,
385 'account_id': acc_id,
386 'payment_term': partner_payment_term,
387 'fiscal_position': fiscal_position
391 if type in ('in_invoice', 'in_refund'):
392 result['value']['partner_bank'] = bank_id
394 if partner_bank != bank_id:
395 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
396 result['value'].update(to_update['value'])
399 def onchange_currency_id(self, cr, uid, ids, curr_id):
402 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
403 if not payment_term_id:
406 pt_obj= self.pool.get('account.payment.term')
407 if not date_invoice :
408 date_invoice = time.strftime('%Y-%m-%d')
410 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
413 pterm_list = [line[0] for line in pterm_list]
415 res= {'value':{'date_due': pterm_list[-1]}}
417 raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
421 def onchange_invoice_line(self, cr, uid, ids, lines):
424 def onchange_partner_bank(self, cursor, user, ids, partner_bank):
427 # go from canceled state to draft state
428 def action_cancel_draft(self, cr, uid, ids, *args):
429 self.write(cr, uid, ids, {'state':'draft'})
430 wf_service = netsvc.LocalService("workflow")
432 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
438 # return the ids of the move lines which has the same account than the invoice
440 def move_line_id_payment_get(self, cr, uid, ids, *args):
442 if not ids: return res
445 from account_move_line l \
446 left join account_invoice i on (i.move_id=l.move_id) \
447 where i.id in ('+','.join(map(str,ids))+') and l.account_id=i.account_id')
448 res = map(lambda x: x[0], cr.fetchall())
451 def copy(self, cr, uid, id, default=None, context=None):
454 default = default.copy()
455 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
456 if 'date_invoice' not in default:
457 default['date_invoice'] = False
458 if 'date_due' not in default:
459 default['date_due'] = False
460 return super(account_invoice, self).copy(cr, uid, id, default, context)
462 def test_paid(self, cr, uid, ids, *args):
463 res = self.move_line_id_payment_get(cr, uid, ids)
468 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
469 ok = ok and bool(cr.fetchone()[0])
472 def button_reset_taxes(self, cr, uid, ids, context=None):
475 ait_obj = self.pool.get('account.invoice.tax')
477 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
478 partner = self.browse(cr, uid, id,context=context).partner_id
480 context.update({'lang': partner.lang})
481 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
482 ait_obj.create(cr, uid, taxe)
483 # Update the stored value (fields.function), so we write to trigger recompute
484 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=context)
485 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
488 def button_compute(self, cr, uid, ids, context=None, set_total=False):
489 self.button_reset_taxes(cr, uid, ids, context)
490 for inv in self.browse(cr, uid, ids):
492 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
495 def _convert_ref(self, cr, uid, ref):
496 return (ref or '').replace('/','')
498 def _get_analytic_lines(self, cr, uid, id):
499 inv = self.browse(cr, uid, [id])[0]
500 cur_obj = self.pool.get('res.currency')
502 company_currency = inv.company_id.currency_id.id
503 if inv.type in ('out_invoice', 'in_refund'):
508 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
510 if il['account_analytic_id']:
511 if inv.type in ('in_invoice', 'in_refund'):
514 ref = self._convert_ref(cr, uid, inv.number)
515 il['analytic_lines'] = [(0,0, {
517 'date': inv['date_invoice'],
518 'account_id': il['account_analytic_id'],
519 'unit_amount': il['quantity'],
520 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
521 'product_id': il['product_id'],
522 'product_uom_id': il['uos_id'],
523 'general_account_id': il['account_id'],
524 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
529 def action_date_assign(self, cr, uid, ids, *args):
530 for inv in self.browse(cr, uid, ids):
531 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
532 if res and res['value']:
533 self.write(cr, uid, [inv.id], res['value'])
536 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
537 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
538 Hook method to be overridden in additional modules to verify and possibly alter the
539 move lines to be created by an invoice, for special cases.
540 :param invoice_browse: browsable record of the invoice that is generating the move lines
541 :param move_lines: list of dictionaries with the account.move.lines (as for create())
542 :return: the (possibly updated) final move_lines to create for this invoice
546 def action_move_create(self, cr, uid, ids, *args):
547 ait_obj = self.pool.get('account.invoice.tax')
548 cur_obj = self.pool.get('res.currency')
550 for inv in self.browse(cr, uid, ids):
554 if not inv.date_invoice:
555 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
556 company_currency = inv.company_id.currency_id.id
557 # create the analytical lines
558 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
559 # one move line per invoice line
560 iml = self._get_analytic_lines(cr, uid, inv.id)
561 # check if taxes are all computed
563 context.update({'lang': inv.partner_id.lang})
564 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
566 for tax in compute_taxes.values():
567 ait_obj.create(cr, uid, tax)
570 for tax in inv.tax_line:
573 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
575 if not key in compute_taxes:
576 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
577 base = compute_taxes[key]['base']
578 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
579 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
580 for key in compute_taxes:
581 if not key in tax_key:
582 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
584 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
585 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
587 # one move line per tax line
588 iml += ait_obj.move_line_get(cr, uid, inv.id)
590 if inv.type in ('in_invoice', 'in_refund'):
593 ref = self._convert_ref(cr, uid, inv.number)
595 diff_currency_p = inv.currency_id.id <> company_currency
596 # create one move line for the total and possibly adjust the other lines amount
600 if inv.currency_id.id != company_currency:
601 i['currency_id'] = inv.currency_id.id
602 i['amount_currency'] = i['price']
603 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
604 company_currency, i['price'],
605 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
607 i['amount_currency'] = False
608 i['currency_id'] = False
610 if inv.type in ('out_invoice','in_refund'):
612 total_currency += i['amount_currency'] or i['price']
613 i['price'] = - i['price']
616 total_currency -= i['amount_currency'] or i['price']
617 acc_id = inv.account_id.id
619 name = inv['name'] or '/'
622 totlines = self.pool.get('account.payment.term').compute(cr,
623 uid, inv.payment_term.id, total, inv.date_invoice or False)
625 res_amount_currency = total_currency
628 if inv.currency_id.id != company_currency:
629 amount_currency = cur_obj.compute(cr, uid,
630 company_currency, inv.currency_id.id, t[1])
632 amount_currency = False
634 # last line add the diff
635 res_amount_currency -= amount_currency or 0
637 if i == len(totlines):
638 amount_currency += res_amount_currency
644 'account_id': acc_id,
645 'date_maturity': t[0],
646 'amount_currency': diff_currency_p \
647 and amount_currency or False,
648 'currency_id': diff_currency_p \
649 and inv.currency_id.id or False,
657 'account_id': acc_id,
658 'date_maturity' : inv.date_due or False,
659 'amount_currency': diff_currency_p \
660 and total_currency or False,
661 'currency_id': diff_currency_p \
662 and inv.currency_id.id or False,
666 date = inv.date_invoice or time.strftime('%Y-%m-%d')
667 part = inv.partner_id.id
669 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
671 if inv.journal_id.group_invoice_lines:
674 tmp = str(l['account_id'])
675 tmp += '-'+str(l.get('tax_code_id',"False"))
676 tmp += '-'+str(l.get('product_id',"False"))
677 tmp += '-'+str(l.get('analytic_account_id',"False"))
678 tmp += '-'+str(l.get('date_maturity',"False"))
681 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
682 line2[tmp]['debit'] = (am > 0) and am or 0.0
683 line2[tmp]['credit'] = (am < 0) and -am or 0.0
684 line2[tmp]['tax_amount'] += l['tax_amount']
685 line2[tmp]['analytic_lines'] += l['analytic_lines']
689 for key, val in line2.items():
690 line.append((0,0,val))
692 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
693 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
694 if journal.centralisation:
695 raise osv.except_osv(_('UserError'),
696 _('Cannot create invoice move on centralised journal'))
698 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
700 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
701 period_id=inv.period_id and inv.period_id.id or False
703 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'))])
705 period_id=period_ids[0]
707 move['period_id'] = period_id
709 i[2]['period_id'] = period_id
711 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
712 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
713 # make the invoice point to that move
714 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
715 self.pool.get('account.move').post(cr, uid, [move_id])
716 self._log_event(cr, uid, ids)
719 def line_get_convert(self, cr, uid, x, part, date, context=None):
721 'date_maturity': x.get('date_maturity', False),
723 'name':x['name'][:64],
725 'debit':x['price']>0 and x['price'],
726 'credit':x['price']<0 and -x['price'],
727 'account_id':x['account_id'],
728 'analytic_lines':x.get('analytic_lines', []),
729 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
730 'currency_id':x.get('currency_id', False),
731 'tax_code_id': x.get('tax_code_id', False),
732 'tax_amount': x.get('tax_amount', False),
733 'ref':x.get('ref',False),
734 'quantity':x.get('quantity',1.00),
735 'product_id':x.get('product_id', False),
736 'product_uom_id':x.get('uos_id',False),
737 'analytic_account_id':x.get('account_analytic_id',False),
740 def action_number(self, cr, uid, ids, *args):
741 cr.execute('SELECT id, type, number, move_id, reference ' \
742 'FROM account_invoice ' \
743 'WHERE id IN ('+','.join(map(str,ids))+')')
744 obj_inv = self.browse(cr, uid, ids)[0]
745 for (id, invtype, number, move_id, reference) in cr.fetchall():
747 if obj_inv.journal_id.invoice_sequence_id:
748 sid = obj_inv.journal_id.invoice_sequence_id.id
749 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
751 number = self.pool.get('ir.sequence').get(cr, uid,
752 'account.invoice.' + invtype)
753 if invtype in ('in_invoice', 'in_refund'):
756 ref = self._convert_ref(cr, uid, number)
757 cr.execute('UPDATE account_invoice SET number=%s ' \
758 'WHERE id=%s', (number, id))
759 cr.execute('UPDATE account_move SET ref=%s ' \
760 'WHERE id=%s AND (ref is null OR ref = \'\')',
762 cr.execute('UPDATE account_move_line SET ref=%s ' \
763 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
765 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
766 'FROM account_move_line ' \
767 'WHERE account_move_line.move_id = %s ' \
768 'AND account_analytic_line.move_id = account_move_line.id',
772 def action_cancel(self, cr, uid, ids, *args):
773 account_move_obj = self.pool.get('account.move')
774 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
777 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
778 # delete the move this invoice was pointing to
779 # Note that the corresponding move_lines and move_reconciles
780 # will be automatically deleted too
781 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
783 account_move_line_obj = self.pool.get('account.move.line')
784 pay_ids = account_move_line_obj.browse(cr, uid , i['payment_ids'])
785 for move_line in pay_ids:
786 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
787 raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
789 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
790 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
795 def list_distinct_taxes(self, cr, uid, ids):
796 invoices = self.browse(cr, uid, ids)
799 for tax in inv.tax_line:
800 if not tax['name'] in taxes:
801 taxes[tax['name']] = {'name': tax['name']}
802 return taxes.values()
804 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
805 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
807 part=inv['partner_id'] and inv['partner_id'][0]
809 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
810 total = inv['amount_untaxed']
811 if inv['type'] in ('in_invoice','in_refund'):
812 partnertype='supplier'
813 eventtype = 'purchase'
816 partnertype = 'customer'
819 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
820 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})
823 def name_get(self, cr, uid, ids, context=None):
827 'out_invoice': 'CI: ',
828 'in_invoice': 'SI: ',
829 'out_refund': 'OR: ',
832 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')]
834 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
841 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
843 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
844 return self.name_get(cr, user, ids, context)
846 def _refund_cleanup_lines(self, cr, uid, lines):
849 del line['invoice_id']
850 if 'account_id' in line:
851 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
852 if 'product_id' in line:
853 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
855 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
856 if 'invoice_line_tax_id' in line:
857 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
858 if 'account_analytic_id' in line:
859 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
860 if 'tax_code_id' in line :
861 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
862 line['tax_code_id'] = line['tax_code_id'][0]
863 if 'base_code_id' in line :
864 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
865 line['base_code_id'] = line['base_code_id'][0]
866 return map(lambda x: (0,0,x), lines)
868 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
869 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'])
872 for invoice in invoices:
876 'out_invoice': 'out_refund', # Customer Invoice
877 'in_invoice': 'in_refund', # Supplier Invoice
878 'out_refund': 'out_invoice', # Customer Refund
879 'in_refund': 'in_invoice', # Supplier Refund
883 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
884 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
886 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
887 tax_lines = filter(lambda l: l['manual'], tax_lines)
888 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
890 date = time.strftime('%Y-%m-%d')
892 'type': type_dict[invoice['type']],
893 'date_invoice': date,
896 'invoice_line': invoice_lines,
897 'tax_line': tax_lines
901 'period_id': period_id,
907 # take the id part of the tuple returned for many2one fields
908 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
909 'account_id', 'currency_id', 'payment_term', 'journal_id'):
910 invoice[field] = invoice[field] and invoice[field][0]
911 # create the new invoice
912 new_ids.append(self.create(cr, uid, invoice))
915 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=''):
918 #TODO check if we can use different period for payment and the writeoff line
919 assert len(ids)==1, "Can only pay one invoice at a time"
920 invoice = self.browse(cr, uid, ids[0])
921 src_account_id = invoice.account_id.id
922 # Take the seq as name for move
923 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
924 direction = types[invoice.type]
925 #take the choosen date
926 if 'date_p' in context and context['date_p']:
927 date=context['date_p']
929 date=time.strftime('%Y-%m-%d')
931 # Take the amount in currency and the currency of the payment
932 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
933 amount_currency = context['amount_currency']
934 currency_id = context['currency_id']
936 amount_currency = False
939 if invoice.type in ('in_invoice', 'in_refund'):
940 ref = invoice.reference
942 ref = self._convert_ref(cr, uid, invoice.number)
943 # Pay attention to the sign for both debit/credit AND amount_currency
945 'debit': direction * pay_amount>0 and direction * pay_amount,
946 'credit': direction * pay_amount<0 and - direction * pay_amount,
947 'account_id': src_account_id,
948 'partner_id': invoice.partner_id.id,
951 'currency_id':currency_id,
952 'amount_currency':amount_currency and direction * amount_currency or 0.0,
955 'debit': direction * pay_amount<0 and - direction * pay_amount,
956 'credit': direction * pay_amount>0 and direction * pay_amount,
957 'account_id': pay_account_id,
958 'partner_id': invoice.partner_id.id,
961 'currency_id':currency_id,
962 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
966 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
970 lines = [(0, 0, l1), (0, 0, l2)]
971 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
972 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
976 line = self.pool.get('account.move.line')
977 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
978 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
979 for l in lines+invoice.payment_ids:
980 if l.account_id.id==src_account_id:
981 line_ids.append(l.id)
982 total += (l.debit or 0.0) - (l.credit or 0.0)
983 if (not round(total,int(config['price_accuracy']))) or writeoff_acc_id:
984 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
986 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
988 # Update the stored value (fields.function), so we write to trigger recompute
989 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
993 class account_invoice_line(osv.osv):
994 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
996 cur_obj=self.pool.get('res.currency')
997 for line in self.browse(cr, uid, ids):
999 res[line.id] = line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0)
1000 cur = line.invoice_id.currency_id
1001 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1003 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),int(config['price_accuracy']))
1007 def _price_unit_default(self, cr, uid, context=None):
1010 if 'check_total' in context:
1011 t = context['check_total']
1012 for l in context.get('invoice_line', {}):
1013 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1014 tax_obj = self.pool.get('account.tax')
1015 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1016 t = t - (p * l[2].get('quantity'))
1017 taxes = l[2].get('invoice_line_tax_id')
1018 if len(taxes[0]) >= 3 and taxes[0][2]:
1019 taxes=tax_obj.browse(cr, uid, taxes[0][2])
1020 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)):
1021 t = t - tax['amount']
1025 _name = "account.invoice.line"
1026 _description = "Invoice line"
1028 'name': fields.char('Description', size=256, required=True),
1029 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1030 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
1031 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1032 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1033 '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."),
1034 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
1035 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True, type="float", digits=(16, int(config['price_accuracy']))),
1036 'quantity': fields.float('Quantity', required=True),
1037 'discount': fields.float('Discount (%)', digits=(16, int(config['price_accuracy']))),
1038 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1039 'note': fields.text('Notes'),
1040 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1043 'quantity': lambda *a: 1,
1044 'discount': lambda *a: 0.0,
1045 'price_unit': _price_unit_default,
1048 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
1049 tax_obj = self.pool.get('account.tax')
1051 taxes = tax_obj.browse(cr, uid, tax_id)
1052 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
1053 price_unit = price_unit - tax['amount']
1054 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
1056 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):
1060 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1062 if type in ('in_invoice', 'in_refund'):
1063 return {'value':{}, 'domain':{'product_uom':[]}}
1065 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1066 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1067 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1070 context.update({'lang': part.lang})
1072 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1074 if type in ('out_invoice','out_refund'):
1075 a = res.product_tmpl_id.property_account_income.id
1077 a = res.categ_id.property_account_income_categ.id
1079 a = res.product_tmpl_id.property_account_expense.id
1081 a = res.categ_id.property_account_expense_categ.id
1083 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1085 result['account_id'] = a
1088 tax_obj = self.pool.get('account.tax')
1089 if type in ('out_invoice', 'out_refund'):
1090 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1091 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1093 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)
1094 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1095 if type in ('in_invoice', 'in_refund'):
1096 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)
1097 result.update(to_update)
1099 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1102 result['name'] = res.partner_ref
1105 result['uos_id'] = uom or res.uom_id.id or False
1106 if result['uos_id']:
1107 res2 = res.uom_id.category_id.id
1109 domain = {'uos_id':[('category_id','=',res2 )]}
1110 return {'value':result, 'domain':domain}
1112 def move_line_get(self, cr, uid, invoice_id, context=None):
1115 tax_obj = self.pool.get('account.tax')
1116 cur_obj = self.pool.get('res.currency')
1117 ait_obj = self.pool.get('account.invoice.tax')
1118 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1119 company_currency = inv.company_id.currency_id.id
1120 cur = inv.currency_id
1122 for line in inv.invoice_line:
1123 mres = self.move_line_get_item(cr, uid, line, context)
1127 tax_code_found= False
1128 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1129 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1130 line.quantity, inv.address_invoice_id.id, line.product_id,
1133 if inv.type in ('out_invoice', 'in_invoice'):
1134 tax_code_id = tax['base_code_id']
1135 tax_amount = line.price_subtotal * tax['base_sign']
1137 tax_code_id = tax['ref_base_code_id']
1138 tax_amount = line.price_subtotal * tax['ref_base_sign']
1143 res.append(self.move_line_get_item(cr, uid, line, context))
1144 res[-1]['price'] = 0.0
1145 res[-1]['account_analytic_id'] = False
1146 elif not tax_code_id:
1148 tax_code_found = True
1150 res[-1]['tax_code_id'] = tax_code_id
1151 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1154 def move_line_get_item(self, cr, uid, line, context=None):
1157 'name': line.name[:64],
1158 'price_unit':line.price_unit,
1159 'quantity':line.quantity,
1160 'price':line.price_subtotal,
1161 'account_id':line.account_id.id,
1162 'product_id':line.product_id.id,
1163 'uos_id':line.uos_id.id,
1164 'account_analytic_id':line.account_analytic_id.id,
1165 'taxes':line.invoice_line_tax_id,
1168 # Set the tax field according to the account and the fiscal position
1170 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1173 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1174 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1175 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1176 r = {'value':{'invoice_line_tax_id': res}}
1178 account_invoice_line()
1180 class account_invoice_tax(osv.osv):
1181 _name = "account.invoice.tax"
1182 _description = "Invoice Tax"
1184 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1185 'name': fields.char('Tax Description', size=64, required=True),
1186 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1187 'base': fields.float('Base', digits=(16,int(config['price_accuracy']))),
1188 'amount': fields.float('Amount', digits=(16,int(config['price_accuracy']))),
1189 'manual': fields.boolean('Manual'),
1190 'sequence': fields.integer('Sequence'),
1192 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1193 'base_amount': fields.float('Base Code Amount', digits=(16,int(config['price_accuracy']))),
1194 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1195 'tax_amount': fields.float('Tax Code Amount', digits=(16,int(config['price_accuracy']))),
1198 def base_change(self, cr, uid, ids, base,currency_id=False,company_id=False,date_invoice=False):
1199 cur_obj = self.pool.get('res.currency')
1200 company_obj = self.pool.get('res.company')
1201 company_currency=False
1203 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1204 if currency_id and company_currency:
1205 base = cur_obj.compute(cr, uid, currency_id, company_currency, base, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1206 return {'value': {'base_amount':base}}
1208 def amount_change(self, cr, uid, ids, amount,currency_id=False,company_id=False,date_invoice=False):
1209 cur_obj = self.pool.get('res.currency')
1210 company_obj = self.pool.get('res.company')
1211 company_currency=False
1213 company_currency = company_obj.read(cr,uid,[company_id],['currency_id'])[0]['currency_id'][0]
1214 if currency_id and company_currency:
1215 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1216 return {'value': {'tax_amount':amount}}
1220 'manual': lambda *a: 1,
1221 'base_amount': lambda *a: 0.0,
1222 'tax_amount': lambda *a: 0.0,
1224 def compute(self, cr, uid, invoice_id, context={}):
1226 tax_obj = self.pool.get('account.tax')
1227 cur_obj = self.pool.get('res.currency')
1228 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1229 cur = inv.currency_id
1230 company_currency = inv.company_id.currency_id.id
1232 for line in inv.invoice_line:
1233 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):
1235 val['invoice_id'] = inv.id
1236 val['name'] = tax['name']
1237 val['amount'] = tax['amount']
1238 val['manual'] = False
1239 val['sequence'] = tax['sequence']
1240 val['base'] = tax['price_unit'] * line['quantity']
1242 if inv.type in ('out_invoice','in_invoice'):
1243 val['base_code_id'] = tax['base_code_id']
1244 val['tax_code_id'] = tax['tax_code_id']
1245 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)
1246 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)
1247 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1249 val['base_code_id'] = tax['ref_base_code_id']
1250 val['tax_code_id'] = tax['ref_tax_code_id']
1251 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)
1252 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)
1253 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1255 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1256 if not key in tax_grouped:
1257 tax_grouped[key] = val
1259 tax_grouped[key]['amount'] += val['amount']
1260 tax_grouped[key]['base'] += val['base']
1261 tax_grouped[key]['base_amount'] += val['base_amount']
1262 tax_grouped[key]['tax_amount'] += val['tax_amount']
1264 for t in tax_grouped.values():
1265 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1266 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1267 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1270 def move_line_get(self, cr, uid, invoice_id):
1272 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1273 for t in cr.dictfetchall():
1274 if not t['amount'] \
1275 and not t['tax_code_id'] \
1276 and not t['tax_amount']:
1281 'price_unit': t['amount'],
1283 'price': t['amount'] or 0.0,
1284 'account_id': t['account_id'],
1285 'tax_code_id': t['tax_code_id'],
1286 'tax_amount': t['tax_amount']
1289 account_invoice_tax()
1291 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: