[MERGE]: Merge with lp:openobject-addons
[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 import time
24
25 from osv import osv, fields
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
66 account_cashbox_line()
67
68 class account_cash_statement(osv.osv):
69
70     _inherit = 'account.bank.statement'
71
72     def _get_starting_balance(self, cr, uid, ids, context=None):
73
74         """ Find starting balance
75         @param name: Names of fields.
76         @param arg: User defined arguments
77         @return: Dictionary of values.
78         """
79         res = {}
80         for statement in self.browse(cr, uid, ids):
81             amount_total = 0.0
82
83             if statement.journal_id.type not in('cash'):
84                 continue
85
86             for line in statement.starting_details_ids:
87                 amount_total+= line.pieces * line.number
88             res[statement.id] = {
89                 'balance_start':amount_total
90             }
91         return res
92
93     def _balance_end_cash(self, cr, uid, ids, name, arg, context=None):
94         """ Find ending balance  "
95         @param name: Names of fields.
96         @param arg: User defined arguments
97         @return: Dictionary of values.
98         """
99         res ={}
100         for statement in self.browse(cr, uid, ids):
101             amount_total = 0.0
102             for line in statement.ending_details_ids:
103                 amount_total += line.pieces * line.number
104             res[statement.id] = amount_total
105         return res
106
107     def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None):
108
109         """ Find encoding total of statements "
110         @param name: Names of fields.
111         @param arg: User defined arguments
112         @return: Dictionary of values.
113         """
114         res2={}
115         for statement in self.browse(cr, uid, ids):
116             encoding_total=0.0
117             for line in statement.line_ids:
118                encoding_total += line.amount
119             res2[statement.id] = encoding_total
120         return res2
121
122     def _end_balance(self, cursor, user, ids, name, attr, context=None):
123         res_currency_obj = self.pool.get('res.currency')
124         res_users_obj = self.pool.get('res.users')
125         res = {}
126
127         company_currency_id = res_users_obj.browse(cursor, user, user,
128                 context=context).company_id.currency_id.id
129
130         statements = self.browse(cursor, user, ids, context=context)
131         for statement in statements:
132             res[statement.id] = statement.balance_start
133             currency_id = statement.currency.id
134             for line in statement.move_line_ids:
135                 if line.debit > 0:
136                     if line.account_id.id == \
137                             statement.journal_id.default_debit_account_id.id:
138                         res[statement.id] += res_currency_obj.compute(cursor,
139                                 user, company_currency_id, currency_id,
140                                 line.debit, context=context)
141                 else:
142                     if line.account_id.id == \
143                             statement.journal_id.default_credit_account_id.id:
144                         res[statement.id] -= res_currency_obj.compute(cursor,
145                                 user, company_currency_id, currency_id,
146                                 line.credit, context=context)
147
148             if statement.state in ('draft', 'open'):
149                 for line in statement.line_ids:
150                     res[statement.id] += line.amount
151         for r in res:
152             res[r] = round(res[r], 2)
153         return res
154
155     def _get_company(self, cr, uid, context=None):
156         user_pool = self.pool.get('res.users')
157         company_pool = self.pool.get('res.company')
158         user = user_pool.browse(cr, uid, uid, context=context)
159         company_id = user.company_id
160         if not company_id:
161             company_id = company_pool.search(cr, uid, [])
162         return company_id and company_id[0] or False
163
164     def _get_cash_open_box_lines(self, cr, uid, context={}):
165         res = []
166         curr = [1, 2, 5, 10, 20, 50, 100, 500]
167         for rs in curr:
168             dct = {
169                 'pieces':rs,
170                 'number':0
171             }
172             res.append(dct)
173         journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','cash')], context=context)
174         if journal_ids:
175             results = self.search(cr, uid, [('journal_id','in',journal_ids),('state','=','confirm')],context=context)
176             if results:
177                 cash_st = self.browse(cr, uid, results, context)[0]
178                 for cash_line in cash_st.ending_details_ids:
179                     for r in res:
180                         if cash_line.pieces == r['pieces']:
181                             r['number'] = cash_line.number
182         return res
183
184     def _get_default_cash_close_box_lines(self, cr, uid, context={}):
185         res = []
186         curr = [1, 2, 5, 10, 20, 50, 100, 500]
187         for rs in curr:
188             dct = {
189                 'pieces':rs,
190                 'number':0
191             }
192             res.append(dct)
193         return res
194
195     def _get_cash_close_box_lines(self, cr, uid, context={}):
196         res = []
197         curr = [1, 2, 5, 10, 20, 50, 100, 500]
198         for rs in curr:
199             dct = {
200                 'pieces':rs,
201                 'number':0
202             }
203             res.append((0,0,dct))
204         return res
205
206     def _get_cash_open_close_box_lines(self, cr, uid, context={}):
207         res = {}
208         start_l = []
209         end_l = []
210         starting_details = self._get_cash_open_box_lines(cr, uid, context)
211         ending_details = self._get_default_cash_close_box_lines(cr, uid, context)
212         for start in starting_details:
213             start_l.append((0,0,start))
214         for end in ending_details:
215             end_l.append((0,0,end))
216         res['start'] = start_l
217         res['end'] = end_l
218         return res
219
220     _columns = {
221         '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"),
222         'state': fields.selection(
223             [('draft', 'Draft'),
224             ('confirm', 'Closed'),
225             ('open','Open')], 'State', required=True, states={'confirm': [('readonly', True)]}, readonly="1"),
226         'total_entry_encoding':fields.function(_get_sum_entry_encoding, method=True, store=True, string="Cash Transaction", help="Total cash transactions"),
227         'closing_date':fields.datetime("Closed On"),
228         'balance_end': fields.function(_end_balance, method=True, store=True, string='Balance', help="Closing balance based on Starting Balance and Cash Transactions"),
229         'balance_end_cash': fields.function(_balance_end_cash, method=True, store=True, string='Balance', help="Closing balance based on cashBox"),
230         'starting_details_ids': fields.one2many('account.cashbox.line', 'starting_id', string='Opening Cashbox'),
231         'ending_details_ids': fields.one2many('account.cashbox.line', 'ending_id', string='Closing Cashbox'),
232         'name': fields.char('Name', size=64, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'),
233         'user_id':fields.many2one('res.users', 'Responsible', required=False),
234     }
235     _defaults = {
236         'state': 'draft',
237         'date': time.strftime("%Y-%m-%d %H:%M:%S"),
238         'user_id': lambda self, cr, uid, context=None: uid,
239         'starting_details_ids':_get_cash_open_box_lines,
240         'ending_details_ids':_get_default_cash_close_box_lines
241      }
242
243     def create(self, cr, uid, vals, context=None):
244         if 'journal_id' not in vals:
245             raise osv.except_osv('Error', _('You cannot create a bank or cash register without a journal!'))
246         sql = [
247                 ('journal_id', '=', vals['journal_id']),
248                 ('state', '=', 'open')
249         ]
250         open_jrnl = self.search(cr, uid, sql)
251         if open_jrnl:
252             raise osv.except_osv('Error', _('You can not have two open register for the same journal'))
253
254         if self.pool.get('account.journal').browse(cr, uid, vals['journal_id']).type == 'cash':
255             open_close = self._get_cash_open_close_box_lines(cr, uid, context)
256             if vals.get('starting_details_ids',False):
257                 for start in vals.get('starting_details_ids'):
258                     dict_val = start[2]
259                     for end in open_close['end']:
260                        if end[2]['pieces'] == dict_val['pieces']:
261                            end[2]['number'] += dict_val['number']
262             vals.update({
263                 'ending_details_ids':open_close['start'],
264                 'starting_details_ids':open_close['end']
265             })
266         else:
267             vals.update({
268                 'ending_details_ids':False,
269                 'starting_details_ids':False
270             })
271         res_id = super(account_cash_statement, self).create(cr, uid, vals, context=context)
272         self.write(cr, uid, [res_id], {})
273         return res_id
274
275     def write(self, cr, uid, ids, vals, context=None):
276         """
277         Update redord(s) comes in {ids}, with new value comes as {vals}
278         return True on success, False otherwise
279
280         @param cr: cursor to database
281         @param user: id of current user
282         @param ids: list of record ids to be update
283         @param vals: dict of new values to be set
284         @param context: context arguments, like lang, time zone
285
286         @return: True on success, False otherwise
287         """
288
289         super(account_cash_statement, self).write(cr, uid, ids, vals)
290         res = self._get_starting_balance(cr, uid, ids)
291         for rs in res:
292             super(account_cash_statement, self).write(cr, uid, [rs], res.get(rs))
293         return True
294
295     def onchange_journal_id(self, cr, uid, statement_id, journal_id, context=None):
296         """ Changes balance start and starting details if journal_id changes"
297         @param statement_id: Changed statement_id
298         @param journal_id: Changed journal_id
299         @return:  Dictionary of changed values
300         """
301         cash_pool = self.pool.get('account.cashbox.line')
302         statement_pool = self.pool.get('account.bank.statement')
303         res = {}
304         balance_start = 0.0
305
306         if not journal_id:
307             res.update({
308                 'balance_start': balance_start
309             })
310             return res
311         return super(account_cash_statement, self).onchange_journal_id(cr, uid, statement_id, journal_id, context=context)
312
313     def _equal_balance(self, cr, uid, cash_id, context=None):
314         statement = self.browse(cr, uid, cash_id, context=context)
315         self.write(cr, uid, [cash_id], {'balance_end_real': statement.balance_end})
316         statement.balance_end_real = statement.balance_end
317         if statement.balance_end != statement.balance_end_cash:
318             return False
319         else:
320             return True
321
322     def _user_allow(self, cr, uid, ids, statement, context={}):
323         return True
324
325     def button_open(self, cr, uid, ids, context=None):
326
327         """ Changes statement state to Running.
328         @return: True
329         """
330         cash_pool = self.pool.get('account.cashbox.line')
331         statement_pool = self.pool.get('account.bank.statement')
332         statement = statement_pool.browse(cr, uid, ids[0])
333         vals = {}
334
335         if not self._user_allow(cr, uid, ids, statement, context={}):
336             raise osv.except_osv(_('Error !'), _('User %s does not have rights to access %s journal !' % (statement.user_id.name, statement.journal_id.name)))
337
338         if statement.name and statement.name == '/':
339             number = self.pool.get('ir.sequence').get(cr, uid, 'account.cash.statement')
340             vals.update({
341                 'name': number
342             })
343
344         vals.update({
345             'date':time.strftime("%Y-%m-%d %H:%M:%S"),
346             'state':'open',
347
348         })
349         return self.write(cr, uid, ids, vals)
350
351     def balance_check(self, cr, uid, cash_id, journal_type='bank', context=None):
352         if journal_type == 'bank':
353             return super(account_cash_statement, self).balance_check(cr, uid, cash_id, journal_type, context)
354         if not self._equal_balance(cr, uid, cash_id, context):
355             raise osv.except_osv(_('Error !'), _('CashBox Balance is not matching with Calculated Balance !'))
356         return True
357
358     def statement_close(self, cr, uid, ids, journal_type='bank', context=None):
359         if journal_type == 'bank':
360             return super(account_cash_statement, self).statement_close(cr, uid, ids, journal_type, context)
361         vals = {
362             'state':'confirm',
363             'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")
364         }
365         return self.write(cr, uid, ids, vals, context=context)
366
367     def check_status_condition(self, cr, uid, state, journal_type='bank'):
368         if journal_type == 'bank':
369             return super(account_cash_statement, self).check_status_condition(cr, uid, state, journal_type)
370         return state=='open'
371
372     def button_confirm_cash(self, cr, uid, ids, context=None):
373         super(account_cash_statement, self).button_confirm_bank(cr, uid, ids, context=context)
374         return self.write(cr, uid, ids, {'closing_date':time.strftime("%Y-%m-%d %H:%M:%S")}, context=context)
375
376     def button_cancel(self, cr, uid, ids, context=None):
377         cash_box_line_pool = self.pool.get('account.cashbox.line')
378         super(account_cash_statement, self).button_cancel(cr, uid, ids, context=context)
379         for st in self.browse(cr, uid, ids, context):
380             for end in st.ending_details_ids:
381                 cash_box_line_pool.write(cr, uid, [end.id], {'number':0})
382         return True
383
384 account_cash_statement()
385
386 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: