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
29 from mx.DateTime import RelativeDateTime
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 journal_obj = self.pool.get('account.journal')
67 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
73 def _get_currency(self, cr, uid, context):
74 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
76 return user.company_id.currency_id.id
78 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
80 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
81 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
82 tt = type2journal.get(type_inv, 'sale')
83 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
85 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal of type '%s' !") % (tt,))
88 def _get_type(self, cr, uid, context=None):
91 type = context.get('type', 'out_invoice')
94 def _reconciled(self, cr, uid, ids, name, args, context):
97 res[id] = self.test_paid(cr, uid, [id])
100 def _get_reference_type(self, cr, uid, context=None):
101 return [('none', _('Free Reference'))]
103 def _amount_residual(self, cr, uid, ids, name, args, context=None):
105 data_inv = self.browse(cr, uid, ids)
108 to_pay = inv.amount_total
109 for lines in inv.move_lines:
110 paid_amt = paid_amt - lines.credit + lines.debit
111 res[inv.id] = to_pay - abs(paid_amt)
114 def _get_lines(self, cr, uid, ids, name, arg, context=None):
117 move_lines = self.move_line_id_payment_get(cr,uid,[id])
121 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
122 for line in data_lines:
124 if line.reconcile_id:
125 ids_line = line.reconcile_id.line_id
126 elif line.reconcile_partial_id:
127 ids_line = line.reconcile_partial_id.line_partial_ids
128 l = map(lambda x: x.id, ids_line)
129 res[id]=[x for x in l if x <> line.id]
132 def _get_invoice_line(self, cr, uid, ids, context=None):
134 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
135 result[line.invoice_id.id] = True
138 def _get_invoice_tax(self, cr, uid, ids, context=None):
140 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
141 result[tax.invoice_id.id] = True
144 def _compute_lines(self, cr, uid, ids, name, args, context=None):
146 for invoice in self.browse(cr, uid, ids, context):
147 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
150 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
152 lines += map(lambda x: x.id, m.reconcile_id.line_id)
153 elif m.reconcile_partial_id:
154 lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
156 lines = filter(lambda x: x not in src, lines)
157 result[invoice.id] = lines
160 def _get_invoice_from_line(self, cr, uid, ids, context={}):
162 for line in self.pool.get('account.move.line').browse(cr, uid, ids):
163 if line.reconcile_partial_id:
164 for line2 in line.reconcile_partial_id.line_partial_ids:
165 move[line2.move_id.id] = True
166 if line.reconcile_id:
167 for line2 in line.reconcile_id.line_id:
168 move[line2.move_id.id] = True
171 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
174 def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
176 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
177 for line in r.line_partial_ids:
178 move[line.move_id.id] = True
179 for line in r.line_id:
180 move[line.move_id.id] = True
183 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
186 _name = "account.invoice"
187 _description = 'Invoice'
190 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
191 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
192 'type': fields.selection([
193 ('out_invoice','Customer Invoice'),
194 ('in_invoice','Supplier Invoice'),
195 ('out_refund','Customer Refund'),
196 ('in_refund','Supplier Refund'),
197 ],'Type', readonly=True, select=True),
199 'number': fields.char('Invoice Number', size=32, readonly=True, help="Uniq number of the invoice, computed automatically when the invoice is created."),
200 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
201 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
203 'comment': fields.text('Additionnal Information'),
205 'state': fields.selection([
207 ('proforma','Pro-forma'),
208 ('proforma2','Pro-forma'),
211 ('cancel','Canceled')
212 ],'State', select=True, readonly=True),
214 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
215 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
216 help="If you use payment terms, the due date will be computed automatically at the generation "\
217 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment."),
218 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
219 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
220 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
221 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
222 help="If you use payment terms, the due date will be computed automatically at the generation "\
223 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
224 "The payment term may compute several due dates: 50% now, 50% in one month."),
225 'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation date."),
227 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
228 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
229 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
231 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
232 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
234 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
235 'account.invoice.tax': (_get_invoice_tax, None, 20),
236 'account.invoice.line': (_get_invoice_line, None, 20),
239 'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
241 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
242 'account.invoice.tax': (_get_invoice_tax, None, 20),
243 'account.invoice.line': (_get_invoice_line, None, 20),
246 'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
248 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
249 'account.invoice.tax': (_get_invoice_tax, None, 20),
250 'account.invoice.line': (_get_invoice_line, None, 20),
253 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
254 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
255 'company_id': fields.many2one('res.company', 'Company', required=True),
256 'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
257 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
258 store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
259 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
260 help='The bank account to pay to or to be paid from'),
261 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
262 'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
264 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
265 'account.invoice.tax': (_get_invoice_tax, None, 50),
266 'account.invoice.line': (_get_invoice_line, None, 50),
267 'account.move.line': (_get_invoice_from_line, None, 50),
268 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
270 help="Remaining amount due."),
271 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
272 'move_name': fields.char('Account Move', size=64),
273 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
277 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
278 'state': lambda *a: 'draft',
279 'journal_id': _get_journal,
280 'currency_id': _get_currency,
281 'company_id': lambda self, cr, uid, context: \
282 self.pool.get('res.users').browse(cr, uid, uid,
283 context=context).company_id.id,
284 'reference_type': lambda *a: 'none',
287 def unlink(self, cr, uid, ids):
288 invoices = self.read(cr, uid, ids, ['state'])
291 if t['state'] in ('draft', 'cancel'):
292 unlink_ids.append(t['id'])
294 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
295 osv.osv.unlink(self, cr, uid, unlink_ids)
298 # def get_invoice_address(self, cr, uid, ids):
299 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
302 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
303 date_invoice=False, payment_term=False, partner_bank_id=False):
304 invoice_addr_id = False
305 contact_addr_id = False
306 partner_payment_term = False
309 fiscal_position = False
311 opt = [('uid', str(uid))]
314 opt.insert(0, ('id', partner_id))
315 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
316 contact_addr_id = res['contact']
317 invoice_addr_id = res['invoice']
318 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
319 if type in ('out_invoice', 'out_refund'):
320 acc_id = p.property_account_receivable.id
322 acc_id = p.property_account_payable.id
323 fiscal_position = p.property_account_position and p.property_account_position.id or False
324 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
326 bank_id = p.bank_ids[0].id
329 'address_contact_id': contact_addr_id,
330 'address_invoice_id': invoice_addr_id,
331 'account_id': acc_id,
332 'payment_term': partner_payment_term,
333 'fiscal_position': fiscal_position
337 if type in ('in_invoice', 'in_refund'):
338 result['value']['partner_bank'] = bank_id
340 if payment_term != partner_payment_term:
341 if partner_payment_term:
342 to_update = self.onchange_payment_term_date_invoice(
343 cr,uid,ids,partner_payment_term,date_invoice)
344 result['value'].update(to_update['value'])
346 result['value']['date_due'] = False
348 if partner_bank_id != bank_id:
349 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
350 result['value'].update(to_update['value'])
353 def onchange_currency_id(self, cr, uid, ids, curr_id):
356 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
357 if not payment_term_id:
360 pt_obj= self.pool.get('account.payment.term')
362 if not date_invoice :
363 date_invoice = time.strftime('%Y-%m-%d')
365 pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
368 pterm_list = [line[0] for line in pterm_list]
370 res= {'value':{'date_due': pterm_list[-1]}}
374 def onchange_invoice_line(self, cr, uid, ids, lines):
377 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
380 # go from canceled state to draft state
381 def action_cancel_draft(self, cr, uid, ids, *args):
382 self.write(cr, uid, ids, {'state':'draft'})
383 wf_service = netsvc.LocalService("workflow")
385 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
391 # return the ids of the move lines which has the same account than the invoice
393 def move_line_id_payment_get(self, cr, uid, ids, *args):
394 ml = self.pool.get('account.move.line')
396 for inv in self.read(cr, uid, ids, ['move_id','account_id']):
398 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
399 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
400 if line['account_id']==inv['account_id']:
401 res.append(line['id'])
404 def copy(self, cr, uid, id, default=None, context=None):
407 default = default.copy()
408 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
409 if 'date_invoice' not in default:
410 default['date_invoice'] = False
411 if 'date_due' not in default:
412 default['date_due'] = False
413 return super(account_invoice, self).copy(cr, uid, id, default, context)
415 def test_paid(self, cr, uid, ids, *args):
416 res = self.move_line_id_payment_get(cr, uid, ids)
421 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
422 ok = ok and bool(cr.fetchone()[0])
425 def button_reset_taxes(self, cr, uid, ids, context=None):
426 ait_obj = self.pool.get('account.invoice.tax')
428 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
429 for taxe in ait_obj.compute(cr, uid, id).values():
430 ait_obj.create(cr, uid, taxe)
431 # Update the stored value (fields.function), so we write to trigger recompute
432 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
435 def button_compute(self, cr, uid, ids, context=None, set_total=False):
436 self.button_reset_taxes(cr, uid, ids, context)
437 for inv in self.browse(cr, uid, ids):
439 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
442 def _convert_ref(self, cr, uid, ref):
443 return (ref or '').replace('/','')
445 def _get_analytic_lines(self, cr, uid, id):
446 inv = self.browse(cr, uid, [id])[0]
447 cur_obj = self.pool.get('res.currency')
449 company_currency = inv.company_id.currency_id.id
450 if inv.type in ('out_invoice', 'in_refund'):
455 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
457 if il['account_analytic_id']:
458 if inv.type in ('in_invoice', 'in_refund'):
461 ref = self._convert_ref(cr, uid, inv.number)
462 il['analytic_lines'] = [(0,0, {
464 'date': inv['date_invoice'],
465 'account_id': il['account_analytic_id'],
466 'unit_amount': il['quantity'],
467 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
468 'product_id': il['product_id'],
469 'product_uom_id': il['uos_id'],
470 'general_account_id': il['account_id'],
471 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
476 def action_move_create(self, cr, uid, ids, *args):
477 ait_obj = self.pool.get('account.invoice.tax')
478 cur_obj = self.pool.get('res.currency')
480 for inv in self.browse(cr, uid, ids):
482 self.button_compute(cr, uid, [inv.id], context={}, set_total=False)
486 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
487 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
488 if not inv.date_invoice:
489 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
490 company_currency = inv.company_id.currency_id.id
491 # create the analytical lines
492 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
493 ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
494 # one move line per invoice line
495 iml = self._get_analytic_lines(cr, uid, inv.id)
496 # check if taxes are all computed
497 compute_taxes = ait_obj.compute(cr, uid, inv.id)
499 for tax in compute_taxes.values():
500 ait_obj.create(cr, uid, tax)
503 for tax in inv.tax_line:
506 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
508 if not key in compute_taxes:
509 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
510 base = compute_taxes[key]['base']
511 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
512 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
513 for key in compute_taxes:
514 if not key in tax_key:
515 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
517 # one move line per tax line
518 iml += ait_obj.move_line_get(cr, uid, inv.id)
520 if inv.type in ('in_invoice', 'in_refund'):
523 ref = self._convert_ref(cr, uid, inv.number)
525 diff_currency_p = inv.currency_id.id <> company_currency
526 # create one move line for the total and possibly adjust the other lines amount
530 if inv.currency_id.id != company_currency:
531 i['currency_id'] = inv.currency_id.id
532 i['amount_currency'] = i['price']
533 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
534 company_currency, i['price'],
535 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
537 i['amount_currency'] = False
538 i['currency_id'] = False
540 if inv.type in ('out_invoice','in_refund'):
542 total_currency += i['amount_currency'] or i['price']
543 i['price'] = - i['price']
546 total_currency -= i['amount_currency'] or i['price']
547 acc_id = inv.account_id.id
549 name = inv['name'] or '/'
552 totlines = self.pool.get('account.payment.term').compute(cr,
553 uid, inv.payment_term.id, total, inv.date_invoice or False)
555 res_amount_currency = total_currency
558 if inv.currency_id.id != company_currency:
559 amount_currency = cur_obj.compute(cr, uid,
560 company_currency, inv.currency_id.id, t[1])
562 amount_currency = False
564 # last line add the diff
565 res_amount_currency -= amount_currency or 0
567 if i == len(totlines):
568 amount_currency += res_amount_currency
574 'account_id': acc_id,
575 'date_maturity': t[0],
576 'amount_currency': diff_currency_p \
577 and amount_currency or False,
578 'currency_id': diff_currency_p \
579 and inv.currency_id.id or False,
587 'account_id': acc_id,
588 'date_maturity' : inv.date_due or False,
589 'amount_currency': diff_currency_p \
590 and total_currency or False,
591 'currency_id': diff_currency_p \
592 and inv.currency_id.id or False,
596 date = inv.date_invoice or time.strftime('%Y-%m-%d')
597 part = inv.partner_id.id
599 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
601 if inv.journal_id.group_invoice_lines:
604 tmp = str(l['account_id'])
605 tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
606 tmp += '-'+str('product_id' in l and l['product_id'] or "False")
607 tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
610 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
611 line2[tmp]['debit'] = (am > 0) and am or 0.0
612 line2[tmp]['credit'] = (am < 0) and -am or 0.0
613 line2[tmp]['tax_amount'] += l['tax_amount']
614 line2[tmp]['analytic_lines'] += l['analytic_lines']
618 for key, val in line2.items():
619 line.append((0,0,val))
621 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
622 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
623 if journal.centralisation:
624 raise osv.except_osv(_('UserError'),
625 _('Can not create invoice move on centralized journal'))
626 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
627 period_id=inv.period_id and inv.period_id.id or False
629 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'))])
631 period_id=period_ids[0]
633 move['period_id'] = period_id
635 i[2]['period_id'] = period_id
637 if not 'name' in move:
638 move['name'] = inv.name or '/'
640 move_id = self.pool.get('account.move').create(cr, uid, move)
641 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
642 # make the invoice point to that move
643 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
644 self.pool.get('account.move').post(cr, uid, [move_id])
645 self._log_event(cr, uid, ids)
648 def line_get_convert(self, cr, uid, x, part, date, context=None):
650 'date_maturity': x.get('date_maturity', False),
652 'name':x['name'][:64],
653 'debit':x['price']>0 and x['price'],
654 'credit':x['price']<0 and -x['price'],
655 'account_id':x['account_id'],
656 'analytic_lines':x.get('analytic_lines', []),
657 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
658 'currency_id':x.get('currency_id', False),
659 'tax_code_id': x.get('tax_code_id', False),
660 'tax_amount': x.get('tax_amount', False),
661 'ref':x.get('ref',False),
662 'quantity':x.get('quantity',1.00),
663 'product_id':x.get('product_id', False),
664 'product_uom_id':x.get('uos_id',False),
665 'analytic_account_id':x.get('account_analytic_id',False),
668 def action_number(self, cr, uid, ids, *args):
669 cr.execute('SELECT id, type, number, move_id, reference ' \
670 'FROM account_invoice ' \
671 'WHERE id IN ('+','.join(map(str,ids))+')')
672 obj_inv = self.browse(cr, uid, ids)[0]
673 for (id, invtype, number, move_id, reference) in cr.fetchall():
676 for seq in obj_inv.journal_id.fy_seq_id:
677 if seq.fiscalyear_id.id == obj_inv.move_id.period_id.fiscalyear_id.id:
678 number = self.pool.get('ir.sequence').get_id(cr, uid,seq.sequence_id.id)
682 number = self.pool.get('ir.sequence').get(cr, uid,
683 'account.invoice.' + invtype)
684 if invtype in ('in_invoice', 'in_refund'):
687 ref = self._convert_ref(cr, uid, number)
688 cr.execute('UPDATE account_invoice SET number=%s ' \
689 'WHERE id=%s', (number, id))
690 cr.execute('UPDATE account_move SET ref=%s ' \
691 'WHERE id=%s AND (ref is null OR ref = \'\')',
693 cr.execute('UPDATE account_move_line SET ref=%s ' \
694 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
696 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
697 'FROM account_move_line ' \
698 'WHERE account_move_line.move_id = %s ' \
699 'AND account_analytic_line.move_id = account_move_line.id',
703 def action_cancel(self, cr, uid, ids, *args):
704 account_move_obj = self.pool.get('account.move')
705 invoices = self.read(cr, uid, ids, ['move_id'])
708 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
709 # delete the move this invoice was pointing to
710 # Note that the corresponding move_lines and move_reconciles
711 # will be automatically deleted too
712 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
713 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
714 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
719 def list_distinct_taxes(self, cr, uid, ids):
720 invoices = self.browse(cr, uid, ids)
723 for tax in inv.tax_line:
724 if not tax['name'] in taxes:
725 taxes[tax['name']] = {'name': tax['name']}
726 return taxes.values()
728 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
729 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
731 part=inv['partner_id'] and inv['partner_id'][0]
733 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
734 total = inv['amount_untaxed']
735 if inv['type'] in ('in_invoice','in_refund'):
736 partnertype='supplier'
737 eventtype = 'purchase'
740 partnertype = 'customer'
743 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
744 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})
747 def name_get(self, cr, uid, ids, context=None):
751 'out_invoice': 'CI: ',
752 'in_invoice': 'SI: ',
753 'out_refund': 'OR: ',
756 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')]
758 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
765 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
767 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
768 return self.name_get(cr, user, ids, context)
770 def _refund_cleanup_lines(self, lines):
773 del line['invoice_id']
774 if 'account_id' in line:
775 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
776 if 'product_id' in line:
777 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
779 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
780 if 'invoice_line_tax_id' in line:
781 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
782 if 'account_analytic_id' in line:
783 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
784 if 'tax_code_id' in line :
785 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
786 line['tax_code_id'] = line['tax_code_id'][0]
787 if 'base_code_id' in line :
788 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
789 line['base_code_id'] = line['base_code_id'][0]
790 return map(lambda x: (0,0,x), lines)
792 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
793 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'])
796 for invoice in invoices:
800 'out_invoice': 'out_refund', # Customer Invoice
801 'in_invoice': 'in_refund', # Supplier Invoice
802 'out_refund': 'out_invoice', # Customer Refund
803 'in_refund': 'in_invoice', # Supplier Refund
807 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
808 invoice_lines = self._refund_cleanup_lines(invoice_lines)
810 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
811 tax_lines = filter(lambda l: l['manual'], tax_lines)
812 tax_lines = self._refund_cleanup_lines(tax_lines)
814 date = time.strftime('%Y-%m-%d')
816 'type': type_dict[invoice['type']],
817 'date_invoice': date,
820 'invoice_line': invoice_lines,
821 'tax_line': tax_lines
825 'period_id': period_id,
831 # take the id part of the tuple returned for many2one fields
832 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
833 'account_id', 'currency_id', 'payment_term', 'journal_id'):
834 invoice[field] = invoice[field] and invoice[field][0]
835 # create the new invoice
836 new_ids.append(self.create(cr, uid, invoice))
839 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=''):
842 #TODO check if we can use different period for payment and the writeoff line
843 assert len(ids)==1, "Can only pay one invoice at a time"
844 invoice = self.browse(cr, uid, ids[0])
845 src_account_id = invoice.account_id.id
846 # Take the seq as name for move
847 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
848 direction = types[invoice.type]
849 #take the choosen date
850 if 'date_p' in context and context['date_p']:
851 date=context['date_p']
853 date=time.strftime('%Y-%m-%d')
855 'debit': direction * pay_amount>0 and direction * pay_amount,
856 'credit': direction * pay_amount<0 and - direction * pay_amount,
857 'account_id': src_account_id,
858 'partner_id': invoice.partner_id.id,
859 'ref':invoice.number,
862 'debit': direction * pay_amount<0 and - direction * pay_amount,
863 'credit': direction * pay_amount>0 and direction * pay_amount,
864 'account_id': pay_account_id,
865 'partner_id': invoice.partner_id.id,
866 'ref':invoice.number,
869 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
873 lines = [(0, 0, l1), (0, 0, l2)]
874 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
875 move_id = self.pool.get('account.move').create(cr, uid, move)
879 line = self.pool.get('account.move.line')
880 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
881 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
883 if l.account_id.id==src_account_id:
884 line_ids.append(l.id)
885 total += (l.debit or 0.0) - (l.credit or 0.0)
886 if (not total) or writeoff_acc_id:
887 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
889 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
891 # Update the stored value (fields.function), so we write to trigger recompute
892 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
896 class account_invoice_line(osv.osv):
897 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
899 for line in self.browse(cr, uid, ids):
900 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
903 def _price_unit_default(self, cr, uid, context=None):
906 if 'check_total' in context:
907 t = context['check_total']
908 for l in context.get('invoice_line', {}):
909 if len(l) >= 3 and l[2]:
910 tax_obj = self.pool.get('account.tax')
911 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
912 t = t - (p * l[2].get('quantity'))
913 taxes = l[2].get('invoice_line_tax_id')
914 if len(taxes[0]) >= 3 and taxes[0][2]:
915 taxes=tax_obj.browse(cr, uid, taxes[0][2])
916 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)):
917 t = t - tax['amount']
921 _name = "account.invoice.line"
922 _description = "Invoice line"
924 'name': fields.char('Description', size=256, required=True),
925 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
926 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
927 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
928 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
929 '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."),
930 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
931 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
932 'quantity': fields.float('Quantity', required=True),
933 'discount': fields.float('Discount (%)', digits=(16,2)),
934 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
935 'note': fields.text('Notes'),
936 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
939 'quantity': lambda *a: 1,
940 'discount': lambda *a: 0.0,
941 'price_unit': _price_unit_default,
944 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
945 tax_obj = self.pool.get('account.tax')
947 taxes = tax_obj.browse(cr, uid, tax_id)
948 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
949 price_unit = price_unit - tax['amount']
950 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
952 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):
956 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
958 if type in ('in_invoice', 'in_refund'):
959 return {'domain':{'product_uom':[]}}
961 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
962 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
963 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
966 context.update({'lang': lang})
967 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
969 tax_obj = self.pool.get('account.tax')
970 if type in ('out_invoice', 'out_refund'):
971 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.taxes_id)
973 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.supplier_taxes_id)
974 if type in ('in_invoice', 'in_refund'):
975 result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
977 result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
980 result['name'] = res.name
982 if type in ('out_invoice','out_refund'):
983 a = res.product_tmpl_id.property_account_income.id
985 a = res.categ_id.property_account_income_categ.id
987 a = res.product_tmpl_id.property_account_expense.id
989 a = res.categ_id.property_account_expense_categ.id
991 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
993 result['account_id'] = a
996 result['uos_id'] = uom or res.uom_id.id or False
998 res2 = res.uom_id.category_id.id
1000 domain = {'uos_id':[('category_id','=',res2 )]}
1001 return {'value':result, 'domain':domain}
1003 def move_line_get(self, cr, uid, invoice_id, context=None):
1006 tax_obj = self.pool.get('account.tax')
1007 cur_obj = self.pool.get('res.currency')
1008 ait_obj = self.pool.get('account.invoice.tax')
1009 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1010 company_currency = inv.company_id.currency_id.id
1011 cur = inv.currency_id
1013 for line in inv.invoice_line:
1014 mres = self.move_line_get_item(cr, uid, line, context)
1018 tax_code_found= False
1019 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1020 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1021 line.quantity, inv.address_invoice_id.id, line.product_id,
1024 if inv.type in ('out_invoice', 'in_invoice'):
1025 tax_code_id = tax['base_code_id']
1026 tax_amount = line.price_subtotal * tax['base_sign']
1028 tax_code_id = tax['ref_base_code_id']
1029 tax_amount = line.price_subtotal * tax['ref_base_sign']
1034 res.append(self.move_line_get_item(cr, uid, line, context))
1035 res[-1]['price'] = 0.0
1036 res[-1]['account_analytic_id'] = False
1037 elif not tax_code_id:
1039 tax_code_found = True
1041 res[-1]['tax_code_id'] = tax_code_id
1042 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1045 def move_line_get_item(self, cr, uid, line, context=None):
1048 'name': line.name[:64],
1049 'price_unit':line.price_unit,
1050 'quantity':line.quantity,
1051 'price':line.price_subtotal,
1052 'account_id':line.account_id.id,
1053 'product_id':line.product_id.id,
1054 'uos_id':line.uos_id.id,
1055 'account_analytic_id':line.account_analytic_id.id,
1056 'taxes':line.invoice_line_tax_id,
1059 # Set the tax field according to the account and the partner
1061 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1062 if not (fposition_id and account_id):
1064 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1065 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1066 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1067 r = {'value':{'invoice_line_tax_id': res}}
1069 account_invoice_line()
1071 class account_invoice_tax(osv.osv):
1072 _name = "account.invoice.tax"
1073 _description = "Invoice Tax"
1075 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1076 'name': fields.char('Tax Description', size=64, required=True),
1077 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1078 'base': fields.float('Base', digits=(16,2)),
1079 'amount': fields.float('Amount', digits=(16,2)),
1080 'manual': fields.boolean('Manual'),
1081 'sequence': fields.integer('Sequence'),
1083 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1084 'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1085 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1086 'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1088 def base_change(self, cr, uid, ids, base):
1089 return {'value': {'base_amount':base}}
1090 def amount_change(self, cr, uid, ids, amount):
1091 return {'value': {'tax_amount':amount}}
1094 'manual': lambda *a: 1,
1095 'base_amount': lambda *a: 0.0,
1096 'tax_amount': lambda *a: 0.0,
1098 def compute(self, cr, uid, invoice_id):
1100 tax_obj = self.pool.get('account.tax')
1101 cur_obj = self.pool.get('res.currency')
1102 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1103 cur = inv.currency_id
1104 company_currency = inv.company_id.currency_id.id
1106 for line in inv.invoice_line:
1107 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):
1109 val['invoice_id'] = inv.id
1110 val['name'] = tax['name']
1111 val['amount'] = tax['amount']
1112 val['manual'] = False
1113 val['sequence'] = tax['sequence']
1114 val['base'] = tax['price_unit'] * line['quantity']
1116 if inv.type in ('out_invoice','in_invoice'):
1117 val['base_code_id'] = tax['base_code_id']
1118 val['tax_code_id'] = tax['tax_code_id']
1119 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')})
1120 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')})
1121 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1123 val['base_code_id'] = tax['ref_base_code_id']
1124 val['tax_code_id'] = tax['ref_tax_code_id']
1125 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')})
1126 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')})
1127 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1129 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1130 if not key in tax_grouped:
1131 tax_grouped[key] = val
1133 tax_grouped[key]['amount'] += val['amount']
1134 tax_grouped[key]['base'] += val['base']
1135 tax_grouped[key]['base_amount'] += val['base_amount']
1136 tax_grouped[key]['tax_amount'] += val['tax_amount']
1138 for t in tax_grouped.values():
1139 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1142 def move_line_get(self, cr, uid, invoice_id):
1144 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1145 for t in cr.dictfetchall():
1146 if not t['amount'] \
1147 and not t['tax_code_id'] \
1148 and not t['tax_amount']:
1153 'price_unit': t['amount'],
1155 'price': t['amount'] or 0.0,
1156 'account_id': t['account_id'],
1157 'tax_code_id': t['tax_code_id'],
1158 'tax_amount': t['tax_amount']
1161 account_invoice_tax()
1163 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: