def change_budget_amounts(budg_data, budg_path): """ Changes budget amounts selected by the user """ done = False while not done: print(env.OUTPUT_SEP_STR) prompt = "Which budget month would you like to edit eh? (q) to abort: " dates = util.select_indices_of_list(prompt, list(budg_data.keys()), return_matches=True, abortchar='q') if dates is None: # none type returned if user aborts return None for date in dates: print(f"--- Editing {date} ---") expenses = util.select_dict_keys_using_integer( budg_data[date], "Select the expenses you wish to change: ", print_children=False, quit_str='q', print_vals=True) if expenses is not None: # quit if user says so. for exp in expenses: amnt = util.get_float_input( f"Enter the new amount for '{exp}': ", force_pos=True, roundto=2) budg_data[date][exp] = amnt else: continue data_help.write_to_jsonFile(budg_path, budg_data)
def add_expense(exp_data, exp_stor_data, exp_path, exp_stor_data_path): """ Adds an expense to the expense database """ flag = False while not flag: exps = util.format_input_to_list( "Enter the expenses you wish to add: ", mode='string', quit_str='q') if exps is None: return None for exp in exps: if exp not in exp_data[env.EXPENSE_DATA_KEY]: exp_data[env.EXPENSE_DATA_KEY].append(exp) data_help.write_to_jsonFile(exp_path, exp_data) user_in = util.get_user_input_for_chars( f"Do you want to add expense [{exp}] expense to some stores [y/n]? ", ['y', 'n']) if user_in == 'y': add_expenses_to_store(exp_stor_data, exp_stor_data_path, [exp], force_add=True) else: flag = True else: print( f"That expense already exists! Try another one. Heres the list of existing expenses: {exp_data[env.EXPENSE_DATA_KEY]}" )
def change_storename(db_exp_data_fpaths, df, db_exprec_data_fpath, df_rec, exp_stor_data, stor_data, stor_pair_path, exp_stor_data_path): storenames = util.select_dict_keys_using_integer( exp_stor_data, 'Please select storename(s) to change: ', print_children=False, quit_str='q') if storenames != None: # select_dict_key_using_integer returns none if quitstr is given for storename in storenames: new_name = util.prompt_with_warning( f"Please enter your new storename for [{storename}]: ", ret_lowercase=True) if new_name != None: # none is returned from prompt_with_warning when user wants to abort. print(f"--- Editing {env.OUT_EXP_DATA_TEMPL} --- ") edit_df_entries(df, db_exp_data_fpaths[0], env.FILT_STORENAME, storename, new_name) print(f"--- Editing {env.OUT_EXPREC_DATA_TEMPL} --- ") edit_df_entries(df_rec, db_exprec_data_fpath, env.FILT_STORENAME, storename, new_name) print(f"--- Editing {env.STORE_PAIR_FNAME} --- ") stor_data = data_help.match_mod_dict_vals( stor_data, storename, new_name) data_help.write_to_jsonFile(stor_pair_path, stor_data) print(f"--- Editing {env.EXP_STOR_DB_FNAME} --- ") exp_stor_data = data_help.modify_dict_key( exp_stor_data, storename, new_name) data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data)
def select_store_for_purchase(storename, stor_data_path, stor_db, exp_stor_db, stor_exp_data_path): """ Uses user input to match a storename to a storename in the database. If no store is found, takes user through adding a store. """ if storename not in stor_db.keys(): prompt = f"I cannot filter for the store '{storename}'. Please select the storename for this store and I will remember it for next time. If it is a new store, type 'n': " matched_storename = util.select_dict_key_using_integer( exp_stor_db, prompt, quit_str='n', print_children=False, print_aborting=False) if matched_storename == None: matched_storename = util.process_input( f"Could you enter a storename so I remember this store in the future? " ) # if new store is added, add it to the exp_stor_db, with empty expenses to be added later by user exp_stor_db.update({matched_storename: []}) data_help.write_to_jsonFile(stor_exp_data_path, exp_stor_db) stor_db.update({storename: matched_storename}) data_help.write_to_jsonFile(stor_data_path, stor_db) else: matched_storename = stor_db[storename] return matched_storename, stor_db, exp_stor_db
def edit_settings(settings_path): """ Main interface for editing settings for the app """ done = False prompt = "Please select a setting to edit: " while not done: settings = data_help.read_jsonFile(settings_path) setting_sels = util.select_indices_of_list(prompt, list(settings.keys()), return_matches=True, abortchar='q', print_lst=True) if setting_sels is None: return None for setting in setting_sels: if setting == env.BANK_SELECTION_KEY: value = util.select_indices_of_list( "Please select from the list: ", settings[env.BANK_CHOICES_KEY], return_matches=True, abortchar='q') else: data_type = type(settings[setting]) value = util.get_input_given_type( f"Enter your '{data_type}' for {setting}={settings[setting]}. ", data_type, abortchar='q', setting=settings[setting]) if value is not None: # none type returned upon quit settings[setting] = value done = True data_help.write_to_jsonFile(settings_path, settings)
def edit_notes(prompt, notes, months, notes_path): """ Editing notes method params: prompt: Output prompt to user notes - the notes object notes_path - the path to the notes object months - the months given for editing or adding notes to notes_path - path to notes file """ for month in months: if month in notes.keys(): editable = notes[month] else: editable = "" note = util.get_editable_input( prompt + f"[{month}], or ctrl-c to quit process. " + "Tip: use (\\n) to denote new lines for plotting: ", editable=editable) if note is not None: # note is None type upon quit notes[month] = note data_help.write_to_jsonFile(notes_path, notes) else: return None return notes
def set_dict_keys_to_lowercase(dct_path): """ Function I wrote just to run on the stores exp database to keep all stores lowercase. """ dct = data_help.read_jsonFile(dct_path) for key in dct.keys(): dct = data_help.modify_dict_key(dct, key, key.lower()) data_help.write_to_jsonFile(dct_path, dct)
def choose_bank(json_path: str): """ Prompt user for banks """ settings_json = data_help.read_jsonFile(json_path) if env.BANK_SELECTION_KEY not in settings_json.keys(): prompt = f"Please choose your bank(s) from the list of banks: " choices = util.select_indices_of_list(prompt, env.BANK_OPTIONS, return_matches=True) settings_json[env.BANK_SELECTION_KEY] = choices data_help.write_to_jsonFile(json_path, settings_json)
def change_storepair(db_exp_data_fpaths, df, db_exprec_data_fpath, df_rec, exp_stor_data, stor_data, stor_pair_path, exp_stor_data_path, exp_data, budg_db): """ Allows user to change the pairing setup within stor_data, opting for the creation of a new store, or the repairing to a different store name """ bank_storenames = util.select_dict_keys_using_integer( stor_data, 'Please select your bank storename(s) to change its pairing, ', print_children=False, quit_str='q', print_vals=True) if bank_storenames != None: for bank_storename in bank_storenames: user_in = util.get_user_input_for_chars( f"\nDo you want to:\n(a) - re-pair [{bank_storename}] to an existing store you setup\n(b) - re-pair [{bank_storename}] to a new store name?\n(q) quit\nType here: ", ['a', 'b', 'q']) if user_in == 'a': stor_exp_keys = list(exp_stor_data.keys()) stor_exp_keys.sort() new_pairing = util.select_from_list( stor_exp_keys, f"\nPlease select a store to pair [{bank_storename}] to, or 'q' to quit: ", abortchar='q', ret_match=True) elif user_in == 'b': new_pairing = util.process_input( f"\nPlease input your new storename to be used with [{bank_storename}]: " ) exp_stor_data[new_pairing] = [ ] # add new storename to exp_stor_data elif user_in == 'q': new_pairing = None if new_pairing is not None: # None type indicates user quit stor_data[ bank_storename] = new_pairing # set the new pairing into the stor_db prompt = f"Searching {db_exp_data_fpaths[0]} for any old references to [{bank_storename}]." replace_store_in_df(prompt, df, db_exp_data_fpaths[0], new_pairing, exp_stor_data, budg_db, exp_stor_data_path, stor_data, stor_pair_path, bank_storename) prompt = f"Searching {db_exprec_data_fpath} for any old references to [{bank_storename}]." replace_store_in_df(prompt, df_rec, db_exprec_data_fpath, new_pairing, exp_stor_data, budg_db, exp_stor_data_path, stor_data, stor_pair_path, bank_storename) data_help.write_to_jsonFile(stor_pair_path, stor_data)
def sync_expenses(exp_data, exp_stor_data, exp_path, exp_stor_data_path): """ Makes sure that the expenses in storesWithExpenses.json are matched to expenses.json """ matched_expenses = [] for stor, expenses in exp_stor_data.items(): for expense in expenses: if expense in exp_data[env.EXPENSE_DATA_KEY]: matched_expenses.append(expense) else: print(f"REMOVED: {expense} from {stor}") exp_stor_data[stor] = matched_expenses matched_expenses = [] data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data)
def initialize_settings(settings_path): settings = data_help.read_jsonFile(settings_path) for key in env.SETTINGS_TEMPL.keys(): # add keys in globvar if key not in settings.keys(): settings[key] = env.SETTINGS_TEMPL[key] keys_to_rem = [] for key in settings.keys(): # remove keys not in globvar if key not in env.SETTINGS_TEMPL.keys(): keys_to_rem.append(key) for key in keys_to_rem: settings.pop(key) data_help.write_to_jsonFile(settings_path, settings)
def edit_list_in_dict(prompt, options, dct, key, dct_path, add=True): items = select_indices_of_list(prompt, list_to_compare_to=options, return_matches=True, abortchar='q') if items is not None: if add == True: for item in items: dct[key].append(item) else: for item in items: dct[key].remove(item) data_help.write_to_jsonFile(dct_path, dct) else: return None
def search_store_relationships(storename, exp_stor_db, budg_db, stor_exp_data_path, stor_db, stor_data_path): """ Searches the store expense relationship (exp_stor_db) for an expense and if multiple exist, prompts the user to select one. params: storename - a store's name from the dataframe exp_stor_db - a python dict containing stores as keys, and arrays of expenses as values budg_db - expense budget dict database stor_exp_data_path - filepath to storesWithExpenses.json stor_db - the storename to store strings database stor_data_path - the path to stor_db returns: the selected expense string, and the modified exp_stor_db """ exp_stor_dbKeys = exp_stor_db.keys() if storename not in exp_stor_dbKeys: storename, stor_db, exp_stor_db = select_store_for_purchase( storename, stor_data_path, stor_db, exp_stor_db, stor_exp_data_path) exps_fr_store = exp_stor_db[storename] if len(exps_fr_store) == 0: selected_exps = util.select_indices_of_list( f"No expenses for '{storename}'. Please select one or multiple to go with this store from now on.", list(budg_db.keys()), return_matches=True) if len(selected_exps) == 1: selected_exp = selected_exps[0] else: selected_exp = util.select_from_list( selected_exps, f"Please select which expense you want for this transaction at '{storename}': ", ret_match=True) exp_stor_db[storename] = selected_exps data_help.write_to_jsonFile(stor_exp_data_path, exp_stor_db) elif len(exps_fr_store) == 1: selected_exp = exps_fr_store[0] else: selected_exp = exps_fr_store[util.select_from_list( exps_fr_store, f"Please select an expense for this transaction at '{storename}': " )] return selected_exp, dict(exp_stor_db), stor_db, storename
def get_budgets(budg_path, exp_path, dates=None): """ Prompts user for budgeting options given a new month if no budget is present for that month """ exp_budg = data_help.read_jsonFile(budg_path) exp_data = data_help.read_jsonFile(exp_path) if dates == None: dates = [util.get_current_month()] for date in dates: exp_budg_keys = exp_budg.keys() if date not in exp_budg_keys: # check for current month to find exp categories print( f"I have detected some data with for the month {date} that has no budget set." ) print( "Please set the budget for this month.. or delete the data and run the program again." ) if len(exp_budg) != 0: user_in = util.get_user_input_for_chars( "Would you like to the whole thing (w) or create new (n)? ", ['w', 'n']) if user_in == 'w': key = util.select_dict_key_using_integer( exp_budg, "Please select a budget to copy: ", print_children=True, print_vals=False, print_child_vals=True) exp_budg[date] = exp_budg[key] elif user_in == 'n': exp_budg[date] = declare_new_budget(date, exp_data) else: exp_budg[date] = declare_new_budget(date, exp_data) print(f"Your budget is now saved for {date}.") else: print(f"Your monthly budget for {date} is: ") util.print_simple_dict(exp_budg[date], print_vals=True) data_help.write_to_jsonFile(budg_path, exp_budg) return
def add_expenses_to_store(exp_stor_data, exp_stor_data_path, exp_list: list, force_add=False): """ Adds an expense to a store within storesWithExpenses.json params: exp_stor_data : the dict object of storesWithExpenses.json exp_stor_data_keylist : the list of keys of exp_stor_data exp_list : the expense to add to the store selected by the user force_add : force the list of expenses into the store selection """ stores = util.select_dict_keys_using_integer( exp_stor_data, "Select the store(s) you wish to add expense(s) to.", print_children=False, quit_str='q', print_aborting=False, print_vals=True) if stores != None: for store in stores: if not force_add: pair_prompt = f"Which expenses do you want to add to '{store}', separated by a space.. (q) to abort: " expenses = util.select_indices_of_list(pair_prompt, exp_list, return_matches=True, abortchar='q') else: expenses = exp_list for expense in expenses: if expense not in exp_stor_data[store]: exp_stor_data[store].append(expense) print(f"Added '{expense}' to '{store}'") else: print( f"Ignoring addition! '{expense}' already is in '{store}'" ) data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data)
def setup_expense_names(exp_path: str): """ Gets a list of expense names from the user and saves them. """ exp_list = data_help.read_jsonFile(exp_path) if len(exp_list) == 0: exp_list[env.EXPENSE_DATA_KEY] = util.format_input_to_list( "Please input your expense categories, I will add a Misc category since it is reserved. " ) idxs_matched = util.check_lst_for_values( exp_list[env.EXPENSE_DATA_KEY], env.MISC_POS_VALUES) for idx in sorted(idxs_matched, reverse=True): print( f"Found {exp_list[env.EXPENSE_DATA_KEY][idx]} in your expenses. Removing since '{env.EXPENSE_MISC_STR}' is reserved as miscellaneous category." ) del exp_list[env.EXPENSE_DATA_KEY][idx] exp_list[env.EXPENSE_DATA_KEY].append(env.EXPENSE_MISC_STR) data_help.write_to_jsonFile(exp_path, exp_list) if env.EXPENSES_SUBTRACTED_KEY not in exp_list.keys(): exp_list[env.EXPENSES_SUBTRACTED_KEY] = [] data_help.write_to_jsonFile(exp_path, exp_list)
def edit_expense_name(exp_db_data_filepath, df, exp_recbin_path, df_rec, exp_data, budg_data, exp_stor_data, exp_path, budg_path, exp_stor_data_path): """ Edits an expense name across storesWithExpenses.json, Budgjet.json, expenses.json, and exp_db.csv """ exps_to_edit = util.select_indices_of_list( "Which expense(s) would you like to edit?: ", exp_data[env.EXPENSE_DATA_KEY], abortchar='q', return_matches=True) if exps_to_edit is None: # none type quits return None for exp_to_edit in exps_to_edit: if exp_to_edit != env.EXPENSE_MISC_STR: expense_new_name = util.select_item_not_in_list( f"Enter your new expense name for '{exp_to_edit}' 'q' to abort: ", exp_data[env.EXPENSE_DATA_KEY], ignorecase=False, abortchar='q') if expense_new_name is None: return None # edit the exp_stor_db print(f"--- Editing {env.EXP_STOR_DB_FNAME} --- ") for store in exp_stor_data.keys(): if exp_to_edit in exp_stor_data[store]: print(f"In {store}: ", end=" ") exp_stor_data[store] = util.replace_string_in_list( exp_stor_data[store], exp_to_edit, expense_new_name) # Edit the budg_db print(f"\n--- Editing {env.BUDGET_FNAME} --- ") for date in budg_data.keys(): budg_data[date] = data_help.modify_dict_key( budg_data[date], exp_to_edit, expense_new_name) # Edit expenses.json print(f"--- Editing {env.EXP_FNAME} --- ") exp_data[env.EXPENSE_DATA_KEY] = util.replace_string_in_list( exp_data[env.EXPENSE_DATA_KEY], exp_to_edit, expense_new_name) # Edit exp_db.csv and (writes itself) print(f"--- Editing {env.OUT_EXP_DATA_TEMPL} --- ") edit_df_entries(df, exp_db_data_filepath, env.EXPENSE, exp_to_edit, expense_new_name) print(f"--- Editing {env.OUT_EXPREC_DATA_TEMPL} --- ") edit_df_entries(df_rec, exp_recbin_path, env.EXPENSE, exp_to_edit, expense_new_name) # Write changes data_help.write_to_jsonFile(budg_path, budg_data) data_help.write_to_jsonFile(exp_path, exp_data) data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data) else: print( f"'{env.EXPENSE_MISC_STR}' is a reserved expense category, and its name cannot be changed." )
def remove_expense_from_dbs(exp_db_data_filepath, exp_recbin_path, exp_stor_data, exp_data, budg_data, df, df_rec, exp_stor_data_path, budg_path, exp_path): """ Removes an expense from the storesWithExpenses.json, Budgjet.json, expenses.json """ print( "Warning! Removing an expense is no small task. I will be pruning your store-expense database, your overall transactions, your expenses database and you budgets." ) print( "This is irreversible, and will result in any reference to that expense being reverted to 'Misc'." ) print( "Any budget amnt for that expense will be added to Misc to maintain balance in the force." ) exps_to_rem = util.select_indices_of_list( "Which expense(s) would you like to go? ", exp_data[env.EXPENSE_DATA_KEY], abortchar='q', return_matches=True) if exps_to_rem is None: return None for exp_to_rem in exps_to_rem: if exp_to_rem != env.EXPENSE_MISC_STR: # delete from exp_stor db print(f"--- Editing {env.EXP_STOR_DB_FNAME} --- ") for store in exp_stor_data.keys(): if exp_to_rem in exp_stor_data[store]: exp_stor_data[store].remove(exp_to_rem) print(f"Removed {exp_to_rem} from {store}.") # delete from expenses db print(f"--- Editing {env.EXP_FNAME} --- ") exp_data[env.EXPENSE_DATA_KEY].remove(exp_to_rem) print(f"Removed {exp_to_rem} from {env.EXP_FNAME}.") # remove from budget, adding amnt to Misc print(f"\n--- Editing {env.BUDGET_FNAME} --- ") for date in budg_data.keys(): if exp_to_rem in budg_data[date].keys(): amnt_to_misc = budg_data[date][exp_to_rem] budg_data[date][env.EXPENSE_MISC_STR] += amnt_to_misc budg_data[date].pop(exp_to_rem) else: print("No expense in this months budget.") print(f"--- Editing {env.OUT_EXP_DATA_TEMPL} --- ") edit_df_entries(df, exp_db_data_filepath, env.EXPENSE, exp_to_rem, env.EXPENSE_MISC_STR) print(f"--- Editing {env.OUT_EXPREC_DATA_TEMPL} --- ") edit_df_entries(df_rec, exp_recbin_path, env.EXPENSE, exp_to_rem, env.EXPENSE_MISC_STR) # write changes data_help.write_to_jsonFile(budg_path, budg_data) data_help.write_to_jsonFile(exp_path, exp_data) data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data) else: print( f"'{env.EXPENSE_MISC_STR}' is a reserved expense category, and it cannot be deleted." )
def remove_exp_from_store(df_path, df, exp_recbin_path, df_rec, exp_stor_data, exp_stor_data_path): """ Removes an expense from exp_stor_data dict, replacing the reference with user selection or misc in the dataframe """ prompt = "Select which store you wish to remove expense(s) from. (q) to abort." storenames = util.select_dict_keys_using_integer(exp_stor_data, prompt, print_children=False, quit_str='q', print_aborting=False, print_vals=True) if storenames is None: return None for storename in storenames: removed_expenses = util.select_indices_of_list( "Select which expense(s) to remove. (q) to abort.", exp_stor_data[storename], return_matches=True, abortchar='q') if removed_expenses is None: return None print(f"--- Editing {env.EXP_STOR_DB_FNAME} --- ") for expense in removed_expenses: exp_stor_data[storename].remove(expense) print(f"Removed {expense} from {storename}.") if len(exp_stor_data[storename] ) == 0: # if all was deleted, append misc into this store. print( f"You deleted all expenses from {storename}, I am adding {env.EXPENSE_MISC_STR} to preserve your data." ) exp_stor_data[storename].append(env.EXPENSE_MISC_STR) new_exp = env.EXPENSE_MISC_STR print( f"--- Editing {env.OUT_EXP_DATA_TEMPL} & {env.OUT_EXPREC_DATA_TEMPL} --- " ) for rem_exp in removed_expenses: if len(exp_stor_data[storename]) == 1: print( f"Only expense left for '{storename}' is '{exp_stor_data[storename][0]}'. Replacing '{rem_exp}' with '{exp_stor_data[storename][0]}'." ) new_exp = exp_stor_data[storename][0] else: new_exp = util.select_from_list( exp_stor_data[storename], f"Which expense do you want to use to replace '{rem_exp}' in '{storename}'? (q) to quit. ", abortchar='q', ret_match=True) if new_exp is None: # user quits return None edit_df_entries_given_columns(df, df_path, env.EXPENSE, env.FILT_STORENAME, storename, rem_exp, new_exp) edit_df_entries_given_columns(df_rec, exp_recbin_path, env.EXPENSE, env.FILT_STORENAME, storename, rem_exp, new_exp) data_help.write_to_jsonFile(exp_stor_data_path, exp_stor_data)