[IMP]: Improvement usability
[odoo/odoo.git] / addons / account_voucher / voucher.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from osv import fields
24 from osv import osv
25 from tools.translate import _
26
27 journal2type = {
28     'cash':'rec_voucher',
29     'bank':'bank_rec_voucher',
30     'cash':'pay_voucher',
31     'sale':'journal_sale_vou',
32     'purchase':'journal_pur_voucher',
33     'general':'journal_voucher'
34 }
35
36 type2journal = {
37     'rec_voucher': 'cash',
38     'bank_rec_voucher': 'bank',
39     'pay_voucher': 'cash',
40     'bank_pay_voucher': 'bank',
41     'cont_voucher': 'cash',
42     'journal_sale_vou': 'sale',
43     'journal_pur_voucher': 'purchase',
44     'journal_voucher':'general'
45 }
46
47 class ir_sequence_type(osv.osv):
48     _inherit = "ir.sequence.type"
49     _columns = {
50         'name': fields.char('Sequence Name',size=128, required=True),
51         'code': fields.char('Sequence Code',size=128, required=True),
52     }
53 ir_sequence_type()
54
55 class account_journal(osv.osv):
56     _inherit = "account.journal"
57     _columns = {
58         'max_amount': fields.float('Verify Transaction', digits=(16, 2), help="Validate voucher entry twice before posting it, if transection amount more then entered here"),
59     }
60 account_journal()
61
62 class account_voucher(osv.osv):
63
64     def _get_period(self, cr, uid, context={}):
65         if context.get('period_id', False):
66             return context.get('period_id')
67
68         periods = self.pool.get('account.period').find(cr, uid)
69         if periods:
70             return periods[0]
71         else:
72             return False
73
74     def _get_type(self, cr, uid, context={}):
75         vtype = context.get('type', 'bank')
76         voucher_type = journal2type.get(vtype)
77         return voucher_type
78
79     def _get_reference_type(self, cursor, user, context=None):
80         return [('none', 'Free Reference')]
81
82     def _get_journal(self, cr, uid, context={}):
83         journal_pool = self.pool.get('account.journal')
84
85         if context.get('journal_id', False):
86             return context.get('journal_id')
87
88         type_inv = context.get('type', 'rec_voucher')
89
90         ttype = type2journal.get(type_inv, type_inv)
91         res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
92
93         if res:
94             return res[0]
95         else:
96             return False
97
98     def _get_currency(self, cr, uid, context):
99         user = self.pool.get('res.users').browse(cr, uid, uid)
100         if user.company_id:
101             return user.company_id.currency_id.id
102         else:
103             return self.pool.get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
104
105     _name = 'account.voucher'
106     _description = 'Accounting Voucher'
107     _order = "id desc"
108     _columns = {
109         'name':fields.char('Name', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}),
110         'type': fields.selection([
111             ('pay_voucher','Cash Payment'),
112             ('bank_pay_voucher','Bank Payment'),
113             ('rec_voucher','Cash Receipt'),
114             ('bank_rec_voucher','Bank Receipt'),
115             ('journal_sale_vou','Journal Sale'),
116             ('journal_pur_voucher','Journal Purchase'),
117             ('journal_voucher','Journal Voucher'),
118             ],'Entry Type', select=True , size=128, readonly=True, states={'draft':[('readonly',False)]}),
119         'date':fields.date('Date', readonly=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
120         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
121         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, domain=[('type','<>','view')]),
122         'payment_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
123         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'posted':[('readonly',False)]}),
124         'narration':fields.text('Narration', readonly=True, states={'draft':[('readonly',False)]}),
125         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
126         'company_id': fields.many2one('res.company', 'Company', required=True),
127         'state':fields.selection(
128             [('draft','Draft'),
129              ('proforma','Pro-forma'),
130              ('posted','Posted'),
131              ('recheck','Waiting for Re-checking'),
132              ('cancel','Cancel'),
133              ('audit','Audit Complete')
134             ], 'State', readonly=True, size=32,
135             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
136                         \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
137                         \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
138                         \n* The \'Cancelled\' state is used when user cancel voucher.'),
139         'amount':fields.float('Amount', readonly=True),
140         'reference': fields.char('Reference', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Payment or Receipt transaction number, i.e. Bank cheque number or payorder number or Wire transfer number or Acknowledge number."),
141         'reference_type': fields.selection(_get_reference_type, 'Reference Type', required=True),
142         'number': fields.related('move_id', 'name', type="char", readonly=True, string='Number'),
143         'move_id':fields.many2one('account.move', 'Account Entry'),
144         'move_ids':fields.many2many('account.move.line', 'voucher_id', 'account_id', 'rel_account_move', 'Real Entry'),
145         'partner_id':fields.many2one('res.partner', 'Partner', readonly=True, states={'draft':[('readonly',False)]})
146     }
147
148     _defaults = {
149         'period_id': _get_period,
150         'type': _get_type,
151         'journal_id':_get_journal,
152         'currency_id': _get_currency,
153         'state': lambda *a: 'draft',
154         'date' : lambda *a: time.strftime('%Y-%m-%d'),
155         'reference_type': lambda *a: "none",
156         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
157     }
158
159     def onchange_account(self, cr, uid, ids, account_id):
160         if not account_id:
161             return {
162                 'value':{'amount':False}
163             }
164         account = self.pool.get('account.account').browse(cr, uid, account_id)
165         balance=account.balance
166         return {
167             'value':{'amount':balance}
168         }
169
170     def onchange_journal(self, cr, uid, ids, journal_id, type):
171         if not journal_id:
172             return {
173                 'value':{'account_id':False}
174             }
175         journal = self.pool.get('account.journal')
176
177         if journal_id and (type in ('rec_voucher','bank_rec_voucher','journal_pur_voucher','journal_voucher')):
178             account_id = journal.browse(cr, uid, journal_id).default_debit_account_id
179             return {
180                 'value':{'account_id':account_id.id}
181             }
182         elif journal_id and (type in ('pay_voucher','bank_pay_voucher','journal_sale_vou')) :
183                 account_id = journal.browse(cr, uid, journal_id).default_credit_account_id
184                 return {
185                     'value':{'account_id':account_id.id}
186                 }
187         else:
188             account_id = journal.browse(cr, uid, journal_id).default_credit_account_id
189             return {
190                 'value':{'account_id':account_id.id}
191             }
192
193     def open_voucher(self, cr, uid, ids, context={}):
194         voucher = self.pool.get('account.voucher').browse(cr, uid, ids)[0]
195         total = 0
196         for line in voucher.payment_ids:
197             total += line.amount
198         
199         if total != 0:
200             res = {
201                 'amount':total, 
202                 'state':'proforma'
203             }
204             self.write(cr, uid, ids, res)
205         else:
206             raise osv.except_osv(_('Invalid action !'), _('You can not post to Pro-Forma a voucher with Total amount = 0 !'))
207         return True
208
209     def proforma_voucher(self, cr, uid, ids, context={}):
210         self.action_move_line_create(cr, uid, ids)
211         self.write(cr, uid, ids, {'state':'posted'})
212         return True
213     
214     def action_cancel_draft(self, cr, uid, ids, context={}):
215         self.write(cr, uid, ids, {'state':'draft'})
216         return True
217     
218     def audit_pass(self, cr, uid, ids, context={}):
219         move_pool = self.pool.get('account.move')
220         result = True
221         audit_pass = []
222         for voucher in self.browse(cr, uid, ids):
223             if voucher.move_id and voucher.move_id.state == 'draft':
224                 result = result and move_pool.button_validate(cr, uid, [voucher.move_id.id])
225             audit_pass += [voucher.id]
226         
227         self.write(cr, uid, audit_pass, {'state':'audit'})
228         return result
229         
230     def cancel_voucher(self, cr, uid, ids, context={}):
231         move_pool = self.pool.get('account.move')
232         
233         for voucher in self.browse(cr, uid, ids):
234             if voucher.move_id:
235                 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
236                 move_pool.unlink(cr, uid, [voucher.move_id.id])
237         
238         res = {
239             'state':'cancel', 
240             'move_id':False,
241             'move_ids':[(6, 0,[])]
242         }
243         self.write(cr, uid, ids, res)
244         return True
245
246     def unlink(self, cr, uid, ids, context=None):
247         vouchers = self.read(cr, uid, ids, ['state'])
248         unlink_ids = []
249         for t in vouchers:
250             if t['state'] in ('draft', 'cancel'):
251                 unlink_ids.append(t['id'])
252             else:
253                 raise osv.except_osv('Invalid action !', 'Cannot delete Voucher(s) which are already opened or paid !')
254         return super(account_voucher, self).unlink(cr, uid, unlink_ids, context=context)
255
256     def action_move_line_create(self, cr, uid, ids, *args):
257
258         journal_pool = self.pool.get('account.journal')
259         sequence_pool = self.pool.get('ir.sequence')
260         move_pool = self.pool.get('account.move')
261         move_line_pool = self.pool.get('account.move.line')
262         analytic_pool = self.pool.get('account.analytic.line')
263         currency_pool = self.pool.get('res.currency')
264         invoice_pool = self.pool.get('account.invoice')
265         
266         for inv in self.browse(cr, uid, ids):
267             
268             if inv.move_id:
269                 continue
270
271             journal = journal_pool.browse(cr, uid, inv.journal_id.id)
272             if inv.type in ('journal_pur_voucher', 'journal_sale_vou'):
273                 if journal.invoice_sequence_id:
274                     name = sequence_pool.get_id(cr, uid, journal.invoice_sequence_id.id)
275                 else:
276                     raise osv.except_osv(_('Error !'), _('Please define invoice sequence on %s journal !' % (journal.name)))
277             else:
278                 if journal.sequence_id:
279                     name = sequence_pool.get_id(cr, uid, journal.sequence_id.id)
280                 else:
281                     raise osv.except_osv(_('Error !'), _('Please define sequence on journal !'))
282             
283             ref = False
284             if inv.type in ('journal_pur_voucher', 'bank_rec_voucher', 'rec_voucher'):
285                 ref = inv.reference
286             else:
287                 ref = invoice_pool._convert_ref(cr, uid, name)
288             
289             company_currency = inv.company_id.currency_id.id
290             diff_currency_p = inv.currency_id.id <> company_currency
291             
292             move = {
293                 'name':name,
294                 'journal_id':journal.id,
295                 'type':inv.type,
296                 'narration':inv.narration and inv.narration or inv.name,
297                 'date':inv.date,
298                 'ref':ref
299             }
300             
301             if inv.period_id:
302                 move.update({
303                     'period_id': inv.period_id.id
304                 })
305             
306             move_id = move_pool.create(cr, uid, move)
307             
308             #create the first line manually
309             move_line = {
310                 'name': inv.name,
311                 'debit': False,
312                 'credit':False,
313                 'account_id': inv.account_id.id or False,
314                 'move_id': move_id ,
315                 'journal_id': inv.journal_id.id,
316                 'period_id': inv.period_id.id,
317                 'partner_id': False,
318                 'ref':ref,
319                 'date': inv.date
320             }
321             if diff_currency_p:
322                 amount_currency = currency_pool.compute(cr, uid, inv.currency_id.id, company_currency, inv.amount)
323                 inv.amount = amount_currency
324                 move_line.update({
325                     'amount_currency':amount_currency,
326                     'currency_id':inv.currency_id.id
327                 })
328             
329             if inv.type in ('rec_voucher', 'bank_rec_voucher', 'journal_pur_voucher', 'journal_voucher'):
330                 move_line['debit'] = inv.amount
331             else:
332                 move_line['credit'] = inv.amount
333             
334             line_ids = []
335             line_ids += [move_line_pool.create(cr, uid, move_line)]
336             for line in inv.payment_ids:
337                 amount=0.0
338                 
339                 if inv.type in ('bank_pay_voucher', 'pay_voucher', 'journal_voucher'):
340                     ref = line.ref
341                     
342                 move_line = {
343                      'name':line.name,
344                      'debit':False,
345                      'credit':False,
346                      'account_id':line.account_id.id or False,
347                      'move_id':move_id ,
348                      'journal_id':inv.journal_id.id,
349                      'period_id':inv.period_id.id,
350                      'partner_id':line.partner_id.id or False,
351                      'ref':ref,
352                      'date':inv.date,
353                      'analytic_account_id':False
354                 }
355                 
356                 if diff_currency_p:
357                     amount_currency = currency_pool.compute(cr, uid, inv.currency_id.id, company_currency, line.amount)
358                     line.amount = amount_currency
359                     move_line.update({
360                         'amount_currency':amount_currency,
361                         'currency_id':inv.currency_id.id
362                     })
363                 
364                 if line.account_analytic_id:
365                     move_line.update({
366                         'analytic_account_id':line.account_analytic_id.id
367                     })
368                 
369                 if line.type == 'dr':
370                     move_line.update({
371                         'debit': line.amount or False
372                     })
373                     amount = line.amount
374                     
375                 elif line.type == 'cr':
376                     move_line.update({
377                         'credit': line.amount or False
378                     })
379                     amount = line.amount * (-1)
380
381                 move_line_id = move_line_pool.create(cr, uid, move_line)
382                 line_ids += [move_line_id]
383             
384             rec = {
385                 'move_id': move_id,
386                 'move_ids':[(6, 0,line_ids)]
387             }
388             
389             message = _('Voucher ') + " '" + inv.name + "' "+ _("is confirmed")
390             self.log(cr, uid, inv.id, message)
391             
392             self.write(cr, uid, [inv.id], rec)
393             
394         return True
395
396     def _convert_ref(self, cr, uid, ref):
397         return (ref or '').replace('/','')
398
399     def name_get(self, cr, uid, ids, context={}):
400         if not len(ids):
401             return []
402         types = {
403             'pay_voucher': 'CPV: ',
404             'rec_voucher': 'CRV: ',
405             'cont_voucher': 'CV: ',
406             'bank_pay_voucher': 'BPV: ',
407             'bank_rec_voucher': 'BRV: ',
408             'journal_sale_vou': 'JSV: ',
409             'journal_pur_voucher': 'JPV: ',
410             'journal_voucher':'JV'
411         }
412         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')]
413
414     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
415         if not args:
416             args=[]
417         if not context:
418             context={}
419         ids = []
420         if name:
421             ids = self.search(cr, user, [('number','=',name)]+args, limit=limit, context=context)
422         if not ids:
423             ids = self.search(cr, user, [('name',operator,name)]+args, limit=limit, context=context)
424         return self.name_get(cr, user, ids, context)
425
426     def copy(self, cr, uid, id, default=None, context=None):
427         if default is None:
428             default = {}
429         default = default.copy()
430         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_ids':False, 'payment_ids':False})
431         if 'date' not in default:
432             default['date'] = time.strftime('%Y-%m-%d')
433         return super(account_voucher, self).copy(cr, uid, id, default, context)
434
435 account_voucher()
436
437 class account_voucher_line(osv.osv):
438     _name = 'account.voucher.line'
439     _description = 'Voucher Line'
440     _columns = {
441         'voucher_id':fields.many2one('account.voucher', 'Voucher'),
442         'name':fields.char('Description', size=256, required=True),
443         'account_id':fields.many2one('account.account','Account', required=True, domain=[('type','<>','view')]),
444         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True),
445         'amount':fields.float('Amount'),
446         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Type'),
447         'ref':fields.char('Reference', size=32),
448         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account')
449     }
450     _defaults = {
451         'type': lambda *a: 'cr'
452     }
453
454     def onchange_partner(self, cr, uid, ids, partner_id, ttype ,type1, currency):
455         currency_pool = self.pool.get('res.currency')
456         company = self.pool.get('res.users').browse(cr, uid, uid).company_id
457         
458         vals = {
459             'account_id': False, 
460             'type': False ,
461             'amount': False
462         }
463         
464         if not partner_id:
465             return {
466                 'value' : vals
467             }
468         
469         partner_pool = self.pool.get('res.partner')
470         account_id = False
471
472         partner = partner_pool.browse(cr, uid, partner_id)
473         balance = 0.0
474         
475         if type1 in ('rec_voucher', 'bank_rec_voucher', 'journal_voucher'):
476             account_id = partner.property_account_receivable.id
477             balance = partner.credit
478             ttype = 'cr'
479             
480         elif type1 in ('pay_voucher', 'bank_pay_voucher', 'journal_voucher') :
481             account_id = partner.property_account_payable.id
482             balance = partner.debit
483             ttype = 'dr'
484             
485         elif type1 in ('journal_sale_vou') :
486             account_id = partner.property_account_receivable.id
487             ttype = 'dr'
488             
489         elif type1 in ('journal_pur_voucher') :
490             account_id = partner.property_account_payable.id
491             ttype = 'cr'
492         
493         if company.currency_id != currency:
494             balance = currency_pool.compute(cr, uid, company.currency_id.id, currency, balance)
495         
496         vals.update({
497             'account_id': account_id, 
498             'type': ttype, 
499             'amount':balance
500         })
501         
502         return {
503             'value' : vals
504         }
505     
506 account_voucher_line()