bug-fix for the account voucher
[odoo/odoo.git] / addons / account_voucher / voucher.py
1 import time
2 import netsvc
3 from osv import fields, osv
4 import ir
5 import pooler
6 import mx.DateTime
7 from mx.DateTime import RelativeDateTime
8
9 from tools import config
10
11 class account_voucher(osv.osv):
12     def _get_period(self, cr, uid, context):
13         periods = self.pool.get('account.period').find(cr, uid)
14         if periods:
15             return periods[0]
16         else:
17             return False
18         
19     def _get_type(self, cr, uid, context={}):
20         type = context.get('type', 'rec_voucher')
21         return type
22     
23     def _get_reference_type(self, cursor, user, context=None):
24         return [('none', 'Free Reference')]
25     
26     def _get_journal(self, cr, uid, context):
27         type_inv = context #.get('type', 'rec_voucher')
28         type2journal = {'rec_voucher': 'cash', 'bank_rec_voucher': 'cash','pay_voucher': 'cash','bank_pay_voucher': 'cash', 'cont_voucher': 'cash','journal_sale_voucher': 'sale','journal_pur_voucher': 'purchase' }
29         journal_obj = self.pool.get('account.journal')
30         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'cash'))], limit=1)
31         if res:
32             return res[0]
33         else:
34             return False
35         
36     def _get_currency(self, cr, uid, context):
37         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
38         if user.company_id:
39             return user.company_id.currency_id.id
40         else:
41             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
42         
43     _name = 'account.voucher'
44     _description = 'Accounting Voucher'
45     _order = "number"
46     _columns = {
47         'name':fields.char('Name', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}),
48         'type': fields.selection([
49             ('pay_voucher','Cash Payment Voucher'),
50             ('bank_pay_voucher','Bank Payment Voucher'),
51             ('rec_voucher','Cash Receipt Voucher'),
52             ('bank_rec_voucher','Bank Receipt Voucher'),
53             ('cont_voucher','Contra Voucher'),
54             ('journal_sale_vou','Journal Sale Voucher'),
55             ('journal_pur_voucher','Journal Purchase Voucher'),
56             ],'Type', readonly=True, select=True),
57         'date':fields.date('Date', readonly=True, states={'draft':[('readonly',False)]}),
58         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
59         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
60         'payment_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=False, states={'proforma':[('readonly',True)]}),
61         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
62         'narration':fields.text('Narration', readonly=True, states={'draft':[('readonly',False)]}),
63         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
64         'company_id': fields.many2one('res.company', 'Company', required=True),
65         'state':fields.selection(
66                     [('draft','Draft'),
67                      ('proforma','Pro-forma'),
68                      ('posted','Posted'),
69                      ('cancel','Cancel')
70                     ], 'State', 
71                     readonly=True),
72         'amount':fields.float('Amount'),
73         'number':fields.char('Number', size=32, readonly=True),
74         'reference': fields.char('Voucher Reference', size=64),
75         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
76             required=True),
77         'move_id':fields.many2one('account.move', 'Account Entry'),
78         'move_ids':fields.many2many('account.move.line', 'voucher_id', 'account_id', 'rel_account_move', 'Real Entry'),
79     }
80     
81 #    def get_bank(self, cr, uid, context={}):
82 #        type = context.get('type', 'bank_payment')
83 #        journal = self.pool.get('account.journal')
84 #        if type == 'bank_payment':
85 #            id = journal.search(cr, uid, [('name','ilike','Bank')])
86 #            return id
87 #        elif type == 'cash_payment':
88 #            id = journal.search(cr, uid, [('name','ilike','Cash')])
89 #            return id
90 #
91 #        return 3
92     
93     _defaults = {
94         #'journal_id':get_bank,
95         'state': lambda *a: 'draft',
96         'date' : lambda *a: time.strftime('%Y-%m-%d'),
97         'period_id': _get_period,
98         'type': _get_type,
99         'reference_type': lambda *a: 'none',
100         'journal_id':_get_journal,
101         'company_id': lambda self, cr, uid, context: \
102                 self.pool.get('res.users').browse(cr, uid, uid,
103                     context=context).company_id.id,
104         'currency_id': _get_currency,
105
106
107     }
108     
109     def _get_analityc_lines(self, cr, uid, id):
110         inv = self.browse(cr, uid, [id])[0]
111         cur_obj = self.pool.get('res.currency')
112         
113     def onchange_account(self, cr, uid, ids, account_id):
114         if not account_id:
115             return {'value':{'amount':False}}
116         account = self.pool.get('account.account').browse(cr,uid,account_id)
117         balance=account.balance
118         return {'value':{'amount':balance}}
119
120     
121     def onchange_journal(self, cr, uid, ids, journal_id,type):
122         if not journal_id:
123             return {'value':{'account_id':False}}
124         journal = self.pool.get('account.journal')
125         if journal_id and (type in ('rec_voucher','bank_rec_voucher','journal_pur_voucher')):
126             account_id = journal.browse(cr, uid, journal_id).default_debit_account_id
127             return {'value':{'account_id':account_id.id}}
128         elif journal_id and (type in ('pay_voucher','bank_pay_voucher','journal_sale_vou')) :
129                 account_id = journal.browse(cr, uid, journal_id).default_credit_account_id
130                 return {'value':{'account_id':account_id.id}}
131         else:
132             account_id = journal.browse(cr, uid, journal_id).default_credit_account_id
133             return {'value':{'account_id':account_id.id}}
134     
135     
136    
137         
138     def open_voucher(self, cr, uid, ids, context={}):
139         obj=self.pool.get('account.voucher').browse(cr,uid,ids)
140         total=0
141         for i in obj[0].payment_ids:
142             total+=i.amount
143         self.write(cr,uid,ids,{'amount':total})
144         self.write(cr, uid, ids, {'state':'proforma'})
145         return True
146     
147     def proforma_voucher(self, cr, uid, ids, context={}):
148         self.action_move_line_create(cr, uid, ids)
149         self.action_number(cr, uid, ids)
150         self.write(cr, uid, ids, {'state':'posted'})
151         return True
152     
153     def cancel_voucher(self,cr,uid,ids,context={}):
154         self.action_cancel(cr, uid, ids)
155         self.write(cr, uid, ids, {'state':'cancel'})
156         return True
157         
158     def action_cancel_draft(self, cr, uid, ids, *args):
159         self.write(cr, uid, ids, {'state':'draft'})
160         return True
161
162     
163     def unlink(self, cr, uid, ids):
164         vouchers = self.read(cr, uid, ids, ['state'])
165         unlink_ids = []
166         for t in vouchers:
167             if t['state'] in ('draft', 'cancel'):
168                 unlink_ids.append(t['id'])
169             else:
170                 raise osv.except_osv('Invalid action !', 'Cannot delete invoice(s) which are already opened or paid !')
171         osv.osv.unlink(self, cr, uid, unlink_ids)
172         return True
173     
174
175     def _get_analityc_lines(self, cr, uid, id):
176         inv = self.browse(cr, uid, [id])[0]
177         cur_obj = self.pool.get('res.currency')
178
179         company_currency = inv.company_id.currency_id.id
180         if inv.type in ('rec_voucher'):
181             sign = 1
182         else:
183             sign = -1
184
185         iml = self.pool.get('account.voucher.line').move_line_get(cr, uid, inv.id)
186         
187         for il in iml:
188             if il['account_analytic_id']:
189                 if inv.type in ('pay_voucher', 'rec_voucher','cont_voucher','bank_pay_voucher','bank_rec_voucher','journal_sale_vou','journal_pur_voucher'):
190                     ref = inv.reference
191                 else:
192                     ref = self._convert_ref(cr, uid, inv.number)
193                 
194                 il['analytic_lines'] = [(0,0, {
195                     'name': il['name'],
196                     'date': inv['date'],
197                     'account_id': il['account_analytic_id'],
198                     'amount': inv['amount'] * sign,
199                     #'partner_id': il['partner_id'] or False,
200                     'general_account_id': il['account_id'] or False,
201                     'journal_id': self._get_journal(cr, uid, inv.type),
202                     'ref': ref,
203                 })]
204
205         return iml
206     
207     def action_move_line_create(self, cr, uid, ids, *args):
208         
209         for inv in self.browse(cr, uid, ids):
210             
211             if inv.move_id:
212                 continue
213             
214             company_currency = inv.company_id.currency_id.id
215             
216             # create the analytical lines
217             line_ids = self.read(cr, uid, [inv.id], ['payment_ids'])[0]['payment_ids']
218             ils = self.pool.get('account.voucher.line').read(cr, uid, line_ids)
219             # one move line per invoice line
220             iml = self._get_analityc_lines(cr, uid, inv.id)
221             # check if taxes are all computed
222             diff_currency_p = inv.currency_id.id <> company_currency
223             # create one move line for the total and possibly adjust the other lines amount
224             total = 0
225             if inv.type in ('pay_voucher', 'rec_voucher','cont_voucher','bank_pay_voucher','bank_rec_voucher','journal_sale_vou','journal_pur_voucher'):
226                 ref = inv.reference
227             else:
228                 ref = self._convert_ref(cr, uid, inv.number)
229             date = inv.date
230             total_currency = 0
231             for i in iml:
232                 if inv.currency_id.id != company_currency:
233                     i['currency_id'] = inv.currency_id.id
234                     i['amount_currency'] = i['amount']
235
236                 else:
237                     i['amount_currency'] = False
238                     i['currency_id'] = False
239                 i['ref'] = ref
240                 if inv.type in ('rec_voucher','bank_rec_voucher','journal_pur_voucher'):
241                     total += i['amount']
242                     total_currency += i['amount_currency'] or i['amount']
243                     i['amount'] = - i['amount']
244                 else:
245                     total -= i['amount']
246                     total_currency -= i['amount_currency'] or i['amount']
247             acc_id = inv.account_id.id
248
249             name = inv['name'] or '/'
250             totlines = False
251
252             iml.append({
253                 'type': 'dest',
254                 'name': name,
255                 'amount': total,
256                 'account_id': acc_id,
257                 'amount_currency': diff_currency_p \
258                         and total_currency or False,
259                 'currency_id': diff_currency_p \
260                         and inv.currency_id.id or False,
261                 'ref': ref
262             })
263
264             date = inv.date
265             inv.amount=total
266
267             line = map(lambda x:(0, 0, self.line_get_convert(cr, uid, x,date, context={})), iml)
268
269             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
270             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
271             if journal.sequence_id:
272                 name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
273
274             move = {'name': name, 'line_id': line, 'journal_id': journal_id}
275             
276             if inv.period_id:
277                 move['period_id'] = inv.period_id.id
278                 for i in line:
279                     i[2]['period_id'] = inv.period_id.id
280             move_id = self.pool.get('account.move').create(cr, uid, move)
281             # make the invoice point to that move
282             self.write(cr, uid, [inv.id], {'move_id': move_id})
283             obj=self.pool.get('account.move').browse(cr,uid,move_id)
284             for line in obj.line_id :
285                 cr.execute('insert into voucher_id (account_id,rel_account_move) values (%d, %d)',(int(ids[0]),int(line.id)))
286
287         return True
288
289     def line_get_convert(self, cr, uid, x, date, context={}):
290         return {
291             'date':date,
292             'date_maturity': x.get('date_maturity', False),
293             'partner_id':x.get('partner_id',False),
294             'name':x['name'][:64],
295             'debit':x['amount']>0 and x['amount'],
296             'credit':x['amount']<0 and -x['amount'],
297             'account_id':x['account_id'],
298             'analytic_lines':x.get('analytic_lines', []),
299             'amount_currency':x.get('amount_currency', False),
300             'currency_id':x.get('currency_id', False),
301             'tax_code_id': x.get('tax_code_id', False),
302             'tax_amount': x.get('tax_amount', False),
303             'ref':x.get('ref',False)
304         }
305     def _convert_ref(self, cr, uid, ref):
306         return (ref or '').replace('/','')
307     
308     
309     def action_number(self, cr, uid, ids, *args):
310         cr.execute('SELECT id, type, number, move_id, reference ' \
311                 'FROM account_voucher ' \
312                 'WHERE id IN ('+','.join(map(str,ids))+')')
313         for (id, invtype, number, move_id, reference) in cr.fetchall():
314             if not number:
315                 number = self.pool.get('ir.sequence').get(cr, uid,
316                         'account.voucher.' + invtype)
317
318                 if type in ('pay_voucher', 'rec_voucher','cont_voucher','bank_pay_voucher','bank_rec_voucher','journal_sale_vou','journal_pur_voucher'):
319                     ref = reference
320                 else:
321                     ref = self._convert_ref(cr, uid, number)
322                 cr.execute('UPDATE account_voucher SET number=%s ' \
323                         'WHERE id=%d', (number, id))
324                 cr.execute('UPDATE account_move_line SET ref=%s ' \
325                         'WHERE move_id=%d AND (ref is null OR ref = \'\')',
326                         (ref, move_id))
327                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
328                         'FROM account_move_line ' \
329                         'WHERE account_move_line.move_id = %d ' \
330                             'AND account_analytic_line.move_id = account_move_line.id',
331                             (ref, move_id))
332         return True
333
334
335     
336     def name_get(self, cr, uid, ids, context={}):
337         if not len(ids):
338             return []
339         types = {
340                 'pay_voucher': 'CPV: ',
341                 'rec_voucher': 'CRV: ',
342                 'cont_voucher': 'CV: ',
343                 'bank_pay_voucher': 'BPV: ',
344                 'bank_rec_voucher': 'BRV: ',
345                 'journal_sale_vou': 'JSV: ',
346                 'journal_pur_voucher': 'JPV: ',
347                 
348                 }
349         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')]
350
351     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
352         if not args:
353             args=[]
354         if not context:
355             context={}
356         ids = []
357         if name:
358             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
359         if not ids:
360             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
361         return self.name_get(cr, user, ids, context)
362     
363     def copy(self, cr, uid, id, default=None, context=None):
364         if default is None:
365             default = {}
366         default = default.copy()
367         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_ids':False})
368         if 'date' not in default:
369             default['date'] = time.strftime('%Y-%m-%d')
370         return super(account_voucher, self).copy(cr, uid, id, default, context)
371     
372     def action_cancel(self, cr, uid, ids, *args):
373         account_move_obj = self.pool.get('account.move')
374         voucher = self.read(cr, uid, ids, ['move_id'])
375         for i in voucher:
376             if i['move_id']:
377                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
378                 # delete the move this invoice was pointing to
379                 # Note that the corresponding move_lines and move_reconciles
380                 # will be automatically deleted too
381                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
382         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
383 #        self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
384         return True
385     
386 account_voucher()
387
388 class VoucherLine(osv.osv):
389     _name = 'account.voucher.line'
390     _description = 'Voucher Line'
391     _columns = {
392         'voucher_id':fields.many2one('account.voucher', 'Voucher'),
393         'name':fields.char('Description', size=256, required=True),
394         'account_id':fields.many2one('account.account','Account', required=True),
395         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True,  required=True, ),
396         'amount':fields.float('Amount'),
397         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Type'),
398         'ref':fields.char('Ref.', size=32),
399         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account')
400     }
401     _defaults = {
402         'type': lambda *a: 'cr'
403     }
404     def move_line_get(self, cr, uid, voucher_id, context={}):
405         res = []
406
407         cur_obj = self.pool.get('res.currency')
408         inv = self.pool.get('account.voucher').browse(cr, uid, voucher_id)
409         company_currency = inv.company_id.currency_id.id
410         cur = inv.currency_id
411
412         for line in inv.payment_ids:
413             res.append(self.move_line_get_item(cr, uid, line, context))
414         return res
415     
416     def onchange_partner(self, cr, uid, ids, partner_id, type,type1):
417         if not partner_id:
418             return {'value' : {'account_id' : False, 'type' : False ,'amount':False}}
419         obj = self.pool.get('res.partner')
420         account_id = False
421         
422         if type1 in ('rec_voucher','bank_rec_voucher'):
423             account_id = obj.browse(cr, uid, partner_id).property_account_receivable
424             balance=obj.browse(cr,uid,partner_id).credit
425             type = 'cr'
426         elif type1 in ('pay_voucher','bank_pay_voucher','cont_voucher') : 
427             account_id = obj.browse(cr, uid, partner_id).property_account_payable
428             balance=obj.browse(cr,uid,partner_id).debit
429             type = 'dr'
430         elif type1 in ('journal_sale_vou') : 
431             account_id = obj.browse(cr, uid, partner_id).property_account_receivable
432             balance=obj.browse(cr,uid,partner_id).credit
433             type = 'dr'
434             
435         elif type1 in ('journal_pur_vou') : 
436             account_id = obj.browse(cr, uid, partner_id).property_account_payable
437             balance=obj.browse(cr,uid,partner_id).debit
438             type = 'cr'
439             
440         return {
441             'value' : {'account_id' : account_id.id, 'type' : type, 'amount':balance}
442         }
443         
444     def onchange_amount(self, cr, uid, ids,partner_id,amount, type,type1):
445         if not amount:
446             return {'value' : {'type' : False}}
447         obj = self.pool.get('res.partner')
448         if type1 in ('rec_voucher','bank_rec_voucher'):
449             if amount < 0 :
450                 account_id = obj.browse(cr, uid, partner_id).property_account_payable 
451                 type = 'dr'
452             else:
453                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable 
454                 type = 'cr'
455            
456         elif type1 in ('pay_voucher','bank_pay_voucher','cont_voucher') : 
457             if amount < 0 :
458                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable 
459                 type = 'cr'
460             else:
461                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
462                 type = 'dr'
463                 
464         elif type1 in ('journal_sale_vou') : 
465             if amount < 0 :
466                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
467                 type = 'cr'
468             else:
469                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable
470                 type = 'dr'
471             
472         elif type1 in ('journal_pur_vou') : 
473             if amount< 0 :
474                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable
475                 type = 'dr'
476             else:
477                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
478                 type = 'cr'
479             
480         return {
481             'value' : { 'type' : type , 'amount':amount}
482         }
483         
484         
485     def onchange_type(self, cr, uid, ids,partner_id,amount,type,type1):
486         if not partner_id:
487             return {'value' : {'type' : False}}
488         obj = self.pool.get('res.partner')
489         
490         if type1 in ('rec_voucher','bank_rec_voucher'):
491             if type == 'dr' :
492                 account_id = obj.browse(cr, uid, partner_id).property_account_payable 
493                 total=amount*(-1)
494             else:
495                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable 
496                 total=amount*1
497            
498         elif type1 in ('pay_voucher','bank_pay_voucher','cont_voucher') : 
499             if type == 'cr' :
500                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable 
501                 amount*=-1
502             else:
503                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
504                 amount*=1
505                 
506         elif type1 in ('journal_sale_vou') : 
507             if type == 'cr' :
508                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
509                 amount*=-1
510             else:
511                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable
512                 amount*=1
513             
514         elif type1 in ('journal_pur_vou') : 
515             if type == 'dr' :
516                 account_id = obj.browse(cr, uid, partner_id).property_account_receivable
517                 amount*=-1
518             else:
519                 account_id = obj.browse(cr, uid, partner_id).property_account_payable
520                 amount*=1
521             
522         return {
523             'value' : {'type' : type , 'amount':total}
524         }
525         
526
527     def move_line_get_item(self, cr, uid, line, context={}):
528         return {
529                 'type':'src',
530                 'name': line.name[:64],
531                 'amount':line.amount,
532                 'account_id':line.account_id.id,
533                 'partner_id':line.partner_id.id or False ,
534                 'account_analytic_id':line.account_analytic_id.id or False,
535             }
536 VoucherLine()
537