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="Unique 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('Additional Information'),
205 'state': fields.selection([
207 ('proforma','Pro-forma'),
208 ('proforma2','Pro-forma'),
211 ('cancel','Cancelled')
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, context=None):
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, context=context)
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)
433 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
436 def button_compute(self, cr, uid, ids, context=None, set_total=False):
437 self.button_reset_taxes(cr, uid, ids, context)
438 for inv in self.browse(cr, uid, ids):
440 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
443 def _convert_ref(self, cr, uid, ref):
444 return (ref or '').replace('/','')
446 def _get_analytic_lines(self, cr, uid, id):
447 inv = self.browse(cr, uid, [id])[0]
448 cur_obj = self.pool.get('res.currency')
450 company_currency = inv.company_id.currency_id.id
451 if inv.type in ('out_invoice', 'in_refund'):
456 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
458 if il['account_analytic_id']:
459 if inv.type in ('in_invoice', 'in_refund'):
462 ref = self._convert_ref(cr, uid, inv.number)
463 il['analytic_lines'] = [(0,0, {
465 'date': inv['date_invoice'],
466 'account_id': il['account_analytic_id'],
467 'unit_amount': il['quantity'],
468 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
469 'product_id': il['product_id'],
470 'product_uom_id': il['uos_id'],
471 'general_account_id': il['account_id'],
472 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
477 def action_move_create(self, cr, uid, ids, *args):
478 ait_obj = self.pool.get('account.invoice.tax')
479 cur_obj = self.pool.get('res.currency')
481 for inv in self.browse(cr, uid, ids):
483 self.button_compute(cr, uid, [inv.id], context={}, set_total=False)
487 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
488 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
489 if not inv.date_invoice:
490 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
491 company_currency = inv.company_id.currency_id.id
492 # create the analytical lines
493 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
494 ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
495 # one move line per invoice line
496 iml = self._get_analytic_lines(cr, uid, inv.id)
497 # check if taxes are all computed
498 compute_taxes = ait_obj.compute(cr, uid, inv.id)
500 for tax in compute_taxes.values():
501 ait_obj.create(cr, uid, tax)
504 for tax in inv.tax_line:
507 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
509 if not key in compute_taxes:
510 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
511 base = compute_taxes[key]['base']
512 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
513 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
514 for key in compute_taxes:
515 if not key in tax_key:
516 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
518 # one move line per tax line
519 iml += ait_obj.move_line_get(cr, uid, inv.id)
521 if inv.type in ('in_invoice', 'in_refund'):
524 ref = self._convert_ref(cr, uid, inv.number)
526 diff_currency_p = inv.currency_id.id <> company_currency
527 # create one move line for the total and possibly adjust the other lines amount
531 if inv.currency_id.id != company_currency:
532 i['currency_id'] = inv.currency_id.id
533 i['amount_currency'] = i['price']
534 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
535 company_currency, i['price'],
536 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
538 i['amount_currency'] = False
539 i['currency_id'] = False
541 if inv.type in ('out_invoice','in_refund'):
543 total_currency += i['amount_currency'] or i['price']
544 i['price'] = - i['price']
547 total_currency -= i['amount_currency'] or i['price']
548 acc_id = inv.account_id.id
550 name = inv['name'] or '/'
553 totlines = self.pool.get('account.payment.term').compute(cr,
554 uid, inv.payment_term.id, total, inv.date_invoice or False)
556 res_amount_currency = total_currency
559 if inv.currency_id.id != company_currency:
560 amount_currency = cur_obj.compute(cr, uid,
561 company_currency, inv.currency_id.id, t[1])
563 amount_currency = False
565 # last line add the diff
566 res_amount_currency -= amount_currency or 0
568 if i == len(totlines):
569 amount_currency += res_amount_currency
575 'account_id': acc_id,
576 'date_maturity': t[0],
577 'amount_currency': diff_currency_p \
578 and amount_currency or False,
579 'currency_id': diff_currency_p \
580 and inv.currency_id.id or False,
588 'account_id': acc_id,
589 'date_maturity' : inv.date_due or False,
590 'amount_currency': diff_currency_p \
591 and total_currency or False,
592 'currency_id': diff_currency_p \
593 and inv.currency_id.id or False,
597 date = inv.date_invoice or time.strftime('%Y-%m-%d')
598 part = inv.partner_id.id
600 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
602 if inv.journal_id.group_invoice_lines:
605 tmp = str(l['account_id'])
606 tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
607 tmp += '-'+str('product_id' in l and l['product_id'] or "False")
608 tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
611 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
612 line2[tmp]['debit'] = (am > 0) and am or 0.0
613 line2[tmp]['credit'] = (am < 0) and -am or 0.0
614 line2[tmp]['tax_amount'] += l['tax_amount']
615 line2[tmp]['analytic_lines'] += l['analytic_lines']
619 for key, val in line2.items():
620 line.append((0,0,val))
622 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
623 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
624 if journal.centralisation:
625 raise osv.except_osv(_('UserError'),
626 _('Can not create invoice move on centralised journal'))
627 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
628 period_id=inv.period_id and inv.period_id.id or False
630 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'))])
632 period_id=period_ids[0]
634 move['period_id'] = period_id
636 i[2]['period_id'] = period_id
638 if not 'name' in move:
639 move['name'] = inv.name or '/'
641 move_id = self.pool.get('account.move').create(cr, uid, move)
642 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
643 # make the invoice point to that move
644 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
645 self.pool.get('account.move').post(cr, uid, [move_id])
646 self._log_event(cr, uid, ids)
649 def line_get_convert(self, cr, uid, x, part, date, context=None):
651 'date_maturity': x.get('date_maturity', False),
653 'name':x['name'][:64],
654 'debit':x['price']>0 and x['price'],
655 'credit':x['price']<0 and -x['price'],
656 'account_id':x['account_id'],
657 'analytic_lines':x.get('analytic_lines', []),
658 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
659 'currency_id':x.get('currency_id', False),
660 'tax_code_id': x.get('tax_code_id', False),
661 'tax_amount': x.get('tax_amount', False),
662 'ref':x.get('ref',False),
663 'quantity':x.get('quantity',1.00),
664 'product_id':x.get('product_id', False),
665 'product_uom_id':x.get('uos_id',False),
666 'analytic_account_id':x.get('account_analytic_id',False),
669 def action_number(self, cr, uid, ids, *args):
670 cr.execute('SELECT id, type, number, move_id, reference ' \
671 'FROM account_invoice ' \
672 'WHERE id IN ('+','.join(map(str,ids))+')')
673 obj_inv = self.browse(cr, uid, ids)[0]
674 for (id, invtype, number, move_id, reference) in cr.fetchall():
677 for seq in obj_inv.journal_id.fy_seq_id:
678 if seq.fiscalyear_id.id == obj_inv.move_id.period_id.fiscalyear_id.id:
679 number = self.pool.get('ir.sequence').get_id(cr, uid,seq.sequence_id.id)
683 number = self.pool.get('ir.sequence').get(cr, uid,
684 'account.invoice.' + invtype)
685 if invtype in ('in_invoice', 'in_refund'):
688 ref = self._convert_ref(cr, uid, number)
689 cr.execute('UPDATE account_invoice SET number=%s ' \
690 'WHERE id=%s', (number, id))
691 cr.execute('UPDATE account_move SET ref=%s ' \
692 'WHERE id=%s AND (ref is null OR ref = \'\')',
694 cr.execute('UPDATE account_move_line SET ref=%s ' \
695 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
697 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
698 'FROM account_move_line ' \
699 'WHERE account_move_line.move_id = %s ' \
700 'AND account_analytic_line.move_id = account_move_line.id',
704 def action_cancel(self, cr, uid, ids, *args):
705 account_move_obj = self.pool.get('account.move')
706 invoices = self.read(cr, uid, ids, ['move_id'])
709 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
710 # delete the move this invoice was pointing to
711 # Note that the corresponding move_lines and move_reconciles
712 # will be automatically deleted too
713 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
714 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
715 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
720 def list_distinct_taxes(self, cr, uid, ids):
721 invoices = self.browse(cr, uid, ids)
724 for tax in inv.tax_line:
725 if not tax['name'] in taxes:
726 taxes[tax['name']] = {'name': tax['name']}
727 return taxes.values()
729 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
730 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
732 part=inv['partner_id'] and inv['partner_id'][0]
734 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
735 total = inv['amount_untaxed']
736 if inv['type'] in ('in_invoice','in_refund'):
737 partnertype='supplier'
738 eventtype = 'purchase'
741 partnertype = 'customer'
744 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
745 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})
748 def name_get(self, cr, uid, ids, context=None):
752 'out_invoice': 'CI: ',
753 'in_invoice': 'SI: ',
754 'out_refund': 'OR: ',
757 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')]
759 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
766 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
768 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
769 return self.name_get(cr, user, ids, context)
771 def _refund_cleanup_lines(self, lines):
774 del line['invoice_id']
775 if 'account_id' in line:
776 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
777 if 'product_id' in line:
778 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
780 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
781 if 'invoice_line_tax_id' in line:
782 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
783 if 'account_analytic_id' in line:
784 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
785 if 'tax_code_id' in line :
786 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
787 line['tax_code_id'] = line['tax_code_id'][0]
788 if 'base_code_id' in line :
789 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
790 line['base_code_id'] = line['base_code_id'][0]
791 return map(lambda x: (0,0,x), lines)
793 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
794 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'])
797 for invoice in invoices:
801 'out_invoice': 'out_refund', # Customer Invoice
802 'in_invoice': 'in_refund', # Supplier Invoice
803 'out_refund': 'out_invoice', # Customer Refund
804 'in_refund': 'in_invoice', # Supplier Refund
808 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
809 invoice_lines = self._refund_cleanup_lines(invoice_lines)
811 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
812 tax_lines = filter(lambda l: l['manual'], tax_lines)
813 tax_lines = self._refund_cleanup_lines(tax_lines)
815 date = time.strftime('%Y-%m-%d')
817 'type': type_dict[invoice['type']],
818 'date_invoice': date,
821 'invoice_line': invoice_lines,
822 'tax_line': tax_lines
826 'period_id': period_id,
832 # take the id part of the tuple returned for many2one fields
833 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
834 'account_id', 'currency_id', 'payment_term', 'journal_id'):
835 invoice[field] = invoice[field] and invoice[field][0]
836 # create the new invoice
837 new_ids.append(self.create(cr, uid, invoice))
840 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=''):
843 #TODO check if we can use different period for payment and the writeoff line
844 assert len(ids)==1, "Can only pay one invoice at a time"
845 invoice = self.browse(cr, uid, ids[0])
846 src_account_id = invoice.account_id.id
847 # Take the seq as name for move
848 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
849 direction = types[invoice.type]
850 #take the choosen date
851 if 'date_p' in context and context['date_p']:
852 date=context['date_p']
854 date=time.strftime('%Y-%m-%d')
856 'debit': direction * pay_amount>0 and direction * pay_amount,
857 'credit': direction * pay_amount<0 and - direction * pay_amount,
858 'account_id': src_account_id,
859 'partner_id': invoice.partner_id.id,
860 'ref':invoice.number,
863 'debit': direction * pay_amount<0 and - direction * pay_amount,
864 'credit': direction * pay_amount>0 and direction * pay_amount,
865 'account_id': pay_account_id,
866 'partner_id': invoice.partner_id.id,
867 'ref':invoice.number,
870 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
874 lines = [(0, 0, l1), (0, 0, l2)]
875 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
876 move_id = self.pool.get('account.move').create(cr, uid, move)
880 line = self.pool.get('account.move.line')
881 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
882 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
884 if l.account_id.id==src_account_id:
885 line_ids.append(l.id)
886 total += (l.debit or 0.0) - (l.credit or 0.0)
887 if (not total) or writeoff_acc_id:
888 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
890 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
892 # Update the stored value (fields.function), so we write to trigger recompute
893 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
897 class account_invoice_line(osv.osv):
898 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
900 for line in self.browse(cr, uid, ids):
901 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
904 def _price_unit_default(self, cr, uid, context=None):
907 if 'check_total' in context:
908 t = context['check_total']
909 for l in context.get('invoice_line', {}):
910 if len(l) >= 3 and l[2]:
911 tax_obj = self.pool.get('account.tax')
912 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
913 t = t - (p * l[2].get('quantity'))
914 taxes = l[2].get('invoice_line_tax_id')
915 if len(taxes[0]) >= 3 and taxes[0][2]:
916 taxes=tax_obj.browse(cr, uid, taxes[0][2])
917 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)):
918 t = t - tax['amount']
922 _name = "account.invoice.line"
923 _description = "Invoice line"
925 'name': fields.char('Description', size=256, required=True),
926 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
927 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
928 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
929 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
930 '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."),
931 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
932 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
933 'quantity': fields.float('Quantity', required=True),
934 'discount': fields.float('Discount (%)', digits=(16,2)),
935 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
936 'note': fields.text('Notes'),
937 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
940 'quantity': lambda *a: 1,
941 'discount': lambda *a: 0.0,
942 'price_unit': _price_unit_default,
945 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
946 tax_obj = self.pool.get('account.tax')
948 taxes = tax_obj.browse(cr, uid, tax_id)
949 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
950 price_unit = price_unit - tax['amount']
951 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
953 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):
957 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
959 if type in ('in_invoice', 'in_refund'):
960 return {'domain':{'product_uom':[]}}
962 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
963 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
964 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
967 context.update({'lang': lang})
968 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
970 tax_obj = self.pool.get('account.tax')
971 if type in ('out_invoice', 'out_refund'):
972 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.taxes_id)
974 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.supplier_taxes_id)
975 if type in ('in_invoice', 'in_refund'):
976 result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
978 result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
981 result['name'] = res.name
983 if type in ('out_invoice','out_refund'):
984 a = res.product_tmpl_id.property_account_income.id
986 a = res.categ_id.property_account_income_categ.id
988 a = res.product_tmpl_id.property_account_expense.id
990 a = res.categ_id.property_account_expense_categ.id
992 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
994 result['account_id'] = a
997 result['uos_id'] = uom or res.uom_id.id or False
999 res2 = res.uom_id.category_id.id
1001 domain = {'uos_id':[('category_id','=',res2 )]}
1002 return {'value':result, 'domain':domain}
1004 def move_line_get(self, cr, uid, invoice_id, context=None):
1007 tax_obj = self.pool.get('account.tax')
1008 cur_obj = self.pool.get('res.currency')
1009 ait_obj = self.pool.get('account.invoice.tax')
1010 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1011 company_currency = inv.company_id.currency_id.id
1012 cur = inv.currency_id
1014 for line in inv.invoice_line:
1015 mres = self.move_line_get_item(cr, uid, line, context)
1019 tax_code_found= False
1020 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1021 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1022 line.quantity, inv.address_invoice_id.id, line.product_id,
1025 if inv.type in ('out_invoice', 'in_invoice'):
1026 tax_code_id = tax['base_code_id']
1027 tax_amount = line.price_subtotal * tax['base_sign']
1029 tax_code_id = tax['ref_base_code_id']
1030 tax_amount = line.price_subtotal * tax['ref_base_sign']
1035 res.append(self.move_line_get_item(cr, uid, line, context))
1036 res[-1]['price'] = 0.0
1037 res[-1]['account_analytic_id'] = False
1038 elif not tax_code_id:
1040 tax_code_found = True
1042 res[-1]['tax_code_id'] = tax_code_id
1043 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1046 def move_line_get_item(self, cr, uid, line, context=None):
1049 'name': line.name[:64],
1050 'price_unit':line.price_unit,
1051 'quantity':line.quantity,
1052 'price':line.price_subtotal,
1053 'account_id':line.account_id.id,
1054 'product_id':line.product_id.id,
1055 'uos_id':line.uos_id.id,
1056 'account_analytic_id':line.account_analytic_id.id,
1057 'taxes':line.invoice_line_tax_id,
1060 # Set the tax field according to the account and the partner
1062 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1063 if not (fposition_id and account_id):
1065 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1066 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1067 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1068 r = {'value':{'invoice_line_tax_id': res}}
1070 account_invoice_line()
1072 class account_invoice_tax(osv.osv):
1073 _name = "account.invoice.tax"
1074 _description = "Invoice Tax"
1076 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1077 'name': fields.char('Tax Description', size=64, required=True),
1078 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1079 'base': fields.float('Base', digits=(16,2)),
1080 'amount': fields.float('Amount', digits=(16,2)),
1081 'manual': fields.boolean('Manual'),
1082 'sequence': fields.integer('Sequence'),
1084 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1085 'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1086 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1087 'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1089 def base_change(self, cr, uid, ids, base):
1090 return {'value': {'base_amount':base}}
1091 def amount_change(self, cr, uid, ids, amount):
1092 return {'value': {'tax_amount':amount}}
1095 'manual': lambda *a: 1,
1096 'base_amount': lambda *a: 0.0,
1097 'tax_amount': lambda *a: 0.0,
1099 def compute(self, cr, uid, invoice_id):
1101 tax_obj = self.pool.get('account.tax')
1102 cur_obj = self.pool.get('res.currency')
1103 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1104 cur = inv.currency_id
1105 company_currency = inv.company_id.currency_id.id
1107 for line in inv.invoice_line:
1108 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):
1110 val['invoice_id'] = inv.id
1111 val['name'] = tax['name']
1112 val['amount'] = tax['amount']
1113 val['manual'] = False
1114 val['sequence'] = tax['sequence']
1115 val['base'] = tax['price_unit'] * line['quantity']
1117 if inv.type in ('out_invoice','in_invoice'):
1118 val['base_code_id'] = tax['base_code_id']
1119 val['tax_code_id'] = tax['tax_code_id']
1120 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')})
1121 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')})
1122 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1124 val['base_code_id'] = tax['ref_base_code_id']
1125 val['tax_code_id'] = tax['ref_tax_code_id']
1126 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')})
1127 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')})
1128 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1130 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1131 if not key in tax_grouped:
1132 tax_grouped[key] = val
1134 tax_grouped[key]['amount'] += val['amount']
1135 tax_grouped[key]['base'] += val['base']
1136 tax_grouped[key]['base_amount'] += val['base_amount']
1137 tax_grouped[key]['tax_amount'] += val['tax_amount']
1139 for t in tax_grouped.values():
1140 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1143 def move_line_get(self, cr, uid, invoice_id):
1145 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1146 for t in cr.dictfetchall():
1147 if not t['amount'] \
1148 and not t['tax_code_id'] \
1149 and not t['tax_amount']:
1154 'price_unit': t['amount'],
1156 'price': t['amount'] or 0.0,
1157 'account_id': t['account_id'],
1158 'tax_code_id': t['tax_code_id'],
1159 'tax_amount': t['tax_amount']
1162 account_invoice_tax()
1164 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: