2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 PC Solutions (<http://pcsol.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 ##############################################################################
23 from osv import osv, fields
25 from mx import DateTime
26 from decimal import Decimal
27 from tools.translate import _
28 import decimal_precision as dp
30 class account_cashbox_line(osv.osv):
32 """ Cash Box Details """
34 _name = 'account.cashbox.line'
35 _description = 'CashBox Line'
37 def _sub_total(self, cr, uid, ids, name, arg, context=None):
39 """ Calculates Sub total
40 @param name: Names of fields.
41 @param arg: User defined arguments
42 @return: Dictionary of values.
45 for obj in self.browse(cr, uid, ids):
46 res[obj.id] = obj.pieces * obj.number
49 def on_change_sub(self, cr, uid, ids, pieces, number,*a):
51 """ Calculates Sub total on change of number
52 @param pieces: Names of fields.
56 return {'value':{'subtotal': sub or 0.0}}
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'),
65 account_cashbox_line()
67 class account_cash_statement(osv.osv):
69 _inherit = 'account.bank.statement'
71 def _get_starting_balance(self, cr, uid, ids, context=None):
73 """ Find starting balance
74 @param name: Names of fields.
75 @param arg: User defined arguments
76 @return: Dictionary of values.
79 for statement in self.browse(cr, uid, ids):
82 if statement.journal_id.type not in('cash'):
85 for line in statement.starting_details_ids:
86 amount_total+= line.pieces * line.number
88 'balance_start':amount_total
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.
99 for statement in self.browse(cr, uid, ids):
101 for line in statement.ending_details_ids:
102 amount_total+= line.pieces * line.number
103 res[statement.id]=amount_total
106 def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None):
108 """ Find encoding total of statements "
109 @param name: Names of fields.
110 @param arg: User defined arguments
111 @return: Dictionary of values.
114 for statement in self.browse(cr, uid, ids):
116 for line in statement.line_ids:
117 encoding_total+= line.amount
118 res2[statement.id]=encoding_total
121 # def _default_journal_id(self, cr, uid, context={}):
123 # """ To get default journal for the object"
124 # @param name: Names of fields.
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)])
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')
140 company_currency_id = res_users_obj.browse(cursor, user, user,
141 context=context).company_id.currency_id.id
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:
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)
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
164 res[r] = round(res[r], 2)
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
173 company_id = company_pool.search(cr, uid, [])[0]
177 def _get_cash_open_box_lines(self, cr, uid, ids, context={}):
179 curr = [1, 2, 5, 10, 20, 50, 100, 500]
188 def _get_cash_close_box_lines(self, cr, uid, ids, context={}):
190 curr = [1, 2, 5, 10, 20, 50, 100, 500]
196 res.append((0, 0, dct))
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(
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),
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
225 def create(self, cr, uid, vals, context=None):
226 company_id = vals and vals.get('company_id',False)
229 ('company_id', '=', vals['company_id']),
230 ('journal_id', '=', vals['journal_id']),
231 ('state', '=', 'open')
233 open_jrnl = self.search(cr, uid, sql)
235 raise osv.except_osv('Error', _('You can not have two open register for the same journal'))
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)
240 'ending_details_ids':lines
244 'ending_details_ids':False,
245 'starting_details_ids':False
247 res_id = super(account_cash_statement, self).create(cr, uid, vals, context=context)
248 self.write(cr, uid, [res_id], {})
251 def write(self, cr, uid, ids, vals, context=None):
253 Update redord(s) comes in {ids}, with new value comes as {vals}
254 return True on success, False otherwise
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
262 @return: True on success, False otherwise
265 super(account_cash_statement, self).write(cr, uid, ids, vals)
266 res = self._get_starting_balance(cr, uid, ids)
268 super(account_cash_statement, self).write(cr, uid, rs, res.get(rs))
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
278 cash_pool = self.pool.get('account.cashbox.line')
279 statement_pool = self.pool.get('account.bank.statement')
286 'balance_start': balance_start
291 res = super(account_cash_statement, self).onchange_journal_id(cr, uid, statement_id, journal_id, context)
294 def _equal_balance(self, cr, uid, ids, statement, context={}):
295 if statement.balance_end != statement.balance_end_cash:
300 def _user_allow(self, cr, uid, ids, statement, context={}):
303 def button_open(self, cr, uid, ids, context=None):
305 """ Changes statement state to Running.
308 cash_pool = self.pool.get('account.cashbox.line')
309 statement_pool = self.pool.get('account.bank.statement')
311 statement = statement_pool.browse(cr, uid, ids[0])
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)))
317 if statement.name and statement.name == '/':
318 number = self.pool.get('ir.sequence').get(cr, uid, 'account.cash.statement')
323 # if len(statement.starting_details_ids) > 0:
325 # for line in statement.starting_details_ids:
326 # sid.append(line.id)
327 # cash_pool.unlink(cr, uid, sid)
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'))
331 # rs = rs and rs[0] or None
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:
338 # 'ending_id': False,
339 # 'starting_id':ids[0]
341 # cash_pool.copy(cr, uid, sid, default)
344 'date':time.strftime("%Y-%m-%d %H:%M:%S"),
349 self.write(cr, uid, ids, vals)
352 def button_confirm_cash(self, cr, uid, ids, context={}):
354 """ Check the starting and ending detail of statement
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')
364 company_currency_id = res_users_obj.browse(cr, uid, uid, context=context).company_id.currency_id.id
366 for st in self.browse(cr, uid, ids, context):
368 self.write(cr, uid, [st.id], {'balance_end_real':st.balance_end})
369 st.balance_end_real = st.balance_end
371 if not st.state == 'open':
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 !'))
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.'))
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,
400 account_bank_statement_line_obj.write(cr, uid, [move.id], {
401 'move_ids': [(4,move_id, False)]
408 account_id = st.journal_id.default_credit_account_id.id
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,
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
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,
434 amount = res_currency_obj.compute(cr, uid, st.currency.id,
435 company_currency_id, move.amount, context=context,
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,
441 val['amount_currency'] = -amount_cur
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
448 amount_cur = res_currency_obj.compute(cr, uid, company_currency_id,
449 move.account_id.currency_id.id, amount, context=context,
451 val['amount_currency'] = amount_cur
453 torec.append(account_move_line_obj.create(cr, uid, val , context=context))
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,
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,
473 # Fill the secondary amount/currency
474 # if currency is not the same than the company
475 amount_currency = 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, {
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,
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],
500 if line.state <> 'valid':
501 raise osv.except_osv(_('Error !'),
502 _('Ledger Posting line "%s" is not valid') % line.name)
504 if move.reconcile_id and move.reconcile_id.line_ids:
505 torec += map(lambda x: x.id, move.reconcile_id.line_ids)
507 if abs(move.reconcile_amount-move.amount)<0.0001:
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
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)
517 account_move_line_obj.reconcile_partial(cr, uid, torec, 'statement', context)
519 if st.journal_id.entry_posted:
520 account_move_obj.write(cr, uid, [move_id], {'state':'posted'})
525 'closing_date':time.strftime("%Y-%m-%d %H:%M:%S")
527 self.write(cr, uid, done, vals, context=context)
530 def button_cancel(self, cr, uid, ids, context={}):
532 for st in self.browse(cr, uid, ids, context):
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)
538 self.write(cr, uid, done, {'state':'draft'}, context=context)
541 account_cash_statement()