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})
969 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
971 if type in ('out_invoice','out_refund'):
972 a = res.product_tmpl_id.property_account_income.id
974 a = res.categ_id.property_account_income_categ.id
976 a = res.product_tmpl_id.property_account_expense.id
978 a = res.categ_id.property_account_expense_categ.id
980 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
982 result['account_id'] = a
985 tax_obj = self.pool.get('account.tax')
986 if type in ('out_invoice', 'out_refund'):
987 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
988 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
990 taxes = res.supplier_taxes_id and res.supplier_taxes_id or a.tax_ids
991 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
992 if type in ('in_invoice', 'in_refund'):
993 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
994 result.update(to_update['value'])
996 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
999 result['name'] = res.name
1002 result['uos_id'] = uom or res.uom_id.id or False
1003 if result['uos_id']:
1004 res2 = res.uom_id.category_id.id
1006 domain = {'uos_id':[('category_id','=',res2 )]}
1007 return {'value':result, 'domain':domain}
1009 def move_line_get(self, cr, uid, invoice_id, context=None):
1012 tax_obj = self.pool.get('account.tax')
1013 cur_obj = self.pool.get('res.currency')
1014 ait_obj = self.pool.get('account.invoice.tax')
1015 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1016 company_currency = inv.company_id.currency_id.id
1017 cur = inv.currency_id
1019 for line in inv.invoice_line:
1020 mres = self.move_line_get_item(cr, uid, line, context)
1024 tax_code_found= False
1025 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1026 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1027 line.quantity, inv.address_invoice_id.id, line.product_id,
1030 if inv.type in ('out_invoice', 'in_invoice'):
1031 tax_code_id = tax['base_code_id']
1032 tax_amount = line.price_subtotal * tax['base_sign']
1034 tax_code_id = tax['ref_base_code_id']
1035 tax_amount = line.price_subtotal * tax['ref_base_sign']
1040 res.append(self.move_line_get_item(cr, uid, line, context))
1041 res[-1]['price'] = 0.0
1042 res[-1]['account_analytic_id'] = False
1043 elif not tax_code_id:
1045 tax_code_found = True
1047 res[-1]['tax_code_id'] = tax_code_id
1048 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1051 def move_line_get_item(self, cr, uid, line, context=None):
1054 'name': line.name[:64],
1055 'price_unit':line.price_unit,
1056 'quantity':line.quantity,
1057 'price':line.price_subtotal,
1058 'account_id':line.account_id.id,
1059 'product_id':line.product_id.id,
1060 'uos_id':line.uos_id.id,
1061 'account_analytic_id':line.account_analytic_id.id,
1062 'taxes':line.invoice_line_tax_id,
1065 # Set the tax field according to the account and the fiscal position
1067 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1070 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1071 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1072 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1073 r = {'value':{'invoice_line_tax_id': res}}
1075 account_invoice_line()
1077 class account_invoice_tax(osv.osv):
1078 _name = "account.invoice.tax"
1079 _description = "Invoice Tax"
1081 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1082 'name': fields.char('Tax Description', size=64, required=True),
1083 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1084 'base': fields.float('Base', digits=(16,2)),
1085 'amount': fields.float('Amount', digits=(16,2)),
1086 'manual': fields.boolean('Manual'),
1087 'sequence': fields.integer('Sequence'),
1089 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1090 'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1091 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1092 'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1094 def base_change(self, cr, uid, ids, base):
1095 return {'value': {'base_amount':base}}
1096 def amount_change(self, cr, uid, ids, amount):
1097 return {'value': {'tax_amount':amount}}
1100 'manual': lambda *a: 1,
1101 'base_amount': lambda *a: 0.0,
1102 'tax_amount': lambda *a: 0.0,
1104 def compute(self, cr, uid, invoice_id):
1106 tax_obj = self.pool.get('account.tax')
1107 cur_obj = self.pool.get('res.currency')
1108 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1109 cur = inv.currency_id
1110 company_currency = inv.company_id.currency_id.id
1112 for line in inv.invoice_line:
1113 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):
1115 val['invoice_id'] = inv.id
1116 val['name'] = tax['name']
1117 val['amount'] = tax['amount']
1118 val['manual'] = False
1119 val['sequence'] = tax['sequence']
1120 val['base'] = tax['price_unit'] * line['quantity']
1122 if inv.type in ('out_invoice','in_invoice'):
1123 val['base_code_id'] = tax['base_code_id']
1124 val['tax_code_id'] = tax['tax_code_id']
1125 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')})
1126 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')})
1127 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1129 val['base_code_id'] = tax['ref_base_code_id']
1130 val['tax_code_id'] = tax['ref_tax_code_id']
1131 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')})
1132 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')})
1133 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1135 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1136 if not key in tax_grouped:
1137 tax_grouped[key] = val
1139 tax_grouped[key]['amount'] += val['amount']
1140 tax_grouped[key]['base'] += val['base']
1141 tax_grouped[key]['base_amount'] += val['base_amount']
1142 tax_grouped[key]['tax_amount'] += val['tax_amount']
1144 for t in tax_grouped.values():
1145 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1148 def move_line_get(self, cr, uid, invoice_id):
1150 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1151 for t in cr.dictfetchall():
1152 if not t['amount'] \
1153 and not t['tax_code_id'] \
1154 and not t['tax_amount']:
1159 'price_unit': t['amount'],
1161 'price': t['amount'] or 0.0,
1162 'account_id': t['account_id'],
1163 'tax_code_id': t['tax_code_id'],
1164 'tax_amount': t['tax_amount']
1167 account_invoice_tax()
1169 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: