1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 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 account_invoice(osv.osv):
34 def _amount_all(self, cr, uid, ids, name, args, context={}):
36 for invoice in self.browse(cr,uid,ids, context=context):
38 'amount_untaxed': 0.0,
42 for line in invoice.invoice_line:
43 res[invoice.id]['amount_untaxed'] += line.price_subtotal
44 for line in invoice.tax_line:
45 res[invoice.id]['amount_tax'] += line.amount
46 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
49 def _get_journal(self, cr, uid, context):
50 type_inv = context.get('type', 'out_invoice')
51 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
52 journal_obj = self.pool.get('account.journal')
53 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
59 def _get_currency(self, cr, uid, context):
60 user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
62 return user.company_id.currency_id.id
64 return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
66 def _get_journal_analytic(self, cr, uid, type_inv, context={}):
67 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
68 tt = type2journal.get(type_inv, 'sale')
69 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
71 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal of type '%s' !") % (tt,))
74 def _get_type(self, cr, uid, context={}):
75 type = context.get('type', 'out_invoice')
78 def _reconciled(self, cr, uid, ids, name, args, context):
81 res[id] = self.test_paid(cr, uid, [id])
84 def _get_reference_type(self, cr, uid, context=None):
85 return [('none', _('Free Reference'))]
87 def _amount_residual(self, cr, uid, ids, name, args, context={}):
89 data_inv = self.browse(cr, uid, ids)
92 to_pay = inv.amount_total
93 for lines in inv.move_lines:
94 paid_amt = paid_amt + lines.credit
95 res[inv.id] = to_pay - paid_amt
98 def _get_lines(self, cr, uid, ids, name, arg, context=None):
101 move_lines = self.move_line_id_payment_get(cr,uid,[id])
105 data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
106 for line in data_lines:
108 if line.reconcile_id:
109 ids_line = line.reconcile_id.line_id
110 elif line.reconcile_partial_id:
111 ids_line = line.reconcile_partial_id.line_partial_ids
112 l = map(lambda x: x.id, ids_line)
113 res[id]=[x for x in l if x <> line.id]
116 def _get_invoice_line(self, cr, uid, ids, context={}):
118 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
119 result[line.invoice_id.id] = True
122 def _get_invoice_tax(self, cr, uid, ids, context={}):
124 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
125 result[tax.invoice_id.id] = True
128 def _compute_lines(self, cr, uid, ids, name, args, context={}):
130 for invoice in self.browse(cr, uid, ids, context):
131 moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
134 for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
136 lines += map(lambda x: x.id, m.reconcile_id.line_id)
137 elif m.reconcile_partial_id:
138 lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
140 lines = filter(lambda x: x not in src, lines)
141 result[invoice.id] = lines
144 _name = "account.invoice"
145 _description = 'Invoice'
148 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
149 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
150 'type': fields.selection([
151 ('out_invoice','Customer Invoice'),
152 ('in_invoice','Supplier Invoice'),
153 ('out_refund','Customer Refund'),
154 ('in_refund','Supplier Refund'),
155 ],'Type', readonly=True, select=True),
157 'number': fields.char('Invoice Number', size=32, readonly=True, help="Uniq number of the invoice, computed automatically when the invoice is created."),
158 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
159 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
161 'comment': fields.text('Additionnal Information'),
163 'state': fields.selection([
165 ('proforma','Pro-forma'),
166 ('proforma2','Pro-forma'),
169 ('cancel','Canceled')
170 ],'State', select=True, readonly=True),
172 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
173 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
175 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
176 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
177 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
179 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]} ),
181 'period_id': fields.many2one('account.period', 'Force Period', help="Keep empty to use the period of the validation date."),
183 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
184 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
185 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
187 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
188 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
190 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
191 'account.invoice.tax': (_get_invoice_tax, None, 20),
192 'account.invoice.line': (_get_invoice_line, None, 20),
195 'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
197 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
198 'account.invoice.tax': (_get_invoice_tax, None, 20),
199 'account.invoice.line': (_get_invoice_line, None, 20),
202 'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
204 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
205 'account.invoice.tax': (_get_invoice_tax, None, 20),
206 'account.invoice.line': (_get_invoice_line, None, 20),
209 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
210 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
211 'company_id': fields.many2one('res.company', 'Company', required=True),
212 'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
213 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
214 store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
215 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
216 help='The bank account to pay to or to be paid from'),
217 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
218 'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
220 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
221 'account.invoice.tax': (_get_invoice_tax, None, 50),
222 'account.invoice.line': (_get_invoice_line, None, 50),
224 help="Remaining amount due."),
225 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
226 'move_name': fields.char('Account Move', size=64),
230 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
231 'state': lambda *a: 'draft',
232 'journal_id': _get_journal,
233 'currency_id': _get_currency,
234 'company_id': lambda self, cr, uid, context: \
235 self.pool.get('res.users').browse(cr, uid, uid,
236 context=context).company_id.id,
237 'reference_type': lambda *a: 'none',
240 def unlink(self, cr, uid, ids):
241 invoices = self.read(cr, uid, ids, ['state'])
244 if t['state'] in ('draft', 'cancel'):
245 unlink_ids.append(t['id'])
247 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
248 osv.osv.unlink(self, cr, uid, unlink_ids)
251 # def get_invoice_address(self, cr, uid, ids):
252 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
255 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
256 date_invoice=False, payment_term=False, partner_bank_id=False):
257 invoice_addr_id = False
258 contact_addr_id = False
259 partner_payment_term = False
263 opt = [('uid', str(uid))]
266 opt.insert(0, ('id', partner_id))
267 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
268 contact_addr_id = res['contact']
269 invoice_addr_id = res['invoice']
270 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
271 if type in ('out_invoice', 'out_refund'):
272 acc_id = p.property_account_receivable.id
274 acc_id = p.property_account_payable.id
276 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
278 bank_id = p.bank_ids[0].id
281 'address_contact_id': contact_addr_id,
282 'address_invoice_id': invoice_addr_id,
283 'account_id': acc_id,
284 'payment_term': partner_payment_term,
288 if type in ('in_invoice', 'in_refund'):
289 result['value']['partner_bank'] = bank_id
291 if payment_term != partner_payment_term:
292 if partner_payment_term:
293 to_update = self.onchange_payment_term_date_invoice(
294 cr,uid,ids,partner_payment_term,date_invoice)
295 result['value'].update(to_update['value'])
297 result['value']['date_due'] = False
299 if partner_bank_id != bank_id:
300 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
301 result['value'].update(to_update['value'])
304 def onchange_currency_id(self, cr, uid, ids, curr_id):
307 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
308 if not payment_term_id:
311 pt_obj= self.pool.get('account.payment.term')
313 if not date_invoice :
314 date_invoice = time.strftime('%Y-%m-%d')
316 pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
319 pterm_list = [line[0] for line in pterm_list]
321 res= {'value':{'date_due': pterm_list[-1]}}
325 def onchange_invoice_line(self, cr, uid, ids, lines):
328 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
331 # go from canceled state to draft state
332 def action_cancel_draft(self, cr, uid, ids, *args):
333 self.write(cr, uid, ids, {'state':'draft'})
334 wf_service = netsvc.LocalService("workflow")
336 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
342 # return the ids of the move lines which has the same account than the invoice
344 def move_line_id_payment_get(self, cr, uid, ids, *args):
345 ml = self.pool.get('account.move.line')
347 for inv in self.read(cr, uid, ids, ['move_id','account_id']):
349 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
350 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
351 if line['account_id']==inv['account_id']:
352 res.append(line['id'])
355 def copy(self, cr, uid, id, default=None, context=None):
358 default = default.copy()
359 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
360 if 'date_invoice' not in default:
361 default['date_invoice'] = False
362 if 'date_due' not in default:
363 default['date_due'] = False
364 return super(account_invoice, self).copy(cr, uid, id, default, context)
366 def test_paid(self, cr, uid, ids, *args):
367 res = self.move_line_id_payment_get(cr, uid, ids)
372 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
373 ok = ok and bool(cr.fetchone()[0])
376 def button_reset_taxes(self, cr, uid, ids, context={}):
377 ait_obj = self.pool.get('account.invoice.tax')
379 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
380 for taxe in ait_obj.compute(cr, uid, id).values():
381 ait_obj.create(cr, uid, taxe)
384 def button_compute(self, cr, uid, ids, context={}, set_total=False):
385 self.button_reset_taxes(cr, uid, ids, context)
386 for inv in self.browse(cr, uid, ids):
388 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
391 def _convert_ref(self, cr, uid, ref):
392 return (ref or '').replace('/','')
394 def _get_analytic_lines(self, cr, uid, id):
395 inv = self.browse(cr, uid, [id])[0]
396 cur_obj = self.pool.get('res.currency')
398 company_currency = inv.company_id.currency_id.id
399 if inv.type in ('out_invoice', 'in_refund'):
404 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
406 if il['account_analytic_id']:
407 if inv.type in ('in_invoice', 'in_refund'):
410 ref = self._convert_ref(cr, uid, inv.number)
411 il['analytic_lines'] = [(0,0, {
413 'date': inv['date_invoice'],
414 'account_id': il['account_analytic_id'],
415 'unit_amount': il['quantity'],
416 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
417 'product_id': il['product_id'],
418 'product_uom_id': il['uos_id'],
419 'general_account_id': il['account_id'],
420 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
425 def action_move_create(self, cr, uid, ids, *args):
426 ait_obj = self.pool.get('account.invoice.tax')
427 cur_obj = self.pool.get('res.currency')
428 acc_obj = self.pool.get('account.account')
429 self.button_compute(cr, uid, ids, context={}, set_total=False)
430 for inv in self.browse(cr, uid, ids):
433 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
434 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
435 if not inv.date_invoice:
436 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
437 company_currency = inv.company_id.currency_id.id
438 # create the analytical lines
439 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
440 ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
441 # one move line per invoice line
442 iml = self._get_analytic_lines(cr, uid, inv.id)
443 # check if taxes are all computed
444 compute_taxes = ait_obj.compute(cr, uid, inv.id)
446 for tax in compute_taxes.values():
447 ait_obj.create(cr, uid, tax)
450 for tax in inv.tax_line:
453 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
455 if not key in compute_taxes:
456 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
457 base = compute_taxes[key]['base']
458 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
459 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
460 for key in compute_taxes:
461 if not key in tax_key:
462 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
464 # one move line per tax line
465 iml += ait_obj.move_line_get(cr, uid, inv.id)
467 if inv.type in ('in_invoice', 'in_refund'):
470 ref = self._convert_ref(cr, uid, inv.number)
472 diff_currency_p = inv.currency_id.id <> company_currency
473 # create one move line for the total and possibly adjust the other lines amount
478 if 'account_id' in i and 'taxes' in i:
479 if not (i['account_id'],i['taxes']) in key_line:
480 key_line.append((i['account_id'],i['taxes']))
481 if inv.currency_id.id != company_currency:
482 i['currency_id'] = inv.currency_id.id
483 i['amount_currency'] = i['price']
484 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
485 company_currency, i['price'],
486 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
488 i['amount_currency'] = False
489 i['currency_id'] = False
491 if inv.type in ('out_invoice','in_refund'):
493 total_currency += i['amount_currency'] or i['price']
494 i['price'] = - i['price']
497 total_currency -= i['amount_currency'] or i['price']
498 acc_id = inv.account_id.id
500 name = inv['name'] or '/'
504 for item in key_line:
506 if acc_obj.browse(cr,uid,item[0]).merge_invoice:
508 for move_line in iml:
509 if (move_line.has_key('account_id') and move_line['account_id']==item[0]) and ('taxes' in move_line and move_line['taxes']==item[1]):
510 move_list.append(move_line)
512 for key in move_line:
513 if key in ['name','amount_currency','price_unit','price','quantity']:
515 move_temp[key]=move_temp[key] + "," +move_line[key]
517 move_temp[key] +=move_line[key]
519 for key in move_line:
520 move_temp[key]=move_line[key]
523 iml_temp.append(move_temp)
525 if len(iml_temp)<len(move_list):
526 for old_elem in move_list:
528 for new_elem in iml_temp:
533 totlines = self.pool.get('account.payment.term').compute(cr,
534 uid, inv.payment_term.id, total, inv.date_invoice or False)
536 res_amount_currency = total_currency
539 if inv.currency_id.id != company_currency:
540 amount_currency = cur_obj.compute(cr, uid,
541 company_currency, inv.currency_id.id, t[1])
543 amount_currency = False
545 # last line add the diff
546 res_amount_currency -= amount_currency or 0
548 if i == len(totlines):
549 amount_currency += res_amount_currency
555 'account_id': acc_id,
556 'date_maturity': t[0],
557 'amount_currency': diff_currency_p \
558 and amount_currency or False,
559 'currency_id': diff_currency_p \
560 and inv.currency_id.id or False,
568 'account_id': acc_id,
569 'date_maturity' : inv.date_due or False,
570 'amount_currency': diff_currency_p \
571 and total_currency or False,
572 'currency_id': diff_currency_p \
573 and inv.currency_id.id or False,
577 date = inv.date_invoice or time.strftime('%Y-%m-%d')
578 part = inv.partner_id.id
580 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
582 if inv.journal_id.group_invoice_lines:
585 tmp = str(l['account_id'])
586 tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
587 tmp += '-'+str('product_id' in l and l['product_id'] or "False")
588 tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
591 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
592 line2[tmp]['debit'] = (am > 0) and am or 0.0
593 line2[tmp]['credit'] = (am < 0) and -am or 0.0
594 line2[tmp]['tax_amount'] += l['tax_amount']
595 line2[tmp]['analytic_lines'] += l['analytic_lines']
599 for key, val in line2.items():
600 line.append((0,0,val))
602 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
603 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
604 if journal.centralisation:
605 raise osv.except_osv(_('UserError'),
606 _('Can not create invoice move on centralized journal'))
607 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
608 period_id=inv.period_id and inv.period_id.id or False
610 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'))])
612 period_id=period_ids[0]
614 move['period_id'] = period_id
616 i[2]['period_id'] = period_id
618 if not 'name' in move:
619 move['name'] = inv.name or '/'
621 move_id = self.pool.get('account.move').create(cr, uid, move)
622 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
623 # make the invoice point to that move
624 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
625 self.pool.get('account.move').post(cr, uid, [move_id])
626 self._log_event(cr, uid, ids)
629 def line_get_convert(self, cr, uid, x, part, date, context={}):
631 'date_maturity': x.get('date_maturity', False),
633 'name':x['name'][:64],
634 'debit':x['price']>0 and x['price'],
635 'credit':x['price']<0 and -x['price'],
636 'account_id':x['account_id'],
637 'analytic_lines':x.get('analytic_lines', []),
638 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
639 'currency_id':x.get('currency_id', False),
640 'tax_code_id': x.get('tax_code_id', False),
641 'tax_amount': x.get('tax_amount', False),
642 'ref':x.get('ref',False),
643 'quantity':x.get('quantity',1.00),
644 'product_id':x.get('product_id', False),
645 'product_uom_id':x.get('uos_id',False),
646 'analytic_account_id':x.get('account_analytic_id',False),
649 def action_number(self, cr, uid, ids, *args):
650 cr.execute('SELECT id, type, number, move_id, reference ' \
651 'FROM account_invoice ' \
652 'WHERE id IN ('+','.join(map(str,ids))+')')
653 for (id, invtype, number, move_id, reference) in cr.fetchall():
655 number = self.pool.get('ir.sequence').get(cr, uid,
656 'account.invoice.' + invtype)
657 if invtype in ('in_invoice', 'in_refund'):
660 ref = self._convert_ref(cr, uid, number)
661 cr.execute('UPDATE account_invoice SET number=%s ' \
662 'WHERE id=%s', (number, id))
663 cr.execute('UPDATE account_move SET ref=%s ' \
664 'WHERE id=%s AND (ref is null OR ref = \'\')',
666 cr.execute('UPDATE account_move_line SET ref=%s ' \
667 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
669 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
670 'FROM account_move_line ' \
671 'WHERE account_move_line.move_id = %s ' \
672 'AND account_analytic_line.move_id = account_move_line.id',
676 def action_cancel(self, cr, uid, ids, *args):
677 account_move_obj = self.pool.get('account.move')
678 invoices = self.read(cr, uid, ids, ['move_id'])
681 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
682 # delete the move this invoice was pointing to
683 # Note that the corresponding move_lines and move_reconciles
684 # will be automatically deleted too
685 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
686 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
687 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
692 def list_distinct_taxes(self, cr, uid, ids):
693 invoices = self.browse(cr, uid, ids)
696 for tax in inv.tax_line:
697 if not tax['name'] in taxes:
698 taxes[tax['name']] = {'name': tax['name']}
699 return taxes.values()
701 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
702 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
704 part=inv['partner_id'] and inv['partner_id'][0]
706 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
707 total = inv['amount_untaxed']
708 if inv['type'] in ('in_invoice','in_refund'):
709 partnertype='supplier'
710 eventtype = 'purchase'
713 partnertype = 'customer'
716 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
717 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})
720 def name_get(self, cr, uid, ids, context={}):
724 'out_invoice': 'CI: ',
725 'in_invoice': 'SI: ',
726 'out_refund': 'OR: ',
729 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')]
731 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
738 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
740 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
741 return self.name_get(cr, user, ids, context)
743 def _refund_cleanup_lines(self, lines):
746 del line['invoice_id']
747 if 'account_id' in line:
748 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
749 if 'product_id' in line:
750 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
752 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
753 if 'invoice_line_tax_id' in line:
754 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
755 if 'account_analytic_id' in line:
756 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
757 if 'tax_code_id' in line :
758 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
759 line['tax_code_id'] = line['tax_code_id'][0]
760 if 'base_code_id' in line :
761 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
762 line['base_code_id'] = line['base_code_id'][0]
763 return map(lambda x: (0,0,x), lines)
765 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
766 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'])
769 for invoice in invoices:
773 'out_invoice': 'out_refund', # Customer Invoice
774 'in_invoice': 'in_refund', # Supplier Invoice
775 'out_refund': 'out_invoice', # Customer Refund
776 'in_refund': 'in_invoice', # Supplier Refund
780 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
781 invoice_lines = self._refund_cleanup_lines(invoice_lines)
783 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
784 tax_lines = filter(lambda l: l['manual'], tax_lines)
785 tax_lines = self._refund_cleanup_lines(tax_lines)
787 date = time.strftime('%Y-%m-%d')
789 'type': type_dict[invoice['type']],
790 'date_invoice': date,
793 'invoice_line': invoice_lines,
794 'tax_line': tax_lines
798 'period_id': period_id,
804 # take the id part of the tuple returned for many2one fields
805 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
806 'account_id', 'currency_id', 'payment_term', 'journal_id'):
807 invoice[field] = invoice[field] and invoice[field][0]
808 # create the new invoice
809 new_ids.append(self.create(cr, uid, invoice))
812 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={}, name=''):
813 #TODO check if we can use different period for payment and the writeoff line
814 assert len(ids)==1, "Can only pay one invoice at a time"
815 invoice = self.browse(cr, uid, ids[0])
816 src_account_id = invoice.account_id.id
817 # Take the seq as name for move
818 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
819 direction = types[invoice.type]
820 #take the choosen date
821 if 'date_p' in context and context['date_p']:
822 date=context['date_p']
824 date=time.strftime('%Y-%m-%d')
826 'debit': direction * pay_amount>0 and direction * pay_amount,
827 'credit': direction * pay_amount<0 and - direction * pay_amount,
828 'account_id': src_account_id,
829 'partner_id': invoice.partner_id.id,
830 'ref':invoice.number,
833 'debit': direction * pay_amount<0 and - direction * pay_amount,
834 'credit': direction * pay_amount>0 and direction * pay_amount,
835 'account_id': pay_account_id,
836 'partner_id': invoice.partner_id.id,
837 'ref':invoice.number,
840 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
844 lines = [(0, 0, l1), (0, 0, l2)]
845 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
846 move_id = self.pool.get('account.move').create(cr, uid, move)
850 line = self.pool.get('account.move.line')
851 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
852 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
854 if l.account_id.id==src_account_id:
855 line_ids.append(l.id)
856 total += (l.debit or 0.0) - (l.credit or 0.0)
857 if (not total) or writeoff_acc_id:
858 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
860 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
862 # Update the stored value (fields.function), so we write to trigger recompute
863 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
867 class account_invoice_line(osv.osv):
868 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
870 for line in self.browse(cr, uid, ids):
871 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
874 def _price_unit_default(self, cr, uid, context={}):
875 if 'check_total' in context:
876 t = context['check_total']
877 for l in context.get('invoice_line', {}):
878 if len(l) >= 3 and l[2]:
879 tax_obj = self.pool.get('account.tax')
880 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
881 t = t - (p * l[2].get('quantity'))
882 taxes = l[2].get('invoice_line_tax_id')
883 if len(taxes[0]) >= 3 and taxes[0][2]:
884 taxes=tax_obj.browse(cr, uid, taxes[0][2])
885 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)):
886 t = t - tax['amount']
890 _name = "account.invoice.line"
891 _description = "Invoice line"
893 'name': fields.char('Description', size=256, required=True),
894 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
895 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
896 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
897 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
898 '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."),
899 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
900 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
901 'quantity': fields.float('Quantity', required=True),
902 'discount': fields.float('Discount (%)', digits=(16,2)),
903 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
904 'note': fields.text('Notes'),
905 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
908 'quantity': lambda *a: 1,
909 'discount': lambda *a: 0.0,
910 'price_unit': _price_unit_default,
913 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context={}):
914 tax_obj = self.pool.get('account.tax')
916 taxes = tax_obj.browse(cr, uid, tax_id)
917 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
918 price_unit = price_unit - tax['amount']
919 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
921 def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, price_unit=False, address_invoice_id=False, context={}):
923 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
925 if type in ('in_invoice', 'in_refund'):
926 return {'domain':{'product_uom':[]}}
928 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
929 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
931 context.update({'lang': lang})
932 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
934 tax_obj = self.pool.get('account.tax')
935 if type in ('out_invoice', 'out_refund'):
936 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.taxes_id)
938 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, res.supplier_taxes_id)
939 if type in ('in_invoice', 'in_refund'):
940 result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
942 result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
945 result['name'] = res.name
947 if type in ('out_invoice','out_refund'):
948 a = res.product_tmpl_id.property_account_income.id
950 a = res.categ_id.property_account_income_categ.id
952 a = res.product_tmpl_id.property_account_expense.id
954 a = res.categ_id.property_account_expense_categ.id
956 a = self.pool.get('account.fiscal.position').map_account(cr, uid, part, a)
958 result['account_id'] = a
961 result['uos_id'] = uom or res.uom_id.id or False
963 res2 = res.uom_id.category_id.id
965 domain = {'uos_id':[('category_id','=',res2 )]}
966 return {'value':result, 'domain':domain}
968 def move_line_get(self, cr, uid, invoice_id, context={}):
971 tax_obj = self.pool.get('account.tax')
972 cur_obj = self.pool.get('res.currency')
973 ait_obj = self.pool.get('account.invoice.tax')
974 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
975 company_currency = inv.company_id.currency_id.id
976 cur = inv.currency_id
978 for line in inv.invoice_line:
979 mres = self.move_line_get_item(cr, uid, line, context)
983 tax_code_found= False
984 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
985 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
986 line.quantity, inv.address_invoice_id.id, line.product_id,
989 if inv.type in ('out_invoice', 'in_invoice'):
990 tax_code_id = tax['base_code_id']
991 tax_amount = line.price_subtotal * tax['base_sign']
993 tax_code_id = tax['ref_base_code_id']
994 tax_amount = line.price_subtotal * tax['ref_base_sign']
999 res.append(self.move_line_get_item(cr, uid, line, context))
1000 res[-1]['price'] = 0.0
1001 res[-1]['account_analytic_id'] = False
1002 elif not tax_code_id:
1004 tax_code_found = True
1006 res[-1]['tax_code_id'] = tax_code_id
1007 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1010 def move_line_get_item(self, cr, uid, line, context={}):
1013 'name': line.name[:64],
1014 'price_unit':line.price_unit,
1015 'quantity':line.quantity,
1016 'price':line.price_subtotal,
1017 'account_id':line.account_id.id,
1018 'product_id':line.product_id.id,
1019 'uos_id':line.uos_id.id,
1020 'account_analytic_id':line.account_analytic_id.id,
1021 'taxes':line.invoice_line_tax_id,
1024 # Set the tax field according to the account and the partner
1026 def onchange_account_id(self, cr, uid, ids, partner_id,account_id):
1027 if not (partner_id and account_id):
1029 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1030 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1032 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, part, taxes)
1033 r = {'value':{'invoice_line_tax_id': res}}
1035 account_invoice_line()
1037 class account_invoice_tax(osv.osv):
1038 _name = "account.invoice.tax"
1039 _description = "Invoice Tax"
1041 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1042 'name': fields.char('Tax Description', size=64, required=True),
1043 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1044 'base': fields.float('Base', digits=(16,2)),
1045 'amount': fields.float('Amount', digits=(16,2)),
1046 'manual': fields.boolean('Manual'),
1047 'sequence': fields.integer('Sequence'),
1049 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1050 'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1051 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1052 'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1054 def base_change(self, cr, uid, ids, base):
1055 return {'value': {'base_amount':base}}
1056 def amount_change(self, cr, uid, ids, amount):
1057 return {'value': {'tax_amount':amount}}
1060 'manual': lambda *a: 1,
1061 'base_amount': lambda *a: 0.0,
1062 'tax_amount': lambda *a: 0.0,
1064 def compute(self, cr, uid, invoice_id):
1066 tax_obj = self.pool.get('account.tax')
1067 cur_obj = self.pool.get('res.currency')
1068 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1069 cur = inv.currency_id
1070 company_currency = inv.company_id.currency_id.id
1072 for line in inv.invoice_line:
1073 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):
1075 val['invoice_id'] = inv.id
1076 val['name'] = tax['name']
1077 val['amount'] = tax['amount']
1078 val['manual'] = False
1079 val['sequence'] = tax['sequence']
1080 val['base'] = tax['price_unit'] * line['quantity']
1082 if inv.type in ('out_invoice','in_invoice'):
1083 val['base_code_id'] = tax['base_code_id']
1084 val['tax_code_id'] = tax['tax_code_id']
1085 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')})
1086 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')})
1087 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1089 val['base_code_id'] = tax['ref_base_code_id']
1090 val['tax_code_id'] = tax['ref_tax_code_id']
1091 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')})
1092 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')})
1093 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1095 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1096 if not key in tax_grouped:
1097 tax_grouped[key] = val
1099 tax_grouped[key]['amount'] += val['amount']
1100 tax_grouped[key]['base'] += val['base']
1101 tax_grouped[key]['base_amount'] += val['base_amount']
1102 tax_grouped[key]['tax_amount'] += val['tax_amount']
1104 for t in tax_grouped.values():
1105 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1108 def move_line_get(self, cr, uid, invoice_id):
1110 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1111 for t in cr.dictfetchall():
1112 if not t['amount'] \
1113 and not t['tax_code_id'] \
1114 and not t['tax_amount']:
1119 'price_unit': t['amount'],
1121 'price': t['amount'] or 0.0,
1122 'account_id': t['account_id'],
1123 'tax_code_id': t['tax_code_id'],
1124 'tax_amount': t['tax_amount']
1127 account_invoice_tax()
1129 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: