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 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 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 drop_rows(prompt, df): util.print_fulldf(df) rows = util.select_indices_of_list(prompt, list( df.index), abortchar='q', print_lst=False) if rows is not None: # above returns none if user aborts df.drop(index=rows, inplace=True) return df else: return None
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 choose_months_in_dfs(df_exp, df_budg, df_inc, years): for year in years: months = data_help.extract_year_month(df_exp[year].index.to_series()) months.sort() months_to_show = util.select_indices_of_list( f"Which months in {year} do you want to see? 'a' for all: ", list(months), return_matches=True, abortchar='q', ret_all_char='a') if months_to_show == None: return None, None, None, None dfs = data_help.drop_dt_indices_not_in_selection( months_to_show, months, [df_exp, df_budg, df_inc]) return dfs[0], dfs[1], dfs[2], months_to_show
def edit_cell_in_dfcol(db_data_filepath: str, df, col_name, opt_col=None, opt_dict=None, col_type=None): """ Edits a single cell in the df based upon options provided in opt_dict params: db_data_filepath - the path to the dataframes csv data df - (DataFrame) object col_name - the column to set the new value of opt_col - the column to grab a key from to search opt_dict for the list of options. If none, user can edit cell manually with input opt_dict - the dictionary containing the pairs between keys and options for a key col_type - the columns type to check for on user inputs """ index_list = df.index.tolist() util.print_fulldf(df) prompt = f"Select some indices from the above dataframe column '{col_name}' to edit: : " indices = util.select_indices_of_list(prompt, index_list, return_matches=True, abortchar='q', print_lst=False) if indices != None: for index in indices: if opt_col != None: opt_key = df.loc[index, opt_col] val = util.select_from_list( opt_dict[opt_key], f"Please select an option for cell [{index}] col '{col_name}' or (q) to quit: ", abortchar='q', ret_match=True) else: if col_type == 'float': val = util.get_float_input( f"Please input ({col_type}) for this entry at row [{index}] col [{col_name}]: ", force_pos=False, roundto=2) if val != None: # nonetype aborts df.at[index, col_name] = val data_help.write_data(df, db_data_filepath) else: break
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 df_swap(prompt=None, df_to_move_from=None, df_to_move_to=None, df_to_move_from_path=None, df_to_move_to_path=None, rows=None, cross_check_df=None, cross_check_col=None, cross_check_df_path=None): """ Performs a swap of data from one dataframe to another params prompt - the output to the user df_to_move_from - the dataframe to move rows from df_to_move_to - the dataframe to move the rows to rows - (default) None. If none, will prompt user, else will use the given rows to perform a swap. cross_check_df - perform a cross check on this dataframe for a value in cross_check_col and return matches cross_check_col - column to perform the cross check on """ if rows is None: util.print_fulldf(df_to_move_from) rows = util.select_indices_of_list(prompt, list(df_to_move_from.index), abortchar='q', print_lst=False) if rows is not None: # above returns none if user aborts if cross_check_df is not None: data_help.check_for_match_in_rows(rows, df_to_move_from, env.AMOUNT, cross_check_df, cross_check_col, cross_check_df_path, env.ADJUSTMENT) df_to_move_from, df_to_move_to = data_help.locate_and_move_data_between_dfs( df_to_move_from, rows, df_to_move_to, cross_check_col) data_help.write_data(df_to_move_to, df_to_move_to_path, sortby=env.DATE) data_help.write_data(df_to_move_from, df_to_move_from_path, sortby=env.DATE)
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 notes_editor(db_exp_data_fpaths, db_inc_data_fpaths, notes_path, bankconfig=None): """ Main menu for editing notes """ done = False while not done: exp_df = data_help.load_csvs(db_exp_data_fpaths, dtype=bankconfig.exp_dtypes, parse_dates=env.pdates_colname) inc_df = data_help.load_csvs(db_inc_data_fpaths, dtype=bankconfig.inc_dtypes, parse_dates=env.pdates_colname) notes_dict = data_help.read_jsonFile(notes_path) exp_months = data_help.extract_months(exp_df[env.DATE], start=False) inc_months = data_help.extract_months(inc_df[env.DATE], start=False) months_in_data = util.add_set_to_set(exp_months, inc_months, sort=True) if notes_dict == {}: prompt = "You have not entered any notes yet. Which month(s) would you like to add notes for? " edit_prompt_base = "Please enter your note below for month " else: prompt = "Please select a month to edit : " edit_prompt_base = "Edit your note below for month " sel_months = util.select_indices_of_list( prompt, list_to_compare_to=months_in_data, return_matches=True, abortchar='q') if sel_months is not None: notes = edit_notes(edit_prompt_base, notes_dict, sel_months, notes_path) else: done = True
def view_money_data(db_exp_data_fpaths, db_inc_data_fpaths, stor_pair_path, stor_exp_data_path, budg_path, notes_path, exp_path, dont_print_cols=None, bankconfig=None, settings_path=None): """ main method for the viewing of data params: exp_path - path to expenses.json dont_print_cols - ignore columns for output to CLI """ df_exp = data_help.load_csvs(db_exp_data_fpaths, dtype=bankconfig.exp_dtypes, parse_dates=env.pdates_colname, index=env.DATE) df_inc = data_help.load_csvs( db_inc_data_fpaths, dtype=bankconfig.inc_dtypes, parse_dates=env.pdates_colname, index=env.DATE) # TODO SHOW NET INCOME ON PLOTS df_exp = data_help.combine_and_drop(df_exp, env.AMOUNT, env.ADJUSTMENT, 'subtract') df_inc = data_help.combine_and_drop(df_inc, env.AMOUNT, env.ADJUSTMENT, 'subtract') exp_dict = data_help.read_jsonFile(exp_path) settings = data_help.read_jsonFile(settings_path) if df_inc.empty: # set index to datetime if empty. df_inc.set_index(pd.to_datetime(df_inc.index), inplace=True) budg_db = data_help.read_jsonFile(budg_path) # load the budget data df_budg = pd.DataFrame(budg_db) df_budg = df_budg.T # transpose to make index date str df_budg.set_index(pd.to_datetime(df_budg.index), inplace=True) # set index to date time notes_dict = data_help.read_jsonFile(notes_path) years = data_help.extract_years(df_exp.index.to_series()) years_to_show = util.select_indices_of_list( "Which of the above year(s) would you like to take a peak at, 'a' for all: ", years, return_matches=True, abortchar='q', ret_all_char='a') if years_to_show is None: # select_indices_of_list returns None if user aborts return None dfs = data_help.drop_dt_indices_not_in_selection(years_to_show, years, [df_exp, df_budg, df_inc]) df_exp = dfs[0] df_budg = dfs[1] df_inc = dfs[2] if df_exp is None: # quit condition return None freq, freq_desc = get_plotting_frequency() if freq == None: return None if freq == env.YEAR_FREQ: plot_for_date(years, dont_print_cols, exp_dict, df_inc, df_budg, df_exp, settings, notes_dict, freq=freq, freq_desc=freq_desc) elif freq == env.MONTH_FREQ: df_exp, df_budg, df_inc, months = choose_months_in_dfs( df_exp, df_budg, df_inc, years_to_show) if df_exp is None: return None plot_for_date(years, dont_print_cols, exp_dict, df_inc, df_budg, df_exp, settings, notes_dict, freq=env.MONTH_FREQ, freq_desc=freq_desc, months=months) elif freq == 'b': df_exp_mnth, df_budg_mnth, df_inc_mnth, months = choose_months_in_dfs( df_exp, df_budg, df_inc, years_to_show) if df_exp_mnth is None: return None plot_for_date(years, dont_print_cols, exp_dict, df_inc_mnth, df_budg_mnth, df_exp_mnth, settings, notes_dict, freq=env.MONTH_FREQ, freq_desc='month', override_show=True, months=months) plot_for_date(years, dont_print_cols, exp_dict, df_inc, df_budg, df_exp, settings, notes_dict, freq=env.YEAR_FREQ, freq_desc='year') else: pass
def edit_df_transaction_price(df_to_edit, df_to_edit_path, col_to_use, df_to_move_reduction_to=None, df_to_move_reduction_to_path=None, df_with_reductions=None, df_with_reductions_path=None, reduction_col=None, uuid_col=None, df_reduct_uuid_col=None, perform_swap=None): """ params: df_to_edit - the dataframe to edit the price on col_to_use - the column across all dataframes to be using df_with_reductions - the dataframe carrying transaction values that can be inserted into df_to_edit df_to_move_reduction_to - the df that will take the reduction transaction from df_with_reductions reduction_col - the column to grab reduction value from restorable - whether the df is one which transactions can be restored from. e.g. a recycle bin """ index_list = df_to_edit.index.tolist() util.print_fulldf(df_to_edit) prompt = f"Select some indices from the above dataframe column '{col_to_use}' to edit: : " indices = util.select_indices_of_list(prompt, index_list, return_matches=True, abortchar='q', print_lst=False) if indices is None: # none type aborts return None for index in indices: reductions_index_list = df_with_reductions.index.tolist() util.print_fulldf(df_with_reductions) prompt = f"Which index contains the transaction you want? " reduction_indices = util.select_indices_of_list(prompt, reductions_index_list, abortchar='q', return_matches=False, print_lst=False) if reduction_indices is not None: # none type aborts for reduction_index in reduction_indices: val = df_with_reductions.at[ reduction_index, reduction_col] # get transaction val if df_to_edit.at[index, col_to_use] == np.nan: # check for nan value df_to_edit.at[index, col_to_use] = 0.0 df_with_reductions.at[reduction_index, uuid_col] = df_to_edit.at[index, uuid_col] df_to_edit.at[index, col_to_use] = df_to_edit.at[index, col_to_use] + val if perform_swap: df_swap(df_to_move_from=df_with_reductions, df_to_move_to=df_to_move_reduction_to, df_to_move_from_path=df_with_reductions_path, df_to_move_to_path=df_to_move_reduction_to_path, rows=reduction_indices) # writes changes else: data_help.write_data(df_with_reductions, df_with_reductions_path, sortby=env.DATE) else: break data_help.write_data(df_to_edit, df_to_edit_path, sortby=env.DATE) # writes changes to the edited df.
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)
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." )