From 4bf7ed5bdfa6d98fb066674be9b486cad39719a1 Mon Sep 17 00:00:00 2001 From: Sho <51792152+shonilbhide@users.noreply.github.com> Date: Thu, 12 Oct 2023 18:24:55 -0400 Subject: [PATCH 01/40] Delete docs/dollar_bot/** directory Deleting as it created problems while cloning --- docs/dollar_bot/**/*.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/dollar_bot/**/*.html diff --git a/docs/dollar_bot/**/*.html b/docs/dollar_bot/**/*.html deleted file mode 100644 index e69de29bb..000000000 From 1b6c419e4ac0c850d0332f04b7a23fe76b6ccd77 Mon Sep 17 00:00:00 2001 From: Sho <51792152+shonilbhide@users.noreply.github.com> Date: Thu, 12 Oct 2023 18:26:37 -0400 Subject: [PATCH 02/40] Delete docs/dollar_bot/code directory --- docs/dollar_bot/code/*.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/dollar_bot/code/*.html diff --git a/docs/dollar_bot/code/*.html b/docs/dollar_bot/code/*.html deleted file mode 100644 index e69de29bb..000000000 From 937f555492579af763478398f26bfebbf0603a44 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Fri, 13 Oct 2023 14:08:23 -0500 Subject: [PATCH 03/40] Issue3_bug : Code for handling null data for pdf --- code/pdf.py | 94 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/code/pdf.py b/code/pdf.py index 6996ad32f..b90650dbd 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -13,41 +13,65 @@ def run(message, bot): helper.read_json() chat_id = message.chat.id user_history = helper.getUserHistory(chat_id) - message = "Alright. I just created a pdf of your expense history!" - bot.send_message(chat_id, message) - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - top = 0.8 - if len(user_history) == 0: - plt.text( - 0.1, - top, - "No record found!", - horizontalalignment="left", - verticalalignment="center", - transform=ax.transAxes, - fontsize=20, - ) - for rec in user_history: - date, category, amount = rec.split(",") - date, time = date.split(" ") - print(date, category, amount) - rec_str = f"{amount}$ {category} expense on {date} at {time}" - plt.text( - 0, - top, - rec_str, - horizontalalignment="left", - verticalalignment="center", - transform=ax.transAxes, - fontsize=14, - bbox=dict(facecolor="red", alpha=0.3), - ) - top -= 0.15 - plt.axis("off") - plt.savefig("expense_history.pdf") - plt.close() - bot.send_document(chat_id, open("expense_history.pdf", "rb")) + print('User-history--> ',user_history) + + #Issue 3 - added the if condition - start + if user_history != None: + #Issue 3 - added the if condition - end + message = "Alright. I just created a pdf of your expense history!" + bot.send_message(chat_id, message) + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + top = 0.8 + if len(user_history) == 0: + plt.text( + 0.1, + top, + "No record found!", + horizontalalignment="left", + verticalalignment="center", + transform=ax.transAxes, + fontsize=20, + ) + for rec in user_history: + date, category, amount = rec.split(",") + date, time = date.split(" ") + print(date, category, amount) + rec_str = f"{amount}$ {category} expense on {date} at {time}" + plt.text( + 0, + top, + rec_str, + horizontalalignment="left", + verticalalignment="center", + transform=ax.transAxes, + fontsize=14, + bbox=dict(facecolor="red", alpha=0.3), + ) + top -= 0.15 + plt.axis("off") + plt.savefig("expense_history.pdf") + plt.close() + bot.send_document(chat_id, open("expense_history.pdf", "rb")) + + #Issue 3 - added the else condition - start + else: + message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf." + bot.send_message(chat_id, message) + + display_text = "" + commands = helper.getCommands() + for ( + c + ) in ( + commands + ): # generate help text out of the commands dictionary defined at the top + display_text += "/" + c + ": " + display_text += commands[c] + "\n" + bot.send_message(chat_id, "Please select a menu option from below:") + bot.send_message(chat_id, display_text) + #Issue 3 - added the else condition - end + except Exception as e: logging.exception(str(e)) bot.reply_to(message, "Oops!" + str(e)) From adbb669e2e03993eeae2295f5e11e65bc8d2aad5 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Fri, 13 Oct 2023 15:59:28 -0400 Subject: [PATCH 04/40] changes for iss#1 to add feature foe new category --- code/add.py | 8 +++++-- code/add_category.py | 52 +++++++++++++++++++++++++++++++++++++++++++ code/code.py | 13 +++++++++-- code/helper.py | 4 ++++ docs/add.md | 1 + docs/add_category.md | 40 +++++++++++++++++++++++++++++++++ python_docs/code.html | 5 ++++- 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 code/add_category.py create mode 100644 docs/add_category.md diff --git a/code/add.py b/code/add.py index 906956ad1..dd92b0f71 100644 --- a/code/add.py +++ b/code/add.py @@ -22,8 +22,12 @@ def run(message, bot): option.pop(chat_id, None) # remove temp choice markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 - m = bot.send_message(chat_id, "Do you want to add a new category? Y/N") - bot.register_next_step_handler(m, post_user_def_category, bot) + m = bot.send_message(chat_id, "Select a category") + for c in helper.getSpendCategories(): + markup.add(c) + msg = bot.reply_to(message, "Select Category", reply_markup=markup) + bot.register_next_step_handler(msg, post_category_selection, bot) + def post_user_def_category(message, bot): diff --git a/code/add_category.py b/code/add_category.py new file mode 100644 index 000000000..52d97f747 --- /dev/null +++ b/code/add_category.py @@ -0,0 +1,52 @@ +import helper +import logging +from telebot import types +from datetime import datetime + + +option = {} + +# === Documentation of add.py === + + +def run(message, bot): + """ + run(message, bot): This is the main function used to implement the add feature. + It pop ups a menu on the bot asking the user to choose their expense category, + after which control is given to post_category_selection(message, bot) for further proccessing. + It takes 2 arguments for processing - message which is the message from the user, + and bot which is the telegram bot object from the main code.py function. + """ + helper.read_json() + chat_id = message.chat.id + option.pop(chat_id, None) # remove temp choice + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + message1 = bot.send_message(chat_id, "Please enter your category") + bot.register_next_step_handler(message1, post_append_spend, bot) + + + +def post_append_spend(message, bot): + chat_id = message.chat.id + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + selected_category = message.text + if selected_category.lower() in [x.lower() for x in helper.spend_categories]: + bot.send_message( + chat_id, "Category already exists", reply_markup=types.ReplyKeyboardRemove() + ) + message1 = bot.send_message(chat_id, "Please enter a new category") + bot.register_next_step_handler(message1, post_append_spend, bot) + + else: + helper.spend_categories.append(selected_category) + for c in helper.getSpendCategories(): + markup.add(c) + bot.send_message( + chat_id, + "The following category has been added: {} ".format( + selected_category + ), + ) + diff --git a/code/code.py b/code/code.py index 10bc43540..7f8eedafd 100644 --- a/code/code.py +++ b/code/code.py @@ -11,6 +11,7 @@ import estimate import delete import add +import add_category import budget from datetime import datetime from jproperties import Properties @@ -96,7 +97,8 @@ def faq(m): ('"What does this bot do?"\n' ">> DollarBot lets you manage your expenses so you can always stay on top of them! \n\n" '"How can I add an epxense?" \n' - ">> Type /add, then select a category to type the expense. \n\n" + ">> Type /add_category, then add a category for the expense. \n\n" + ">> Type /add_category, then select a category to type the expense. \n\n" '"Can I see history of my expenses?" \n' ">> Yes! Use /display to get a graphical display, or /history to view detailed summary.\n\n" '"I added an incorrect expense. How can I edit it?"\n' @@ -151,7 +153,14 @@ def command_add(message): """ add.run(message, bot) - +@bot.message_handler(commands=["add_category"]) +def command_add_category(message): + """ + command_add(message) Takes 1 argument message which contains the message from + the user along with the chat ID of the user chat. It then calls add.py to run to execute + the add functionality. Commands used to run this: commands=['add'] + """ + add_category.run(message, bot) # function to fetch expenditure history of the user diff --git a/code/helper.py b/code/helper.py index 70c8c48d4..2f5ff5369 100644 --- a/code/helper.py +++ b/code/helper.py @@ -32,6 +32,10 @@ \n 1. It will give you the list of categories to choose from. \ \n 2. You will be prompted to enter the amount corresponding to your spending \ \n 3.The message will be prompted to notify the addition of your expense with the amount,date, time and category ", + "add_category": "This option is for adding new category \ + \n 1. You will be prompted to enter a new category \ + \n 2.The message will be prompted to notify the addition of your category ", + "display": "This option gives user a graphical representation(bar graph) of their expenditures \ \n You will get an option to choose from day or month for better analysis of the expenses.", "estimate": "This option gives you the estimate of expenditure for the next day/month. It calcuates based on your recorded spendings", diff --git a/docs/add.md b/docs/add.md index 118589551..63c8f3770 100644 --- a/docs/add.md +++ b/docs/add.md @@ -1,4 +1,5 @@ # About MyDollarBot's /add Feature +#TO-DO This feature enables the user to add a new expense to their expense tracker. Currently we have the following expense categories set by default: diff --git a/docs/add_category.md b/docs/add_category.md new file mode 100644 index 000000000..01185bc52 --- /dev/null +++ b/docs/add_category.md @@ -0,0 +1,40 @@ +# About MyDollarBot's /add_category Feature +#TO-DO +This feature enables the user to add a new category. +Currently we have the following expense categories set by default: + +- Food +- Groceries +- Utilities +- Transport +- Shopping +- Miscellaneous + + +# Location of Code for this Feature +The code that implements this feature can be found [here](https://github.com/sak007/MyDollarBot-BOTGo/blob/main/code/add_category.py) + +# Code Description +## Functions + +1. run(message, bot): +This is the main function used to implement the add_category feature. It asks the user to add a new category, after which control is given to post_append_spend(message, bot) for further proccessing. It takes 2 arguments for processing - **message** which is the message from the user, and **bot** which is the telegram bot object from the main code.py function. + +2. post_append_spend(message, bot): + It takes 2 arguments for processing - **message** which is the message from the user, and **bot** which is the telegram bot object from the run(message, bot): function in the add_category.py file. It requests the user to enter the a new category. It also handles the case of keeping the category names unique. + + +# How to run this feature? +Once the project is running(please follow the instructions given in the main README.md for this), please type /add_category into the telegram bot. + +Sho, [13-10-2023 15:04] +/add_category + +testbot_SSAR, [13-10-2023 15:13] +Please enter your category + +Sho, [13-10-2023 15:13] +vehicle + +testbot_SSAR, [13-10-2023 15:13] +The following category has been added: vehicle \ No newline at end of file diff --git a/python_docs/code.html b/python_docs/code.html index 187903a33..b02ce5b58 100644 --- a/python_docs/code.html +++ b/python_docs/code.html @@ -158,7 +158,10 @@

Documen faq_message = '"What does this bot do?"\n' + \ '>> DollarBot lets you manage your expenses so you can always stay on top of them! \n\n' + \ - '"How can I add an epxense?" \n' + \ + '"How can I add a category?" \n' + \ + '>> Type /add_category, then add a category for the expense. \n\n' + \ + + '"How can I add an expense?" \n' + \ '>> Type /add, then select a category to type the expense. \n\n' + \ '"Can I see history of my expenses?" \n' + \ '>> Yes! Use /display to get a graphical display, or /history to view detailed summary.\n\n' + \ From bf912a6e97b21b2b0a544eb7cab3108cd1fbd63c Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Fri, 13 Oct 2023 16:34:45 -0400 Subject: [PATCH 05/40] Issue_6_Enhancement: Code changes done to show category list after add command. --- code/add.py | 8 ++++---- user.properties | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/add.py b/code/add.py index dd92b0f71..6ddef135a 100644 --- a/code/add.py +++ b/code/add.py @@ -22,11 +22,11 @@ def run(message, bot): option.pop(chat_id, None) # remove temp choice markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 - m = bot.send_message(chat_id, "Select a category") + categories = [] for c in helper.getSpendCategories(): - markup.add(c) - msg = bot.reply_to(message, "Select Category", reply_markup=markup) - bot.register_next_step_handler(msg, post_category_selection, bot) + categories.append(c) + m = bot.send_message(chat_id, f"Following are the list of categories:\n{categories}\n Do you want to add new category? Y/N") + bot.register_next_step_handler(m, post_user_def_category, bot) diff --git a/user.properties b/user.properties index 6923b0754..d197af9de 100644 --- a/user.properties +++ b/user.properties @@ -1,2 +1,3 @@ -api_token=2124576840:AAF4GNT5QuNmfnOFjfCwU4JPu2xOqkOVgJA +api_token=6533627668:AAElMwQQPlMcQARxTt8c_ZNx-SniR2uUoFU + From c1ec38f588497aef5022a00e88ae679b01033136 Mon Sep 17 00:00:00 2001 From: sakshibasapure <40641044+sakshibasapure@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:39:19 -0400 Subject: [PATCH 06/40] Updated user.properties --- user.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.properties b/user.properties index d197af9de..fe5a204c9 100644 --- a/user.properties +++ b/user.properties @@ -1,3 +1,3 @@ -api_token=6533627668:AAElMwQQPlMcQARxTt8c_ZNx-SniR2uUoFU +api_token=2124576840:AAF4GNT5QuNmfnOFjfCwU4JPu2xOqkOVgJA From e696f1f6eeeebb4e7106b3e71b0cd9f49a169444 Mon Sep 17 00:00:00 2001 From: agmalpur <144184451+agmalpur@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:49:17 -0400 Subject: [PATCH 07/40] Update history.md changing the documentation according to changes in issue#8 --- docs/history.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/docs/history.md b/docs/history.md index c259a341a..739d94824 100644 --- a/docs/history.md +++ b/docs/history.md @@ -13,23 +13,11 @@ This is the main function used to implement the delete feature. It takes 2 argum # How to run this feature? Once the project is running(please follow the instructions given in the main README.md for this), please type /add into the telegram bot. -Below you can see an example in text format: - -Sri Athithya Kruth, [20.10.21 20:33] -/display - -Sri Athithya Kruth, [20.10.21 20:33] -Day - -mydollarbot20102021, [20.10.21 20:33] -Hold on! Calculating... - -Sri Athithya Kruth, [20.10.21 20:53] +AGM, [13-10-2023 05:48 PM] /history -mydollarbot20102021, [20.10.21 20:53] -Here is your spending history : -DATE, CATEGORY, AMOUNT ----------------------- -20-Oct-2021 20:33,Transport,1022.0 -20-Oct-2021 20:33,Groceries,12.0 \ No newline at end of file +agmalpur, [13-10-2023 05:48 PM] +| DATE | CATEGORY | AMOUNT | ++-------------------+-------------------+-------------+ +| 13-Oct-2023 16:55 | Transport | 8.0 | ++-------------------+-------------------+-------------+ From 2d572b80994aee6260eb8d4c46ced6afe660c252 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Fri, 13 Oct 2023 17:54:00 -0400 Subject: [PATCH 08/40] Added Tabular format to display history --- code/history.py | 59 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/code/history.py b/code/history.py index c52978512..df99dcad9 100644 --- a/code/history.py +++ b/code/history.py @@ -1,6 +1,7 @@ import helper import logging - +import csv +from io import StringIO # === Documentation of history.py === @@ -12,20 +13,62 @@ def run(message, bot): historical data and based on whether there is data available, it either prints an error message or displays the user's historical data. """ + + # try: + # helper.read_json() + # chat_id = message.chat.id + # user_history = helper.getUserHistory(chat_id) + + # if user_history is None: + # raise Exception("Sorry! No spending records found!") + + # if len(user_history) == 0: + # bot.send_message(chat_id, "Sorry! No spending records found!") + # else: + # # Create a CSV representation of the data + # csv_data = "DATE, CATEGORY, AMOUNT\n" + + # for line in user_history: + # rec = line.split(",") # Assuming data is comma-separated + # if len(rec) == 3: + # csv_data += f"{rec[0]}, {rec[1]}, {rec[2]}\n" + + # # Send the CSV data as a text message + # bot.send_message(chat_id, csv_data) + + # except Exception as e: + # logging.exception(str(e)) + # bot.reply_to(message, "Oops! " + str(e)) + try: helper.read_json() chat_id = message.chat.id user_history = helper.getUserHistory(chat_id) - spend_total_str = "" + if user_history is None: raise Exception("Sorry! No spending records found!") - spend_total_str = "Here is your spending history : \nDATE, CATEGORY, AMOUNT\n----------------------\n" + if len(user_history) == 0: - spend_total_str = "Sorry! No spending records found!" + bot.send_message(chat_id, "Sorry! No spending records found!") else: - for rec in user_history: - spend_total_str += str(rec) + "\n" - bot.send_message(chat_id, spend_total_str) + # Create a tabular representation of the data + tabular_data = "```" + tabular_data += "+-------------------+-------------------+-------------+\n" + tabular_data += "| DATE | CATEGORY | AMOUNT |\n" + tabular_data += "+-------------------+-------------------+-------------+\n" + + for line in user_history: + rec = line.split(",") # Assuming data is comma-separated + if len(rec) == 3: + tabular_data += "| {:<15} | {:<17} | {:<11} |\n".format(rec[0], rec[1], rec[2]) + + tabular_data += "+-------------------+-------------------+-------------+" + tabular_data += "```" + + # Send the tabular data as a Markdown-formatted message + bot.send_message(chat_id, tabular_data, parse_mode="Markdown") + except Exception as e: logging.exception(str(e)) - bot.reply_to(message, "Oops!" + str(e)) + bot.reply_to(message, "Oops! " + str(e)) + From 0cdcb8aa86bb12b7df413540da76cb8347459905 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Sat, 14 Oct 2023 23:37:39 -0400 Subject: [PATCH 09/40] Issue_9_Enhancement: Registration of new users --- code/add_user.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ code/code.py | 7 ++++++ code/helper.py | 1 + 3 files changed, 67 insertions(+) create mode 100644 code/add_user.py diff --git a/code/add_user.py b/code/add_user.py new file mode 100644 index 000000000..3a41e4899 --- /dev/null +++ b/code/add_user.py @@ -0,0 +1,59 @@ +import helper +import logging +from telebot import types + +# Initialize a dictionary to store registered users +registered_users = {} +user_list=helper.read_json() +def register_people(message, bot): + chat_id = message.chat.id + if "users" in user_list[str(chat_id)].keys(): + registered_users={chat_id : user_list[str(chat_id)]["users"]} + else: + registered_users = {} + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + msg = bot.send_message(chat_id, "Enter the name of the person you want to register:") + bot.register_next_step_handler(msg, add_person, bot,registered_users) + +def add_person(message, bot,registered_users): + chat_id = message.chat.id + name = message.text + + # Check if the name is unique for this chat_id + if chat_id in registered_users and name in registered_users[chat_id]: + bot.send_message(chat_id, f"{name} is already registered.") + else: + if chat_id not in registered_users.keys(): + registered_users[chat_id] = [] + + registered_users[chat_id].append(name) + + bot.send_message(chat_id, f"{name} has been registered successfully!") + + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + markup.add("Register Another Person", "Finish Registration") + msg = bot.send_message(chat_id, "What would you like to do next?", reply_markup=markup) + + bot.register_next_step_handler(msg, handle_registration_choice, bot,registered_users) + +def handle_registration_choice(message, bot,registered_users): + chat_id = message.chat.id + choice = message.text + + if choice == "Register Another Person": + msg = bot.send_message(chat_id, "Enter the name of the person you want to register:") + bot.register_next_step_handler(msg, add_person, bot,registered_users) + elif choice == "Finish Registration": + # Display the names of registered users + if chat_id in registered_users: + users = registered_users[chat_id] + user_list[str(chat_id)]["users"]=users + helper.write_json(user_list) + if users: + bot.send_message(chat_id, "Registered Users:\n" + '\n'.join(registered_users[chat_id])) + else: + bot.send_message(chat_id, "No users registered yet.") + else: + bot.send_message(chat_id, "Invalid choice. Please select a valid option.") diff --git a/code/code.py b/code/code.py index 7f8eedafd..a9131bcf6 100644 --- a/code/code.py +++ b/code/code.py @@ -13,6 +13,7 @@ import add import add_category import budget +import add_user from datetime import datetime from jproperties import Properties @@ -153,6 +154,12 @@ def command_add(message): """ add.run(message, bot) + +@bot.message_handler(commands=["add_user"]) +def command_add_user(message): + chat_id = message.chat.id + add_user.register_people(message,bot) + @bot.message_handler(commands=["add_category"]) def command_add_category(message): """ diff --git a/code/helper.py b/code/helper.py index 2f5ff5369..da63ae72c 100644 --- a/code/helper.py +++ b/code/helper.py @@ -28,6 +28,7 @@ commands = { "help": "Display the list of commands.", "pdf": "Save history as PDF.", + "add_user": "Add users to expense tracker", "add": "This option is for adding your expenses \ \n 1. It will give you the list of categories to choose from. \ \n 2. You will be prompted to enter the amount corresponding to your spending \ From d08df8c66ac381b64a47a5dcf1a897f684517f57 Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Sun, 15 Oct 2023 16:44:45 -0400 Subject: [PATCH 10/40] Issue_16_Enhancement: Added delete_expense feature --- code/code.py | 11 ++++ code/delete_expense.py | 114 +++++++++++++++++++++++++++++++++++++++++ code/helper.py | 3 +- 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 code/delete_expense.py diff --git a/code/code.py b/code/code.py index 7f8eedafd..78e28bcfd 100644 --- a/code/code.py +++ b/code/code.py @@ -12,6 +12,7 @@ import delete import add import add_category +import delete_expense import budget from datetime import datetime from jproperties import Properties @@ -223,6 +224,16 @@ def command_delete(message): """ delete.run(message, bot) +# handles "/delete_expense" command +@bot.message_handler(commands=["delete_expense"]) +def command_delete(message): + """ + command_delete(message): Takes 1 argument message which contains the message from the user + along with the chat ID of the user chat. It then calls delete_expense.py to run to execute the add functionality. + Commands used to run this: commands=['display'] + """ + delete_expense.run(message, bot) + @bot.message_handler(commands=["budget"]) def command_budget(message): diff --git a/code/delete_expense.py b/code/delete_expense.py new file mode 100644 index 000000000..5e1528fca --- /dev/null +++ b/code/delete_expense.py @@ -0,0 +1,114 @@ +import helper +from telebot import types +import history +# === Documentation of delete_expense.py === + +def run(m, bot): + """ + run(message, bot): This is the main function used to implement the delete feature. + It takes 2 arguments for processing - message which is the message from the user, and + bot which is the telegram bot object from the main code.py function. It gets the details + for the expense to be edited from here and passes control onto edit2(m, bot): for further processing. + """ + chat_id = m.chat.id + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + for c in helper.getUserHistory(chat_id): + expense_data = c.split(",") + str_date = "Date=" + expense_data[0] + str_category = ",\t\tCategory=" + expense_data[1] + str_amount = ",\t\tAmount=$" + expense_data[2] + markup.add(str_date + str_category + str_amount) + info = bot.reply_to(m, "Select expense to be deleted:", reply_markup=markup) + bot.register_next_step_handler(info, select_category_to_be_deleted, bot) + +def select_category_to_be_deleted(m, bot): + info = m.text + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + choice = bot.reply_to(m, "Are you sure you want to delete? Y/N") + bot.register_next_step_handler(choice, delete_selected_data, bot, info) + + + +def delete_selected_data(message, bot, selected_data): + chat_id = message.chat.id + user_history = helper.getUserHistory(chat_id) + + # Check if the user has selected any data + if not selected_data: + bot.send_message(chat_id, "No data selected for deletion.") + return + + if str(message.text) == "Y" or str(message.text) == "y": + # Initialize a list to keep track of the deleted records + deleted_records = [] + + components = selected_data.split(',') + formatted_data = [] + + for component in components: + key, value = component.split('=') + value = value.strip() + + # Check if the component is the "Amount" and remove the '$' sign + if key.strip() == "Amount" and value.startswith("$"): + value = value[1:] + + formatted_data.append(value) + + formatted_string = ','.join(formatted_data) + + # Compare each item in user_history with selected_data + for expense_data in user_history: + if formatted_string in expense_data: + user_history.remove(expense_data) + deleted_records.append(expense_data) + + # Update the user's history + user_list = helper.read_json() + user_list[str(chat_id)]["data"] = user_history + helper.write_json(user_list) + + # Provide feedback to the user about the deleted records + if deleted_records: + bot.send_message(chat_id, "The following record has been deleted:") + # Create a tabular representation of the data + tabular_data = "```" + tabular_data += "+-------------------+-------------------+-------------+\n" + tabular_data += "| DATE | CATEGORY | AMOUNT |\n" + tabular_data += "+-------------------+-------------------+-------------+\n" + + for line in deleted_records: + rec = line.split(",") # Assuming data is comma-separated + if len(rec) == 3: + tabular_data += "| {:<15} | {:<17} | {:<11} |\n".format(rec[0], rec[1], rec[2]) + + tabular_data += "+-------------------+-------------------+-------------+" + tabular_data += "```" + + # Send the tabular data as a Markdown-formatted message + bot.send_message(chat_id, tabular_data, parse_mode="Markdown") + + msg = bot.send_message(chat_id, "Do you want to see the updated expense history? Y/N") + bot.register_next_step_handler(msg, show_updated_expense_history, bot) + + else: + bot.send_message(chat_id, "No matching records found for deletion.") + else: + bot.send_message(chat_id, "No data deleted.") + +def show_updated_expense_history(message, bot): + if str(message.text) == "Y" or str(message.text) == "y": + history.run(message, bot) + +# function to delete a record +def deleteHistory(chat_id): + """ + deleteHistory(chat_id): It takes 1 argument for processing - chat_id which is the + chat_id of the user whose data is to deleted from the user list. It removes this entry from the user list. + """ + global user_list + if str(chat_id) in user_list: + del user_list[str(chat_id)] + return user_list diff --git a/code/helper.py b/code/helper.py index 2f5ff5369..d6f66d42c 100644 --- a/code/helper.py +++ b/code/helper.py @@ -4,7 +4,6 @@ from datetime import datetime from notify import notify - spend_categories = [ "Food", "Groceries", @@ -41,6 +40,7 @@ "estimate": "This option gives you the estimate of expenditure for the next day/month. It calcuates based on your recorded spendings", "history": "This option is to give you the detailed summary of your expenditure with Date, time ,category and amount. A quick lookup into your spendings", "delete": "This option is to Clear/Erase all your records", + "delete_expense": "This option is to Clear/Erase individual record from expense history records.", "edit": "This option helps you to go back and correct/update the missing details \ \n 1. It will give you the list of your expenses you wish to edit \ \n 2. It will let you change the specific field based on your requirements like amount/date/category", @@ -113,7 +113,6 @@ def getUserHistory(chat_id): return data["data"] return None - def getUserData(chat_id): user_list = read_json() if user_list is None: From 1a81087dcbd9a8af0a9f72b6f2b1f540d24dbbe3 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Sun, 15 Oct 2023 17:32:55 -0500 Subject: [PATCH 11/40] Issue15_Enhancement-modified generated pdf format --- code/pdf.py | 64 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/code/pdf.py b/code/pdf.py index b90650dbd..3d678c5bb 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -2,6 +2,10 @@ import logging from matplotlib import pyplot as plt +#Issue 15 - Added tabulate and fpdf libraries +from tabulate import tabulate +from fpdf import FPDF + # === Documentation of pdf.py === @@ -33,26 +37,46 @@ def run(message, bot): transform=ax.transAxes, fontsize=20, ) - for rec in user_history: - date, category, amount = rec.split(",") - date, time = date.split(" ") - print(date, category, amount) - rec_str = f"{amount}$ {category} expense on {date} at {time}" - plt.text( - 0, - top, - rec_str, - horizontalalignment="left", - verticalalignment="center", - transform=ax.transAxes, - fontsize=14, - bbox=dict(facecolor="red", alpha=0.3), - ) - top -= 0.15 - plt.axis("off") - plt.savefig("expense_history.pdf") - plt.close() - bot.send_document(chat_id, open("expense_history.pdf", "rb")) + + #issue 15 - modified the format of pdf document - start + table_data = [entry.split(',') for entry in user_history] + + # Create a PDF document + pdf = FPDF() + pdf.add_page() + + # Set font + pdf.set_font("helvetica", size=12) + + # Define the table columns + columns = ["Date & Time", "Category", "Amount"] + + # Create a table and set its properties + pdf.set_fill_color(135, 206, 235) # Light blue + pdf.set_font(style="B") + pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) + pdf.set_fill_color(255, 255, 255) # White + pdf.ln() + pdf.ln() + # Add table headers + pdf.set_font("helvetica", size=12, style="B") + for col in columns: + pdf.cell(64, 10, col, border=1, align="C", fill=True) + pdf.ln() + + # Add table data + pdf.set_font("helvetica", size=10) + for row in table_data: + for item in row: + pdf.cell(64, 10, item, border=1, align="C", fill=True) + pdf.ln() + + # Save the PDF + pdf.output("expense_report.pdf") + bot.send_document(chat_id, open("expense_report.pdf", "rb")) + print("PDF generated successfully.") + #issue 15 - modified the format of pdf document - start + #Issue 3 - added the else condition - start else: From 23cf646167cf1eb86dcde2ff1cd80ac36e2188ed Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Sun, 15 Oct 2023 18:01:33 -0400 Subject: [PATCH 12/40] Adding Fn to add expeses as per issue18 --- code/add.py | 109 +++++++++++++++++++++++++++-------------------- code/add_user.py | 28 +++++++++--- code/code.py | 18 +++++--- 3 files changed, 95 insertions(+), 60 deletions(-) diff --git a/code/add.py b/code/add.py index 6ddef135a..dbb86f342 100644 --- a/code/add.py +++ b/code/add.py @@ -1,3 +1,5 @@ + + import helper import logging from telebot import types @@ -6,8 +8,6 @@ option = {} -# === Documentation of add.py === - def run(message, bot): """ @@ -17,46 +17,67 @@ def run(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object from the main code.py function. """ - helper.read_json() + user_list=helper.read_json() + chat_id = message.chat.id + owed_by =[] chat_id = message.chat.id option.pop(chat_id, None) # remove temp choice - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - markup.row_width = 2 - categories = [] - for c in helper.getSpendCategories(): - categories.append(c) - m = bot.send_message(chat_id, f"Following are the list of categories:\n{categories}\n Do you want to add new category? Y/N") - bot.register_next_step_handler(m, post_user_def_category, bot) + if str(chat_id) not in user_list: + user_list[str(chat_id)] = helper.createNewUserRecord() + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = len(user_list[str(chat_id)]["users"]) + for c in user_list[str(chat_id)]["users"]: + markup.add(c) + m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup) + bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None) -def post_user_def_category(message, bot): +def select_user(message,bot,owed_by,user_list,paid_by): + chat_id = message.chat.id + text_m = message.text + if text_m in user_list[str(chat_id)]["users"]: + paid_by = text_m markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 + + for c in [item for item in user_list[str(chat_id)]["users"] if item not in owed_by]: + markup.add(c) + m = bot.send_message(chat_id, "Select who shares the Expense",reply_markup=markup) + bot.register_next_step_handler(m, add_shared_user, bot,owed_by,user_list,paid_by) + +def add_shared_user(message,bot,owed_by,user_list,paid_by): chat_id = message.chat.id - if str(message.text) == "Y" or str(message.text) == "y": - message1 = bot.send_message(chat_id, "Please enter your category") - bot.register_next_step_handler(message1, post_append_spend, bot) + user = message.text + if user in user_list[str(chat_id)]["users"]: + owed_by.append(user) else: - for c in helper.getSpendCategories(): - markup.add(c) - msg = bot.reply_to(message, "Select Category", reply_markup=markup) - bot.register_next_step_handler(msg, post_category_selection, bot) + pass + choice = bot.reply_to(message, "Do you want to add more user to share the expense? Y/N") + bot.register_next_step_handler(choice, user_choice, bot, owed_by,user_list,paid_by) + +def user_choice(message, bot,owed_by, user_list,paid_by): + chat_id = message.chat.id + Choice = message.text + if Choice == "Y": + select_user(message,bot,owed_by,user_list,paid_by) + elif Choice == "N": + post_append_spend(message,bot,owed_by,paid_by) -def post_append_spend(message, bot): +def post_append_spend(message, bot,owed_by,paid_by): + chat_id = message.chat.id markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 - selected_category = message.text - helper.spend_categories.append(selected_category) + m = bot.send_message(chat_id, "Select a category") for c in helper.getSpendCategories(): - markup.add(c) + markup.add(c) msg = bot.reply_to(message, "Select Category", reply_markup=markup) - bot.register_next_step_handler(msg, post_category_selection, bot) + bot.register_next_step_handler(msg, post_category_selection, bot,owed_by,paid_by) -def post_category_selection(message, bot): +def post_category_selection(message, bot,owed_by,paid_by): """ post_category_selection(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object @@ -83,7 +104,7 @@ def post_category_selection(message, bot): ), ) bot.register_next_step_handler( - message, post_amount_input, bot, selected_category + message, post_amount_input, bot, selected_category,owed_by,paid_by ) except Exception as e: logging.exception(str(e)) @@ -101,7 +122,7 @@ def post_category_selection(message, bot): bot.send_message(chat_id, display_text) -def post_amount_input(message, bot, selected_category): +def post_amount_input(message, bot, selected_category,owed_by,paid_by): """ post_amount_input(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot @@ -110,14 +131,8 @@ def post_amount_input(message, bot, selected_category): calls add_user_record to store it. """ try: - print("---------------------------------------------------") - chat_id = message.chat.id - print(chat_id) amount_entered = message.text - print("0000000000000000000000000000000000000000000000000") - print(amount_entered) - print(selected_category) amount_value = helper.validate_entered_amount(amount_entered) # validate if amount_value == 0: # cannot be $0 spending raise Exception("Spent amount has to be a non-zero number.") @@ -134,7 +149,7 @@ def post_amount_input(message, bot, selected_category): helper.write_json( add_user_record( - chat_id, "{},{},{}".format(date_str, category_str, amount_str) + chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by ) ) @@ -144,30 +159,30 @@ def post_amount_input(message, bot, selected_category): amount_str, category_str, date_str ), ) - helper.display_remaining_budget(message, bot, selected_category) except Exception as e: logging.exception(str(e)) bot.reply_to(message, "Oh no. " + str(e)) -def add_user_record(chat_id, record_to_be_added): +def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): """ add_user_record(chat_id, record_to_be_added): Takes 2 arguments - chat_id or the chat_id of the user's chat, and record_to_be_added which is the expense record to be added to the store. It then stores this expense record in the store. """ user_list = helper.read_json() - print("!" * 5) - print("before") - print(user_list) - print("!" * 5) if str(chat_id) not in user_list: user_list[str(chat_id)] = helper.createNewUserRecord() - - user_list[str(chat_id)]["data"].append(record_to_be_added) - - print("!" * 5) - print("after") - print(user_list) - print("!" * 5) - return user_list + owed_amount = float(amount_value)/len(set(owed_by)) + if "data" in user_list[str(chat_id)]: + user_list[str(chat_id)]["data"].append(record_to_be_added) + else: + user_list[str(chat_id)]["data"] = [record_to_be_added] + user_list[str(chat_id)]["owed"][paid_by] += float(amount_value) + for user in set(owed_by): + if user == paid_by: + user_list[str(chat_id)]["owed"][paid_by] -= owed_amount + elif paid_by in user_list[str(chat_id)]["owing"][user].keys(): + user_list[str(chat_id)]["owing"][user][paid_by] += owed_amount + else: + user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount diff --git a/code/add_user.py b/code/add_user.py index 3a41e4899..f9b09a073 100644 --- a/code/add_user.py +++ b/code/add_user.py @@ -4,9 +4,11 @@ # Initialize a dictionary to store registered users registered_users = {} -user_list=helper.read_json() -def register_people(message, bot): +# user_list=helper.read_json() +def register_people(message, bot,user_list): chat_id = message.chat.id + if str(chat_id) not in user_list: + user_list[str(chat_id)] = helper.createNewUserRecord() if "users" in user_list[str(chat_id)].keys(): registered_users={chat_id : user_list[str(chat_id)]["users"]} else: @@ -14,9 +16,9 @@ def register_people(message, bot): markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 msg = bot.send_message(chat_id, "Enter the name of the person you want to register:") - bot.register_next_step_handler(msg, add_person, bot,registered_users) + bot.register_next_step_handler(msg, add_person, bot,registered_users,user_list) -def add_person(message, bot,registered_users): +def add_person(message, bot,registered_users,user_list): chat_id = message.chat.id name = message.text @@ -36,20 +38,32 @@ def add_person(message, bot,registered_users): markup.add("Register Another Person", "Finish Registration") msg = bot.send_message(chat_id, "What would you like to do next?", reply_markup=markup) - bot.register_next_step_handler(msg, handle_registration_choice, bot,registered_users) + bot.register_next_step_handler(msg, handle_registration_choice, bot,registered_users,user_list) -def handle_registration_choice(message, bot,registered_users): +def handle_registration_choice(message, bot,registered_users,user_list): chat_id = message.chat.id choice = message.text if choice == "Register Another Person": msg = bot.send_message(chat_id, "Enter the name of the person you want to register:") - bot.register_next_step_handler(msg, add_person, bot,registered_users) + bot.register_next_step_handler(msg, add_person, bot,registered_users,user_list) elif choice == "Finish Registration": # Display the names of registered users if chat_id in registered_users: users = registered_users[chat_id] user_list[str(chat_id)]["users"]=users + for user in users: + if str(chat_id) in user_list: + if "owed" in user_list[str(chat_id)]: + user_list[str(chat_id)]["owed"][user] = 0 + else: + user_list[str(chat_id)]["owed"] = {user: 0} + if "owing" in user_list[str(chat_id)]: + user_list[str(chat_id)]["owing"][user] = {} + else: + user_list[str(chat_id)]["owing"] = {user: {}} + else: + user_list[str(chat_id)] = {"owed": {user: 0},"owing": {user: {}}} helper.write_json(user_list) if users: bot.send_message(chat_id, "Registered Users:\n" + '\n'.join(registered_users[chat_id])) diff --git a/code/code.py b/code/code.py index 6dffd0f49..6b46ef941 100644 --- a/code/code.py +++ b/code/code.py @@ -22,7 +22,7 @@ with open("user.properties", "rb") as read_prop: configs.load(read_prop) - +user_list = helper.read_json() api_token = str(configs.get("api_token").data) bot = telebot.TeleBot(api_token) @@ -83,7 +83,6 @@ def help(m): commands = helper.getCommands() for c in commands: message += "/" + c + ", " - # message += commands[c] + "\n\n" message += "\nUse /menu for detailed instructions about these commands." bot.send_message(chat_id, message) @@ -119,9 +118,17 @@ def start_and_menu_command(m): bot offers and the corresponding commands to be run from the Telegram UI to use these features. Commands used to run this: commands=['start', 'menu'] """ - helper.read_json() - global user_list + global user_list + user_list = helper.read_json() chat_id = m.chat.id + print(user_list) + if (str(chat_id) in user_list.keys()) and ("users" in user_list[str(chat_id)].keys()): + user_list[str(chat_id)]["users"].insert(0,m.from_user.first_name) + user_list[str(chat_id)]["owed"][m.from_user.first_name] = 0 + user_list[str(chat_id)]["owing"][m.from_user.first_name] = {} + else: + user_list[str(chat_id)] = {"users" : [m.from_user.first_name],"owed": {m.from_user.first_name: 0},"owing": {m.from_user.first_name: {}}} + # print('receieved start or menu command.') # text_into = "Welcome to the Dollar Bot!" @@ -158,8 +165,7 @@ def command_add(message): @bot.message_handler(commands=["add_user"]) def command_add_user(message): - chat_id = message.chat.id - add_user.register_people(message,bot) + add_user.register_people(message,bot,user_list) @bot.message_handler(commands=["add_category"]) def command_add_category(message): From ff45348b36efff9c9309a2b23e76d13ad73726ec Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Sun, 15 Oct 2023 18:28:16 -0400 Subject: [PATCH 13/40] Refactoring return st bugs after merging --- code/add.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/add.py b/code/add.py index dbb86f342..9130a4511 100644 --- a/code/add.py +++ b/code/add.py @@ -186,3 +186,5 @@ def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): user_list[str(chat_id)]["owing"][user][paid_by] += owed_amount else: user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount + print("################",user_list) + return user_list From 613ecf192a7020378290421dac938e3e17d89e53 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Sun, 15 Oct 2023 18:51:51 -0400 Subject: [PATCH 14/40] fixing pdf exception bug --- code/pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/pdf.py b/code/pdf.py index 3d678c5bb..37562d30f 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -98,4 +98,4 @@ def run(message, bot): except Exception as e: logging.exception(str(e)) - bot.reply_to(message, "Oops!" + str(e)) + bot.send_message(message, "Oops!" + str(e)) From aa48d094ae3ad1c93b85fc7303c2837fcfa205a8 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Sun, 15 Oct 2023 19:08:44 -0400 Subject: [PATCH 15/40] Dev: Font family error resolved --- code/pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/pdf.py b/code/pdf.py index 37562d30f..b5848c14b 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -53,7 +53,7 @@ def run(message, bot): # Create a table and set its properties pdf.set_fill_color(135, 206, 235) # Light blue - pdf.set_font(style="B") + pdf.set_font("helvetica",style="B") pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) pdf.set_fill_color(255, 255, 255) # White pdf.ln() From 1234cc48216c9f65f999244e179b946e2418883b Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Sun, 15 Oct 2023 19:12:34 -0400 Subject: [PATCH 16/40] Adding lowercase options for selecting users --- code/add.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/add.py b/code/add.py index 9130a4511..7f566ed87 100644 --- a/code/add.py +++ b/code/add.py @@ -59,9 +59,9 @@ def add_shared_user(message,bot,owed_by,user_list,paid_by): def user_choice(message, bot,owed_by, user_list,paid_by): chat_id = message.chat.id Choice = message.text - if Choice == "Y": + if Choice == "Y" or Choice == 'y': select_user(message,bot,owed_by,user_list,paid_by) - elif Choice == "N": + elif Choice == "N" or Choice == 'n': post_append_spend(message,bot,owed_by,paid_by) From 124bb4dfc6511a65033079bd8f5213c83be94690 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Sun, 15 Oct 2023 20:29:50 -0400 Subject: [PATCH 17/40] Issue_19_Enhancement: Delete user functionality --- code/add.py | 32 +++++++++++++++++------------ code/code.py | 7 +++++++ code/delete_user.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ code/helper.py | 1 + code/pdf.py | 2 +- 5 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 code/delete_user.py diff --git a/code/add.py b/code/add.py index 9130a4511..a302693b7 100644 --- a/code/add.py +++ b/code/add.py @@ -17,20 +17,24 @@ def run(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object from the main code.py function. """ - user_list=helper.read_json() - chat_id = message.chat.id - owed_by =[] - chat_id = message.chat.id - option.pop(chat_id, None) # remove temp choice + try: + user_list=helper.read_json() + chat_id = message.chat.id + owed_by =[] + chat_id = message.chat.id + option.pop(chat_id, None) # remove temp choice + + if str(chat_id) not in user_list: + user_list[str(chat_id)] = helper.createNewUserRecord() + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = len(user_list[str(chat_id)]["users"]) + for c in user_list[str(chat_id)]["users"]: + markup.add(c) + m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup) + bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None) + except: + bot.send_message(chat_id,"First add users to add an expense!") - if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - markup.row_width = len(user_list[str(chat_id)]["users"]) - for c in user_list[str(chat_id)]["users"]: - markup.add(c) - m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup) - bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None) def select_user(message,bot,owed_by,user_list,paid_by): @@ -188,3 +192,5 @@ def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount print("################",user_list) return user_list + + diff --git a/code/code.py b/code/code.py index 6b46ef941..a5bb004b6 100644 --- a/code/code.py +++ b/code/code.py @@ -15,6 +15,7 @@ import delete_expense import budget import add_user +import delete_user from datetime import datetime from jproperties import Properties @@ -167,6 +168,12 @@ def command_add(message): def command_add_user(message): add_user.register_people(message,bot,user_list) +@bot.message_handler(commands=["delete_user"]) +def command_delete_user(message): + # Call the delete_user function from the delete_user module + registered_users=user_list[str(message.chat.id)]["users"] + delete_user.delete_user(message, bot, user_list) + @bot.message_handler(commands=["add_category"]) def command_add_category(message): """ diff --git a/code/delete_user.py b/code/delete_user.py new file mode 100644 index 000000000..d3db0ef6a --- /dev/null +++ b/code/delete_user.py @@ -0,0 +1,50 @@ +import helper +from telebot import types + +# Initialize a dictionary to store registered users +registered_users = {} + +def delete_user(message, bot, user_list): + chat_id = message.chat.id + user_dict = user_list.get(str(chat_id), {}) + + if not user_dict or "users" not in user_dict: + bot.send_message(chat_id, "No users are registered for deletion.") + return + + # Get the list of all users from user_list + all_users = user_dict.get("users", []) + + # Create a custom keyboard to let the user choose which user to delete + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) + for user in all_users: + markup.add(user) + + msg = bot.send_message(chat_id, "Select the user you want to delete:", reply_markup=markup) + bot.register_next_step_handler(msg, confirm_delete, bot, user_list) + +def confirm_delete(message, bot, user_list): + chat_id = message.chat.id + user_name = message.text + + user_dict = user_list.get(str(chat_id), {}) + + if "users" in user_dict and user_name in user_dict["users"]: + user_dict["users"].remove(user_name) + user_dict["owed"].pop(user_name, None) + user_dict["owing"].pop(user_name, None) + + helper.write_json(user_list) + + bot.send_message(chat_id, f"{user_name} has been deleted successfully.") + + if not user_dict["users"]: + bot.send_message(chat_id, "No users are registered after deletion.") + else: + bot.send_message(chat_id, "Updated list of registered users:\n" + '\n '.join(user_dict["users"])) + else: + bot.send_message(chat_id, f"{user_name} is not registered.") + + # Remove the custom keyboard + # markup = types.ReplyKeyboardRemove(selective=False) + # bot.send_message(chat_id, "Keyboard hidden. You can now use other commands.", reply_markup=markup) diff --git a/code/helper.py b/code/helper.py index c2cd19921..efb76e73e 100644 --- a/code/helper.py +++ b/code/helper.py @@ -28,6 +28,7 @@ "help": "Display the list of commands.", "pdf": "Save history as PDF.", "add_user": "Add users to expense tracker", + "delete_user":"Delete user from the registered users", "add": "This option is for adding your expenses \ \n 1. It will give you the list of categories to choose from. \ \n 2. You will be prompted to enter the amount corresponding to your spending \ diff --git a/code/pdf.py b/code/pdf.py index 37562d30f..a2790fd9a 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -53,7 +53,7 @@ def run(message, bot): # Create a table and set its properties pdf.set_fill_color(135, 206, 235) # Light blue - pdf.set_font(style="B") + pdf.set_font(family="helvetica",style="B") pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) pdf.set_fill_color(255, 255, 255) # White pdf.ln() From 668fe50594d825de92a4eafc9aaec2949969cf3f Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Mon, 16 Oct 2023 18:24:25 -0500 Subject: [PATCH 18/40] Issue_23 - code added for providing two pdfs --- code/pdf.py | 187 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 77 deletions(-) diff --git a/code/pdf.py b/code/pdf.py index 3d678c5bb..1e05e0f42 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -1,11 +1,19 @@ import helper import logging from matplotlib import pyplot as plt +from telebot import types #Issue 15 - Added tabulate and fpdf libraries from tabulate import tabulate from fpdf import FPDF +#Issue 23 - Added Tabula library +#import tabula + +from reportlab.lib import colors +from reportlab.lib.pagesizes import letter +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle + # === Documentation of pdf.py === @@ -16,86 +24,111 @@ def run(message, bot): try: helper.read_json() chat_id = message.chat.id + + user_list = helper.read_json() + #print('User-history--> ',user_history) + print('User_list', user_list) + + #Issue 23 - add two pdf types - start + + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + #markup.row_width = 2 + markup.add("PDF for Total Expenses - Category wise", "PDF showing who owes whom how much") + msg = bot.send_message(chat_id, "Which kind of PDF do you want to generate?", reply_markup=markup) + user_history = helper.getUserHistory(chat_id) - print('User-history--> ',user_history) - - #Issue 3 - added the if condition - start - if user_history != None: - #Issue 3 - added the if condition - end - message = "Alright. I just created a pdf of your expense history!" - bot.send_message(chat_id, message) - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - top = 0.8 - if len(user_history) == 0: - plt.text( - 0.1, - top, - "No record found!", - horizontalalignment="left", - verticalalignment="center", - transform=ax.transAxes, - fontsize=20, - ) - - #issue 15 - modified the format of pdf document - start - table_data = [entry.split(',') for entry in user_history] - - # Create a PDF document - pdf = FPDF() - pdf.add_page() - - # Set font - pdf.set_font("helvetica", size=12) - - # Define the table columns - columns = ["Date & Time", "Category", "Amount"] - - # Create a table and set its properties - pdf.set_fill_color(135, 206, 235) # Light blue - pdf.set_font(style="B") - pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) - pdf.set_fill_color(255, 255, 255) # White - pdf.ln() - pdf.ln() - # Add table headers - pdf.set_font("helvetica", size=12, style="B") - for col in columns: - pdf.cell(64, 10, col, border=1, align="C", fill=True) - pdf.ln() - - # Add table data - pdf.set_font("helvetica", size=10) - for row in table_data: - for item in row: - pdf.cell(64, 10, item, border=1, align="C", fill=True) - pdf.ln() - # Save the PDF - pdf.output("expense_report.pdf") - bot.send_document(chat_id, open("expense_report.pdf", "rb")) - print("PDF generated successfully.") - #issue 15 - modified the format of pdf document - start - - - #Issue 3 - added the else condition - start - else: - message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf." - bot.send_message(chat_id, message) - - display_text = "" - commands = helper.getCommands() - for ( - c - ) in ( - commands - ): # generate help text out of the commands dictionary defined at the top - display_text += "/" + c + ": " - display_text += commands[c] + "\n" - bot.send_message(chat_id, "Please select a menu option from below:") - bot.send_message(chat_id, display_text) - #Issue 3 - added the else condition - end + bot.register_next_step_handler(msg, pdfGeneration, bot,user_list, user_history) + #Issue 23 - add two pdf types - end except Exception as e: logging.exception(str(e)) bot.reply_to(message, "Oops!" + str(e)) + +def pdfGeneration(message, bot, user_list, user_history): + chat_id = message.chat.id + choice = message.text + + if choice == 'PDF for Total Expenses - Category wise': + #Issue 3 - added the if condition - start + if user_history != None: + #Issue 3 - added the if condition - end + message = "Alright. I just created a pdf of your expense history!" + bot.send_message(chat_id, message) + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + top = 0.8 + if len(user_history) == 0: + plt.text( + 0.1, + top, + "No record found!", + horizontalalignment="left", + verticalalignment="center", + transform=ax.transAxes, + fontsize=20, + ) + + #issue 15 - modified the format of pdf document - start + table_data = [entry.split(',') for entry in user_history] + + # Create a PDF document + pdf = FPDF() + pdf.add_page() + + # Set font + pdf.set_font("helvetica", size=12) + + # Define the table columns + columns = ["Date & Time", "Category", "Amount"] + + # Create a table and set its properties + pdf.set_fill_color(135, 206, 235) # Light blue + pdf.set_font(style="B") + pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) + pdf.set_fill_color(255, 255, 255) # White + pdf.ln() + pdf.ln() + # Add table headers + pdf.set_font("helvetica", size=12, style="B") + for col in columns: + pdf.cell(64, 10, col, border=1, align="C", fill=True) + pdf.ln() + + # Add table data + pdf.set_font("helvetica", size=10) + for row in table_data: + for item in row: + pdf.cell(64, 10, item, border=1, align="C", fill=True) + pdf.ln() + + # Save the PDF + pdf.output("expense_report.pdf") + bot.send_document(chat_id, open("expense_report.pdf", "rb")) + print("PDF generated successfully.") + #issue 15 - modified the format of pdf document - start + + + #Issue 3 - added the else condition - start + else: + message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf." + bot.send_message(chat_id, message) + + display_text = "" + commands = helper.getCommands() + for ( + c + ) in ( + commands + ): # generate help text out of the commands dictionary defined at the top + display_text += "/" + c + ": " + display_text += commands[c] + "\n" + bot.send_message(chat_id, "Please select a menu option from below:") + bot.send_message(chat_id, display_text) + #Issue 3 - added the else condition - end + #Issue 23 - added code for generating pdf showing who owes whom how much + elif choice == 'PDF showing who owes whom how much': + print('abcd') + bot.send_message(chat_id, "Shutup!!!") + + From 035e2ec7b24c0aeef70a757643db4d9b4492e157 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Mon, 16 Oct 2023 20:17:16 -0400 Subject: [PATCH 19/40] Modified the budget functionality --- code/budget.py | 38 ++---- code/budget_delete.py | 31 +++-- code/budget_update.py | 297 ++++++++++++++++-------------------------- code/budget_view.py | 38 +----- code/pdf.py | 2 +- 5 files changed, 144 insertions(+), 262 deletions(-) diff --git a/code/budget.py b/code/budget.py index 0bd1359ae..035752f7a 100644 --- a/code/budget.py +++ b/code/budget.py @@ -1,21 +1,11 @@ import helper +from telebot import types +import logging import budget_view import budget_update import budget_delete -import logging -from telebot import types - -# === Documentation of budget.py === - def run(message, bot): - """ - run(message, bot): This is the main function used to implement the budget feature. - It pop ups a menu on the bot asking the user to choose to add, remove or display a budget, - after which control is given to post_operation_selection(message, bot) for further proccessing. - It takes 2 arguments for processing - message which is the message from the user, and bot which is the - telegram bot object from the main code.py function. - """ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) options = helper.getBudgetOptions() markup.row_width = 2 @@ -24,29 +14,29 @@ def run(message, bot): msg = bot.reply_to(message, "Select Operation", reply_markup=markup) bot.register_next_step_handler(msg, post_operation_selection, bot) - def post_operation_selection(message, bot): - """ - post_operation_selection(message, bot): It takes 2 arguments for processing - message which - is the message from the user, and bot which is the telegram bot object from the - run(message, bot): function in the budget.py file. Depending on the action chosen by the user, - it passes on control to the corresponding functions which are all located in different files. - """ try: chat_id = message.chat.id + user_list = helper.read_json() op = message.text options = helper.getBudgetOptions() + if op not in options.values(): - bot.send_message( - chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove() - ) + bot.send_message(chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()) raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op)) + + if str(chat_id) not in user_list: + # Initialize the user's data with an empty budget dictionary + user_list[str(chat_id)] = {"budget": {"overall": None, "category": {}}} + if op == options["update"]: budget_update.run(message, bot) elif op == options["view"]: budget_view.run(message, bot) elif op == options["delete"]: budget_delete.run(message, bot) + + helper.write_json(user_list) + except Exception as e: - # print("hit exception") - helper.throw_exception(e, message, bot, logging) + helper.throw_exception(e, message, bot, logging) \ No newline at end of file diff --git a/code/budget_delete.py b/code/budget_delete.py index a4462563c..33a9e508c 100644 --- a/code/budget_delete.py +++ b/code/budget_delete.py @@ -1,22 +1,21 @@ import helper - -# === Documentation of budget_delete.py === - +from telebot import types +import logging def run(message, bot): - """ - run(message, bot): This is the main function used to implement the budget delete feature. - It takes 2 arguments for processing - message which is the message from the user, and bot - which is the telegram bot object from the main code.py function. It gets the user's chat ID - from the message object, and reads all user data through the read_json method from the helper module. - It then proceeds to empty the budget data for the particular user based on the user ID provided from the UI. - It returns a simple message indicating that this operation has been done to the UI. - """ chat_id = message.chat.id user_list = helper.read_json() - print(user_list) - if str(chat_id) in user_list: - user_list[str(chat_id)]["budget"]["overall"] = None - user_list[str(chat_id)]["budget"]["category"] = None + + if str(chat_id) not in user_list: + bot.send_message(chat_id, "You don't have budget data to delete.") + else: + if "budget" in user_list[str(chat_id)]: + # The 'budget' dictionary exists; you can proceed with deleting it + user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}} + else: + bot.send_message(chat_id, "No budget data to delete.") + helper.write_json(user_list) - bot.send_message(chat_id, "Budget deleted!") + bot.send_message(chat_id, "Budget data deleted successfully.") + + helper.write_json(user_list) \ No newline at end of file diff --git a/code/budget_update.py b/code/budget_update.py index df8041f4a..4d2b4be09 100644 --- a/code/budget_update.py +++ b/code/budget_update.py @@ -1,217 +1,138 @@ +#budget_update.py + import helper -import logging -import budget_view from telebot import types - -# === Documentation of budget_update.py === - +import logging def run(message, bot): - """ - run(message, bot): This is the main function used to implement the budget add/update features. - It takes 2 arguments for processing - message which is the message from the user, and bot which - is the telegram bot object from the main code.py function. - """ chat_id = message.chat.id - if helper.isOverallBudgetAvailable(chat_id): - update_overall_budget(chat_id, bot) - elif helper.isCategoryBudgetAvailable(chat_id): - update_category_budget(message, bot) - else: - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - options = helper.getBudgetTypes() - markup.row_width = 2 - for c in options.values(): - markup.add(c) - msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup) - bot.register_next_step_handler(msg, post_type_selection, bot) - - -def post_type_selection(message, bot): - """ - post_type_selection(message, bot): It takes 2 arguments for processing - message - which is the message from the user, and bot which is the telegram bot object. - This function takes input from the user, making them choose which type of budget they - would like to create - category-wise or overall, and then calls the corresponding functions for further processing. - """ + user_list = helper.read_json() + + if str(chat_id) not in user_list: + # Initialize the user's data with an empty budget dictionary + user_list[str(chat_id)] = {"budget": {"overall": None, "category": {}}} + + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + budget_types = helper.getBudgetTypes() + markup.row_width = 2 + for btype in budget_types.values(): + markup.add(btype) + msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup) + bot.register_next_step_handler(msg, set_budget_type, bot, user_list) + +def set_budget_type(message, bot, user_list): try: chat_id = message.chat.id - op = message.text + budget_type = message.text options = helper.getBudgetTypes() - if op not in options.values(): - bot.send_message( - chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove() - ) - raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op)) - if op == options["overall"]: - update_overall_budget(chat_id, bot) - elif op == options["category"]: - update_category_budget(message, bot) + + if budget_type not in options.values(): + bot.send_message(chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()) + raise Exception('Sorry I don\'t recognise this budget type "{}"!'.format(budget_type)) + + if budget_type == options["overall"]: + set_overall_budget(message, bot, user_list) + elif budget_type == options["category"]: + set_category_budget(message, bot, user_list) + except Exception as e: helper.throw_exception(e, message, bot, logging) +def set_overall_budget(message, bot, user_list): + chat_id = message.chat.id -def update_overall_budget(chat_id, bot): - """ - update_overall_budget(message, bot): It takes 2 arguments for processing - message which is the - message from the user, and bot which is the telegram bot object. This function is called when the - user wants to either create a new overall budget or update an existing one. It checks if there is an - existing budget through the helper module's isOverallBudgetAvailable function and if so, displays this - along with the prompt for the new (to be updated) budget, or just asks for the new budget. It passes control - to the post_overall_amount_input function in the same file. - """ - if helper.isOverallBudgetAvailable(chat_id): - currentBudget = helper.getOverallBudget(chat_id) - msg_string = "Current Budget is ${}\n\nHow much is your new monthly budget? \n(Enter numeric values only)" - message = bot.send_message(chat_id, msg_string.format(currentBudget)) - else: - message = bot.send_message( - chat_id, "How much is your monthly budget? \n(Enter numeric values only)" - ) - bot.register_next_step_handler(message, post_overall_amount_input, bot) - - -def post_overall_amount_input(message, bot): - """ - update_overall_budget(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object. - This function is called when the user wants to either create a new overall budget or - update an existing one. It checks if there is an existing budget through the helper module's - isOverallBudgetAvailable function and if so, displays this along with the prompt for the new - (to be updated) budget, or just asks for the new budget. It passes control to the post_overall_amount_input - function in the same file. - """ + if str(chat_id) not in user_list: + bot.send_message(chat_id, "You don't have budget data to set.") + return + try: - chat_id = message.chat.id - amount_value = helper.validate_entered_amount(message.text) - if amount_value == 0: - raise Exception("Invalid amount.") - user_list = helper.read_json() - if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() - user_list[str(chat_id)]["budget"]["overall"] = amount_value + msg = bot.reply_to(message, "Enter the Overall Budget", reply_markup=types.ReplyKeyboardRemove()) + bot.register_next_step_handler(msg, save_overall_budget, bot, user_list) + except Exception as e: + helper.throw_exception(e, message, bot, logging) + +def save_overall_budget(message, bot, user_list): + chat_id = message.chat.id + overall_budget = message.text + + if not overall_budget: + bot.send_message(chat_id, "Budget not set. Please enter a valid budget.") + return + + if str(chat_id) not in user_list: + bot.send_message(chat_id, "You don't have budget data to set.") + return + + try: + # Ensure the 'budget' dictionary exists + if "budget" not in user_list[str(chat_id)]: + user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}} + + # Set the overall budget in the user's data + user_list[str(chat_id)]["budget"]["overall"] = float(overall_budget) + helper.write_json(user_list) - bot.send_message(chat_id, "Budget Updated!") - budget_view.display_overall_budget(message, bot) - return user_list + + bot.send_message(chat_id, f"Overall Budget set to ${str(overall_budget)}") except Exception as e: helper.throw_exception(e, message, bot, logging) +def set_category_budget(message, bot, user_list): + chat_id = message.chat.id -def update_category_budget(message, bot): - """ - update_category_budget(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object. - This function is called in case the user decides to choose category-wise budgest in the run or - post_type_selection stages. It gets the spend categories from the helper module's getSpendCategories - and displays them to the user. It then passes control on to the post_category_selection function. - """ - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - categories = helper.getSpendCategories() - markup.row_width = 2 - for c in categories: - markup.add(c) - msg = bot.reply_to(message, "Select Category", reply_markup=markup) - bot.register_next_step_handler(msg, post_category_selection, bot) - - -def post_category_selection(message, bot): - """ - post_category_selection(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object. - Based on the category chosen by the user, the bot checks if these are part of the pre-defined - categories in helper.getSpendCategories(), else it throws an exception. If there is a budget - already existing for the category, it identifies this case through helper.isCategoryBudgetByCategoryAvailable - and shares this information with the user. If not, it simply proceeds. In either case, it then asks for the - new/updated budget amount. It passes control onto post_category_amount_input. - """ + if str(chat_id) not in user_list: + bot.send_message(chat_id, "You don't have budget data to set.") + return + try: - chat_id = message.chat.id - selected_category = message.text categories = helper.getSpendCategories() - if selected_category not in categories: - bot.send_message( - chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove() - ) - raise Exception( - 'Sorry I don\'t recognise this category "{}"!'.format(selected_category) - ) - if helper.isCategoryBudgetByCategoryAvailable(chat_id, selected_category): - currentBudget = helper.getCategoryBudgetByCategory( - chat_id, selected_category - ) - msg_string = "Current monthly budget for {} is {}\n\nEnter monthly budget for {}\n(Enter numeric values only)" - message = bot.send_message( - chat_id, - msg_string.format(selected_category, currentBudget, selected_category), - ) - else: - message = bot.send_message( - chat_id, - "Enter monthly budget for " + selected_category + "\n(Enter numeric values only)", - ) - bot.register_next_step_handler( - message, post_category_amount_input, bot, selected_category - ) + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.add(*categories) + msg = bot.reply_to(message, "Select a Category for Budget", reply_markup=markup) + bot.register_next_step_handler(msg, set_category_budget_amount, bot, user_list) except Exception as e: helper.throw_exception(e, message, bot, logging) +def set_category_budget_amount(message, bot, user_list): + chat_id = message.chat.id + category = message.text -def post_category_amount_input(message, bot, category): - """ - post_category_amount_input(message, bot, category): It takes 2 arguments for - processing - message which is the message from the user, and bot which is the telegram - bot object, and the category chosen by the user. - """ + if category not in helper.getSpendCategories(): + bot.send_message(chat_id, "Invalid category.", reply_markup=types.ReplyKeyboardRemove()) + return + try: - chat_id = message.chat.id - amount_value = helper.validate_entered_amount(message.text) - if amount_value == 0: - raise Exception("Invalid amount.") - user_list = helper.read_json() - if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() - if user_list[str(chat_id)]["budget"]["category"] is None: - user_list[str(chat_id)]["budget"]["category"] = {} - user_list[str(chat_id)]["budget"]["category"][category] = amount_value - helper.write_json(user_list) - message = bot.send_message( - chat_id, "Budget for " + category + " is now: $" + amount_value - ) - post_category_add(message, bot) - + msg = bot.reply_to(message, f"Enter Budget for {category}", reply_markup=types.ReplyKeyboardRemove()) + bot.register_next_step_handler(msg, save_category_budget, bot, user_list, category) except Exception as e: helper.throw_exception(e, message, bot, logging) +def save_category_budget(message, bot, user_list, category): + chat_id = message.chat.id + category_budget = message.text -def post_category_add(message, bot): - """ - post_category_add(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object. - This exists in case the user wants to add a category-wise budget to another category after adding - it for one category. It prompts the user to choose an option from helper.getUpdateOptions().values() and - passes control to post_option_selection to either continue or exit the add/update feature. - """ - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - options = helper.getUpdateOptions().values() - markup.row_width = 2 - for c in options: - markup.add(c) - msg = bot.reply_to(message, "Select Option", reply_markup=markup) - bot.register_next_step_handler(msg, post_option_selection, bot) - - -def post_option_selection(message, bot): - """ - post_option_selection(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object. - It takes the category chosen by the user from the message object. If the message is "continue", - then it runs update_category_budget (above) allowing the user to get into the add/update process again. - Otherwise, it exits the feature. - """ - print("here") - selected_option = message.text - options = helper.getUpdateOptions() - print("here") - if selected_option == options["continue"]: - update_category_budget(message, bot) + if not category_budget: + bot.send_message(chat_id, "Budget not set. Please enter a valid budget.") + return + + if str(chat_id) not in user_list: + bot.send_message(chat_id, "You don't have budget data to set.") + return + + try: + # Ensure the 'budget' dictionary exists + if "budget" not in user_list[str(chat_id)]: + user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}} + + # Ensure the category budget dictionary exists + if "category" not in user_list[str(chat_id)]["budget"]: + user_list[str(chat_id)]["budget"]["category"] = {} + + # Set the category budget in the user's data + user_list[str(chat_id)]["budget"]["category"][category] = float(category_budget) + + helper.write_json(user_list) + + bot.send_message(chat_id, f"Budget for {category} set to ${str(category_budget)}") + except Exception as e: + helper.throw_exception(e, message, bot, logging) \ No newline at end of file diff --git a/code/budget_view.py b/code/budget_view.py index 077f8327b..a64319303 100644 --- a/code/budget_view.py +++ b/code/budget_view.py @@ -1,23 +1,8 @@ -import graphing import helper import logging -import os - -# === Documentation of budget_view.py === - def run(message, bot): - """ - run(message, bot): This is the main function used to implement the budget feature. - It takes 2 arguments for processing - message which is the message from the user, and bot which - is the telegram bot object from the main code.py function. Depending on whether the user has configured - an overall budget or a category-wise budget, this functions checks for either case using the helper - module's isOverallBudgetAvailable and isCategoryBudgetAvailable functions and passes control on the - respective functions(listed below). If there is no budget configured an exception is raised and the user - is given a message indicating that there is no budget configured. - """ try: - print("here") chat_id = message.chat.id if helper.isOverallBudgetAvailable(chat_id): display_overall_budget(message, bot) @@ -30,29 +15,16 @@ def run(message, bot): except Exception as e: helper.throw_exception(e, message, bot, logging) - def display_overall_budget(message, bot): - """ - display_overall_budget(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot - object from the run(message, bot): in the same file. It gets the budget for the - user based on their chat ID using the helper module and returns the same through the bot to the Telegram UI. - """ chat_id = message.chat.id data = helper.getOverallBudget(chat_id) + if data is not None: + data = str(data) # Convert the float to a string bot.send_message(chat_id, "Overall Budget: $" + data) - def display_category_budget(message, bot): - """ - display_category_budget(message, bot): It takes 2 arguments for processing - - message which is the message from the user, and bot which is the telegram bot object - from the run(message, bot): in the same file. It gets the category-wise budget for the - user based on their chat ID using the helper module.It then processes it into a string - format suitable for display, and returns the same through the bot to the Telegram UI. - """ chat_id = message.chat.id data = helper.getCategoryBudget(chat_id) - graphing.viewBudget(data) - bot.send_photo(chat_id, photo=open("budget.png", "rb")) - os.remove("budget.png") + if data is not None: + formatted_data = "\n".join([f"{category}: ${budget}" for category, budget in data.items()]) + bot.send_message(chat_id, "Category-Wise Budgets:\n" + formatted_data) diff --git a/code/pdf.py b/code/pdf.py index 9da2bc3fb..c7b665754 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -84,7 +84,7 @@ def pdfGeneration(message, bot, user_list, user_history): # Create a table and set its properties pdf.set_fill_color(135, 206, 235) # Light blue - pdf.set_font(style="B") + pdf.set_font(family="helvetica",style="B") pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) pdf.set_fill_color(255, 255, 255) # White pdf.ln() From 4d26cf1f42c66dad8030aaf8ddc62783fb5a5f0a Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Mon, 16 Oct 2023 20:57:50 -0500 Subject: [PATCH 20/40] Issue33_Enhancement-Owing pdf generation code --- code/pdf.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/code/pdf.py b/code/pdf.py index 9da2bc3fb..43d7c1cff 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -10,9 +10,11 @@ #Issue 23 - Added Tabula library #import tabula -from reportlab.lib import colors -from reportlab.lib.pagesizes import letter -from reportlab.platypus import SimpleDocTemplate, Table, TableStyle +#from reportlab.lib import colors +#from reportlab.lib.pagesizes import letter +#from reportlab.platypus import SimpleDocTemplate, Table, TableStyle + +#Issue - 33 # === Documentation of pdf.py === @@ -128,5 +130,60 @@ def pdfGeneration(message, bot, user_list, user_history): #Issue 3 - added the else condition - end #Issue 23 - added code for generating pdf showing who owes whom how much elif choice == 'PDF showing who owes whom how much': - print('abcd') - bot.send_message(chat_id, "Shutup!!!") \ No newline at end of file + message = "Alright. I just created a pdf of your expense history!" + bot.send_message(chat_id, message) + + if user_history != None: + pdf = FPDF() + pdf.add_page() + + pdf.set_font("Arial", size=12) + pdf.set_fill_color(135, 206, 235) # Light blue + pdf.set_font(style="B") + pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) + pdf.set_fill_color(255, 255, 255) # White + pdf.ln() + pdf.ln() + + pdf.set_font("helvetica", size=12, style="B") + pdf.cell(50, 10, "User", border=1, align="C", fill=True) + pdf.cell(50, 10, "Gets back", border=1, align="C", fill=True) + pdf.cell(50, 10, "Gives to", border=1, align="C", fill=True) + pdf.cell(40, 10, "Gives Amount", border=1, align="C", fill=True) + #pdf.cell(40, 10, "Data", border=1) + pdf.ln() + + pdf.set_font("helvetica", size=12) + for user, details in user_list.items(): + #print('User! ', user) + #print('details! ', details) + for i, user_name in enumerate(details["users"]): + #amounts = '' + pdf.cell(50, 10, user_name, border=1, align="C", fill=True) + pdf.cell(50, 10, str(round(details["owed"][user_name], 2)), border=1, align="C", fill=True) + pdf.cell(50, 10, ', '.join(details["owing"][user_name].keys()), border=1, align="C", fill=True) + if details["owing"][user_name].values() != []: + pdf.cell(40, 10, ', '.join([str(round(x,2)) for x in details["owing"][user_name].values()]), border=1, align="C", fill=True) + else: + pdf.cell(40, 10, "None", border=1, align="C", fill=True) + pdf.ln() + + pdf.output("OwingTable.pdf") + bot.send_document(chat_id, open("OwingTable.pdf", "rb")) + print("PDF table created successfully.") + + else: + message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf." + bot.send_message(chat_id, message) + + display_text = "" + commands = helper.getCommands() + for ( + c + ) in ( + commands + ): # generate help text out of the commands dictionary defined at the top + display_text += "/" + c + ": " + display_text += commands[c] + "\n" + bot.send_message(chat_id, "Please select a menu option from below:") + bot.send_message(chat_id, display_text) \ No newline at end of file From 7900e942973fe0e9ca1920f367e7e5559b4c3819 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Tue, 17 Oct 2023 11:49:18 -0400 Subject: [PATCH 21/40] iss#30 fixed display and budget functionality --- code/add.py | 62 ++++++++++++-------------- code/add_category.py | 3 ++ code/add_user.py | 2 +- code/budget.py | 2 + code/budget_update.py | 30 +++++++++---- code/budget_view.py | 2 +- code/code.py | 9 ++-- code/display.py | 101 ++++++++++++++++++++++++++++++++---------- code/helper.py | 46 ++++++++++++++++--- 9 files changed, 179 insertions(+), 78 deletions(-) diff --git a/code/add.py b/code/add.py index 0cff9e43b..2526b5f45 100644 --- a/code/add.py +++ b/code/add.py @@ -1,5 +1,3 @@ - - import helper import logging from telebot import types @@ -17,38 +15,38 @@ def run(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object from the main code.py function. """ - try: - user_list=helper.read_json() - chat_id = message.chat.id - owed_by =[] - chat_id = message.chat.id - option.pop(chat_id, None) # remove temp choice - - if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - markup.row_width = len(user_list[str(chat_id)]["users"]) - for c in user_list[str(chat_id)]["users"]: - markup.add(c) - m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup) - bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None) - except: - bot.send_message(chat_id,"First add users to add an expense!") + user_list=helper.read_json() + chat_id = message.chat.id + owed_by =[] + option.pop(chat_id, None) # remove temp choice + if str(chat_id) not in user_list: + user_list[str(chat_id)] = helper.createNewUserRecord(message) + helper.write_json(user_list) + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = len(user_list[str(chat_id)]["users"]) + for c in user_list[str(chat_id)]["users"]: + markup.add(c) + m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup) + bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None) def select_user(message,bot,owed_by,user_list,paid_by): chat_id = message.chat.id text_m = message.text - if text_m in user_list[str(chat_id)]["users"]: - paid_by = text_m - markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - markup.row_width = 2 - - for c in [item for item in user_list[str(chat_id)]["users"] if item not in owed_by]: - markup.add(c) - m = bot.send_message(chat_id, "Select who shares the Expense",reply_markup=markup) - bot.register_next_step_handler(m, add_shared_user, bot,owed_by,user_list,paid_by) + remaining_users = [item for item in user_list[str(chat_id)]["users"] if item not in owed_by] + if len(remaining_users)==0: + post_append_spend(message,bot,owed_by,paid_by) + else: + if text_m in user_list[str(chat_id)]["users"]: + paid_by = text_m + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + + for c in remaining_users: + markup.add(c) + m = bot.send_message(chat_id, "Select who shares the Expense",reply_markup=markup) + bot.register_next_step_handler(m, add_shared_user, bot,owed_by,user_list,paid_by) def add_shared_user(message,bot,owed_by,user_list,paid_by): chat_id = message.chat.id @@ -61,7 +59,6 @@ def add_shared_user(message,bot,owed_by,user_list,paid_by): bot.register_next_step_handler(choice, user_choice, bot, owed_by,user_list,paid_by) def user_choice(message, bot,owed_by, user_list,paid_by): - chat_id = message.chat.id Choice = message.text if Choice == "Y" or Choice == 'y': select_user(message,bot,owed_by,user_list,paid_by) @@ -153,7 +150,7 @@ def post_amount_input(message, bot, selected_category,owed_by,paid_by): helper.write_json( add_user_record( - chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by + message,chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by ) ) @@ -168,7 +165,7 @@ def post_amount_input(message, bot, selected_category,owed_by,paid_by): bot.reply_to(message, "Oh no. " + str(e)) -def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): +def add_user_record(message,chat_id, record_to_be_added,amount_value,owed_by, paid_by): """ add_user_record(chat_id, record_to_be_added): Takes 2 arguments - chat_id or the chat_id of the user's chat, and record_to_be_added which @@ -176,7 +173,7 @@ def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): """ user_list = helper.read_json() if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() + user_list[str(chat_id)] = helper.createNewUserRecord(message) owed_amount = float(amount_value)/len(set(owed_by)) if "data" in user_list[str(chat_id)]: user_list[str(chat_id)]["data"].append(record_to_be_added) @@ -190,7 +187,6 @@ def add_user_record(chat_id, record_to_be_added,amount_value,owed_by, paid_by): user_list[str(chat_id)]["owing"][user][paid_by] += owed_amount else: user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount - print("################",user_list) return user_list diff --git a/code/add_category.py b/code/add_category.py index 52d97f747..10c8fb304 100644 --- a/code/add_category.py +++ b/code/add_category.py @@ -41,6 +41,9 @@ def post_append_spend(message, bot): else: helper.spend_categories.append(selected_category) + user_list = helper.read_json() + user_list[str(chat_id)]["budget"]["category"][selected_category] = '0' + helper.write_json(user_list) for c in helper.getSpendCategories(): markup.add(c) bot.send_message( diff --git a/code/add_user.py b/code/add_user.py index f9b09a073..a97c639f4 100644 --- a/code/add_user.py +++ b/code/add_user.py @@ -8,7 +8,7 @@ def register_people(message, bot,user_list): chat_id = message.chat.id if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() + user_list[str(chat_id)] = helper.createNewUserRecord(message) if "users" in user_list[str(chat_id)].keys(): registered_users={chat_id : user_list[str(chat_id)]["users"]} else: diff --git a/code/budget.py b/code/budget.py index 0bd1359ae..a570ce506 100644 --- a/code/budget.py +++ b/code/budget.py @@ -41,6 +41,8 @@ def post_operation_selection(message, bot): chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove() ) raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op)) + if op == options["add"]: + budget_update.run(message, bot) if op == options["update"]: budget_update.run(message, bot) elif op == options["view"]: diff --git a/code/budget_update.py b/code/budget_update.py index df8041f4a..0d576774d 100644 --- a/code/budget_update.py +++ b/code/budget_update.py @@ -13,11 +13,8 @@ def run(message, bot): is the telegram bot object from the main code.py function. """ chat_id = message.chat.id - if helper.isOverallBudgetAvailable(chat_id): - update_overall_budget(chat_id, bot) - elif helper.isCategoryBudgetAvailable(chat_id): - update_category_budget(message, bot) - else: + choice = message.text + if choice == "Add": markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) options = helper.getBudgetTypes() markup.row_width = 2 @@ -25,6 +22,19 @@ def run(message, bot): markup.add(c) msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup) bot.register_next_step_handler(msg, post_type_selection, bot) + else: + if helper.isOverallBudgetAvailable(chat_id): + update_overall_budget(chat_id, bot) + elif helper.isCategoryBudgetAvailable(chat_id): + update_category_budget(message, bot) + else: + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + options = helper.getBudgetTypes() + markup.row_width = 2 + for c in options.values(): + markup.add(c) + msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup) + bot.register_next_step_handler(msg, post_type_selection, bot) def post_type_selection(message, bot): @@ -88,8 +98,12 @@ def post_overall_amount_input(message, bot): raise Exception("Invalid amount.") user_list = helper.read_json() if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() - user_list[str(chat_id)]["budget"]["overall"] = amount_value + user_list[str(chat_id)] = helper.createNewUserRecord(message) + if "budget" not in user_list[str(chat_id)]: + user_list[str(chat_id)]["budget"] = {"overall": amount_value} + else: + user_list[str(chat_id)]["budget"]["overall"] = str(amount_value) + helper.write_json(user_list) bot.send_message(chat_id, "Budget Updated!") budget_view.display_overall_budget(message, bot) @@ -170,7 +184,7 @@ def post_category_amount_input(message, bot, category): raise Exception("Invalid amount.") user_list = helper.read_json() if str(chat_id) not in user_list: - user_list[str(chat_id)] = helper.createNewUserRecord() + user_list[str(chat_id)] = helper.createNewUserRecord(message) if user_list[str(chat_id)]["budget"]["category"] is None: user_list[str(chat_id)]["budget"]["category"] = {} user_list[str(chat_id)]["budget"]["category"][category] = amount_value diff --git a/code/budget_view.py b/code/budget_view.py index 077f8327b..49a92ab5c 100644 --- a/code/budget_view.py +++ b/code/budget_view.py @@ -40,7 +40,7 @@ def display_overall_budget(message, bot): """ chat_id = message.chat.id data = helper.getOverallBudget(chat_id) - bot.send_message(chat_id, "Overall Budget: $" + data) + bot.send_message(chat_id, "Overall Budget: $" + str(data)) def display_category_budget(message, bot): diff --git a/code/code.py b/code/code.py index a5bb004b6..11e259c36 100644 --- a/code/code.py +++ b/code/code.py @@ -123,12 +123,9 @@ def start_and_menu_command(m): user_list = helper.read_json() chat_id = m.chat.id print(user_list) - if (str(chat_id) in user_list.keys()) and ("users" in user_list[str(chat_id)].keys()): - user_list[str(chat_id)]["users"].insert(0,m.from_user.first_name) - user_list[str(chat_id)]["owed"][m.from_user.first_name] = 0 - user_list[str(chat_id)]["owing"][m.from_user.first_name] = {} - else: - user_list[str(chat_id)] = {"users" : [m.from_user.first_name],"owed": {m.from_user.first_name: 0},"owing": {m.from_user.first_name: {}}} + if str(chat_id) not in user_list: + user_list[str(chat_id)] = helper.createNewUserRecord(m) + # print('receieved start or menu command.') diff --git a/code/display.py b/code/display.py index 0d7715980..82a1c67d5 100644 --- a/code/display.py +++ b/code/display.py @@ -14,7 +14,7 @@ def run(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object from the main code.py function. """ - helper.read_json() + user_list=helper.read_json() chat_id = message.chat.id history = helper.getUserHistory(chat_id) if history is None: @@ -23,17 +23,56 @@ def run(message, bot): ) else: markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) - markup.row_width = 2 - for mode in helper.getSpendDisplayOptions(): - markup.add(mode) - # markup.add('Day', 'Month') - msg = bot.reply_to( - message, - "Please select a category to see the total expense", - reply_markup=markup, - ) - bot.register_next_step_handler(msg, display_total, bot) + markup.add("Display all expenses") + markup.add("Display owings") + m = bot.send_message(chat_id, "Select what to display",reply_markup=markup) + bot.register_next_step_handler(m, display_choice, bot,user_list,chat_id) + + +def display_choice(message,bot,user_list,chat_id): + chat_id = message.chat.id + choice = message.text + if choice == 'Display all expenses': + display_expenses(message,bot) + elif choice =='Display owings': + display_owings(message,bot,user_list,chat_id) + else: + m = bot.send_message(chat_id, "Select correct choice") + bot.register_next_step_handler(m, run, bot) +def display_owings(message,bot,user_list,chat_id): + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = len(user_list[str(chat_id)]["users"]) + for c in user_list[str(chat_id)]["users"]: + markup.add(c) + m = bot.send_message(chat_id, "Select user who's owings you want to display",reply_markup=markup) + bot.register_next_step_handler(m, select_user, bot,user_list,chat_id) + +def select_user(message,bot,user_list,chat_id): + chat_id = message.chat.id + user = message.text + owing_dictionary = helper.calculate_owing(user_list,chat_id) + final_string = '' + for owed in owing_dictionary[user]["owes"]: + final_string+=str("\n "+owed) + for owing in owing_dictionary[user]["owing"]: + final_string+=str("\n "+owing) + if final_string == '': + final_string = str(user)+' owes or is owed nothing' + m = bot.send_message(chat_id, final_string) + +def display_expenses(message, bot): + markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) + markup.row_width = 2 + for mode in helper.getSpendDisplayOptions(): + markup.add(mode) + # markup.add('Day', 'Month') + msg = bot.reply_to( + message, + "Please select a category to see the total expense", + reply_markup=markup, + ) + bot.register_next_step_handler(msg, display_total, bot) def display_total(message, bot): """ @@ -78,20 +117,36 @@ def display_total(message, bot): ] total_text = calculate_spendings(queryResult) monthly_budget = helper.getCategoryBudget(chat_id) - print("Print Total Spending", total_text) - print("Print monthly budget", monthly_budget) + if monthly_budget == None: + message = "Looks like you have not entered any category-wise budget yet. Please enter your budget and then try to display the expenses." + bot.send_message(chat_id, message) - spending_text = "" - if len(total_text) == 0: - spending_text = "You have no spendings for {}!".format(DayWeekMonth) - bot.send_message(chat_id, spending_text) + display_text = "" + commands = helper.getCommands() + for ( + c + ) in ( + commands + ): # generate help text out of the commands dictionary defined at the top + display_text += "/" + c + ": " + display_text += commands[c] + "\n" + bot.send_message(chat_id, "Please select a menu option from below:") + bot.send_message(chat_id, display_text) else: - spending_text = "Here are your total spendings {}:\nCATEGORIES,AMOUNT \n----------------------\n{}".format( - DayWeekMonth.lower(), total_text - ) - graphing.visualize(total_text, monthly_budget) - bot.send_photo(chat_id, photo=open("expenditure.png", "rb")) - # os.remove('expenditure.png') + print("Print Total Spending", total_text) + print("Print monthly budget", monthly_budget) + + spending_text = "" + if len(total_text) == 0: + spending_text = "You have no spendings for {}!".format(DayWeekMonth) + bot.send_message(chat_id, spending_text) + else: + spending_text = "Here are your total spendings {}:\nCATEGORIES,AMOUNT \n----------------------\n{}".format( + DayWeekMonth.lower(), total_text + ) + graphing.visualize(total_text, monthly_budget) + bot.send_photo(chat_id, photo=open("expenditure.png", "rb")) + # os.remove('expenditure.png') except Exception as e: logging.exception(str(e)) bot.reply_to(message, str(e)) diff --git a/code/helper.py b/code/helper.py index efb76e73e..693f04d04 100644 --- a/code/helper.py +++ b/code/helper.py @@ -17,11 +17,19 @@ spend_estimate_option = ["Next day", "Next month"] update_options = {"continue": "Continue", "exit": "Exit"} -budget_options = {"update": "Add/Update", "view": "View", "delete": "Delete"} +budget_options = {"add":"Add","update": "Update", "view": "View", "delete": "Delete"} budget_types = {"overall": "Overall Budget", "category": "Category-Wise Budget"} -data_format = {"data": [], "budget": {"overall": None, "category": None}} +data_format = {"users":[],"owed":{},"owing":{},"data": [], + "budget": {"overall": '0', "category": {"Food": '0', + "Groceries": '0', + "Utilities": '0', + "Transport": '0', + "Shopping": '0', + "Miscellaneous": '0'} + } +} # set of implemented commands and their description commands = { @@ -129,22 +137,31 @@ def throw_exception(e, message, bot, logging): bot.reply_to(message, "Oh no! " + str(e)) -def createNewUserRecord(): - return data_format +def createNewUserRecord(message): + user_lst = data_format + user_lst["users"].insert(0,message.from_user.first_name) + user_lst["owed"][message.from_user.first_name] = 0 + user_lst["owing"][message.from_user.first_name] = {} + return user_lst def getOverallBudget(chatId): data = getUserData(chatId) if data is None: return None - return data["budget"]["overall"] + if 'budget' in data.keys(): + return data["budget"]["overall"] + return None def getCategoryBudget(chatId): data = getUserData(chatId) if data is None: return None - return data["budget"]["category"] + if 'budget' in data.keys(): + return data["budget"]["category"] + return None + def getCategoryBudgetByCategory(chatId, cat): @@ -214,6 +231,23 @@ def calculate_total_spendings(queryResult): total = total + float(s[2]) return total +def calculate_owing(user_list,chat_id): + owing_dict = {} + users = user_list[str(chat_id)]["users"] + for user in users: + owing_dict[user] = {"owes" : [], "owing":[]} + for k,v in user_list[str(chat_id)]["owing"][user].items(): + if k in owing_dict.keys(): + owing_dict[k]["owing"].append(str(user)+' owes '+str(k)+" an amout of "+"{:.2f}".format(v)) + owing_dict[user]["owes"].append(str(k)+' is owing from '+str(user)+" an amout of "+"{:.2f}".format(v)) + + else: + owing_dict[k] ={"owes" :[str(k)+' is owing from '+str(user)+" an amout of "+"{:.2f}".format(v)],"owing" :[str(user)+' owes '+str(k)+" an amout of "+"{:.2f}".format(v)]} + + return owing_dict + + + def display_remaining_category_budget(message, bot, cat): chat_id = message.chat.id From 7fc2601dd6a85826bd66a4e661818f43173691e6 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Tue, 17 Oct 2023 13:31:27 -0400 Subject: [PATCH 22/40] Added functionality to save csv file --- code/add.py | 20 +++++++++-------- code/code.py | 10 +++++++++ code/csvfile.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ code/helper.py | 10 +++++---- 4 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 code/csvfile.py diff --git a/code/add.py b/code/add.py index cc4230751..378d255e4 100644 --- a/code/add.py +++ b/code/add.py @@ -62,11 +62,11 @@ def user_choice(message, bot,owed_by, user_list,paid_by): if Choice == "Y" or Choice == 'y': select_user(message,bot,owed_by,user_list,paid_by) elif Choice == "N" or Choice == 'n': - post_append_spend(message,bot,owed_by,paid_by) + post_append_spend(message,bot,owed_by,user_list,paid_by) -def post_append_spend(message, bot,owed_by,paid_by): +def post_append_spend(message, bot,owed_by,user_list,paid_by): chat_id = message.chat.id markup = types.ReplyKeyboardMarkup(one_time_keyboard=True) markup.row_width = 2 @@ -74,10 +74,10 @@ def post_append_spend(message, bot,owed_by,paid_by): for c in helper.getSpendCategories(): markup.add(c) msg = bot.reply_to(message, "Select Category", reply_markup=markup) - bot.register_next_step_handler(msg, post_category_selection, bot,owed_by,paid_by) + bot.register_next_step_handler(msg, post_category_selection, bot,owed_by,paid_by,user_list) -def post_category_selection(message, bot,owed_by,paid_by): +def post_category_selection(message, bot,owed_by,paid_by,user_list): """ post_category_selection(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot object @@ -104,7 +104,7 @@ def post_category_selection(message, bot,owed_by,paid_by): ), ) bot.register_next_step_handler( - message, post_amount_input, bot, selected_category,owed_by,paid_by + message, post_amount_input, bot, selected_category,owed_by,paid_by,user_list ) except Exception as e: logging.exception(str(e)) @@ -122,7 +122,7 @@ def post_category_selection(message, bot,owed_by,paid_by): bot.send_message(chat_id, display_text) -def post_amount_input(message, bot, selected_category,owed_by,paid_by): +def post_amount_input(message, bot, selected_category,owed_by,paid_by,user_list): """ post_amount_input(message, bot): It takes 2 arguments for processing - message which is the message from the user, and bot which is the telegram bot @@ -149,7 +149,7 @@ def post_amount_input(message, bot, selected_category,owed_by,paid_by): helper.write_json( add_user_record( - message,chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by + user_list,message,chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by ) ) @@ -164,13 +164,12 @@ def post_amount_input(message, bot, selected_category,owed_by,paid_by): bot.reply_to(message, "Oh no. " + str(e)) -def add_user_record(message,chat_id, record_to_be_added,amount_value,owed_by, paid_by): +def add_user_record(user_list,message,chat_id, record_to_be_added,amount_value,owed_by, paid_by): """ add_user_record(chat_id, record_to_be_added): Takes 2 arguments - chat_id or the chat_id of the user's chat, and record_to_be_added which is the expense record to be added to the store. It then stores this expense record in the store. """ - user_list = helper.read_json() if str(chat_id) not in user_list: user_list[str(chat_id)] = helper.createNewUserRecord(message) owed_amount = float(amount_value)/len(set(owed_by)) @@ -186,6 +185,9 @@ def add_user_record(message,chat_id, record_to_be_added,amount_value,owed_by, pa user_list[str(chat_id)]["owing"][user][paid_by] += owed_amount else: user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount + record_to_be_added+=",{},{}".format(paid_by,' & '.join(owed_by)) + user_list[str(chat_id)]["csv_data"].append(record_to_be_added) + print("####",user_list) return user_list diff --git a/code/code.py b/code/code.py index 11e259c36..48808cdcc 100644 --- a/code/code.py +++ b/code/code.py @@ -14,6 +14,7 @@ import add_category import delete_expense import budget +import csvfile import add_user import delete_user from datetime import datetime @@ -192,6 +193,15 @@ def command_pdf(message): pdf.run(message, bot) +@bot.message_handler(commands=["csv"]) +def command_csv(message): + """ + command_history(message): Takes 1 argument message which contains the message from + the user along with the chat ID of the user chat. It then calls csv.py to run to execute + the add functionality. Commands used to run this: commands=['csv'] + """ + csvfile.run(message, bot) + # function to fetch expenditure history of the user @bot.message_handler(commands=["history"]) def command_history(message): diff --git a/code/csvfile.py b/code/csvfile.py new file mode 100644 index 000000000..6087f1d46 --- /dev/null +++ b/code/csvfile.py @@ -0,0 +1,59 @@ +import helper +import logging +from matplotlib import pyplot as plt +from telebot import types +import csv + +# === Documentation of pdf.py === + + +def run(message, bot): + try: + user_list=helper.read_json() + chat_id = message.chat.id + user_history = helper.getUserHistory(chat_id) + print('User-history--> ',user_history) + data = user_list[str(chat_id)]['csv_data'] + if user_history != None: + message = "Alright. I just created a csv file of your expense history!" + bot.send_message(chat_id, message) + csv_file = 'expense_report.csv' + # Open the CSV file for writing + with open(csv_file, 'w', newline='') as file: + writer = csv.writer(file) + + # Write the header row + writer.writerow(['Date', 'Category', 'Amount', 'Payer', 'Participants']) + + # Write the data from the list + for item in data: + parts = item.split(',') + writer.writerow(parts) + bot.send_document(chat_id, open("expense_report.csv", "rb")) + print("CSV generated successfully.") + #issue 15 - modified the format of pdf document - start + + + #Issue 3 - added the else condition - start + else: + message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf." + bot.send_message(chat_id, message) + + display_text = "" + commands = helper.getCommands() + for ( + c + ) in ( + commands + ): # generate help text out of the commands dictionary defined at the top + display_text += "/" + c + ": " + display_text += commands[c] + "\n" + bot.send_message(chat_id, "Please select a menu option from below:") + bot.send_message(chat_id, display_text) + #Issue 3 - added the else condition - end + + except Exception as e: + logging.exception(str(e)) + bot.send_message(message, "Oops!" + str(e)) + + diff --git a/code/helper.py b/code/helper.py index 693f04d04..6db2d7582 100644 --- a/code/helper.py +++ b/code/helper.py @@ -21,7 +21,7 @@ budget_types = {"overall": "Overall Budget", "category": "Category-Wise Budget"} -data_format = {"users":[],"owed":{},"owing":{},"data": [], +data_format = {"users":[],"owed":{},"owing":{},"data": [],"csv_data":[], "budget": {"overall": '0', "category": {"Food": '0', "Groceries": '0', "Utilities": '0', @@ -35,6 +35,7 @@ commands = { "help": "Display the list of commands.", "pdf": "Save history as PDF.", + "csv": "Save history as a cv file.", "add_user": "Add users to expense tracker", "delete_user":"Delete user from the registered users", "add": "This option is for adding your expenses \ @@ -139,9 +140,10 @@ def throw_exception(e, message, bot, logging): def createNewUserRecord(message): user_lst = data_format - user_lst["users"].insert(0,message.from_user.first_name) - user_lst["owed"][message.from_user.first_name] = 0 - user_lst["owing"][message.from_user.first_name] = {} + if len(user_lst["users"]) == 0: + user_lst["users"].insert(0,message.from_user.first_name) + user_lst["owed"][message.from_user.first_name] = 0 + user_lst["owing"][message.from_user.first_name] = {} return user_lst From 26f50b6b0d9dee8237938c5004d3069b0dcdd170 Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Tue, 17 Oct 2023 16:43:59 -0400 Subject: [PATCH 23/40] Issue_24_Enhancement: Added Send Mail feature --- code/code.py | 5 +++ code/helper.py | 1 + code/send_mail.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 code/send_mail.py diff --git a/code/code.py b/code/code.py index a5bb004b6..8e2934d42 100644 --- a/code/code.py +++ b/code/code.py @@ -13,6 +13,7 @@ import add import add_category import delete_expense +import send_mail import budget import add_user import delete_user @@ -259,6 +260,10 @@ def command_delete(message): def command_budget(message): budget.run(message, bot) +@bot.message_handler(commands=["send_mail"]) +def command_send_mail(message): + send_mail.run(message, bot) + # not used diff --git a/code/helper.py b/code/helper.py index efb76e73e..bef45e357 100644 --- a/code/helper.py +++ b/code/helper.py @@ -43,6 +43,7 @@ "history": "This option is to give you the detailed summary of your expenditure with Date, time ,category and amount. A quick lookup into your spendings", "delete": "This option is to Clear/Erase all your records", "delete_expense": "This option is to Clear/Erase individual record from expense history records.", + "send_mail": "This option is to send mail of calculate owings", "edit": "This option helps you to go back and correct/update the missing details \ \n 1. It will give you the list of your expenses you wish to edit \ \n 2. It will let you change the specific field based on your requirements like amount/date/category", diff --git a/code/send_mail.py b/code/send_mail.py new file mode 100644 index 000000000..94d6980e0 --- /dev/null +++ b/code/send_mail.py @@ -0,0 +1,102 @@ +import helper +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from fpdf import FPDF + +user_emails = {} + +# === Documentation of add.py === +def run(message, bot): + helper.read_json() + chat_id = message.chat.id + message1 = bot.send_message(chat_id, "Please enter the email address") + bot.register_next_step_handler(message1, add_emails, bot) + +def add_emails(message, bot): + chat_id = message.chat.id + email = message.text + # Assuming you want to store the email address in the user_emails dictionary + user_emails[chat_id] = email + + # You can also validate the email address if needed + if not is_valid_email(email): + bot.send_message(chat_id, "Invalid email address. Please enter a valid email.") + return + + # Notify the user that their email address has been recorded + choice = bot.send_message(chat_id, f"Thank you for providing your email. Do you want to send email to {email}? Y/N") + bot.register_next_step_handler(choice, send_email, bot) + +# Example of a basic email validation function (you can expand this) +def is_valid_email(email): + import re + email_pattern = r'^\S+@\S+\.\S+$' + return re.match(email_pattern, email) is not None + +def send_email(choice, bot): + if str(choice.text) == "Y" or str(choice.text) == "y": + # Set up the Gmail API + smtp_server = 'smtp.gmail.com' + smtp_port = 587 # Port for TLS + smtp_username = 'csc510group32@gmail.com' + smtp_password = 'hqrx opxo lviu mubb' + + # Create an SMTP connection + server = smtplib.SMTP(smtp_server, smtp_port) + server.starttls() + server.login(smtp_username, smtp_password) + + + # Compose and send emails to all users + for chat_id, email in user_emails.items(): + # Create a MIME message with a subject + subject = "Calculated Owings" + message_body = format_text_data(helper.read_json()) + message = MIMEMultipart() + message['From'] = smtp_username + message['To'] = email + message['Subject'] = subject + message.attach(MIMEText(message_body, 'plain')) + + # Send the email + server.sendmail(smtp_username, email, message.as_string()) + + # Close the SMTP connection + server.quit() + + + +def format_text_data(user_list): + text_data = "```\n" + + for user, details in user_list.items(): + for user_name in details["users"]: + gets_back_amount = round(details['owed'][user_name], 2) + text_data += f"{user_name} gets back {gets_back_amount} dollars.\n" + + gives_to_list = list(details["owing"][user_name].keys()) + gives_amount_list = list(details["owing"][user_name].values()) + + if not gives_to_list: + text_data += f"{user_name} gives to no one.\n" + else: + for i in range(len(gives_to_list)): + gives_to_entry = gives_to_list[i] + gives_amount_entry = round(gives_amount_list[i], 2) + text_data += f"{user_name} gives {gives_amount_entry} dollars to {gives_to_entry}.\n" + + text_data += "```" + return text_data + + + + + + + + + + + + From 4fd2b98a2e5028bae1c946f6622ae8ff38b5894a Mon Sep 17 00:00:00 2001 From: agmalpur Date: Tue, 17 Oct 2023 18:08:17 -0400 Subject: [PATCH 24/40] fixing bugs --- code/add.py | 2 +- code/csvfile.py | 2 +- code/pdf.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/add.py b/code/add.py index 378d255e4..97342c3a3 100644 --- a/code/add.py +++ b/code/add.py @@ -35,7 +35,7 @@ def select_user(message,bot,owed_by,user_list,paid_by): text_m = message.text remaining_users = [item for item in user_list[str(chat_id)]["users"] if item not in owed_by] if len(remaining_users)==0: - post_append_spend(message,bot,owed_by,paid_by) + post_append_spend(message,bot,owed_by,user_list,paid_by) else: if text_m in user_list[str(chat_id)]["users"]: paid_by = text_m diff --git a/code/csvfile.py b/code/csvfile.py index 6087f1d46..1722eaf48 100644 --- a/code/csvfile.py +++ b/code/csvfile.py @@ -13,8 +13,8 @@ def run(message, bot): chat_id = message.chat.id user_history = helper.getUserHistory(chat_id) print('User-history--> ',user_history) - data = user_list[str(chat_id)]['csv_data'] if user_history != None: + data = user_list[str(chat_id)]['csv_data'] message = "Alright. I just created a csv file of your expense history!" bot.send_message(chat_id, message) csv_file = 'expense_report.csv' diff --git a/code/pdf.py b/code/pdf.py index e256b3dba..faf67500f 100644 --- a/code/pdf.py +++ b/code/pdf.py @@ -139,7 +139,7 @@ def pdfGeneration(message, bot, user_list, user_history): pdf.set_font("Arial", size=12) pdf.set_fill_color(135, 206, 235) # Light blue - pdf.set_font(style="B") + pdf.set_font(family="Arial",style="B") pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True) pdf.set_fill_color(255, 255, 255) # White pdf.ln() From ab427895c6f7bbb120113716e4fd1a079f21da50 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Tue, 17 Oct 2023 18:51:37 -0500 Subject: [PATCH 25/40] Issue_41- Added libraries to requirements.txt --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 082b6f120..ba0924101 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ flake8 matplotlib coverage pytest-mock -python-telegram-bot-calendar \ No newline at end of file +python-telegram-bot-calendar +tabulate +FPDF \ No newline at end of file From 12049282d9a84c24d43721c7e6b946dca8c25d8f Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Tue, 17 Oct 2023 21:50:02 -0500 Subject: [PATCH 26/40] Issue_43-Modified the DOI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e27f5f57..f1ba95d24 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ [![Platform](https://img.shields.io/badge/Platform-Telegram-blue)](https://desktop.telegram.org/) ![GitHub](https://img.shields.io/badge/Language-Python-blue.svg) [![GitHub contributors](https://img.shields.io/github/contributors/sak007/MyDollarBot-BOTGo)](https://github.com/sak007/MyDollarBot-BOTGo/graphs/contributors) -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5759217.svg)](https://doi.org/10.5281/zenodo.5759217) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10015948.svg)](https://doi.org/10.5281/zenodo.5759217) [![Build Status](https://app.travis-ci.com/usmanwardag/dollar_bot.svg?branch=main)](https://app.travis-ci.com/usmanwardag/dollar_bot) [![codecov](https://codecov.io/gh/usmanwardag/dollar_bot/branch/main/graph/badge.svg?token=PYAWX95R67)](https://codecov.io/gh/usmanwardag/dollar_bot) From 6ff79b11266708c669a2f7aeb6e204f2a9c5fd50 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Tue, 17 Oct 2023 21:54:32 -0500 Subject: [PATCH 27/40] Issue_43 - DOI hyperlink modified --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1ba95d24..88e2d164a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ [![Platform](https://img.shields.io/badge/Platform-Telegram-blue)](https://desktop.telegram.org/) ![GitHub](https://img.shields.io/badge/Language-Python-blue.svg) [![GitHub contributors](https://img.shields.io/github/contributors/sak007/MyDollarBot-BOTGo)](https://github.com/sak007/MyDollarBot-BOTGo/graphs/contributors) -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10015948.svg)](https://doi.org/10.5281/zenodo.5759217) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10015948.svg)](https://zenodo.org/records/10015948) [![Build Status](https://app.travis-ci.com/usmanwardag/dollar_bot.svg?branch=main)](https://app.travis-ci.com/usmanwardag/dollar_bot) [![codecov](https://codecov.io/gh/usmanwardag/dollar_bot/branch/main/graph/badge.svg?token=PYAWX95R67)](https://codecov.io/gh/usmanwardag/dollar_bot) From 79fb3452e29ceddfcc6e8283a068c15bd748d824 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Wed, 18 Oct 2023 09:58:44 -0400 Subject: [PATCH 28/40] Changing contributing.md and readme.md for roadmap --- CONTRIBUTING.md | 10 +++++----- README.md | 22 +++++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9af5982f..382f54649 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,10 @@ -# Contributing to MyDollarBot +# Contributing to DollarSplitBot -Follow the set of guidelines below to contribute to MyDollarBot! +Follow the set of guidelines below to contribute to DollarSplitBot! ## Code of Conduct -This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/usmanwardag/dollar_bot/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to upload this code. Please report unacceptable behavior to sbose2@ncsu.edu. +This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/shonilbhide/dollar_bot/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to upload this code. Please report unacceptable behavior to csc510group32@gmail.com. Prerequistes required before starting this project: @@ -41,7 +41,7 @@ A cursory search is necessary to check if the reported bug is already mentioned ## To Submit A Good Bug Report -[GitHub issues](https://github.com/usmanwardag/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug. +[GitHub issues](https://github.com/shonilbhide/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug. * To identify the problem, give the issue a clear and informative term.
@@ -70,7 +70,7 @@ Check out this [debugging guide](https://flight-manual.atom.io/hacking-atom/sect ## To Submit A Good Enhancement Suggestion -[GitHub issues](https://github.com/usmanwardag/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug. +[GitHub issues](https://github.com/shonilbhide/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug. * To identify the problem, give the issue a clear and informative term.
* Describe in as much detail as possible to duplicate the problem. Explain the problem and explain about the exact command sused in the terminal which caus ethe bug to occur. diff --git a/README.md b/README.md index 88e2d164a..cf5d64da6 100644 --- a/README.md +++ b/README.md @@ -125,32 +125,36 @@ coverage report ## License -This project is licensed under the terms of the MIT license. Please check [License](https://github.com/usmanwardag/dollar_bot/blob/main/LICENSE) for more details. +This project is licensed under the terms of the MIT license. Please check [License](https://github.com/shonilbhide/dollar_bot/blob/main/LICENSE) for more details. ## Code Documentation -Checkout the [docs](https://github.com/sak007/MyDollarBot-BOTGo/tree/main/docs) +Checkout the [docs](https://github.com/shonilbhide/dollar_bot/tree/main/docs) ## How to Contribute -We would be happy to receive contributions! If you'd like to, please go through our [CONTRIBUTING.md](https://github.com/usmanwardag/dollar_bot/blob/main/CONTRIBUTING.md) +We would be happy to receive contributions! If you'd like to, please go through our [CONTRIBUTING.md](https://github.com/shonilbhide/dollar_bot/blob/main/CONTRIBUTING.md) -For any feedback, issues, or bug reports, please create an issue [here](https://github.com/usmanwardag/dollar_bot/issues/new). +For any feedback, issues, or bug reports, please create an issue [here](https://github.com/shonilbhide/dollar_bot/issues/new). ## Future RoadMap - More content can be added for the way notifications can be displayed on the user front. This can be done to make the UI more interactive. - Recurring expenses feature can be added for faster addition of expenses instead of following the whole process of everytime. +- This application can be integrated with a group chat to track expenses of a group. +- A better model can be implemented to forecast the budgets and expenses for future. +- Make our bot support multiple languages, and not just english so that it might be helpful in the other regions of the world. +- Integrate the bot with financial services, like bank APIs, for real-time expense tracking and account balance updates. +- Implement a reminder system to notify users of recurring expenses, upcoming bills, or when they need to settle debts. ## Contributors - - - - - + + + +

Usman Khan

Aakriti Aakriti


Suneha Bose


Muskan Gupta


Kriti Khullar


Shonil bhide

Sakshi Basapure


Rutuja Rashinkar


Akshada Malpure

From 7d3f1ca0c0c0dd516f9c93a0babb17955f90a7d9 Mon Sep 17 00:00:00 2001 From: Shonil Bhide Date: Wed, 18 Oct 2023 10:03:33 -0400 Subject: [PATCH 29/40] adding workflow.yml file --- .github/workflows/workflow.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/workflows/workflow.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 000000000..9cdfd5f70 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,4 @@ +- name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From 27870d4e8975aae75fbc4e3d5349f8d09dd96749 Mon Sep 17 00:00:00 2001 From: Sho <51792152+shonilbhide@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:14:33 -0400 Subject: [PATCH 30/40] Update README.md adding a gif --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cf5d64da6..ecec1bf4e 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ ## Why should you use MyDollar Bot? +![Expense Tracker](

via GIPHY

) Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle. With simple commands, this bot allows you to: - Add/Record new spendings From fb2efaf7801b7727d523fc7f41e640ed7c2dcbe1 Mon Sep 17 00:00:00 2001 From: Sho <51792152+shonilbhide@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:16:46 -0400 Subject: [PATCH 31/40] Update README.md Adding gif to readme.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ecec1bf4e..fef92f8ac 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ ## Why should you use MyDollar Bot? -![Expense Tracker](

via GIPHY

) +![Expense Tracker](https://media.giphy.com/media/3o6ZtakuxQq65UboDC/giphy.gif) + Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle. With simple commands, this bot allows you to: - Add/Record new spendings From 84aa95e9eb7c2ca871477b505df1275e8d72f7b7 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Wed, 18 Oct 2023 10:48:13 -0500 Subject: [PATCH 32/40] Issue-47- modified the project name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88e2d164a..0986e38ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 💰 MyDollar Bot 💰 +# 💰 DollarSplitBot 💰
From a2248cf3bc9dda5f5aa78d062c15faaa1a321a1b Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Wed, 18 Oct 2023 11:08:44 -0500 Subject: [PATCH 33/40] Issue47-modified why should you use DollarSplitBot --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0986e38ae..1835c2118 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
Table of Contents
    -
  1. Why should you use Dollar Bot?
  2. +
  3. Why should you use DollarSplitBot?
  4. Check out the video!
  5. What is new in this version?
  6. Installation
  7. @@ -42,7 +42,7 @@
    -## Why should you use MyDollar Bot? +## Why should you use DollarSplitBot? Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle. With simple commands, this bot allows you to: From 97ab14aeb2a9674241972304b471171eef5c3bf4 Mon Sep 17 00:00:00 2001 From: rutuja-39 Date: Wed, 18 Oct 2023 11:43:34 -0500 Subject: [PATCH 34/40] Issue-47-Docs why --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1835c2118..ee3568021 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,23 @@ ## Why should you use DollarSplitBot? -Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle. -With simple commands, this bot allows you to: -- Add/Record new spendings -- Display your spendings through bar graph -- Show the sum of your expenditure for the current day/month -- Display your spending history -- Clear/Erase all your records -- Edit/Change any spending details if you wish to +"Discover a whole new level of financial clarity and fairness – where you'll never have to wonder 'Who owes me, and who do I owe?' again. Say goodbye to financial puzzles, and embrace our extended expense management system to reclaim your peace of mind!" + +Introducing DollarSplitBot, your trusty companion on Telegram, here to turn the mundane task of tracking your daily expenses into a breeze. This ingenious bot simplifies the process of keeping tabs on your spending, even when you're offline. But that's not all; it's also your go-to solution for managing group expenses and ensuring everyone's financial equilibrium. + +With just a few swift commands, DollarSplitBot empowers you to: + +1. **Welcome New Faces:** Add your friends to share expenses with ease. +2. **Log Your Transactions:** Document and store your expenditures effortlessly. +3. **Equitable Divisions:** Showcase your spending history, unraveling who owes what to whom. +4. **Money Matters:** Keep tabs on your daily and monthly expenditure totals. +5. **Your Financial Story:** Access your spending history at any time. +6. **A Clean Slate:** Erase all records when it's time to start anew. +7. **Tailored Details:** Edit any spending particulars to your liking. +8. **Paper Trail:** Generate sleek PDF expenditure reports for a comprehensive overview. +9. **Friendly Nudges:** Send friendly reminders via email to ensure financial settlements. + +DollarSplitBot: Where simplicity meets financial harmony at your fingertips. ## Check out the video! From 1a765e6ef35debfe9a4cc4129ab1f1f28ed5e57c Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Wed, 18 Oct 2023 18:39:11 -0400 Subject: [PATCH 35/40] Issue_54_Documentation: SMTP --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index ee3568021..52264faf7 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,20 @@ A successful run will generate a message on your terminal that says "TeleBot: St To run the script automatically at startup / reboot, simply add the `.run_forever.sh` script to your `.bashrc` file, which executes whenever you reboot your system. +## Configuring Email Credentials for SMTP: Sending Emails from Your Account" + +**SMTP (Simple Mail Transfer Protocol)** is a standard protocol for sending emails. It is widely used for sending email messages from one server to another. In the code, we are using SMTP to send emails via a Gmail account. Here's how the SMTP configuration and usage work: + +1. **SMTP Server**: The `smtp_server` variable is set to 'smtp.gmail.com,' which is the SMTP server for Gmail. This server is responsible for sending your email messages. + +2. **SMTP Port**: The `smtp_port` variable is set to 587. This is the port for TLS (Transport Layer Security) encryption. Gmail uses this port for secure email communication. + +3. **SMTP Username**: The smtp_username variable should be set to your own Gmail email address from which you want to send the emails. Make sure to replace `your-email@gmail.com` with your actual Gmail email address in the code. This ensures that the emails will be sent from your specific Gmail account. + +4. **SMTP Password**: The `smtp_password` variable is set, you need to generate an "App Password". An App Password is a 16-character code that allows you to access your Gmail account without revealing your real password. + +By customizing these settings, you can send emails from any email account using SMTP. Just ensure you are adhering to the security guidelines provided by your email provider. + ## Testing We use pytest to perform testing on all unit tests together. The command needs to be run from the home directory of the project. The command is: From 1cfe8faafb0f1f9e050984de6f22386fca9469a3 Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Wed, 18 Oct 2023 18:40:09 -0400 Subject: [PATCH 36/40] Issue_54_Documentation: App Password Details --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52264faf7..ea0bb6820 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,13 @@ To run the script automatically at startup / reboot, simply add the `.run_foreve 3. **SMTP Username**: The smtp_username variable should be set to your own Gmail email address from which you want to send the emails. Make sure to replace `your-email@gmail.com` with your actual Gmail email address in the code. This ensures that the emails will be sent from your specific Gmail account. -4. **SMTP Password**: The `smtp_password` variable is set, you need to generate an "App Password". An App Password is a 16-character code that allows you to access your Gmail account without revealing your real password. +4. **SMTP Password**: The `smtp_password` variable is set, you need to generate an "App Password". An App Password is a 16-character code that allows you to access your Gmail account without revealing your real password. To generate an App Password, follow these steps: + + a. Go to your Google Account settings (https://myaccount.google.com/). + b. In the "Security" section, under "Signing in to Google", select "App Passwords". + c. Select "Mail" and "Other (Custom name)" from the dropdown menus. + d. Click "Generate". + e. Google will provide you with a 16-character App Password. Use this as your `smtp_password` in your code. By customizing these settings, you can send emails from any email account using SMTP. Just ensure you are adhering to the security guidelines provided by your email provider. From f0fe2c1cbc9f5408e47d7ba4bfbe03294211bbb8 Mon Sep 17 00:00:00 2001 From: sakshibasapure <40641044+sakshibasapure@users.noreply.github.com> Date: Wed, 18 Oct 2023 19:04:59 -0400 Subject: [PATCH 37/40] App Password documentation changes --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c08047c4e..b8f576f62 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ A successful run will generate a message on your terminal that says "TeleBot: St To run the script automatically at startup / reboot, simply add the `.run_forever.sh` script to your `.bashrc` file, which executes whenever you reboot your system. -## Configuring Email Credentials for SMTP: Sending Emails from Your Account" +## Configuring Email Credentials for SMTP: Sending Emails from Your Account **SMTP (Simple Mail Transfer Protocol)** is a standard protocol for sending emails. It is widely used for sending email messages from one server to another. In the code, we are using SMTP to send emails via a Gmail account. Here's how the SMTP configuration and usage work: @@ -126,11 +126,11 @@ To run the script automatically at startup / reboot, simply add the `.run_foreve 4. **SMTP Password**: The `smtp_password` variable is set, you need to generate an "App Password". An App Password is a 16-character code that allows you to access your Gmail account without revealing your real password. To generate an App Password, follow these steps: - a. Go to your Google Account settings (https://myaccount.google.com/). - b. In the "Security" section, under "Signing in to Google", select "App Passwords". - c. Select "Mail" and "Other (Custom name)" from the dropdown menus. - d. Click "Generate". - e. Google will provide you with a 16-character App Password. Use this as your `smtp_password` in your code. + - Go to your Google Account settings (https://myaccount.google.com/). + - In the "Security" section, under "Signing in to Google", select "App Passwords". + - Select "Mail" and "Other (Custom name)" from the dropdown menus. + - Click "Generate". + - Google will provide you with a 16-character App Password. Use this as your `smtp_password` in your code. By customizing these settings, you can send emails from any email account using SMTP. Just ensure you are adhering to the security guidelines provided by your email provider. From 908b43307651194872d07ae5120df35720c3fb0c Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Wed, 18 Oct 2023 19:54:53 -0400 Subject: [PATCH 38/40] Issue_52_Documentation: Added use cases --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index b8f576f62..70c825d6e 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,24 @@ coverage run -m pytest test/ coverage report ``` +## Use Cases + +Common use cases for DollarSplitBot summarized in three points: + +1. **Personal Expense Tracking:** + - Easily log and monitor your individual daily and monthly expenses, including groceries, dining out, transportation, and entertainment. + - Access your spending history and totals at any time, providing valuable insights into your financial habits. + +2. **Group Expense Management:** + - Efficiently manage group expenses with friends or family members. Add participants to track shared costs and responsibilities. + - DollarSplitBot calculates equitable divisions, simplifying the process of determining who owes what to whom in group expenses. + +3. **Expense Reporting and Communication:** + - Generate detailed PDF expenditure reports for a comprehensive overview of your financial activity. + - Utilize the bot's email reminders to facilitate financial settlements and maintain harmony in shared expenses, ensuring everyone is accountable. + +Certainly, you can watch this video [![Demo Video](https://i9.ytimg.com/vi/aCjcT1CHAzU/mq3.jpg?sqp=COSotI0G&rs=AOn4CLD34jFIlq6GRdmTnK6p3F8O2F-Yig)](https://youtu.be/aCjcT1CHAzU) for a step-by-step guide on how to use DollarSplitBot. + ## License This project is licensed under the terms of the MIT license. Please check [License](https://github.com/shonilbhide/dollar_bot/blob/main/LICENSE) for more details. From 187d87a6f7dbccf4b4b569c7a2476c1ab9aec7ea Mon Sep 17 00:00:00 2001 From: sakshibasapure Date: Wed, 18 Oct 2023 20:10:33 -0400 Subject: [PATCH 39/40] Added troubleshooting.md file --- TROUBLESHOOTING.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 TROUBLESHOOTING.md diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 000000000..be7f6891f --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,23 @@ +# Troubleshooting Guide + +## How to Access Your Gmail Account with an 'App Password' + +If you're having trouble accessing your Gmail account, you can use Google's 'App Password' solution to resolve the issue. Follow these steps to generate and use an app password: + +1. **Enable Two-Step Verification:** + - Go to your Google Account settings by visiting [Google Account](https://myaccount.google.com/). + - In the "Security" section, locate and select "Two-step verification." + - Follow the on-screen instructions to set up two-step verification for your account. This adds an extra layer of security. + +2. **Create an App Password:** + - After enabling two-step verification, navigate to your Google Account's security settings. + - In the "Signing in to Google" section, choose "App Passwords." + - Select "Mail" and "Other (Custom name)" from the respective dropdown menus. + - Click the "Generate" button. + - Google will provide you with a 16-character App Password. This password is a one-time use code that allows you to access your Gmail account without revealing your actual account password. + +3. **Use the App Password:** + - When configuring email settings or applications, use the same settings as you would for sending emails from your Gmail account. + - However, replace your regular password with the generated 16-character app password. + - This app password should be entered wherever you are prompted for your email password. + From 790a08d9af4f4943da73f50dad905fadb569d996 Mon Sep 17 00:00:00 2001 From: agmalpur Date: Thu, 19 Oct 2023 00:37:53 -0400 Subject: [PATCH 40/40] Added style chekker --- .pylintrc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..25b374560 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,37 @@ +[MASTER] + +# Specify the maximum line length (79 is PEP 8's recommendation, but you can change it) +max-line-length = 79 + +[MESSAGES CONTROL] + +# Disable some specific warnings or errors +disable = C0114, C0115, C0116 + +[BASIC] + +# Naming style (snake_case) +method-rgx=_*[a-z_][a-z0-9_]*$ +function-rgx=_*[a-z_][a-z0-9_]*$ +attr-rgx=_*[a-z_][a-z0-9_]*$ + +[FORMAT] + +# Ensure consistent whitespace in function call +remove-trailing-whitespace = yes + +# Add or remove whitespace where necessary +single-space-after-comma = yes +single-space-after-keywords = yes + +[TYPECHECK] + +# Enable type hints and type checking (Python 3.5+) +init-import = yes +ignore-mixin-members = yes + +[IMPORTS] + +# Allow wildcard imports in specific cases (e.g., __init__.py) +allow-wildcard-with-all = yes +