From fd2622ac2c2c504de902a4625f5a4176d40ed7c5 Mon Sep 17 00:00:00 2001 From: Lucas Mathews Date: Fri, 31 May 2024 18:27:18 +0200 Subject: [PATCH] Started implementation of new_transacion page and other minor changes --- .../__pycache__/connection.cpython-312.pyc | Bin 14829 -> 15136 bytes application/account.py | 12 +- application/connection.py | 1 + application/dashboard.py | 32 ++--- application/new_transaction.py | 129 ++++++++++++++++-- application/session_data.json | 2 +- manager.py | 4 +- 7 files changed, 151 insertions(+), 29 deletions(-) diff --git a/application/__pycache__/connection.cpython-312.pyc b/application/__pycache__/connection.cpython-312.pyc index 7f0ee9e0c81168033b23f70145f7b658f71db71e..16cfd69a1416a8a82d0750a73fabe8235b42002b 100644 GIT binary patch delta 1200 zcmZ8gO>7%Q6rR~1um3l399MQ?du@{tJ0_*jRy3_sY?ma^rV&k5ngC6W$3<>xyR5fK z2x|%lL{F&AJ0yivEm98=2`FF$9N>U+>;b3Zr?RTT0f`e*n;@k20yB<7Rc5u{eEZ(p zx8Ixh_Rr$AN&5|(&5Y=B-yc17zUOoMRX3i2&)i8o1ncfWJY3%EvEdhPVua?9Msafy ztfP<66>h0v!N`O({y#5A_$6-hX%{MCP2BRFHo&{l7Wk*jDhq^OTTvK6{|+CgSz$07 zbPQE}|S z%jREKQt5QzTwYU1MoVSS6qQp2b;~xMCA|v7ydCuT(%uOvY*u@yI{3ad1h*q*A#6~G zz~Ji(9%Y*8UF4~{chK*GscPN1GPZK4YL5RXB<^xnNV$69qVLOiv6seOv?iix!Ie<> zFXG*lR(zPDA|}F8r9)yo>P>d?t0qS>z^}Gp#seb7G=^&-XZf<RtxNDb&P# zLCX|XCo{A%coAOf%r!k4rM?2cc0Mbfp#Hh?V~@XwvB<>WdAPJqfv4>IymSU8y6D?< zVoHLJbxudRpz}mr5K9t#Y!7=4ti434qj84v^9EvKPr9^hhCl2t3kPp0 zk?MitRWflG(LZF6quVj=1a}W1$2fPvaJ!{#E_Nw)-L+_33+!0wSPMM!P2XDJ`Rd4c z_2k*LkvC}kTR6`Bg^)7=DO)?lp7g_=?w+S*^nokE^Vj3Pgp4Y@n6OF}qr|xEO`7;h z)R7eUN*|_tRS+qrQGGy0qP@I0LB)Psp51=HfM11Qb`2YHtg1XjcMpgfr8mmcyFcL) z!_0b=0h<}Mz<@PbWyPv2iOQl?iGhWIiNXWpzFdKvJCo^Cr-AGl+b-iQUJu$S-cy_u X7x@ju_VuWZq0ff<@rEx%c|FO$QlBOu delta 688 zcmY+COGuPa6vyxV9y86edCTa0^A<-&F~J;hMr)j~2Q@hM@)4nr3WM5uMwj=)n2i|M$P=p6`5ocXjr2-u&8R z(i4pBH^Wyiw>~#NcGK_h)SaSfsJf5Q0X1H4qUsml4uycy1uJeRtSA=T-B6~44L$5S zL$DLHp>fjNt3QXGW-;?M@P|_H(W}xL)Q5Z3x4wu#Z-Wqw(9;kN=5*`~mNjeGFqDG* z@&$aj2MwWLGCPk_9SRqOKpGW`bBrL~$v3BGuH>168%~Zv2sGs^|5ve#@TlpqZ5;O> zV&)quA$LecvcZI_Nv(@ys0*FHp+O~L))rx@Zqz&}&67#VjYgI(h_GgEhEEM{%e=Uy z+vH^!3(~v!8gvDhwQyDiN8D+C`>$A|3sVDqkO3&yhlV9DCKsCjR1SngTsS$7rol>_b18q0yM zQan=*WJ;sgO4o0dN9QVpq=e(b9wCksFk$i-4OADY5X6r?BTV?ZV;%T{yT(2(x$jEG z#r32MLTQj1Z#lzd`ivveAbPU?Pc2Hso z%@msK28_jy$_(X8bu;!v2oG?V-vP#dD9dq-bMQ}Pyb)tC*R9Z5SneKaGt(@s`Z>S) Z9ms-MBYds47&!6b_0yU+gqj)f>~D2FuowUU diff --git a/application/account.py b/application/account.py index b965fdb..a26ae0d 100644 --- a/application/account.py +++ b/application/account.py @@ -4,6 +4,7 @@ import tkinter as tk import sys import os +import json import customtkinter from tkinter import ttk, messagebox from config import CONFIG @@ -152,7 +153,16 @@ def save_details(): except Exception as e: messagebox.showerror("Error", f"Could not update account details: {str(e)}") - +def open_transaction_window(): + """Opens a new window for creating a new transaction.""" + try: + session = json.load(open('application\\session_data.json', 'r')) + command = f"python application\\new_transaction.py {session['client_id']}" + return_code = os.system(command) + if return_code != 0: + print(f"Error: The command failed with return code {return_code}") + except Exception as e: + print(f"Error: {e}") ############## ### Layout ### diff --git a/application/connection.py b/application/connection.py index 784eb51..429c721 100644 --- a/application/connection.py +++ b/application/connection.py @@ -117,6 +117,7 @@ def get_transactions(account_id): def get_account(account_id): """Retrieves the account details for the given account_id.""" + print(f"Getting account details for account_id: {account_id}") try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) diff --git a/application/dashboard.py b/application/dashboard.py index da63840..d92462b 100644 --- a/application/dashboard.py +++ b/application/dashboard.py @@ -8,17 +8,12 @@ import os from config import CONFIG from connection import logout_client, get_client, update_client, get_accounts, format_balance, generate_otp, change_password, new_transaction - -# Global variables email_entry = None phone_entry = None address_entry = None otp_entry = None frame = None -fields = [('Name', 'name'), ('Client ID', 'client_id'), ('Email', 'email'), ('Phone', 'phone_number'), - ('Address', 'address'), ('Account Opened', 'opening_timestamp')] - ################# ### Functions ### ################# @@ -58,7 +53,6 @@ def display_client_info(): else: frame = customtkinter.CTkFrame(root) frame.grid(row=1, column=0, padx=20, pady=20) - try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) @@ -66,6 +60,8 @@ def display_client_info(): client_info = get_client(client_id) if client_info.get('success'): client = client_info['data'] + fields = [('Name', 'name'), ('Client ID', 'client_id'), ('Email', 'email'), ('Phone', 'phone_number'), + ('Address', 'address'), ('Account Opened', 'opening_timestamp')] for i, (display_name, key) in enumerate(fields): value = client.get(key, 'N/A') label_key = customtkinter.CTkLabel(frame, text=f"{display_name}: ", font=("Helvetica", 14)) @@ -123,18 +119,14 @@ def save_details(): new_phone = phone_entry.get() if phone_entry.get() != '' else None new_address = address_entry.get() if address_entry.get() != '' else None otp_code = otp_entry.get() - if not otp_code: messagebox.showerror("Error", "OTP code must be entered.") return - with open('application\\session_data.json', 'r') as f: session_data = json.load(f) client_id = session_data['client_id'] - if not messagebox.askyesno("Confirmation", "Are you sure you want to update the details?"): return - try: result = update_client(client_id, otp_code, new_email, new_phone, new_address) if result['success']: @@ -161,7 +153,6 @@ def populate_table(): accounts = response.get('data', []) if not isinstance(accounts, list): raise ValueError(f"Expected a list of accounts, but got {type(accounts)}") - for account in accounts: formatted_balance = format_balance(account['balance']) table.insert('', 'end', values=(account['description'], account['account_id'], formatted_balance, account['account_type'])) @@ -233,26 +224,20 @@ def change_password_save(): new_password = password_entry.get() confirm_password = confirm_password_entry.get() otp_code = otp_entry.get() - if not otp_code: messagebox.showerror("Error", "OTP code must be entered.") return - if not new_password or not confirm_password: messagebox.showerror("Error", "New password and confirm password must be entered.") return - if new_password != confirm_password: messagebox.showerror("Error", "New password and confirm password do not match.") return - with open('application\\session_data.json', 'r') as f: session_data = json.load(f) client_id = session_data['client_id'] - if not messagebox.askyesno("Confirmation", "Are you sure you want to change the password?"): return - try: response = change_password(client_id, old_password, new_password, otp_code) if response['success']: @@ -263,6 +248,17 @@ def change_password_save(): except Exception as e: messagebox.showerror("Error", f"Could not change password: {str(e)}") +def open_transaction_window(): + """Opens a new window for creating a new transaction.""" + try: + session = json.load(open('application\\session_data.json', 'r')) + command = f"python application\\new_transaction.py {session['client_id']}" + return_code = os.system(command) + if return_code != 0: + print(f"Error: The command failed with return code {return_code}") + except Exception as e: + print(f"Error: {e}") + ############## ### Layout ### ############## @@ -288,7 +284,7 @@ otp_button = customtkinter.CTkButton(button_frame, text="Get OTP Code", command= otp_button.grid(row=0, column=0, padx=3) # Create the new transaction button -transaction_button = customtkinter.CTkButton(button_frame, text="New Transaction", command=new_transaction, width=100) +transaction_button = customtkinter.CTkButton(button_frame, text="New Transaction", command=open_transaction_window, width=100) transaction_button.grid(row=0, column=1, padx=3) # Create the edit client Details button diff --git a/application/new_transaction.py b/application/new_transaction.py index 89ed569..2ea9050 100644 --- a/application/new_transaction.py +++ b/application/new_transaction.py @@ -5,21 +5,84 @@ import tkinter as tk from tkinter import ttk, messagebox import customtkinter from config import CONFIG -from connection import format_balance, get_account, new_transaction, generate_otp +from connection import new_transaction, generate_otp, get_account, get_accounts import sys +import requests + +account_id = None +recipient_id = None ################# ### Functions ### ################# -if len(sys.argv) > 3: # Check if the account description is provided as a command line argument - account_id = sys.argv[1] - account_description = sys.argv[3] # This is passed so that the window can be titled appopriately +if len(sys.argv) > 1: # Check if the account description is provided as a command line argument + client_id = sys.argv[1] else: messagebox.showerror("Error", "Account ID and description were not provided by the server.") sys.exit(1) +def submit_transaction(): + """Submit a new transaction to the server.""" + global account_id, recipient_id + account_id = account_combobox.get() + recipient_id = recipient_text.get() + amount = amount_text.get() if amount_text.get() != "" else None + description = description_text.get() if description_text.get() != "" else None + if not description or not amount or not recipient_id or not account_id: + messagebox.showerror("Error", "Please fill in all fields.") + return + account_verification = verify_accounts(account_id, recipient_id) + if account_verification is False: + messagebox.showerror("Error", "Could not verify account IDs.") + return + response = new_transaction(account_id, description, amount, recipient_id) + if response is None or "data" not in response: + messagebox.showerror("Error", "Could not submit transaction.") + return + transaction_id = response["data"] + otp = generate_otp(account_id, transaction_id) + if otp is None or "data" not in otp: + messagebox.showerror("Error", "Could not generate OTP.") + return + messagebox.showinfo("Success", f"Transaction submitted successfully. OTP: {otp['data']}") +def verify_accounts(): + """Verify that the account IDs are valid.""" + try: + account = get_account(account_id) + recipient_account = get_account(recipient_id) + print(account) + print(recipient_account) + except requests.exceptions.RequestException as e: + messagebox.showerror("Error", f"Failed to get account details: {e}") + return False + + if account is None or recipient_account is None or "data" not in account or "data" not in recipient_account: + messagebox.showerror("Error", "Server did not return the expected response.") + return False + if "account_id" not in account["data"]: + messagebox.showerror("Error", "Account ID not found.") + return False + if "account_id" not in recipient_account["data"]: + messagebox.showerror("Error", "Recipient Account ID not found.") + return False + #check balance + if account["data"]["balance"] < float(amount_text.get("1.0", "end-1c")): + messagebox.showerror("Error", "Insufficient funds.") + return False + submit_button.configure(state=tk.NORMAL) # Enable the submit button if the accounts are valid + messagebox.showinfo("Success", "Accounts verified successfully.") + return True + +def populate_accounts(client_id): + """Populate the account combobox with accounts for the given client ID.""" + accounts = get_accounts(client_id) + if accounts is None or "data" not in accounts: + messagebox.showerror("Error", "Could not retrieve accounts.") + return + account_ids = [account["account_id"] for account in accounts["data"]] + account_combobox['values'] = account_ids ############## ### Layout ### @@ -27,9 +90,9 @@ else: # Initialise the main window root = customtkinter.CTk() -root.title(f"Transactions: {transaction_description}") +root.title("New Transaction") root.iconbitmap("application/luxbank.ico") -root.geometry("800x400") +root.geometry("400x600") if CONFIG["preferences"]["dark_theme"] == "dark": # Check if dark mode is enabled customtkinter.set_appearance_mode("dark") # Set the style for dark mode @@ -37,7 +100,57 @@ else: customtkinter.set_appearance_mode("light") # Set the style for light mode # Display main window title -welcome_label = customtkinter.CTkLabel(root, text=f"Transactions for: {account_description}", font=("Helvetica", 24)) -welcome_label.pack(pady=10) +welcome_label = customtkinter.CTkLabel(root, text="New Transaction", font=("Helvetica", 24)) +welcome_label.pack(pady=20) +# Create a close button at the top of the window +close_button = customtkinter.CTkButton(root, text="Cancel and Close", command=root.destroy) +close_button.pack(side="top", anchor="e", padx=10, pady=10) + +# Create the account ID label and combobox +account_label = customtkinter.CTkLabel(root, text="Account ID:", font=("Helvetica", 14)) +account_label.pack(pady=5) +account_combobox = ttk.Combobox(root, height=1, width=250) +account_combobox.pack(pady=5) + +# Create the recipient ID label and text box +recipient_label = customtkinter.CTkLabel(root, text="Recipient ID:", font=("Helvetica", 14)) +recipient_label.pack(pady=5) +recipient_text = customtkinter.CTkTextbox(root, height=2, width=250) +recipient_text.pack(pady=5) + +# Create the verify buttons +verify_button = customtkinter.CTkButton(root, text="Verify Accounts", command=verify_accounts) +verify_button.pack(pady=10) + +# Create the transaction description label and text box +description_label = customtkinter.CTkLabel(root, text="Description:", font=("Helvetica", 14)) +description_label.pack(pady=5) +description_text = customtkinter.CTkTextbox(root, height=4, width=250) +description_text.pack(pady=5) + +# Create the transaction amount label and text box +amount_label = customtkinter.CTkLabel(root, text="Amount:", font=("Helvetica", 14)) +amount_label.pack(pady=5) +amount_text = customtkinter.CTkTextbox(root, height=2, width=250) +amount_text.pack(pady=5) + +# Create the OTP button +otp_button = customtkinter.CTkButton(root, text="Request OTP", command=generate_otp) +otp_button.pack(pady=10) + +# Create the OTP label and text box +otp_label = customtkinter.CTkLabel(root, text="OTP:", font=("Helvetica", 14)) +otp_label.pack(pady=5) +otp_text = customtkinter.CTkTextbox(root, height=2, width=250) +otp_text.pack(pady=5) + +# Create the submit button +submit_button = customtkinter.CTkButton(root, text="Submit", command=submit_transaction, state=tk.DISABLED) +submit_button.pack(pady=5) + +# Populate accounts combobox with the given client_id +populate_accounts(client_id) + +# Display the main window root.mainloop() diff --git a/application/session_data.json b/application/session_data.json index 376b7b1..02b93f2 100644 --- a/application/session_data.json +++ b/application/session_data.json @@ -1 +1 @@ -{"session_cookie": {"session": "9pRNUPAL2SDrVihKeZcAAb_uw6GzpPm2uTHqrE8urjE"}, "client_id": "9755c18f"} \ No newline at end of file +{"session_cookie": {"session": "rVrt6XfG-HNcgZwJHJGmwEg8boBPPRljoCFcrRfQ-ss"}, "client_id": "9755c18f"} \ No newline at end of file diff --git a/manager.py b/manager.py index d875e7e..bb72215 100644 --- a/manager.py +++ b/manager.py @@ -162,7 +162,7 @@ def generate_otp(client_id: str): otps[client_id] = (password, time.time()) # Store the OTP and the current time return format_response(True, "OTP generated and sent successfully."), 200 except EmailSendingError as e: - print(f"Error sending email: {e}") + log_event(f"Error sending email: {str(e)}") error_message = "Error sending email. Please try again later." if e.original_error: error_message += f" Original error: {str(e.original_error)}" @@ -251,6 +251,8 @@ def get_accounts(client_id: str): @login_required def get_account(account_id:str): + """Returns a specific account in the database. If the account is not found, returns an error message.""" + print(account_id) current_client_id, is_admin = get_current_client() account_owner = session.query(Account).filter_by(account_id=account_id).one_or_none().client_id if not is_admin and account_owner != current_client_id: