[ADD]: add a new feature to account, cash register based on bank statement

[IMP]: implement cash register in account

bzr revid: mga@tinyerp.com-20100626191611-uelrhietfd81g3fb
This commit is contained in:
Mantavya Gajjar 2010-06-27 00:46:11 +05:30
parent b93efbe7c4
commit b535556b0b
3 changed files with 594 additions and 16 deletions

View File

@ -25,6 +25,7 @@ import project
import partner
import invoice
import account_bank_statement
import account_cash_statement
import account_move_line
import account_analytic_line
import wizard
@ -33,4 +34,4 @@ import product
import sequence
import company
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,448 @@
# encoding: utf-8
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
# $Id$
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
import time
from mx import DateTime
from decimal import Decimal
from tools.translate import _
class singer_statement(osv.osv):
""" Singer Statements """
_name = 'singer.statement'
_description = 'Statement'
def _sub_total(self, cr, uid, ids, name, arg, context=None):
""" Calculates Sub total"
@param name: Names of fields.
@param arg: User defined arguments
@return: Dictionary of values.
"""
res = {}
for obj in self.browse(cr, uid, ids):
res[obj.id] = obj.pieces * obj.number
return res
def on_change_sub(self, cr, uid, ids, pieces, number,*a):
""" Calculates Sub total on change of number
@param pieces: Names of fields.
@param number:
"""
sub=pieces*number
return {'value':{'subtotal': sub or 0.0}}
_columns = {
'pieces': fields.float('Values', digits=(16,2)),
'number': fields.integer('Number'),
'subtotal': fields.function(_sub_total, method=True, string='Sub Total', type='float',digits=(16,2)),
'starting_id': fields.many2one('account.bank.statement',ondelete='cascade'),
'ending_id': fields.many2one('account.bank.statement',ondelete='cascade'),
}
singer_statement()
class account_cash_statement(osv.osv):
_inherit = 'account.bank.statement'
def _get_starting_balance(self, cr, uid, ids, name, arg, context=None):
""" Find starting balance "
@param name: Names of fields.
@param arg: User defined arguments
@return: Dictionary of values.
"""
res ={}
for statement in self.browse(cr, uid, ids):
amount_total=0.0
for line in statement.starting_details_ids:
amount_total+= line.pieces * line.number
res[statement.id]=amount_total
return res
def _balance_end_cash(self, cr, uid, ids, name, arg, context=None):
""" Find ending balance "
@param name: Names of fields.
@param arg: User defined arguments
@return: Dictionary of values.
"""
res ={}
for statement in self.browse(cr, uid, ids):
amount_total=0.0
for line in statement.ending_details_ids:
amount_total+= line.pieces * line.number
res[statement.id]=amount_total
return res
def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None):
""" Find encoding total of statements "
@param name: Names of fields.
@param arg: User defined arguments
@return: Dictionary of values.
"""
res2={}
for statement in self.browse(cr, uid, ids):
encoding_total=0.0
for line in statement.line_ids:
encoding_total+= line.amount
res2[statement.id]=encoding_total
return res2
def _default_journal_id(self, cr, uid, context={}):
""" To get default journal for the object"
@param name: Names of fields.
@return: journal
"""
company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
journal = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', company_id)])
if journal:
return journal[0]
else:
return False
def _end_balance(self, cursor, user, ids, name, attr, context=None):
res_currency_obj = self.pool.get('res.currency')
res_users_obj = self.pool.get('res.users')
res = {}
company_currency_id = res_users_obj.browse(cursor, user, user,
context=context).company_id.currency_id.id
statements = self.browse(cursor, user, ids, context=context)
for statement in statements:
res[statement.id] = statement.balance_start
currency_id = statement.currency.id
for line in statement.move_line_ids:
if line.debit > 0:
if line.account_id.id == \
statement.journal_id.default_debit_account_id.id:
res[statement.id] += res_currency_obj.compute(cursor,
user, company_currency_id, currency_id,
line.debit, context=context)
else:
if line.account_id.id == \
statement.journal_id.default_credit_account_id.id:
res[statement.id] -= res_currency_obj.compute(cursor,
user, company_currency_id, currency_id,
line.credit, context=context)
if statement.state in ('draft', 'open'):
for line in statement.line_ids:
res[statement.id] += line.amount
for r in res:
res[r] = round(res[r], 2)
return res
_columns = {
'company_id':fields.many2one('res.company', 'Company', required=False),
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
'balance_start': fields.function(_get_starting_balance, method=True, string='Opening Balance', type='float',digits=(16,2)),
'balance_end_real': fields.float('Closing Balance', digits=(16,2), states={'confirm':[('readonly', True)]}),
'state': fields.selection(
[('draft', 'Draft'),
('confirm', 'Confirm'),
('open','Open')], 'State', required=True, states={'confirm': [('readonly', True)]}, readonly="1"),
'total_entry_encoding':fields.function(_get_sum_entry_encoding, method=True, string="Cash Transaction"),
'date':fields.datetime("Open On"),
'closing_date':fields.datetime("Closed On"),
'balance_end': fields.function(_end_balance, method=True, string='Balance'),
'balance_end_cash': fields.function(_balance_end_cash, method=True, string='Balance'),
'starting_details_ids': fields.one2many('singer.statement', 'starting_id', string='Opening Cashbox'),
'ending_details_ids': fields.one2many('singer.statement', 'ending_id', string='Closing Cashbox'),
'name': fields.char('Name', size=64, required=True, readonly=True),
'user_id':fields.many2one('res.users', 'Responsible', required=False),
}
_defaults = {
'state': lambda *a: 'draft',
'name': lambda *a: '/',
'date': lambda *a:time.strftime("%Y-%m-%d %H:%M:%S"),
'journal_id': _default_journal_id,
'user_id': lambda self, cr, uid, context=None: uid
}
def create(self, cr, uid, vals, context=None):
company_id = vals and vals.get('company_id',False)
if company_id:
open_jrnl = self.search(cr, uid, [('company_id', '=', vals['company_id']), ('journal_id', '=', vals['journal_id']), ('state', '=', 'open')])
if open_jrnl:
raise osv.except_osv('Error', u'Une caisse de type espèce est déjà ouverte')
if 'starting_details_ids' in vals:
vals['starting_details_ids'] = starting_details_ids = map(list, vals['starting_details_ids'])
for i in starting_details_ids:
if i and i[0] and i[1]:
i[0], i[1] = 0, 0
res = super(account_cash_statement, self).create(cr, uid, vals, context=context)
return res
def onchange_journal_id(self, cr, uid, statement_id, journal_id, context={}):
""" Changes balance start and starting details if journal_id changes"
@param statement_id: Changed statement_id
@param journal_id: Changed journal_id
@return: Dictionary of changed values
"""
cash_pool = self.pool.get('singer.statement')
statement_pool = self.pool.get('account.bank.statement')
res = {}
balance_start = 0.0
if not journal_id:
res.update({
'balance_start': balance_start
})
return res
res = super(account_cash_statement, self).onchange_journal_id(cr, uid, statement_id, journal_id, context)
return res
def button_open(self, cr, uid, ids, context=None):
""" Changes statement state to Running.
@return: True
"""
cash_pool = self.pool.get('singer.statement')
statement_pool = self.pool.get('account.bank.statement')
statement = statement_pool.browse(cr, uid, ids[0])
number = self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement')
if len(statement.starting_details_ids) > 0:
sid = []
for line in statement.starting_details_ids:
sid.append(line.id)
cash_pool.unlink(cr, uid, sid)
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'))
rs = cr.fetchone()
rs = rs and rs[0] or None
if rs:
statement = statement_pool.browse(cr, uid, rs)
balance_start = statement.balance_end_real or 0.0
open_ids = cash_pool.search(cr, uid, [('ending_id','=',statement.id)])
for sid in open_ids:
default = {
'ending_id': False,
'starting_id':ids[0]
}
cash_pool.copy(cr, uid, sid, default)
vals = {
'date':time.strftime("%Y-%m-%d %H:%M:%S"),
'state':'open',
'name':number
}
self.write(cr, uid, ids, vals)
return True
def button_confirm(self, cr, uid, ids, context={}):
""" Check the starting and ending detail of statement
@return: True
"""
done = []
res_currency_obj = self.pool.get('res.currency')
res_users_obj = self.pool.get('res.users')
account_move_obj = self.pool.get('account.move')
account_move_line_obj = self.pool.get('account.move.line')
account_bank_statement_line_obj = self.pool.get('account.bank.statement.line')
company_currency_id = res_users_obj.browse(cr, uid, uid, context=context).company_id.currency_id.id
for st in self.browse(cr, uid, ids, context):
if not st.state == 'open':
continue
if st.balance_end != st.balance_end_cash:
raise osv.except_osv(_('Error !'), _('Cash balance is not match with closing balance !'))
if not (abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001):
raise osv.except_osv(_('Error !'),
_('The statement balance is incorrect !\n') +
_('The expected balance (%.2f) is different than the computed one. (%.2f)') % (st.balance_end_real, st.balance_end))
if (not st.journal_id.default_credit_account_id) \
or (not st.journal_id.default_debit_account_id):
raise osv.except_osv(_('Configuration Error !'),
_('Please verify that an account is defined in the journal.'))
for line in st.move_line_ids:
if line.state <> 'valid':
raise osv.except_osv(_('Error !'),
_('The account entries lines are not in valid state.'))
# for bank.statement.lines
# In line we get reconcile_id on bank.ste.rec.
# in bank stat.rec we get line_new_ids on bank.stat.rec.line
for move in st.line_ids:
context.update({'date':move.date})
move_id = account_move_obj.create(cr, uid, {
'journal_id': st.journal_id.id,
'period_id': st.period_id.id,
'date': move.date,
}, context=context)
account_bank_statement_line_obj.write(cr, uid, [move.id], {
'move_ids': [(4,move_id, False)]
})
if not move.amount:
continue
torec = []
if move.amount >= 0:
account_id = st.journal_id.default_credit_account_id.id
else:
account_id = st.journal_id.default_debit_account_id.id
acc_cur = ((move.amount<=0) and st.journal_id.default_debit_account_id) or move.account_id
amount = res_currency_obj.compute(cr, uid, st.currency.id,
company_currency_id, move.amount, context=context,
account=acc_cur)
if move.reconcile_id and move.reconcile_id.line_new_ids:
for newline in move.reconcile_id.line_new_ids:
amount += newline.amount
val = {
'name': move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': (move.account_id) and move.account_id.id,
'credit': ((amount>0) and amount) or 0.0,
'debit': ((amount<0) and -amount) or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': st.period_id.id,
'currency_id': st.currency.id,
}
amount = res_currency_obj.compute(cr, uid, st.currency.id,
company_currency_id, move.amount, context=context,
account=acc_cur)
if st.currency.id <> company_currency_id:
amount_cur = res_currency_obj.compute(cr, uid, company_currency_id,
st.currency.id, amount, context=context,
account=acc_cur)
val['amount_currency'] = -amount_cur
if move.account_id and move.account_id.currency_id and move.account_id.currency_id.id <> company_currency_id:
val['currency_id'] = move.account_id.currency_id.id
if company_currency_id==move.account_id.currency_id.id:
amount_cur = move.amount
else:
amount_cur = res_currency_obj.compute(cr, uid, company_currency_id,
move.account_id.currency_id.id, amount, context=context,
account=acc_cur)
val['amount_currency'] = amount_cur
torec.append(account_move_line_obj.create(cr, uid, val , context=context))
if move.reconcile_id and move.reconcile_id.line_new_ids:
for newline in move.reconcile_id.line_new_ids:
account_move_line_obj.create(cr, uid, {
'name': newline.name or move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': (newline.account_id) and newline.account_id.id,
'debit': newline.amount>0 and newline.amount or 0.0,
'credit': newline.amount<0 and -newline.amount or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': st.period_id.id,
'analytic_account_id':newline.analytic_id and newline.analytic_id.id or False,
}, context=context)
# Fill the secondary amount/currency
# if currency is not the same than the company
amount_currency = False
currency_id = False
if st.currency.id <> company_currency_id:
amount_currency = move.amount
currency_id = st.currency.id
account_move_line_obj.create(cr, uid, {
'name': move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': account_id,
'credit': ((amount < 0) and -amount) or 0.0,
'debit': ((amount > 0) and amount) or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': st.period_id.id,
'amount_currency': amount_currency,
'currency_id': currency_id,
}, context=context)
for line in account_move_line_obj.browse(cr, uid, [x.id for x in
account_move_obj.browse(cr, uid, move_id,
context=context).line_id],
context=context):
if line.state <> 'valid':
raise osv.except_osv(_('Error !'),
_('Ledger Posting line "%s" is not valid') % line.name)
if move.reconcile_id and move.reconcile_id.line_ids:
torec += map(lambda x: x.id, move.reconcile_id.line_ids)
#try:
if abs(move.reconcile_amount-move.amount)<0.0001:
writeoff_acc_id = False
#There should only be one write-off account!
for entry in move.reconcile_id.line_new_ids:
writeoff_acc_id = entry.account_id.id
break
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)
else:
account_move_line_obj.reconcile_partial(cr, uid, torec, 'statement', context)
#except:
# raise osv.except_osv(_('Error !'), _('Unable to reconcile entry "%s": %.2f') % (move.name, move.amount))
if st.journal_id.entry_posted:
account_move_obj.write(cr, uid, [move_id], {'state':'posted'})
done.append(st.id)
self.write(cr, uid, done, {'state':'confirm'}, context=context)
return True
def button_cancel(self, cr, uid, ids, context={}):
done = []
for st in self.browse(cr, uid, ids, context):
ids = []
for line in st.line_ids:
ids += [x.id for x in line.move_ids]
self.pool.get('account.move').unlink(cr, uid, ids, context)
done.append(st.id)
self.write(cr, uid, done, {'state':'draft'}, context=context)
return True
account_cash_statement()

View File

@ -378,7 +378,7 @@
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Statement">
<form string="Bank Statement">
<group col="6" colspan="4">
<field name="name" select="1"/>
<field name="date" select="1"/>
@ -394,7 +394,7 @@
<field name="balance_end_real"/>
</group>
<notebook colspan="4">
<page string="Entry encoding">
<page string="Transaction">
<field colspan="4" name="line_ids" nolabel="1">
<tree editable="bottom" string="Statement lines">
<field name="sequence" invisible="1"/>
@ -423,7 +423,7 @@
</form>
</field>
</page>
<page string="Real Entries">
<page string="Accounting Entries">
<field colspan="4" name="move_line_ids" nolabel="1"/>
</page>
</notebook>
@ -442,6 +442,7 @@
<field name="res_model">account.bank.statement</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('journal_id.type', '=', 'bank')]</field>
</record>
<menuitem string="Bank Statements" action="action_bank_statement_tree" id="menu_bank_statement_tree" parent="menu_finance_bank_and_cash" sequence="7"/>
@ -1131,16 +1132,6 @@
<field name="view_mode">form</field>
<field name="act_window_id" ref="action_move_line_search"/>
</record>
<record id="action_move_line_search_view3" model="ir.actions.act_window">
<field name="name">Cash Register</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.move.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_move_line_tree"/>
<field name="search_view_id" ref="view_account_move_line_filter"/>
<field name="domain">[('journal_id.type', '=', 'cash')]</field>
</record>
<record id="action_move_line_search_view4" model="ir.actions.act_window">
<field name="name">Checks Register</field>
<field name="type">ir.actions.act_window</field>
@ -1151,7 +1142,7 @@
<field name="search_view_id" ref="view_account_move_line_filter"/>
<field name="domain">[('journal_id.type', '=', 'bank')]</field>
</record>
<menuitem action="action_move_line_search_view3" id="journal_cash_move_lines" parent="menu_finance_bank_and_cash"/>
<menuitem action="action_move_line_search_view4" id="journal_bank_move_lines" parent="menu_finance_bank_and_cash"/>
<!-- <menuitem action="action_move_line_search" id="menu_action_move_line_search" parent="account.next_id_29"/>-->
@ -2037,6 +2028,144 @@
sequence="4"
/>
<!-- Cash Statement -->
<record id="view_bank_statement_tree" model="ir.ui.view">
<field name="name">account.bank.statement.tree</field>
<field name="model">account.bank.statement</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree colors="red:balance_end_real!=balance_end;blue:state=='draft' and (balance_end_real==balance_end);black:state in ('open');blue:state in ('draft')" string="Statement">
<field name="date"/>
<field name="name"/>
<field name="journal_id"/>
<field name="period_id"/>
<field name="balance_start"/>
<field name="balance_end_real"/>
<field name="balance_end"/>
<field name="user_id" select="1"/>
<field name="state"/>
<button type="object" string="Open" name="button_open" states="draft" icon="terp-camera_test"/>
<button type="object" string="Confirm" name="button_confirm" states="open" icon="terp-gtk-go-back-rtl"/>
<button type="object" string="Cancel" name="button_cancel" states="confirm" icon="terp-gtk-stop"/>
</tree>
</field>
</record>
<record id="view_bank_statement_form2" model="ir.ui.view">
<field name="name">account.bank.statement.form</field>
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Statement">
<group col="6" colspan="4">
<field name="name" select="1"/>
<field name="company_id" select="1" groups="base.group_multi_company"/>
<field name="journal_id" on_change="onchange_journal_id(journal_id)" domain="[('type','=','cash')]" select="1" />
<field name="user_id" select="1" readonly="1"/>
<field name="period_id" select="1"/>
<field name="balance_end_real"/>
</group>
<notebook colspan="4">
<page string="Cash Transactions" attrs="{'invisible': [('state','=','draft')]}">
<field colspan="4" name="line_ids" nolabel="1">
<tree editable="bottom" string="Statement lines">
<field name="sequence" invisible="1"/>
<field name="date"/>
<field name="ref"/>
<field name="name"/>
<field name="type"/>
<field name="partner_id" on_change="onchange_partner_id(partner_id, type, parent.currency, {'amount': amount})"/>
<field domain="[('journal_id','=',parent.journal_id)]" name="account_id"/>
<field name="amount"/>
<field context="{'partner_id': partner_id, 'amount': amount, 'account_id': account_id, 'currency_id': parent.currency, 'journal_id': parent.journal_id, 'date':date}" name="reconcile_id"/>
<field invisible="1" name="reconcile_amount"/>
</tree>
</field>
</page>
<page string="Cash Box">
<group col="2" colspan="2" expand="1">
<field name="starting_details_ids" nolabel="1" colspan="2" attrs="{'readonly':[('state','=','draft')]}">
<tree string = "Opening Balance" editable="bottom">
<field name="pieces"/>
<field name="number" on_change="on_change_sub(pieces,number, parent.balance_end)"/>
<field name="subtotal" sum="Total"/>
</tree>
<form string = "Opening Balance">
<field name="pieces"/>
<field name="number" on_change="on_change_sub(pieces,number, parent.balance_end)"/>
<field name="subtotal"/>
</form>
</field>
</group>
<group col="2" colspan="2" expand="1">
<field name="ending_details_ids" nolabel="1" colspan="2" attrs="{'readonly':[('state','=','draft')]}">
<tree string = "Closing Balance" editable="bottom">
<field name="pieces"/>
<field name="number" on_change="on_change_sub(pieces,number, parent.balance_end)"/>
<field name="subtotal" sum="Total"/>
</tree>
<form string = "Closing Balance">
<field name="pieces"/>
<field name="number" on_change="on_change_sub(pieces,number, parent.balance_end)"/>
<field name="subtotal"/>
</form>
</field>
</group>
</page>
<page string="Accounting Entries">
<field colspan="4" name="move_line_ids" nolabel="1"/>
</page>
</notebook>
<group col="6" colspan="4">
<group col="2" colspan="2">
<separator string="Dates" colspan="4"/>
<field name="date" select="1" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="closing_date" select="1" attrs="{'readonly':[('state','=','confirm')]}"/>
</group>
<group col="2" colspan="2">
<separator string="Opening Balance" colspan="4"/>
<field name="balance_start"/>
<field name="total_entry_encoding"/>
</group>
<group col="2" colspan="2">
<separator string="Closing Balance" colspan="4"/>
<field name="balance_end" string="Approx"/>
<field name="balance_end_cash" string="Cash Balance"/>
</group>
</group>
<group col="8" colspan="4">
<field name="state" colspan="4"/>
<button name="button_confirm" states="open" string="Close CashBox" icon="terp-check" type="object"/>
<button name="button_open" states="draft" string="Open CashBox" icon="terp-document-new" type="object"/>
<button name="button_cancel" states="confirm,open" string="Cancel" icon="terp-gtk-stop" type="object" groups="base.group_extended"/>
</group>
</form>
</field>
</record>
<record id="action_view_bank_statement_tree" model="ir.actions.act_window">
<field name="name">Cash Register</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.bank.statement</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_bank_statement_tree"/>
<field name="search_view_id" ref="view_account_bank_statement_filter"/>
<field name="domain">[('journal_id.type', '=', 'cash')]</field>
</record>
<record model="ir.actions.act_window.view" id="act_cash_statement1_all">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_bank_statement_tree"/>
<field name="act_window_id" ref="action_view_bank_statement_tree"/>
</record>
<record model="ir.actions.act_window.view" id="act_cash_statement2_all">
<field name="sequence" eval="1"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_bank_statement_form2"/>
<field name="act_window_id" ref="action_view_bank_statement_tree"/>
</record>
<menuitem action="action_view_bank_statement_tree" id="journal_cash_move_lines" parent="menu_finance_bank_and_cash"/>
</data>
</openerp>