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 must 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
184 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
187 _name = "account.invoice"
188 _description = 'Invoice'
191 'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
192 'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
193 'type': fields.selection([
194 ('out_invoice','Customer Invoice'),
195 ('in_invoice','Supplier Invoice'),
196 ('out_refund','Customer Refund'),
197 ('in_refund','Supplier Refund'),
198 ],'Type', readonly=True, select=True),
200 'number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
201 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
202 'reference_type': fields.selection(_get_reference_type, 'Reference Type',
204 'comment': fields.text('Additional Information'),
206 'state': fields.selection([
208 ('proforma','Pro-forma'),
209 ('proforma2','Pro-forma'),
212 ('cancel','Cancelled')
213 ],'State', select=True, readonly=True),
215 'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
216 'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
217 help="If you use payment terms, the due date will be computed automatically at the generation "\
218 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment."),
219 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
220 'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
221 'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
222 'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
223 help="If you use payment terms, the due date will be computed automatically at the generation "\
224 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
225 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
226 'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation date."),
228 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
229 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
230 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
232 'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
233 'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
235 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
236 'account.invoice.tax': (_get_invoice_tax, None, 20),
237 'account.invoice.line': (_get_invoice_line, None, 20),
240 'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
242 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
243 'account.invoice.tax': (_get_invoice_tax, None, 20),
244 'account.invoice.line': (_get_invoice_line, None, 20),
247 'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
249 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
250 'account.invoice.tax': (_get_invoice_tax, None, 20),
251 'account.invoice.line': (_get_invoice_line, None, 20),
254 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
255 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
256 'company_id': fields.many2one('res.company', 'Company', required=True),
257 'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
258 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
260 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
261 'account.move.line': (_get_invoice_from_line, None, 50),
262 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
263 }, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
264 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
265 help='The bank account to pay to or to be paid from'),
266 'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
267 'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
269 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
270 'account.invoice.tax': (_get_invoice_tax, None, 50),
271 'account.invoice.line': (_get_invoice_line, None, 50),
272 'account.move.line': (_get_invoice_from_line, None, 50),
273 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
275 help="Remaining amount due."),
276 'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
277 'move_name': fields.char('Account Move', size=64),
278 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
282 #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
283 'state': lambda *a: 'draft',
284 'journal_id': _get_journal,
285 'currency_id': _get_currency,
286 'company_id': lambda self, cr, uid, context: \
287 self.pool.get('res.users').browse(cr, uid, uid,
288 context=context).company_id.id,
289 'reference_type': lambda *a: 'none',
292 def unlink(self, cr, uid, ids, context=None):
293 for inv in self.browse(cr, uid, ids, context):
294 print inv.name, inv.amount_total
295 for line in inv.invoice_line:
296 print line.price_subtotal
297 # invoices = self.read(cr, uid, ids, ['state'])
300 # if t['state'] in ('draft', 'cancel'):
301 # unlink_ids.append(t['id'])
303 # raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
304 # osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
307 #def unlink(self, cr, uid, ids, context=None):
308 # invoices = self.read(cr, uid, ids, ['state'])
311 # if t['state'] in ('draft', 'cancel'):
312 # unlink_ids.append(t['id'])
314 # raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
315 # osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
318 # def get_invoice_address(self, cr, uid, ids):
319 # res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
322 def onchange_partner_id(self, cr, uid, ids, type, partner_id,
323 date_invoice=False, payment_term=False, partner_bank_id=False):
324 invoice_addr_id = False
325 contact_addr_id = False
326 partner_payment_term = False
329 fiscal_position = False
331 opt = [('uid', str(uid))]
334 opt.insert(0, ('id', partner_id))
335 res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
336 contact_addr_id = res['contact']
337 invoice_addr_id = res['invoice']
338 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
339 if type in ('out_invoice', 'out_refund'):
340 acc_id = p.property_account_receivable.id
342 acc_id = p.property_account_payable.id
343 fiscal_position = p.property_account_position and p.property_account_position.id or False
344 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
346 bank_id = p.bank_ids[0].id
349 'address_contact_id': contact_addr_id,
350 'address_invoice_id': invoice_addr_id,
351 'account_id': acc_id,
352 'payment_term': partner_payment_term,
353 'fiscal_position': fiscal_position
357 if type in ('in_invoice', 'in_refund'):
358 result['value']['partner_bank'] = bank_id
360 if payment_term != partner_payment_term:
361 if partner_payment_term:
362 to_update = self.onchange_payment_term_date_invoice(
363 cr,uid,ids,partner_payment_term,date_invoice)
364 result['value'].update(to_update['value'])
366 result['value']['date_due'] = False
368 if partner_bank_id != bank_id:
369 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
370 result['value'].update(to_update['value'])
373 def onchange_currency_id(self, cr, uid, ids, curr_id):
376 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
377 if not payment_term_id:
380 pt_obj= self.pool.get('account.payment.term')
382 if not date_invoice :
383 date_invoice = time.strftime('%Y-%m-%d')
385 pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
388 pterm_list = [line[0] for line in pterm_list]
390 res= {'value':{'date_due': pterm_list[-1]}}
394 def onchange_invoice_line(self, cr, uid, ids, lines):
397 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
400 # go from canceled state to draft state
401 def action_cancel_draft(self, cr, uid, ids, *args):
402 self.write(cr, uid, ids, {'state':'draft'})
403 wf_service = netsvc.LocalService("workflow")
405 wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
411 # return the ids of the move lines which has the same account than the invoice
413 def move_line_id_payment_get(self, cr, uid, ids, *args):
414 ml = self.pool.get('account.move.line')
416 for inv in self.read(cr, uid, ids, ['move_id','account_id']):
418 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
419 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
420 if line['account_id']==inv['account_id']:
421 res.append(line['id'])
424 def copy(self, cr, uid, id, default=None, context=None):
427 default = default.copy()
428 default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
429 if 'date_invoice' not in default:
430 default['date_invoice'] = False
431 if 'date_due' not in default:
432 default['date_due'] = False
433 return super(account_invoice, self).copy(cr, uid, id, default, context)
435 def test_paid(self, cr, uid, ids, *args):
436 res = self.move_line_id_payment_get(cr, uid, ids)
441 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
442 ok = ok and bool(cr.fetchone()[0])
445 def button_reset_taxes(self, cr, uid, ids, context=None):
448 ait_obj = self.pool.get('account.invoice.tax')
450 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
451 partner = self.browse(cr, uid, id).partner_id
452 context.update({'lang': partner.lang})
453 for taxe in ait_obj.compute(cr, uid, id, context=context).values():
454 ait_obj.create(cr, uid, taxe)
455 # Update the stored value (fields.function), so we write to trigger recompute
456 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
457 # self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
460 def button_compute(self, cr, uid, ids, context=None, set_total=False):
461 self.button_reset_taxes(cr, uid, ids, context)
462 for inv in self.browse(cr, uid, ids):
464 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
467 def _convert_ref(self, cr, uid, ref):
468 return (ref or '').replace('/','')
470 def _get_analytic_lines(self, cr, uid, id):
471 inv = self.browse(cr, uid, [id])[0]
472 cur_obj = self.pool.get('res.currency')
474 company_currency = inv.company_id.currency_id.id
475 if inv.type in ('out_invoice', 'in_refund'):
480 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
482 if il['account_analytic_id']:
483 if inv.type in ('in_invoice', 'in_refund'):
486 ref = self._convert_ref(cr, uid, inv.number)
487 il['analytic_lines'] = [(0,0, {
489 'date': inv['date_invoice'],
490 'account_id': il['account_analytic_id'],
491 'unit_amount': il['quantity'],
492 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
493 'product_id': il['product_id'],
494 'product_uom_id': il['uos_id'],
495 'general_account_id': il['account_id'],
496 'journal_id': self._get_journal_analytic(cr, uid, inv.type),
501 def action_move_create(self, cr, uid, ids, *args):
502 ait_obj = self.pool.get('account.invoice.tax')
503 cur_obj = self.pool.get('res.currency')
505 for inv in self.browse(cr, uid, ids):
509 if not inv.date_invoice:
510 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
511 company_currency = inv.company_id.currency_id.id
512 # create the analytical lines
513 line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
514 # one move line per invoice line
515 iml = self._get_analytic_lines(cr, uid, inv.id)
516 # check if taxes are all computed
518 context.update({'lang': inv.partner_id.lang})
519 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
521 for tax in compute_taxes.values():
522 ait_obj.create(cr, uid, tax)
525 for tax in inv.tax_line:
528 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
530 if not key in compute_taxes:
531 raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
532 base = compute_taxes[key]['base']
533 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
534 raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
535 for key in compute_taxes:
536 if not key in tax_key:
537 raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
539 if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
540 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
542 # one move line per tax line
543 iml += ait_obj.move_line_get(cr, uid, inv.id)
545 if inv.type in ('in_invoice', 'in_refund'):
548 ref = self._convert_ref(cr, uid, inv.number)
550 diff_currency_p = inv.currency_id.id <> company_currency
551 # create one move line for the total and possibly adjust the other lines amount
555 if inv.currency_id.id != company_currency:
556 i['currency_id'] = inv.currency_id.id
557 i['amount_currency'] = i['price']
558 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
559 company_currency, i['price'],
560 context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
562 i['amount_currency'] = False
563 i['currency_id'] = False
565 if inv.type in ('out_invoice','in_refund'):
567 total_currency += i['amount_currency'] or i['price']
568 i['price'] = - i['price']
571 total_currency -= i['amount_currency'] or i['price']
572 acc_id = inv.account_id.id
574 name = inv['name'] or '/'
577 totlines = self.pool.get('account.payment.term').compute(cr,
578 uid, inv.payment_term.id, total, inv.date_invoice or False)
580 res_amount_currency = total_currency
583 if inv.currency_id.id != company_currency:
584 amount_currency = cur_obj.compute(cr, uid,
585 company_currency, inv.currency_id.id, t[1])
587 amount_currency = False
589 # last line add the diff
590 res_amount_currency -= amount_currency or 0
592 if i == len(totlines):
593 amount_currency += res_amount_currency
599 'account_id': acc_id,
600 'date_maturity': t[0],
601 'amount_currency': diff_currency_p \
602 and amount_currency or False,
603 'currency_id': diff_currency_p \
604 and inv.currency_id.id or False,
612 'account_id': acc_id,
613 'date_maturity' : inv.date_due or False,
614 'amount_currency': diff_currency_p \
615 and total_currency or False,
616 'currency_id': diff_currency_p \
617 and inv.currency_id.id or False,
621 date = inv.date_invoice or time.strftime('%Y-%m-%d')
622 part = inv.partner_id.id
624 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
626 if inv.journal_id.group_invoice_lines:
629 tmp = str(l['account_id'])
630 tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
631 tmp += '-'+str('product_id' in l and l['product_id'] or "False")
632 tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
635 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
636 line2[tmp]['debit'] = (am > 0) and am or 0.0
637 line2[tmp]['credit'] = (am < 0) and -am or 0.0
638 line2[tmp]['tax_amount'] += l['tax_amount']
639 line2[tmp]['analytic_lines'] += l['analytic_lines']
643 for key, val in line2.items():
644 line.append((0,0,val))
646 journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
647 journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
648 if journal.centralisation:
649 raise osv.except_osv(_('UserError'),
650 _('Cannot create invoice move on centralised journal'))
651 move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
652 period_id=inv.period_id and inv.period_id.id or False
654 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'))])
656 period_id=period_ids[0]
658 move['period_id'] = period_id
660 i[2]['period_id'] = period_id
662 move_id = self.pool.get('account.move').create(cr, uid, move)
663 new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
664 # make the invoice point to that move
665 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
666 self.pool.get('account.move').post(cr, uid, [move_id])
667 self._log_event(cr, uid, ids)
670 def line_get_convert(self, cr, uid, x, part, date, context=None):
672 'date_maturity': x.get('date_maturity', False),
674 'name':x['name'][:64],
676 'debit':x['price']>0 and x['price'],
677 'credit':x['price']<0 and -x['price'],
678 'account_id':x['account_id'],
679 'analytic_lines':x.get('analytic_lines', []),
680 'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
681 'currency_id':x.get('currency_id', False),
682 'tax_code_id': x.get('tax_code_id', False),
683 'tax_amount': x.get('tax_amount', False),
684 'ref':x.get('ref',False),
685 'quantity':x.get('quantity',1.00),
686 'product_id':x.get('product_id', False),
687 'product_uom_id':x.get('uos_id',False),
688 'analytic_account_id':x.get('account_analytic_id',False),
691 def action_number(self, cr, uid, ids, *args):
692 cr.execute('SELECT id, type, number, move_id, reference ' \
693 'FROM account_invoice ' \
694 'WHERE id IN ('+','.join(map(str,ids))+')')
695 obj_inv = self.browse(cr, uid, ids)[0]
696 for (id, invtype, number, move_id, reference) in cr.fetchall():
698 if obj_inv.journal_id.invoice_sequence_id:
699 sid = obj_inv.journal_id.invoice_sequence_id.id
700 number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', {'fiscalyear_id': obj_inv.period_id.fiscalyear_id.id})
702 number = self.pool.get('ir.sequence').get(cr, uid,
703 'account.invoice.' + invtype)
704 if invtype in ('in_invoice', 'in_refund'):
707 ref = self._convert_ref(cr, uid, number)
708 cr.execute('UPDATE account_invoice SET number=%s ' \
709 'WHERE id=%s', (number, id))
710 cr.execute('UPDATE account_move SET ref=%s ' \
711 'WHERE id=%s AND (ref is null OR ref = \'\')',
713 cr.execute('UPDATE account_move_line SET ref=%s ' \
714 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
716 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
717 'FROM account_move_line ' \
718 'WHERE account_move_line.move_id = %s ' \
719 'AND account_analytic_line.move_id = account_move_line.id',
723 def action_cancel(self, cr, uid, ids, *args):
724 account_move_obj = self.pool.get('account.move')
725 invoices = self.read(cr, uid, ids, ['move_id'])
728 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
729 # delete the move this invoice was pointing to
730 # Note that the corresponding move_lines and move_reconciles
731 # will be automatically deleted too
732 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
733 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
734 self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
739 def list_distinct_taxes(self, cr, uid, ids):
740 invoices = self.browse(cr, uid, ids)
743 for tax in inv.tax_line:
744 if not tax['name'] in taxes:
745 taxes[tax['name']] = {'name': tax['name']}
746 return taxes.values()
748 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
749 invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
751 part=inv['partner_id'] and inv['partner_id'][0]
753 cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
754 total = inv['amount_untaxed']
755 if inv['type'] in ('in_invoice','in_refund'):
756 partnertype='supplier'
757 eventtype = 'purchase'
760 partnertype = 'customer'
763 if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
764 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})
767 def name_get(self, cr, uid, ids, context=None):
771 'out_invoice': 'CI: ',
772 'in_invoice': 'SI: ',
773 'out_refund': 'OR: ',
776 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')]
778 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
785 ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
787 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
788 return self.name_get(cr, user, ids, context)
790 def _refund_cleanup_lines(self, lines):
793 del line['invoice_id']
794 if 'account_id' in line:
795 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
796 if 'product_id' in line:
797 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
799 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
800 if 'invoice_line_tax_id' in line:
801 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
802 if 'account_analytic_id' in line:
803 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
804 if 'tax_code_id' in line :
805 if isinstance(line['tax_code_id'],tuple) and len(line['tax_code_id']) >0 :
806 line['tax_code_id'] = line['tax_code_id'][0]
807 if 'base_code_id' in line :
808 if isinstance(line['base_code_id'],tuple) and len(line['base_code_id']) >0 :
809 line['base_code_id'] = line['base_code_id'][0]
810 return map(lambda x: (0,0,x), lines)
812 def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
813 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'])
816 for invoice in invoices:
820 'out_invoice': 'out_refund', # Customer Invoice
821 'in_invoice': 'in_refund', # Supplier Invoice
822 'out_refund': 'out_invoice', # Customer Refund
823 'in_refund': 'in_invoice', # Supplier Refund
827 invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
828 invoice_lines = self._refund_cleanup_lines(invoice_lines)
830 tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
831 tax_lines = filter(lambda l: l['manual'], tax_lines)
832 tax_lines = self._refund_cleanup_lines(tax_lines)
834 date = time.strftime('%Y-%m-%d')
836 'type': type_dict[invoice['type']],
837 'date_invoice': date,
840 'invoice_line': invoice_lines,
841 'tax_line': tax_lines
845 'period_id': period_id,
851 # take the id part of the tuple returned for many2one fields
852 for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
853 'account_id', 'currency_id', 'payment_term', 'journal_id'):
854 invoice[field] = invoice[field] and invoice[field][0]
855 # create the new invoice
856 new_ids.append(self.create(cr, uid, invoice))
859 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=''):
862 #TODO check if we can use different period for payment and the writeoff line
863 assert len(ids)==1, "Can only pay one invoice at a time"
864 invoice = self.browse(cr, uid, ids[0])
865 src_account_id = invoice.account_id.id
866 # Take the seq as name for move
867 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
868 direction = types[invoice.type]
869 #take the choosen date
870 if 'date_p' in context and context['date_p']:
871 date=context['date_p']
873 date=time.strftime('%Y-%m-%d')
875 'debit': direction * pay_amount>0 and direction * pay_amount,
876 'credit': direction * pay_amount<0 and - direction * pay_amount,
877 'account_id': src_account_id,
878 'partner_id': invoice.partner_id.id,
879 'ref':invoice.number,
882 'debit': direction * pay_amount<0 and - direction * pay_amount,
883 'credit': direction * pay_amount>0 and direction * pay_amount,
884 'account_id': pay_account_id,
885 'partner_id': invoice.partner_id.id,
886 'ref':invoice.number,
890 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
894 lines = [(0, 0, l1), (0, 0, l2)]
895 move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
896 move_id = self.pool.get('account.move').create(cr, uid, move)
900 line = self.pool.get('account.move.line')
901 cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
902 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
903 for l in lines+invoice.payment_ids:
904 if l.account_id.id==src_account_id:
905 line_ids.append(l.id)
906 total += (l.debit or 0.0) - (l.credit or 0.0)
907 if (not total) or writeoff_acc_id:
908 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
910 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
912 # Update the stored value (fields.function), so we write to trigger recompute
913 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
917 class account_invoice_line(osv.osv):
918 def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
920 for line in self.browse(cr, uid, ids):
921 res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
924 def _price_unit_default(self, cr, uid, context=None):
927 if 'check_total' in context:
928 t = context['check_total']
929 for l in context.get('invoice_line', {}):
930 if len(l) >= 3 and l[2]:
931 tax_obj = self.pool.get('account.tax')
932 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
933 t = t - (p * l[2].get('quantity'))
934 taxes = l[2].get('invoice_line_tax_id')
935 if len(taxes[0]) >= 3 and taxes[0][2]:
936 taxes=tax_obj.browse(cr, uid, taxes[0][2])
937 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)):
938 t = t - tax['amount']
942 _name = "account.invoice.line"
943 _description = "Invoice line"
945 'name': fields.char('Description', size=256, required=True),
946 'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
947 'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
948 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
949 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
950 '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."),
951 'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
952 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
953 'quantity': fields.float('Quantity', required=True),
954 'discount': fields.float('Discount (%)', digits=(16,2)),
955 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
956 'note': fields.text('Notes'),
957 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
960 'quantity': lambda *a: 1,
961 'discount': lambda *a: 0.0,
962 'price_unit': _price_unit_default,
965 def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
966 tax_obj = self.pool.get('account.tax')
968 taxes = tax_obj.browse(cr, uid, tax_id)
969 for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
970 price_unit = price_unit - tax['amount']
971 return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
973 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):
977 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
979 if type in ('in_invoice', 'in_refund'):
980 return {'domain':{'product_uom':[]}}
982 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
983 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
984 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
987 context.update({'lang': lang})
989 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
991 if type in ('out_invoice','out_refund'):
992 a = res.product_tmpl_id.property_account_income.id
994 a = res.categ_id.property_account_income_categ.id
996 a = res.product_tmpl_id.property_account_expense.id
998 a = res.categ_id.property_account_expense_categ.id
1000 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
1002 result['account_id'] = a
1005 tax_obj = self.pool.get('account.tax')
1006 if type in ('out_invoice', 'out_refund'):
1007 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1008 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1010 taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid,a).tax_ids or False)
1011 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1012 if type in ('in_invoice', 'in_refund'):
1013 to_update = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
1014 result.update(to_update)
1016 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1019 result['name'] = res.name
1022 result['uos_id'] = uom or res.uom_id.id or False
1023 if result['uos_id']:
1024 res2 = res.uom_id.category_id.id
1026 domain = {'uos_id':[('category_id','=',res2 )]}
1027 return {'value':result, 'domain':domain}
1029 def move_line_get(self, cr, uid, invoice_id, context=None):
1032 tax_obj = self.pool.get('account.tax')
1033 cur_obj = self.pool.get('res.currency')
1034 ait_obj = self.pool.get('account.invoice.tax')
1035 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1036 company_currency = inv.company_id.currency_id.id
1037 cur = inv.currency_id
1039 for line in inv.invoice_line:
1040 mres = self.move_line_get_item(cr, uid, line, context)
1044 tax_code_found= False
1045 for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1046 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1047 line.quantity, inv.address_invoice_id.id, line.product_id,
1050 if inv.type in ('out_invoice', 'in_invoice'):
1051 tax_code_id = tax['base_code_id']
1052 tax_amount = line.price_subtotal * tax['base_sign']
1054 tax_code_id = tax['ref_base_code_id']
1055 tax_amount = line.price_subtotal * tax['ref_base_sign']
1060 res.append(self.move_line_get_item(cr, uid, line, context))
1061 res[-1]['price'] = 0.0
1062 res[-1]['account_analytic_id'] = False
1063 elif not tax_code_id:
1065 tax_code_found = True
1067 res[-1]['tax_code_id'] = tax_code_id
1068 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1071 def move_line_get_item(self, cr, uid, line, context=None):
1074 'name': line.name[:64],
1075 'price_unit':line.price_unit,
1076 'quantity':line.quantity,
1077 'price':line.price_subtotal,
1078 'account_id':line.account_id.id,
1079 'product_id':line.product_id.id,
1080 'uos_id':line.uos_id.id,
1081 'account_analytic_id':line.account_analytic_id.id,
1082 'taxes':line.invoice_line_tax_id,
1085 # Set the tax field according to the account and the fiscal position
1087 def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1090 taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1091 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1092 res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1093 r = {'value':{'invoice_line_tax_id': res}}
1095 account_invoice_line()
1097 class account_invoice_tax(osv.osv):
1098 _name = "account.invoice.tax"
1099 _description = "Invoice Tax"
1101 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1102 'name': fields.char('Tax Description', size=64, required=True),
1103 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1104 'base': fields.float('Base', digits=(16,2)),
1105 'amount': fields.float('Amount', digits=(16,2)),
1106 'manual': fields.boolean('Manual'),
1107 'sequence': fields.integer('Sequence'),
1109 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1110 'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1111 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1112 'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1114 def base_change(self, cr, uid, ids, base):
1115 return {'value': {'base_amount':base}}
1116 def amount_change(self, cr, uid, ids, amount):
1117 return {'value': {'tax_amount':amount}}
1120 'manual': lambda *a: 1,
1121 'base_amount': lambda *a: 0.0,
1122 'tax_amount': lambda *a: 0.0,
1124 def compute(self, cr, uid, invoice_id, context={}):
1126 tax_obj = self.pool.get('account.tax')
1127 cur_obj = self.pool.get('res.currency')
1128 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context)
1129 cur = inv.currency_id
1130 company_currency = inv.company_id.currency_id.id
1132 for line in inv.invoice_line:
1133 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):
1135 val['invoice_id'] = inv.id
1136 val['name'] = tax['name']
1137 val['amount'] = tax['amount']
1138 val['manual'] = False
1139 val['sequence'] = tax['sequence']
1140 val['base'] = tax['price_unit'] * line['quantity']
1142 if inv.type in ('out_invoice','in_invoice'):
1143 val['base_code_id'] = tax['base_code_id']
1144 val['tax_code_id'] = tax['tax_code_id']
1145 val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1146 val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1147 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1149 val['base_code_id'] = tax['ref_base_code_id']
1150 val['tax_code_id'] = tax['ref_tax_code_id']
1151 val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1152 val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1153 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1155 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1156 if not key in tax_grouped:
1157 tax_grouped[key] = val
1159 tax_grouped[key]['amount'] += val['amount']
1160 tax_grouped[key]['base'] += val['base']
1161 tax_grouped[key]['base_amount'] += val['base_amount']
1162 tax_grouped[key]['tax_amount'] += val['tax_amount']
1164 for t in tax_grouped.values():
1165 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1166 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1167 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1170 def move_line_get(self, cr, uid, invoice_id):
1172 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1173 for t in cr.dictfetchall():
1174 if not t['amount'] \
1175 and not t['tax_code_id'] \
1176 and not t['tax_amount']:
1181 'price_unit': t['amount'],
1183 'price': t['amount'] or 0.0,
1184 'account_id': t['account_id'],
1185 'tax_code_id': t['tax_code_id'],
1186 'tax_amount': t['tax_amount']
1189 account_invoice_tax()
1191 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: