Skip to content

Commit

Permalink
Merge pull request #3 from MalayPalace/ui
Browse files Browse the repository at this point in the history
Added UI module in front of the backend cli commands
  • Loading branch information
MalayPalace authored Jun 13, 2024
2 parents 0c5c9ea + c04ad96 commit a801079
Show file tree
Hide file tree
Showing 26 changed files with 804 additions and 72 deletions.
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ Have created various configurable parser which can read and store bank statement
failure and can log the data which failed storing to database. Re-executable as in same file can be reprocessed without any
duplication issue.

Has below 2 main functionality:
1. <b>Process:</b> To process the statement file and store to the database.
2. <b>Verify:</b> Validate the transactions by comparing the closing balance between transactions. If it fails it might be some transaction might be missing. Script output will print the amount difference found.

## Screens
<p align="center">
<img src="./resources/screenshot/home_screen.png"
alt="Home Screen" />
<i>App: Home Screen</i>
<br><br>
<img src="./resources/screenshot/process_record_output.png"
alt="Process Record" />
<i>App: Record Insertion with Successful message</i>
<br><br>
<img src="./resources/screenshot/verify_output.png"
alt="Verify Record" />
<i>App: Successfully Validated message</i>
</p>


## Banks which are currently supported:

Below are the format information that the utility support for various banks. Download statement as prescribed format below:
Expand Down Expand Up @@ -69,22 +89,19 @@ docker-compose up -d
```bash
pip install bank_statement_utility-<latest-version>-py3-none-any.whl
```
5. Utility will create Config File when it executes first time at `${HOME}/.local/share/bank-statement-app/config.ini` for Utility to read it.
5. Utility will create Config File when it executes first time at `${HOME}/.local/share/bank-statement-app/config.ini`(For Linux & others)
or at `${HOME}/AppData/local/bank-statement-app/config.ini`(For Windows) for Utility to read it.
<br><i>Manually Edit config.ini file to add Cassandra Username/password</i>

## Basic Usage:
Execute the `main.py` from project folder or if you have installed wheel and pip install path is in your Environmental Variable then execute directly `bank_statement_utility`

```bash
bank_statement_utility {save,verify,export} -n {HDFC,KOTAK,SBI,BOB,IDBI} -t {Saving,Current,Creditcard} filename
```
OR
```bash
python bank_statement_utility/main.py {save,verify,export} -n {HDFC,KOTAK,SBI,BOB,IDBI} -t {Saving,Current,Creditcard} filename
```
### <u>UI Usage</u>
Execute the `main_ui.py` from project folder or if you have installed wheel and pip install path is in your Environmental Variable then execute directly `bank_statement_utility_ui`

### Commands Supported:
1. <b><u>save</u></b>: To process the statement file and store to the database.
### <u>CLI Usage</u>
Execute the `main.py` from project folder or if you have installed wheel and pip install path is in your Environmental Variable then execute directly `bank_statement_utility`

1. <b><u>save/process</u></b>: To process the statement file and store to the database.
<br/><u>Usage</u>:
```
python bank_statement_utility/main.py save -n {HDFC,KOTAK,SBI,BOB,IDBI} -t {Saving,Current,Creditcard} filename
Expand Down Expand Up @@ -115,11 +132,15 @@ python setup.py bdist_wheel
## Road Map:
Planning to add more banks and even Credit Card statements.

## Dependencies:
## Credits:
Utility is using below following dependencies.
Thanks to library creator & contributors
<p>
<a href="https://github.com/datastax/python-driver">DataStax Driver for Apache Cassandra</a><br>
<a href="https://pypi.org/project/xlrd/">xlrd</a><br>
<a href="https://pypi.org/project/pytz/">pytz</a><br>
<a href="https://pypi.org/project/pypdf/">pypdf</a><br>
<a href="https://pypi.org/project/ttkbootstrap/">ttkbootstrap</a><br>
<a href="https://pypi.org/project/tkinterdnd2/">tkinterdnd2</a><br>

Icon is by <a href="https://www.iconfinder.com/search?q=webkul+software">Webkul Software</a>
18 changes: 15 additions & 3 deletions bank_statement_utility/Constants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import os
import platform

bank_names = ['HDFC', 'KOTAK', 'SBI', 'BOB', 'IDBI', 'SVC']
account_type = ['Saving', 'Current', 'Creditcard']
BANK_NAMES = ['HDFC', 'KOTAK', 'SBI', 'BOB', 'IDBI', 'SVC']
ACCOUNT_TYPE = ['Saving', 'Current', 'Creditcard']

COMMA = ","
SPACE = " "
TAB = "\t"
CREDIT_CARD_SUFFIX = "_Creditcard"

####################################
# For Release
APP_CONFIG_PATH = os.path.expanduser("~") + "/.local/share/bank-statement-app/"
# APP_CONFIG_PATH = "./config/" # For local Testing
if platform.system() == 'Windows':
APP_CONFIG_PATH = os.path.expanduser("~") + "/AppData/Local/bank-statement-app/"

DB_TABLE_NAME = "bank_statement.statement"

# For Local Test run
# APP_CONFIG_PATH = "./config/"
# DB_TABLE_NAME = "bank_statement.statement_test"
####################################
8 changes: 7 additions & 1 deletion bank_statement_utility/StatementProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from .services.SvcSavingStatementProcessor import SvcSavingStatementProcessor
from .services.KotakCcStatementProcessor import KotakCcStatementProcessor
from .services.SbiCcStatementProcessor import SbiCcStatementProcessor
from .services.Utils import is_ui_execution


class StatementProcessor(object):

Expand Down Expand Up @@ -65,7 +67,11 @@ def process(self):
log.error("No Processor defined for bank {bank_name} and source type {source}".format(bank_name=self.bank_name,
source=self.source))
self.__close()
sys.exit("No processor found for given bank-name and source. Check logs for more details. Exiting...")

if is_ui_execution():
raise IOError("No processor found for given bank-name and source. Check logs for more details.")
else:
sys.exit("No processor found for given bank-name and source. Check logs for more details. Exiting...")

self.__close()
return response
Expand Down
10 changes: 9 additions & 1 deletion bank_statement_utility/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import bank_statement_utility.main as main_app
import bank_statement_utility.main_ui as main_ui_app


def main():
"""
Entry point through installed module
Console script Entry point through installed module
"""
main_app.main()


def main_ui():
"""
UI Entry point through installed module
"""
main_ui_app.main()
4 changes: 3 additions & 1 deletion bank_statement_utility/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
import sys

from .Constants import APP_CONFIG_PATH
from .config_writer import write_default_config
from .config_writer import write_default_config, check_and_update_config_to_latest

# create object
config = configparser.ConfigParser()

# READ CONFIG FILE
try:
config.read_file(open(APP_CONFIG_PATH + "config.ini"))
check_and_update_config_to_latest(config)
except IOError:
write_default_config()
try:
config.read_file(open(APP_CONFIG_PATH + "config.ini"))
check_and_update_config_to_latest(config)
except IOError:
sys.exit("Config file not found. Ensure config.ini is placed at path:" + APP_CONFIG_PATH)
59 changes: 57 additions & 2 deletions bank_statement_utility/config_writer.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
# python 3.x
from configparser import ConfigParser

from .version import __version__
from .Constants import APP_CONFIG_PATH
from .version import __version_1_2_1__, __version_2_0_0__

config = ConfigParser()


def version_compare(v1, v2):
"""
Method to compare two versions.
Return 1 if v2 is smaller, -1 if v1 is smaller,
0 if equal
:param v1:
:param v2:
:return: int
"""
arr1 = v1.split(".")
arr2 = v2.split(".")
n = len(arr1)
m = len(arr2)

# converts to integer from string
arr1 = [int(i) for i in arr1]
arr2 = [int(i) for i in arr2]

# compares which list is bigger and fills
# smaller list with zero (for unequal delimiters)
if n > m:
for i in range(m, n):
arr2.append(0)
elif m > n:
for i in range(n, m):
arr1.append(0)

# returns 1 if version 1 is bigger and -1 if
# version 2 is bigger and 0 if equal
for i in range(len(arr1)):
if arr1[i] > arr2[i]:
return 1
elif arr2[i] > arr1[i]:
return -1
return 0


def check_and_update_config_to_latest(config_file):
"""
Apply incremental changes to config file so to update to latest version
:param config_file:
:return: NaN
"""
if version_compare(config_file['Basic']['version'], __version_2_0_0__) == -1:
__update_to_2_0_0()


def write_default_config():
config.read(APP_CONFIG_PATH + 'config.ini')

config.add_section('Basic')
config.set('Basic', 'version', __version__)
config.set('Basic', 'version', __version_1_2_1__)
config.set('Basic', 'appname', 'Bank Statement Utility')

config.add_section('Cass')
Expand Down Expand Up @@ -56,3 +103,11 @@ def write_default_config():

with open(APP_CONFIG_PATH + 'config.ini', 'w') as f:
config.write(f)


def __update_to_2_0_0():
config.read(APP_CONFIG_PATH + 'config.ini')
config.set('Basic', 'version', __version_2_0_0__)

with open(APP_CONFIG_PATH + 'config.ini', 'w') as f:
config.write(f)
Binary file added bank_statement_utility/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions bank_statement_utility/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import time
from datetime import datetime, timedelta

from bank_statement_utility.Constants import bank_names, account_type
from bank_statement_utility.Constants import BANK_NAMES, ACCOUNT_TYPE
from bank_statement_utility.StatementProcessor import StatementProcessor
from bank_statement_utility.config import config
from bank_statement_utility.logger import log
Expand All @@ -26,19 +26,19 @@ def process():

# Save Command Parser
save_sub_parser = sub_parsers.add_parser("save", help="Store Bank Statement")
save_sub_parser.add_argument("-n", "--name", help="Name of the bank", type=str.upper, choices=bank_names,
save_sub_parser.add_argument("-n", "--name", help="Name of the bank", type=str.upper, choices=BANK_NAMES,
required=True)
save_sub_parser.add_argument("-t", "--type", help="Type of account. Saving|Current|Creditcard", type=str.capitalize,
choices=account_type, required=True)
choices=ACCOUNT_TYPE, required=True)
save_sub_parser.add_argument("filename", help="Bank Statement file to read")

# Verify Command Parser
verify_sub_parser = sub_parsers.add_parser("verify", help="Validation Command")
verify_sub_parser.add_argument("-n", "--name", help="Name of the bank", type=str.upper, choices=bank_names,
verify_sub_parser.add_argument("-n", "--name", help="Name of the bank", type=str.upper, choices=BANK_NAMES,
required=True)
verify_sub_parser.add_argument("-t", "--type", help="Type of account. Saving|Current|Creditcard",
type=str.capitalize,
choices=account_type, required=True)
choices=ACCOUNT_TYPE, required=True)
verify_sub_parser.add_argument("--start-date",
help="Start Date for verification command. Should follow DD-MM-YYYY format",
type=str, required=False)
Expand Down
45 changes: 45 additions & 0 deletions bank_statement_utility/main_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Support module generated by PAGE version 8.0
# in conjunction with Tcl version 8.6
# Mar 25, 2024 05:08:39 PM IST platform: Linux

import tkinter as tk
from inspect import getsourcefile
from os.path import dirname

from tkinterdnd2 import TkinterDnD

import bank_statement_utility.ui.bankstatementutility as bankstatementutility
from bank_statement_utility.config import config
from bank_statement_utility.logger import log
from bank_statement_utility.version import __version__

_debug = True # False to eliminate debug printing from callback functions.


def main(*args):
"""
Main entry point for the application.
"""
global root
root = TkinterDnD.Tk()
root.protocol('WM_DELETE_WINDOW', root.destroy)

# Setting icon of master window
execution_path = dirname(getsourcefile(lambda: 0))
p1 = tk.PhotoImage(file=execution_path + '/icon.png')
root.iconphoto(False, p1)

log.info(config['Basic']['appname'] + " version: " + __version__)

# Creates a toplevel widget.
global _top1, _w1
_top1 = root
_w1 = bankstatementutility.MainScreenView(_top1)
root.mainloop()


if __name__ == '__main__':
main()
20 changes: 14 additions & 6 deletions bank_statement_utility/services/CassandraRepositoryHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import pytz as pytz
from cassandra.cluster import Cluster, PlainTextAuthProvider, NoHostAvailable

from .Utils import is_ui_execution
from ..Constants import DB_TABLE_NAME
from ..config import config
from ..logger import log
from ..model.StatementDB import StatementDB
Expand All @@ -12,7 +14,6 @@
class CassandraRepositoryHelper:
__ins_user = "App"
__IST_TIMEZONE = pytz.timezone('Asia/Kolkata')
__DB_TABLE_NAME = "bank_statement.statement"

def __init__(self):
contact_points = config['Cass']['contact.points']
Expand All @@ -35,17 +36,24 @@ def __init__(self):
log.error("Unable to connect to DB on {host}:{port} with user:{user}. DB host not available".format(
host=contact_points,
port=port, user=username))
sys.exit("Unable to connect to DB. Check logs for more details. Exiting...")

if is_ui_execution():
raise IOError("Unable to connect to DB. Check logs for more details")
else:
sys.exit("Unable to connect to DB. Check logs for more details. Exiting...")
except Exception as err:
log.error("Unknown error occur while connecting to DB. Error:{error}".format(error=err.__str__()),
exc_info=True)
sys.exit("Unknown error occur while connecting to DB. Check logs for more details. Exiting...")
if is_ui_execution():
raise IOError("Unknown error occur while connecting to DB. Check logs for more details.")
else:
sys.exit("Unknown error occur while connecting to DB. Check logs for more details. Exiting...")

def insert_data(self, data):
stmt = self.session.prepare(
"INSERT INTO {0} (bank_name,source,transaction_date,description,debit_amount,credit_amount,closing_balance,"
"cheque_ref_number,value_date,ins_date,ins_user) VALUES (?,?,?,?,?,?,?,?,?,?,?)".format(
self.__DB_TABLE_NAME)
DB_TABLE_NAME)
)

query = stmt.bind([
Expand All @@ -70,7 +78,7 @@ def get_list_by_bank_and_source_ordered(self, bank_name: str, source: str, trans
"SELECT bank_name,source,transaction_date,description,debit_amount,credit_amount,closing_balance,ins_date "
"FROM {0} "
"WHERE bank_name = %s AND source = %s AND transaction_date >= %s ALLOW FILTERING;".format(
self.__DB_TABLE_NAME))
DB_TABLE_NAME))

# Convert the datetime to ISO 8601 format (string)
iso_start_date = transaction_date.date().isoformat()
Expand Down Expand Up @@ -99,7 +107,7 @@ def get_all_ordered_by_latest(self) -> list[StatementDB]:
query = (
"SELECT transaction_date,bank_name,source,debit_amount,credit_amount,description,closing_balance,cheque_ref_number,ins_date "
"FROM {0}".format(
self.__DB_TABLE_NAME))
DB_TABLE_NAME))

# Execute and Convert the ResultSet to a list for sorting
result = self.session.execute(query)
Expand Down
Loading

0 comments on commit a801079

Please sign in to comment.