[REF]
[odoo/odoo.git] / addons / account / account_cash_statement.py
1 # encoding: utf-8
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
6 #    $Id$
7 #
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.
12 #
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.
17 #
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/>.
20 #
21 ##############################################################################
22
23 from osv import osv, fields
24 import time
25 from mx import DateTime
26 from decimal import Decimal
27 from tools.translate import _
28 import decimal_precision as dp
29
30 class account_cashbox_line(osv.osv):
31
32     """ Cash Box Details """
33
34     _name = 'account.cashbox.line'
35     _description = 'CashBox Line'
36
37     def _sub_total(self, cr, uid, ids, name, arg, context=None):
38
39         """ Calculates Sub total
40         @param name: Names of fields.
41         @param arg: User defined arguments
42         @return: Dictionary of values.
43         """
44         res = {}
45         for obj in self.browse(cr, uid, ids):
46             res[obj.id] = obj.pieces * obj.number
47         return res
48
49     def on_change_sub(self, cr, uid, ids, pieces, number,*a):
50
51         """ Calculates Sub total on change of number
52         @param pieces: Names of fields.
53         @param number:
54         """
55         sub=pieces*number
56         return {'value':{'subtotal': sub or 0.0}}
57
58     _columns = {
59         'pieces': fields.float('Values', digits_compute=dp.get_precision('Account')),
60         'number': fields.integer('Number'),
61         'subtotal': fields.function(_sub_total, method=True, string='Sub Total', type='float', digits_compute=dp.get_precision('Account')),
62         'starting_id': fields.many2one('account.bank.statement',ondelete='cascade'),
63         'ending_id': fields.many2one('account.bank.statement',ondelete='cascade'),
64      }
65 account_cashbox_line()
66
67 class account_cash_statement(osv.osv):
68
69     _inherit = 'account.bank.statement'
70
71     def _get_starting_balance(self, cr, uid, ids, context=None):
72
73         """ Find starting balance
74         @param name: Names of fields.
75         @param arg: User defined arguments
76         @return: Dictionary of values.
77         """
78         res ={}
79         for statement in self.browse(cr, uid, ids):
80             amount_total=0.0
81
82             if statement.journal_id.type not in('cash'):
83                 continue
84
85             for line in statement.starting_details_ids:
86                 amount_total+= line.pieces * line.number
87             res[statement.id] = {
88                 'balance_start':amount_total
89             }
90         return res
91
92     def _balance_end_cash(self, cr, uid, ids, name, arg, context=None):
93         """ Find ending balance  "
94         @param name: Names of fields.
95         @param arg: User defined arguments
96         @return: Dictionary of values.
97         """
98         res ={}
99         for statement in self.browse(cr, uid, ids):
100             amount_total=0.0
101             for line in statement.ending_details_ids:
102                 amount_total+= line.pieces * line.number
103             res[statement.id]=amount_total
104         return res
105
106     def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None):
107
108         """ Find encoding total of statements "
109         @param name: Names of fields.
110         @param arg: User defined arguments
111         @return: Dictionary of values.
112         """
113         res2={}
114         for statement in self.browse(cr, uid, ids):
115             encoding_total=0.0
116             for line in statement.line_ids:
117                encoding_total+= line.amount
118             res2[statement.id]=encoding_total
119         return res2
120
121 #    def _default_journal_id(self, cr, uid, context={}):
122
123 #        """ To get default journal for the object"
124 #        @param name: Names of fields.
125 #        @return: journal
126 #        """
127 #        company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
128 #        journal = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', company_id)])
129 #        if journal:
130 #            return journal[0]
131 #        else:
132 #            return False
133
134     def _end_balance(self, cursor, user, ids, name, attr, context=None):
135         res_currency_obj = self.pool.get('res.currency')
136         res_users_obj = self.pool.get('res.users')
137
138         res = {}
139
140         company_currency_id = res_users_obj.browse(cursor, user, user,
141                 context=context).company_id.currency_id.id
142
143         statements = self.browse(cursor, user, ids, context=context)
144         for statement in statements:
145             res[statement.id] = statement.balance_start
146             currency_id = statement.currency.id
147             for line in statement.move_line_ids:
148                 if line.debit > 0:
149                     if line.account_id.id == \
150                             statement.journal_id.default_debit_account_id.id:
151                         res[statement.id] += res_currency_obj.compute(cursor,
152                                 user, company_currency_id, currency_id,
153                                 line.debit, context=context)
154                 else:
155                     if line.account_id.id == \
156                             statement.journal_id.default_credit_account_id.id:
157                         res[statement.id] -= res_currency_obj.compute(cursor,
158                                 user, company_currency_id, currency_id,
159                                 line.credit, context=context)
160             if statement.state in ('draft', 'open'):
161                 for line in statement.line_ids:
162                     res[statement.id] += line.amount
163         for r in res:
164             res[r] = round(res[r], 2)
165         return res
166
167     def _get_company(self, cr, uid, ids, context={}):
168         user_pool = self.pool.get('res.users')
169         company_pool = self.pool.get('res.company')
170         user = user_pool.browse(cr, uid, uid, uid)
171         company_id = user.company_id and user.company_id.id
172         if not company_id:
173             company_id = company_pool.search(cr, uid, [])[0]
174
175         return company_id
176
177     def _get_cash_open_box_lines(self, cr, uid, ids, context={}):
178         res = []
179         curr = [1, 2, 5, 10, 20, 50, 100, 500]
180         for rs in curr:
181             dct = {
182                 'pieces':rs,
183                 'number':0
184             }
185             res.append(dct)
186         return res
187
188     def _get_cash_close_box_lines(self, cr, uid, ids, context={}):
189         res = []
190         curr = [1, 2, 5, 10, 20, 50, 100, 500]
191         for rs in curr:
192             dct = {
193                 'pieces':rs,
194                 'number':0
195             }
196             res.append((0, 0, dct))
197         return res
198
199     _columns = {
200         'company_id':fields.many2one('res.company', 'Company', required=False),
201         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'confirm': [('readonly', True)]}, domain=[('type', '=', 'cash')]),
202         'balance_end_real': fields.float('Closing Balance', digits_compute=dp.get_precision('Account'), states={'confirm':[('readonly', True)]}, help="closing balance entered by the cashbox verifier"),
203         'state': fields.selection(
204             [('draft', 'Draft'),
205             ('confirm', 'Confirmed'),
206             ('open','Open')], 'State', required=True, states={'confirm': [('readonly', True)]}, readonly="1"),
207         'total_entry_encoding':fields.function(_get_sum_entry_encoding, method=True, store=True, string="Cash Transaction", help="Total cash transactions"),
208         'closing_date':fields.datetime("Closed On"),
209         'balance_end': fields.function(_end_balance, method=True, store=True, string='Balance', help="Closing balance based on Starting Balance and Cash Transactions"),
210         'balance_end_cash': fields.function(_balance_end_cash, method=True, store=True, string='Balance', help="Closing balance based on cashBox"),
211         'starting_details_ids': fields.one2many('account.cashbox.line', 'starting_id', string='Opening Cashbox'),
212         'ending_details_ids': fields.one2many('account.cashbox.line', 'ending_id', string='Closing Cashbox'),
213         'name': fields.char('Name', size=64, required=True, readonly=True),
214         'user_id':fields.many2one('res.users', 'Responsible', required=False),
215     }
216     _defaults = {
217         'state': lambda *a: 'draft',
218         'date': lambda *a:time.strftime("%Y-%m-%d %H:%M:%S"),
219         'user_id': lambda self, cr, uid, context=None: uid,
220         'company_id': _get_company,
221         'starting_details_ids':_get_cash_open_box_lines,
222         'ending_details_ids':_get_cash_open_box_lines
223      }
224
225     def create(self, cr, uid, vals, context=None):
226         company_id = vals and vals.get('company_id',False)
227         if company_id:
228             sql = [
229                 ('company_id', '=', vals['company_id']),
230                 ('journal_id', '=', vals['journal_id']),
231                 ('state', '=', 'open')
232             ]
233             open_jrnl = self.search(cr, uid, sql)
234             if open_jrnl:
235                 raise osv.except_osv('Error', _('You can not have two open register for the same journal'))
236
237         if self.pool.get('account.journal').browse(cr, uid, vals['journal_id']).type == 'cash':
238             lines = end_lines = self._get_cash_close_box_lines(cr, uid, [], context)
239             vals.update({
240                 'ending_details_ids':lines
241             })
242         else:
243             vals.update({
244                 'ending_details_ids':False,
245                 'starting_details_ids':False
246             })
247         res_id = super(account_cash_statement, self).create(cr, uid, vals, context=context)
248         self.write(cr, uid, [res_id], {})
249         return res_id
250
251     def write(self, cr, uid, ids, vals, context=None):
252         """
253         Update redord(s) comes in {ids}, with new value comes as {vals}
254         return True on success, False otherwise
255
256         @param cr: cursor to database
257         @param user: id of current user
258         @param ids: list of record ids to be update
259         @param vals: dict of new values to be set
260         @param context: context arguments, like lang, time zone
261
262         @return: True on success, False otherwise
263         """
264
265         super(account_cash_statement, self).write(cr, uid, ids, vals)
266         res = self._get_starting_balance(cr, uid, ids)
267         for rs in res:
268             super(account_cash_statement, self).write(cr, uid, rs, res.get(rs))
269         return True
270
271     def onchange_journal_id(self, cr, uid, statement_id, journal_id, context={}):
272         """ Changes balance start and starting details if journal_id changes"
273         @param statement_id: Changed statement_id
274         @param journal_id: Changed journal_id
275         @return:  Dictionary of changed values
276         """
277
278         cash_pool = self.pool.get('account.cashbox.line')
279         statement_pool = self.pool.get('account.bank.statement')
280
281         res = {}
282         balance_start = 0.0
283
284         if not journal_id:
285             res.update({
286                 'balance_start': balance_start
287             })
288             return res
289
290
291         res = super(account_cash_statement, self).onchange_journal_id(cr, uid, statement_id, journal_id, context)
292         return res
293
294     def _equal_balance(self, cr, uid, ids, statement, context={}):
295         if statement.balance_end != statement.balance_end_cash:
296             return False
297         else:
298             return True
299
300     def _user_allow(self, cr, uid, ids, statement, context={}):
301         return True
302
303     def button_open(self, cr, uid, ids, context=None):
304
305         """ Changes statement state to Running.
306         @return: True
307         """
308         cash_pool = self.pool.get('account.cashbox.line')
309         statement_pool = self.pool.get('account.bank.statement')
310
311         statement = statement_pool.browse(cr, uid, ids[0])
312         vals = {}
313
314         if not self._user_allow(cr, uid, ids, statement, context={}):
315             raise osv.except_osv(_('Error !'), _('User %s does not have rights to access %s journal !' % (statement.user_id.name, statement.journal_id.name)))
316
317         if statement.name and statement.name == '/':
318             number = self.pool.get('ir.sequence').get(cr, uid, 'account.cash.statement')
319             vals.update({
320                 'name':number
321             })
322
323 #        if len(statement.starting_details_ids) > 0:
324 #            sid = []
325 #            for line in statement.starting_details_ids:
326 #                sid.append(line.id)
327 #            cash_pool.unlink(cr, uid, sid)
328 #
329 #        cr.execute("select id from account_bank_statement where journal_id=%s and user_id=%s and state=%s order by id desc limit 1", (statement.journal_id.id, uid, 'confirm'))
330 #        rs = cr.fetchone()
331 #        rs = rs and rs[0] or None
332 #        if rs:
333 #            statement = statement_pool.browse(cr, uid, rs)
334 #            balance_start = statement.balance_end_real or 0.0
335 #            open_ids = cash_pool.search(cr, uid, [('ending_id','=',statement.id)])
336 #            for sid in open_ids:
337 #                default = {
338 #                    'ending_id': False,
339 #                    'starting_id':ids[0]
340 #                }
341 #                cash_pool.copy(cr, uid, sid, default)
342 #
343         vals.update({
344             'date':time.strftime("%Y-%m-%d %H:%M:%S"),
345             'state':'open',
346
347         })
348
349         self.write(cr, uid, ids, vals)
350         return True
351
352     def button_confirm_cash(self, cr, uid, ids, context={}):
353
354         """ Check the starting and ending detail of  statement
355         @return: True
356         """
357         done = []
358         res_currency_obj = self.pool.get('res.currency')
359         res_users_obj = self.pool.get('res.users')
360         account_move_obj = self.pool.get('account.move')
361         account_move_line_obj = self.pool.get('account.move.line')
362         account_bank_statement_line_obj = self.pool.get('account.bank.statement.line')
363
364         company_currency_id = res_users_obj.browse(cr, uid, uid, context=context).company_id.currency_id.id
365
366         for st in self.browse(cr, uid, ids, context):
367
368             self.write(cr, uid, [st.id], {'balance_end_real':st.balance_end})
369             st.balance_end_real = st.balance_end
370
371             if not st.state == 'open':
372                 continue
373
374             if not self._equal_balance(cr, uid, ids, st, context):
375                 raise osv.except_osv(_('Error !'), _('CashBox Balance is not matching with Calculated Balance !'))
376
377 #            if not (abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001):
378 #                raise osv.except_osv(_('Error !'),
379 #                        _('The statement balance is incorrect !\n') +
380 #                        _('The expected balance (%.2f) is different than the computed one. (%.2f)') % (st.balance_end_real, st.balance_end))
381             if (not st.journal_id.default_credit_account_id) \
382                     or (not st.journal_id.default_debit_account_id):
383                 raise osv.except_osv(_('Configuration Error !'),
384                         _('Please verify that an account is defined in the journal.'))
385
386             for line in st.move_line_ids:
387                 if line.state <> 'valid':
388                     raise osv.except_osv(_('Error !'),
389                             _('The account entries lines are not in valid state.'))
390             # for bank.statement.lines
391             # In line we get reconcile_id on bank.ste.rec.
392             # in bank stat.rec we get line_new_ids on bank.stat.rec.line
393             for move in st.line_ids:
394                 context.update({'date':move.date})
395                 move_id = account_move_obj.create(cr, uid, {
396                     'journal_id': st.journal_id.id,
397                     'period_id': st.period_id.id,
398                     'date': move.date,
399                 }, context=context)
400                 account_bank_statement_line_obj.write(cr, uid, [move.id], {
401                     'move_ids': [(4,move_id, False)]
402                 })
403                 if not move.amount:
404                     continue
405
406                 torec = []
407                 if move.amount >= 0:
408                     account_id = st.journal_id.default_credit_account_id.id
409                 else:
410                     account_id = st.journal_id.default_debit_account_id.id
411                 acc_cur = ((move.amount<=0) and st.journal_id.default_debit_account_id) or move.account_id
412                 amount = res_currency_obj.compute(cr, uid, st.currency.id,
413                         company_currency_id, move.amount, context=context,
414                         account=acc_cur)
415                 if move.reconcile_id and move.reconcile_id.line_new_ids:
416                     for newline in move.reconcile_id.line_new_ids:
417                         amount += newline.amount
418
419                 val = {
420                     'name': move.name,
421                     'date': move.date,
422                     'ref': move.ref,
423                     'move_id': move_id,
424                     'partner_id': ((move.partner_id) and move.partner_id.id) or False,
425                     'account_id': (move.account_id) and move.account_id.id,
426                     'credit': ((amount>0) and amount) or 0.0,
427                     'debit': ((amount<0) and -amount) or 0.0,
428                     'statement_id': st.id,
429                     'journal_id': st.journal_id.id,
430                     'period_id': st.period_id.id,
431                     'currency_id': st.currency.id,
432                 }
433
434                 amount = res_currency_obj.compute(cr, uid, st.currency.id,
435                         company_currency_id, move.amount, context=context,
436                         account=acc_cur)
437                 if st.currency.id <> company_currency_id:
438                     amount_cur = res_currency_obj.compute(cr, uid, company_currency_id,
439                                 st.currency.id, amount, context=context,
440                                 account=acc_cur)
441                     val['amount_currency'] = -amount_cur
442
443                 if move.account_id and move.account_id.currency_id and move.account_id.currency_id.id <> company_currency_id:
444                     val['currency_id'] = move.account_id.currency_id.id
445                     if company_currency_id==move.account_id.currency_id.id:
446                         amount_cur = move.amount
447                     else:
448                         amount_cur = res_currency_obj.compute(cr, uid, company_currency_id,
449                                 move.account_id.currency_id.id, amount, context=context,
450                                 account=acc_cur)
451                     val['amount_currency'] = amount_cur
452
453                 torec.append(account_move_line_obj.create(cr, uid, val , context=context))
454
455                 if move.reconcile_id and move.reconcile_id.line_new_ids:
456                     for newline in move.reconcile_id.line_new_ids:
457                         account_move_line_obj.create(cr, uid, {
458                             'name': newline.name or move.name,
459                             'date': move.date,
460                             'ref': move.ref,
461                             'move_id': move_id,
462                             'partner_id': ((move.partner_id) and move.partner_id.id) or False,
463                             'account_id': (newline.account_id) and newline.account_id.id,
464                             'debit': newline.amount>0 and newline.amount or 0.0,
465                             'credit': newline.amount<0 and -newline.amount or 0.0,
466                             'statement_id': st.id,
467                             'journal_id': st.journal_id.id,
468                             'period_id': st.period_id.id,
469                             'analytic_account_id':newline.analytic_id and newline.analytic_id.id or False,
470
471                         }, context=context)
472
473                 # Fill the secondary amount/currency
474                 # if currency is not the same than the company
475                 amount_currency = False
476                 currency_id = False
477                 if st.currency.id <> company_currency_id:
478                     amount_currency = move.amount
479                     currency_id = st.currency.id
480                 account_move_line_obj.create(cr, uid, {
481                     'name': move.name,
482                     'date': move.date,
483                     'ref': move.ref,
484                     'move_id': move_id,
485                     'partner_id': ((move.partner_id) and move.partner_id.id) or False,
486                     'account_id': account_id,
487                     'credit': ((amount < 0) and -amount) or 0.0,
488                     'debit': ((amount > 0) and amount) or 0.0,
489                     'statement_id': st.id,
490                     'journal_id': st.journal_id.id,
491                     'period_id': st.period_id.id,
492                     'amount_currency': amount_currency,
493                     'currency_id': currency_id,
494                     }, context=context)
495
496                 for line in account_move_line_obj.browse(cr, uid, [x.id for x in
497                         account_move_obj.browse(cr, uid, move_id,
498                             context=context).line_id],
499                         context=context):
500                     if line.state <> 'valid':
501                         raise osv.except_osv(_('Error !'),
502                                 _('Ledger Posting line "%s" is not valid') % line.name)
503
504                 if move.reconcile_id and move.reconcile_id.line_ids:
505                     torec += map(lambda x: x.id, move.reconcile_id.line_ids)
506
507                     if abs(move.reconcile_amount-move.amount)<0.0001:
508
509                         writeoff_acc_id = False
510                         #There should only be one write-off account!
511                         for entry in move.reconcile_id.line_new_ids:
512                             writeoff_acc_id = entry.account_id.id
513                             break
514
515                         account_move_line_obj.reconcile(cr, uid, torec, 'statement', writeoff_acc_id=writeoff_acc_id, writeoff_period_id=st.period_id.id, writeoff_journal_id=st.journal_id.id, context=context)
516                     else:
517                         account_move_line_obj.reconcile_partial(cr, uid, torec, 'statement', context)
518
519                 if st.journal_id.entry_posted:
520                     account_move_obj.write(cr, uid, [move_id], {'state':'posted'})
521             done.append(st.id)
522
523         vals = {
524             'state':'confirm',
525             'closing_date':time.strftime("%Y-%m-%d %H:%M:%S")
526         }
527         self.write(cr, uid, done, vals, context=context)
528         return True
529
530     def button_cancel(self, cr, uid, ids, context={}):
531         done = []
532         for st in self.browse(cr, uid, ids, context):
533             ids = []
534             for line in st.line_ids:
535                 ids += [x.id for x in line.move_ids]
536             self.pool.get('account.move').unlink(cr, uid, ids, context)
537             done.append(st.id)
538         self.write(cr, uid, done, {'state':'draft'}, context=context)
539         return True
540
541 account_cash_statement()
542