Skip to content

Instantly share code, notes, and snippets.

@Mikelem
Last active February 15, 2024 18:01
Show Gist options
  • Save Mikelem/f4051cab396ec877b9b08bc26b072c4f to your computer and use it in GitHub Desktop.
Save Mikelem/f4051cab396ec877b9b08bc26b072c4f to your computer and use it in GitHub Desktop.
subscriptions-run subscriptions_json.py first
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from datetime import timedelta
import os
"""
Necessary for sort_maint.py and sort_maint_records.py
"""
# These DATE STRINGS are absolutely necessary to compare against
# First, create a Tuple from today's date for date comparison and sorting:
dateToday = date.today()
current_mo_day_str = dateToday.strftime("%m-%d")
# WHY all of these 'current_mo_day's?!?!?!
current_mo_day = dateToday.strftime("%m-%d") # redundant!!!
current_mo_day = "('{}',)".format(current_mo_day)
current_mo_day = eval(current_mo_day)
current_day = dateToday.strftime("%d") # DD
current_month = dateToday.strftime("%m") # MM
current_year = dateToday.strftime("%Y") # YYYY
current_date_plus_mo = dateToday + relativedelta(months=1)
# currentDT = date.today # redundant!!!
# currentDT_str = datetime.strftime(currentDT, '%d/%m/%Y')
future_yr = current_date_plus_mo.strftime("%Y")
future_mo = current_date_plus_mo.strftime("%m")
# today_day = currentDT.day
def terminal_size():
import os
# print("Terminal size: os:")
size = os.get_terminal_size()
# print("Size:",size, type(size))
# print(f'Height: {size[1]}')
# print(f'Width: {size[0]}')
width=size[0]
height=size[1]
return width, height
t_width = os.get_terminal_size()[0]
t_height = os.get_terminal_size()[1]
from date_config import *
def date_formatted(next_bill):
# Get next_billing_date as string, convert to date obj then format back to string
# next_bill = row[8] # next_billing_date
next_bill_obj = date.fromisoformat(next_bill)
# Format date for nice printing
due_day= next_bill_obj.strftime('%d')
due_mo = next_bill_obj.strftime('%b')
due_yr = next_bill_obj.strftime('%Y')
# full_date = (f'{due_day} {due_mo}, {due_yr}')
full_date = (f'{due_mo} {due_day}, {due_yr}')
# print(full_date)
return full_date
import sqlite3
from sqlalchemy import *
from date_config import *
from format_date import date_formatted
import os
import json
# from menu import Menu_Tree
#### THIS FILE CONTAINS THE FOLLOWING FUNCITONS:
#
# print_header(msg,col_titles):
# Called by other functions when printing to screen
#
# print_vendor_summary(cur, subsTable): # prob don't need subsTable
#
# print_streaming(name_sorted_data, msg,stream_sorted_data):
#
# print_all_subs(subscr_df, msg):
#
# recurring_print():
# def main(subscr_df):
""" Print Headers """
def print_header(msg,col_titles):
t_width = os.get_terminal_size()[0]
t_height = os.get_terminal_size()[1]
# print(f'Width{t_width} Height:{t_height}') # DELETE AFTER TESTING
half_width = int((t_width - len(msg))/2)
quarter_w = int((t_width - len(msg))/4)
print(f'{"="*int(t_width)}')
print(f'{"="*half_width}{msg}{"="*half_width}')
print(col_titles)
print(f'{"="*int(t_width)}')
""" Print Summary """
def print_vendor_summary(subscr_df):
print("PRINTING_VENDOR_SUMMARY")
# List of dictionaries:
name_sorted_data = sorted(subscr_df.items(), key=lambda item: item[0])
name_sorted_msg = " :: VENDOR SUMMARY :: "
msg = name_sorted_msg # Could be redundant or works w/in grand scheme of things
col_titles = (f' {"NAME":<23s}{"CATEGORY":19s}{"$/MO":^10} {"RENEWS":^10} {"OWNER":>10}:{"ACCT":8}')
""" === PRINT HEADER === """
print_header(msg, col_titles)
print("\n")
for i in name_sorted_data:
print(i[0])
# W Notes:
# print(f' {"NAME":<23s}{"CATEGORY":19s}{"$/MO":^10} {"RENEWS":^10}{"OWNER":>10}{"ACCT":>8} {"NOTES":<22}')
# W/O Notes:
print("ALTERNATE Vendor Summary TITLE, not necessarily better:")
print(f'{"="* 85}')
print(f' {"NAME":<23s}{"CATEGORY":19s}{"$/MO":^10} {"RENEWS":^10} {"OWNER":>10}:{"ACCT":8}')
print(f'{"="* 85}\n')
# print Totals for subscriptions and recurring
# print("Menu_Tree(1)")
#
# Menu_Tree()
#
# print("Menu_Tree(2)")
return
def print_streaming_sched(name_sorted_data):
""" PRINTS STREAMING SERVICES and FILMS """
# print("AMZN LEN:", len("Amazon Prime"))
# json_file = "data/subs-test.json"
# f = open(json_file,'r+')
# subscr_df = json.load(f) #Dictionary
# # ALL VENDOR Data Sorted by Vendor Name
# name_sorted_data = sorted(subscr_df.items(), key=lambda item: item[0])
""" Streaming Vendors sorted by upcoming due date: """
total_mo_subs = 0.0
total_yr_subs = 0.0
# streaming_sorted_data_sched = sorted(subscr_df.items(), key=lambda item: item[0])
# streaming_sorted_data_sched = sorted(subscr_df.items(), key=lambda item: item[0] + item[1]['category']=='streaming')
msg = " :: STREAMING SUMMARY :: "
# print_streaming(name_sorted_data, streaming_sorted_msg, stream_sorted_data)
""" === PRINT HEADER === """
col_titles = f'{"NAME":14}{"$/month":>9}{" "*10}{"$/annual":^11}{" "*5}{"Billing Per":<10}{" "*4}{" "*1}{"ACCT":^3}{" "*4}{"RENEW DATE":<16s}'
print_header(msg, col_titles)
len_data = 22+26+47 +4
col_rem = int(t_width - len_data)
for item in name_sorted_data:
if len(item[1]["notes"]) > col_rem:
notes = item[1]["notes"][:col_rem-3]+"..."
else:
notes = item[1]["notes"]
if item[1]['category']=='streaming':
# print("\n")
print(
f'{item[0]:14s}'+
f'${item[1]["price_mo"]:>5.2f}/month'+
f'{" "*6}${item[1]["price_yr"]:>6.2f}/year'+
f'{" "*4}{"Billed: " + item[1]["period"]:>10s}'+
# f'{" "*4}{item[1]["owner"]:>6s}'+
f'{" "*4}{"Acct Chgd: " + item[1]["acct"]:^8s}'+
f'{" "*4}{"Due: " +item[1]["next_billing_date"]}'
)
# print(*(stream_data[item[0]]['films']), sep=", ")
for film in sorted(item[1]['films']):
print(f'{" "*6}{film}')
if item[1]["status"] == "cancelled":
continue
else:
total_mo_subs += item[1]["price_mo"]
total_yr_subs += item[1]["price_yr"]
""" === PRINT FOOTER === """
foot_msg_mo = f'{"="*10} TOTAL MONTHLY COSTS: STREAMING ${total_mo_subs:,.2f}'
foot_msg_yr = f'; YEARLY: ${total_yr_subs:,.2f}'
foot_rtn_msg = " RTN for MAIN MENU " +("="*15)
# third_spc_msg = int((t_width-len(foot_msg_mo)-len(foot_rtn_msg))/3)-3
eq_spacers = t_width-(len(foot_msg_mo) + len(foot_msg_yr) +len(foot_rtn_msg))
""" Calculations for footer message: """
# print("Terminal WIDTH:", t_width)
# print("Foot msg:", len(foot_msg))
# print("Rtn msg:", len(foot_rtn_msg))
# print("Running total: ", t_width-len(foot_msg)-len(foot_rtn_msg))
# print("Third MSG L:", third_spc_msg)
""" 1st FOOTER:"""
print(f'\n{foot_msg_mo}{foot_msg_yr}{"="* eq_spacers}{foot_rtn_msg}')
# print(f'\n{"="*(third_spc_msg-14)}{foot_msg_mo}{total_mo_subs:,.2f}; {foot_msg_yr}{total_yr_subs:,.2f} {"="*third_spc_msg*2}{foot_rtn_msg}')
print(f'\n{"="*t_width}\n')
""" === END FOOTER === """
input()
os.system('clear')
return
def print_streaming(stream_sorted_data, stream_data):
stream_file = "data/streaming.json"
stream_df = open(stream_file,'r+')
stream_data = json.load(stream_df) #Dictionary
""" Just prints the films that are streaming, no due dates or costs"""
""" === PRINT HEADER === """
print(f'\n{"="*t_width}\n')
header_msg = f'{"="*15}{" NOW STREAMING "}'
head_eq_spacers = t_width-(len(header_msg))
print(f'{header_msg}{"="* head_eq_spacers}')
for item in stream_sorted_data:
film_str=""
print(f'\n{item[0]}:')
# print(type(item[1]["films"]))
for film in sorted(stream_data[item[0]]['films']):
print(f'{" "*16}{film}')
# print(*(item[1]["films"]), sep=", ")
# """ === PRINT FOOTER === """
# foot_msg_mo = f'{"="*10} TOTAL MONTHLY COSTS: STREAMING ${total_mo_subs:,.2f}'
# foot_msg_yr = f'; YEARLY: ${total_yr_subs:,.2f}'
foot_rtn_msg = " RTN for MAIN MENU " +("="*15)
eq_spacers = t_width-(len(foot_rtn_msg))
""" Calculations for footer message: """
# print("Terminal WIDTH:", t_width)
# print("Foot msg:", len(foot_msg))
# print("Rtn msg:", len(foot_rtn_msg))
# print("Running total: ", t_width-len(foot_msg)-len(foot_rtn_msg))
# print("Third MSG L:", third_spc_msg)
""" 1st FOOTER:"""
print(f'\n{"="* eq_spacers}{foot_rtn_msg}')
# print(f'\n{"="*(third_spc_msg-14)}{foot_msg_mo}{total_mo_subs:,.2f}; {foot_msg_yr}{total_yr_subs:,.2f} {"="*third_spc_msg*2}{foot_rtn_msg}')
print(f'\n{"="*t_width}\n')
input()
os.system('clear')
return
def print_all_subs(date_sorted_data):
# name_sorted_data
date_sorted_msg = " :: SUBSCRIPTIONS COMING DUE :: "
cancelled_vendors = []
total_mo_subs = 0.0
# TODO:
""" IF 'SHARED' Divide by 2 to get My costs """
""" === PRINT HEADER === """
col_titles = f'{"NAME":22}{"RENEW DATE":<16s}{"$/MONTHLY":>9}{" "*4}{"$/ANNUAL":^11}{" "*5}{"PERIOD":<10}{" "*4}{"OWNER":8}{" "*1}{"ACCT":^3}{" "*4}{"NOTES":<22}'
print_header(date_sorted_msg, col_titles)
len_data = 22+26+47 +4
col_rem = int(t_width - len_data)
for item in date_sorted_data:
next_bill_dateObj=date.fromisoformat(item[1]["next_billing_date"])
if dateToday > next_bill_dateObj and item[1]["status"] == 'cancelled':
""" DELETE CANCELLED SUBSCRIPTIONS: """
""" OR leave in dB and put cost @ $0.00 """
cancelled_vendors.append(item[0])
continue
if len(item[1]["notes"]) > col_rem:
notes = item[1]["notes"][:col_rem-3]+"..."
else:
notes = item[1]["notes"]
if type(item[1]["price_mo"] )is list:
price_mo = sum(item[1]["price_mo"]) / len(item[1]["price_mo"])
else: price_mo = item[1]["price_mo"]
print(
f'{item[0]:22s}'+
f'{item[1]["next_billing_date"]}'+
f'{" "*6}${price_mo:>7.2f}'+
f'{" "*6}${item[1]["price_yr"]:>8.2f}'+
f'{" "*4}{item[1]["period"]:>10s}'+
f'{" "*4}{item[1]["owner"]:>6s}'+
f'{" "*4}{item[1]["acct"]:^8s}'+
f'{" "*2}{notes:{col_rem}}'
)
total_mo_subs += price_mo
""" === PRINT FOOTER === """
foot_msg = " TOTAL MONTHLY COSTS: ALL SUBSCRIPTIONS $"
foot_rtn_msg = " RTN for MAIN MENU"
third_spc_msg = int((t_width-len(foot_msg)-len(foot_rtn_msg))/3)-3
print(f'\n{"="*(third_spc_msg-11)}{foot_msg}{total_mo_subs:,.2f} {"="*third_spc_msg*2}{foot_rtn_msg}')
str_l = ', '.join(cancelled_vendors)
print("Cancelled:", str_l)
input()
os.system('clear')
# RECURRING COSTS REPORT:
def hard_costs_print(subscr_df, stream_sorted_data):
""" DUH! Feed the list of vendors sorted alphabetically and filter out
the ones whose categories don't appear in the list of hard_cost_cats !!!"""
# includes categories: utilities, health_insurance, credit_card_memb,dues,
hard_cost_cats = ['utilities', 'health_insurance', 'credit_card_memb','dues',]
sortedHardCosts = []
total_ute_mo_costs = 0
""" === PRINT HEADER === """
print(len("=========================="))
print(f'{"="*58}RECURRING/MONTHLY HARD COSTS{"="*58}')
print("\n")
print(f'{"DUE DATE":<20s}{"NAME":22}{"$MONTHLY":^9} {"$ANNUAL":^8} {"PERIOD":<10}{"OWNER":10}{"ACCT":^8} {"CATEGORY":<22}')
for item in subscr_df:
pass
# TODO: print MY share, taking the 'shared' accts and dividing that number by 2
return
# print_all_subs(name_sorted_data)
if __name__ == '__main__':
# main()
""" SUBSCRIPTIONS DATA:"""
json_file = "data/subs-test.json"
f = open(json_file,'r+')
subscr_df = json.load(f) #Dictionary
""" STREAMING DATA:"""
stream_file = "data/streaming.json"
stream_df = open(stream_file,'r+')
stream_data = json.load(stream_df) #Dictionary
""" SORTED LISTS: """
# List of dictionaries:
# ALL VENDOR Data Sorted by Vendor Name
name_sorted_data = sorted(subscr_df.items(), key=lambda item: item[0])
# STREAMING Data Sorted by Vendor Name:
stream_sorted_data = sorted(stream_data.items(), key=lambda item: item[0])
# ALL Data Sorted by Next_Billing_Date:
date_sorted_data = sorted(subscr_df.items(), key=lambda item: (item[1]['next_billing_date']+ item[0]))
periods = dict()
for item in date_sorted_data:
per = item[1]['period']
print(per)
periods[per] = periods.get(per, 0) + 1
input("EVALUATE PERIODS")
""" FUNCTIONS: """
# Print all Vendors Alphabetically:
print_vendor_summary(subscr_df)
# Print all vendors according to Due Dates for billing:
print_all_subs(date_sorted_data)
# Streaming data, no films
print_streaming_sched(subscr_df, stream_sorted_data, stream_data)
# Streaming data, just films
print_streaming(stream_sorted_data, stream_data)
# Utilities Vendors - Hard costs sorted by due date: """
# includes categories: utilities, health_insurance, credit_card_memb,dues,
hard_costs_print(subscr_df,stream_sorted_data)
""" Hard Costs Vendors sorted by Name: """
""" A list with due 'days' rather than upcoming due dates """
# hard_costs= dict([(k,v) for (k,v) in name_sorted_data.items() if v['category']=='utilities' or 'insurance' in v['category']])
print("UTILITIES::::")
hard_costs= dict([item for item in name_sorted_data if item[1]['category']=='utilities' or 'insurance' in item[1]['category']])
print("HARD COSTS:")
for k,v in hard_costs.items():
print(k,v)
print()
input("CHECK HARD COSTS")
# Cancelled Vendors sorted by due date: """
import re
from date_config import *
import json
import math
import pandas as pd
import statistics
""" update_dates (subscr_data): (formerly maint_all) """
""" UPDATE RECORDS BEFORE EXECUTING THE REST OF THE PROGRAM: """
""" Sets the last_bill_date and next_bill_date relative to the current date
to assist in sorting records according to what is coming due.
Disregards the past.
"""
""" Maintenance on start:
# update renewal date:
#if renew date is in the past AND period == 'monthly':
# if cancel_chg == 'cancelled':
# delete row
# else: update next_bill_date/'renews'
#if renew date is in the past AND period == 'annual':
# Expires ___ TODO TODO TODO
# if ['status'] == 'cancelled':
print(record)"""
periods = ['3yrs', '4yrs', 'annual', 'four_wks','monthly','quarterly','bi-annual']
def json_file_open():
json_file = "data/subs-test_MASTER.json"
with open(json_file,'r+') as json_file:
subscr_data = json.load(json_file)
return subscr_data, json_file
if zero_val_recs_chgd != 0:
json.dump(subscr_data, json_file, indent = 4)
json_file.close()
else:
json_file.close()
print("No records changed here")
def delete_vendor(vendor):
# When called by if ['status'] == 'cancelled' set default variable in function call
# called as module
# Opens JSON FILE
print("DELETE VENDOR FUNCTION")
subscr_data, json_file = json_file_open()
# LOOP: ask for file to delete
print(f'DELETING RECORD : {vendor}')
del subscr_data[vendor]
json_file.seek(0)
input("CHECK FILE DELETED")
# return to json_file_open function
return
def update_vendor(subscr_data):
pass
# For UPDATING a Vendor:
print(f'Account Details for _______:')
# for num, column in enumerate(data.description):
# try:
# pass
# Print floats as dollar amounts"
# print(f'[{num+1:2d}] {column[0]:11s}: ${row_data[num]:.2f}')
# except:
# Print all others normally
# print(f'[{num+1:2d}] {column[0]:11s}: {row_data[num]}')
print("Update which VENDOR? ")
"""ASK user for column/category to UDPATE"""
print("'0' to EXIT through the giftshop")
is_looping = True
while is_looping == True:
try:
num = int(input("Choice: "))
print(f'{num} {type(num)}')
if num >0 and num <16:
print("Got the number, continue")
break
elif num==0:
return False
else:
print("ENTER a number between 1-15!")
continue
except ValueError:
print("Please input an integer only...")
continue
# else:
# print("ENTER a number between 1-17!")
# continue
data_ind = int(num)-1
# category = data.description[data_ind][0]
# print(F"SELECT {category} FROM subscriptions WHERE name='{vendor_name}'") #DELETE WHEN FINISHED
# input("CHECK CHOICE") #DELETE WHEN FINISHED
# if row_data[data_ind] =="":
# row_data_ind="Nothing"
# else:
# row_data_ind=row_data[data_ind]
# print(f'Change {category}: from "{row_data_ind}" to...? ')
# ASK User for update info to insert
change =input("What are the changes? ") # Thing to change or update
# METHOD 2:
return
def update_quarterly(subscr_data, vendor):
#### (3) PERIOD = QUARTERLY
qtr_renew_dates = ("01-01", "04-01", "07-01", "10-01")
# For quarterly dues (IATSE)
# WE START with a date object and
# FINISH with a date string
# We don't care about the actual last or next_bill_date
# Except when it comes to writing to the file. And even then it's inconsequential.
last_bill_date = ""
next_year_str = (dateToday + relativedelta(years=1)).strftime("%Y") #YYYY
today_yyyy_str = dateToday.strftime("%Y") #YYYY
today_mm_dd_str = dateToday.strftime("%m-%d") #MM-DD
qtr = (dateToday.month-1)//3
# print("QTR:", qtr)
# print("QTR DATE:",qtr_renew_dates[qtr])
# print("TODAY-YEAR:",today_yyyy_str,type(today_yyyy_str))
# print("TODAY-MONTH:",today_mm_dd_str,type(today_mm_dd_str))
# print("NEXT YEAR:", next_year_str, type(next_year_str))
# print()
"""If the update day falls exactly on the quarter:"""
if today_mm_dd_str in qtr_renew_dates:
index = qtr_renew_dates.index(today_mm_dd_str)
print("INDEX:", index)
print("DUE DATE = TODAY")
# We're finished
last_bill_date = (dateToday - relativedelta(months=3)).strftime("%Y-%m-%d")
next_bill_date = dateToday.strftime("%Y-%m-%d")
# print("LAST BILL DATE:", last_bill_date)
# print("NEXT BILL DATE:", next_bill_date)
elif qtr == 3 and today_mm_dd_str not in qtr_renew_dates:
# IF the update day falls in the last quarter we need to make the next billing date in Q1 of the next year
print("SEE YOU NEXT YEAR")
print("NEW RENEW:", next_year_str, type(next_year_str))
next_bill_date = (f'{next_year_str}-{qtr_renew_dates[0]}')
last_bill_date = (f'{today_yyyy_str}-{qtr_renew_dates[3]}')
# print("NEXT_BILL_DATE:", next_bill_date)
# print("LAST_BILL_DATE:", next_bill_date)
else:
qtr+=1
print("NEW QTR:", qtr_renew_dates[qtr]) # 0-2
next_bill_date = (f'{today_yyyy_str}-{qtr_renew_dates[qtr]}')
lbd = date.fromisoformat(next_bill_date)
last_bill_date = (lbd - relativedelta(months=3)).strftime("%Y-%m-%d")
# print("NEXT_BILL_DATE:", next_bill_date)
# print("LAST_BILL_DATE:", next_bill_date)
print("LAST_BILL_DATE:", last_bill_date)
print("NEXT_BILL_DATE:", next_bill_date)
""" WRITE to JSON file: 1"""
subscr_data[vendor]['last_bill_date'] = last_bill_date
subscr_data[vendor]['next_bill_date'] = next_bill_date
# json_file.seek(0)
print("\tOKAY to WRITE to JSON: QUARTERLY")
return
# def new_sort(subscr_data):
# for vendor, vend_dict in subscr_data.items():
# print("VENDOR:", vendor)
# print("DICT:", vend_dict)
# print()
# input("CHECK VENDORS")
def fix_zero_vals(subscr_data, json_file):
"""FIX all the zero values and update the values when 'amt_paid' is a list. """
# TODO: fix the name 'fix_zero_vals'
print("We're at zero fixer-upper")
""" ------------------------------------------------------------------------------------------------------
We are mainly concerned with price_yr and price_mo.
All records should be entered with an 'amt_paid' and a 'period' value or let's face it,
what kind of operation are we running here?
There are THREE instances where we might need some calculating:
1. 'amt_paid' is a <list>. In which case, we need an average of the list of amounts paid.
2. 'amt_paid' is a number but there is a zero amount in 'price_yr' or 'price_amt'
This will trigger both the 'price_yr' and 'price_amt' amounts to be calculated from 'amt_paid'
3. 'amt_paid' = 0. In this case, the services are free and no calculations are necessary.
------------------------------------------------------------------------------------------------------ """
zero_val_recs_chgd = 0
for vendor, vend_dict in subscr_data.items():
print()
print(vendor)
print(vend_dict)
""" IF "amt_paid" is a LIST:"""
if type(subscr_data[vendor]["amt_paid"]) == list:
""" Xcel and Verizon costs may be stored as a list which allows the program to get an average cost
from a list of variable costs."""
# TODO: allow updates to the lists
print("VENDOR: LIST OF AMTS: ", vendor) # DELETE AFTER TESTING
# subscr_data[vendor]["price_mo"] = round((sum(subscr_data[vendor]["amt_paid"])/len(subscr_data[vendor]["amt_paid"])),2) # DELETE AFTER TESTING
# subscr_data[vendor]["price_yr"] = round((subscr_data[vendor]["price_mo"]*12),2) # DELETE AFTER TESTING
subscr_data[vendor]["price_mo"] = round(statistics.fmean(subscr_data[vendor]["amt_paid"]),2)
subscr_data[vendor]["price_yr"] = round((subscr_data[vendor]["price_mo"] * 12),2)
# json_file.seek(0)
zero_val_recs_chgd+=1
elif subscr_data[vendor]["price_mo"]==0.0 or subscr_data[vendor]["price_yr"]==0.0:
""" CALCULATES price_mo AND price_yr if either one has a zero value and
WHEN amt_paid is present:"""
print("ZEROS have been found")
print("PRICE/MO:", subscr_data[vendor]["price_mo"])
print("PRICE/MO:", subscr_data[vendor]["price_yr"])
print("AMT_PAID:", subscr_data[vendor]["amt_paid"])
period = subscr_data[vendor]["period"]
print("PERIOD:", period)
mo_yr_calc_factors = {'monthly': 12, 'annual': 1, 'four_wks': 13, 'quarterly': 4, '4yrs': .25, 'bi-annual': 2, '3yrs': .3}
if subscr_data[vendor]["amt_paid"]>0.0 :
subscr_data[vendor]["price_yr"] = round(subscr_data[vendor]["amt_paid"] * mo_yr_calc_factors[period],2)
subscr_data[vendor]["price_mo"] = round((subscr_data[vendor]["price_yr"]/12),2)
# input("STOP TO EVALUATE MATH FROM AMT_PAID")
zero_val_recs_chgd+=1
else:
if subscr_data[vendor]["amt_paid"] == 0:
print("DID YOU KNOW: no costs are associated with this record?")
print("PRICE/MO 2:", subscr_data[vendor]["price_mo"])
print("PRICE/YR 2:", subscr_data[vendor]["price_yr"])
print("AMT_PAID 2:", subscr_data[vendor]["amt_paid"])
# json_file.seek(0) #->->-> REINSTATE if PROBLEM PERSISTS
if zero_val_recs_chgd == 0:
print("NO RECORDS CHANGED")
else:
print("We're doing a json_file.seek(0)")
# json_file.seek(0)
# zero_val_recs_chgd+=1
# print("ZERO monthly found")
# subscr_data[vendor]["price_yr"] = round((subscr_data[vendor]["price_mo"]*12),2)
# print(f'{vendor} ZERO price/yr ammended to {subscr_data[vendor]["price_yr"]}/year')
print("RECORDS CHD:", zero_val_recs_chgd)
# input("ZERO REST STOP CHECK\n")
# return zero_val_recs_chgd, subscr_data, json_file
return zero_val_recs_chgd
def update_dates(subscr_data):
records_delete=[]
recs_date_chgd = 0
records_unchg = 0
records_del = 0
print("TODAY:", dateToday, type(dateToday))
# new_sort(subscr_data)
# for record in subscr_data:
for vendor, vend_dict in subscr_data.items():
""" TEST BLOCK (remove when finished)"""
print("VENDOR:", vendor)
print("VEND_DICT:", vend_dict)
# input("Check vendor/vend_dict")
print()
""" END TEST BLOCK """
vendor_name = vendor
last_bill_str = subscr_data[vendor]['last_bill_date']
last_bill_dateObj = date.fromisoformat(last_bill_str)
next_bill_str = subscr_data[vendor]['next_bill_date']
next_bill_dateObj = date.fromisoformat(next_bill_str)
status = subscr_data[vendor]['status']
period = subscr_data[vendor]['period']
print(vendor_name)
print("Status:",status)
print("Period:",period.upper())
print("LAST:", last_bill_str)
print("RENEWS:",next_bill_str)
print("Date subtraction:", next_bill_dateObj - dateToday)
days_sub = (dateToday - next_bill_dateObj).days
if days_sub > 0:
date_sub = "days in the past"
elif days_sub < 0:
date_sub = "days from today"
else:
date_sub = "...Today!"
print("DAYS_SUB", type(days_sub),days_sub)
renews_msg = (f'{abs(days_sub)} {date_sub}')
print(renews_msg)
status_msg_completed = ['cancelled','expired','completed']
# REMOVE CANCELLED SUBSCRIPTIONS:
if dateToday > next_bill_dateObj and status in status_msg_completed:
""" DELETE CANCELLED SUBSCRIPTIONS: """
""" OR leave in dB and put cost @ $0.00 """
""" OR leave in dB and don't change cost """
print(f'RECORD DELETE: {vendor_name}')
print("CANCEL FUNCTION ACTIVATED! DEFCON 3!!!")
subscr_data[vendor]["status"]="expired"
records_delete.append(vendor_name)
records_del+=1
# continue
# input("CHECK CANCEL FUNCTION")
# DOWNGRADE: Warn that a subscription will need to be adjusted
if status == 'downgrade' and (dateToday - next_bill_dateObj).days <= 45:
""" Need to know 30-45 days in advance to change payment/status"""
# "next_bill_date": "2022-09-23"
print("To Be Coded: downgrade")
recs_date_chgd+=1
# (1) PERIOD = ANNUAL:
elif dateToday > next_bill_dateObj and period == 'annual':
"""ADJUST ANNUAL RENEWAL DATES: """
# UPDATES annual renewal date if next_bill_date is past
print(f'ANNUAL: {vendor_name}')
print("RENEW_DATE in record:", next_bill_str, type(next_bill_str))
next_bill_dateObj_adv = next_bill_dateObj + relativedelta(years=1)
# Convert to STRING:
next_bill_str_nu = next_bill_dateObj_adv.strftime("%Y-%m-%d")
print("Revised:")
print("Last bill date:",next_bill_str)
print("Next bill date:", next_bill_str_nu)
""" WRITE to JSON file: 2"""
subscr_data[vendor]['next_bill_date'] = next_bill_str_nu
"""REPLACE the last_bill_date w renew_date str:"""
subscr_data[vendor]['last_bill_date'] = next_bill_str #subscr_data[vendor]['next_bill_date']
# json_file.seek(0)
print("\tOKAY to WRITE to JSON: ANNUAL")
recs_date_chgd+=1
#### (2) PERIOD = BI-ANNUAL
elif dateToday > next_bill_dateObj and period == 'bi-annual':
"""ADJUST BI-ANNUAL RENEWAL DATES: """
# UPDATES bi-annual renewal date if next_bill_date is past
print(f'BI-ANNUAL: {vendor_name}')
print("RENEW_DATE in record:", next_bill_str, type(next_bill_str))
next_bill_dateObj_adv = next_bill_dateObj + relativedelta(months=6)
# Convert to STRING:
next_bill_str_nu = next_bill_dateObj_adv.strftime("%Y-%m-%d")
print("Revised:")
print("Last bill date:",next_bill_str)
print("Next bill date:", next_bill_str_nu)
""" WRITE to JSON file: 3"""
subscr_data[vendor]['next_bill_date'] = next_bill_str_nu
"""REPLACE the last_bill_date w renew_date str:"""
subscr_data[vendor]['last_bill_date'] = next_bill_str #subscr_data[vendor]['next_bill_date']
# json_file.seek(0)
print("\tOKAY to WRITE to JSON: BI-ANNUAL")
recs_date_chgd+=1
input("PAUSE-CHECK bi-annual")
#### (3) PERIOD = QUARTERLY
elif dateToday > next_bill_dateObj and period == 'quarterly':
print(f'QUARTERLY: {vendor_name}')
update_quarterly(subscr_data, vendor)
recs_date_chgd+=1
#### (4) PERIOD = MONTHLY
elif dateToday > next_bill_dateObj and period == 'monthly':
print(f'MONTHLY: {vendor_name}')
print("LAST BILLED in record:", last_bill_dateObj)
print("RENEW_DATE in record:", next_bill_dateObj)
while dateToday > next_bill_dateObj:
next_bill_dateObj = next_bill_dateObj + relativedelta(months=1)
print(next_bill_dateObj)
last_date_dateObj = next_bill_dateObj - relativedelta(months=1)
# Convert to STRING:
next_bill_str_nu = next_bill_dateObj.strftime("%Y-%m-%d")
last_bill_str_nu = last_date_dateObj.strftime("%Y-%m-%d")
print("Revised:")
print("Last bill date:",last_bill_str_nu)
print("Next bill date:", next_bill_str_nu)
""" WRITE to JSON file: 4"""
subscr_data[vendor]['last_bill_date'] = last_bill_str_nu
subscr_data[vendor]['next_bill_date'] = next_bill_str_nu
# json_file.seek(0)
print("\tOKAY to WRITE to JSON: MONTHLY")
recs_date_chgd+=1
#### (5) PERIOD = four_wks (NYT):
elif dateToday > next_bill_dateObj and period == 'four_wks':
# FOR the NYT sub which is billed every 4 weeks:
# UPDATE 'next_bill_date' + 'last_bill_cycle'
print(f'FOUR_WKS: {vendor_name}')
print("RENEW_DATE in record:", next_bill_str, type(next_bill_str))
"""Keep adding 28 days to last_bill_date until you reach today"""
"""next_bill_date """
td = timedelta(28)
print("\n NYT test print")
while next_bill_dateObj < dateToday:
next_bill_dateObj +=td
print(next_bill_dateObj)
""" CONVERT TO STR then write to JSON FILE"""
last_bill_str = (next_bill_dateObj - td).strftime("%Y-%m-%d")
next_bill_str = next_bill_dateObj.strftime("%Y-%m-%d")
print(f'Last bill date: {last_bill_str}')
print(f'Next bill date: {next_bill_str}')
""" WRITE to JSON file: 5"""
subscr_data[vendor]['last_bill_date']=last_bill_str
subscr_data[vendor]['next_bill_date']= next_bill_str
# json_file.seek(0)
print("\tOKAY to WRITE to JSON: FOUR WKS")
recs_date_chgd+=1
#### (6) PERIOD = 3 yrs
elif dateToday > next_bill_dateObj and period == '3yrs':
print(f'3YRS: {vendor_name}')
print("To be coded 3yrs")
if "recurring" == "expires":
pass
# Contract expired, count as cancelled/expired
# if date is past mark "status" as "completed"
# print("\tOKAY to WRITE to JSON: 4 YRS")
recs_date_chgd+=1
else:
pass
# Make sure records_unchg gets added to
#### (7) PERIOD = 4 yrs
elif dateToday > next_bill_dateObj and period == '4yrs':
print(f'4YRS: {vendor_name}')
print("To be coded 4yrs")
print(f'4YRS: {vendor_name}')
""" IF EXPIRED: """
if "recurring" == "expires":
pass
# Contract expired, count as cancelled/expired
# if date is past mark "status" as "completed"
# print("\tOKAY to WRITE to JSON: 4 YRS")
recs_date_chgd+=1
else:
pass
# Make sure records_unchg gets added to
else:
print("\tNO CHANGE")
records_unchg +=1
# RUNNING COUNT:
### REMOVE AFTER TESTING:
print("RECORDS CHANGED: ", recs_date_chgd)
print("RECORDS UN-CHANGED: ", records_unchg)
print("RECORDS DELETED: ", records_del)
# input("CHECK RECORDS COUNTS")
print("RECORDS CHANGED: ", recs_date_chgd)
print("RECORDS UN-CHANGED: ", records_unchg)
print("RECORDS DELETED: ", records_del)
return recs_date_chgd
# def maintenance(subscr_data,json_file,f):
# def maintenance(subscr_data, json_file):
""" THE IDEA HERE IS TO be able to use the same file for testing over and over,
resetting the file to its original state, with:
with date references that are out of date and 0.0 values """
def restore_source_json():
print("Restore subs-test_MASTER.json file to original (fucked up state)?")
ans = input("Y/N").lower()
if ans == "y":
out_file = "data/subs-test_MASTER.json"
read_file = "data/subs-test_FRESH.json"
with open(read_file,'r') as in_file:
in_file_obj = json.load(in_file)
print(type(in_file_obj))
with open(out_file, "w") as outfile:
# json.dump(in_file_obj, outfile, sort_keys=True, indent = 4)
json.dump(in_file_obj, outfile, indent = 4)
else:
print("Leaving file alone")
return
def maintenance(json_file):
""" WHEN USED AS A MODULE, THIS IS WHAT IS CALLED
Will fix zero values and update the dates
The json file is opened here, locally and closed before proceeding"""
with open(json_file,'r') as json_file:
subscr_data = json.load(json_file)
"""
FIRST, check the values and fill in the blanks:
"""
zero_val_recs_chgd = fix_zero_vals(subscr_data, json_file)
# fix_zero_vals(subscr_data, json_file)
#recs_date_chgd = update_dates(subscr_data)
recs_date_chgd = update_dates(subscr_data)
if zero_val_recs_chgd != 0 or recs_date_chgd !=0:
with open("data/subs-test_MASTER.json", 'w') as file:
# json.dump(subscr_data, file, sort_keys=True, indent = 4, ensure_ascii=False)
json.dump(subscr_data, file, indent = 4, ensure_ascii=False)
json_file.close()
else:
json_file.close()
print("No records changed here")
# zero_val_recs_chgd, subscr_data, json_file = fix_zero_vals(subscr_data, json_file,f)
# print("RECORDS CHANGED DATES:", recs_date_chgd)
print("RECORDS CHANGED w ZERO VALS:", zero_val_recs_chgd)
return
if __name__ == '__main__':
json_file = "data/subs-test_MASTER.json"
maintenance(json_file)
exit()
periods = {'monthly': 20, 'annual': 22, 'four_wks': 1, 'quarterly': 1, '4yrs': 1, 'bi-annual': 1, '3yrs': 1}
"""
USES dictionary form, each key is the account
"""
json_file = "data/subs-data-test_MASTER-d.json"
with open(json_file,'r+') as json_file:
subscr_data = json.load(json_file)
""" FIXES ERRORS/UPDATES DATES: """
# recs_date_chgd = update_dates(subscr_data)
zero_val_recs_chgd = fix_zero_vals(subscr_data, json_file)
# zero_val_recs_chgd = fix_zero_vals(subscr_data)
# json.dump(subscr_data, json_file, sort_keys=True, indent = 4)
json.dump(subscr_data, json_file, indent = 4)
# print("DATES changed:",recs_date_chgd)
print("DONE: 'ZERO' errors fixed:", zero_val_recs_chgd)
restore_source_json()
# TODO: Should I put all of these under one '__main__' or keep them separated?
import calendar
import csv
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
import json
import pandas as pd
import re
from collections import namedtuple
# Dependencies:
from date_config import *
from sort_maint import *
from sort_maint_records import maintenance
from format_date import *
# from categories_col import *
from reports_print import *
from terminal_size import terminal_size
# import reports_print
# USES data/subscriptions.db OR:
# data/subscriptions_bu.db
# ---------------------------------------------------------------------------
# ------------------------- ORDER OF OPERATIONS: ----------------------------
# Create a connection to db
# (OPTIONAL) Evaluate db row by row using 'select_all_subscriptions()'
# Use for eval only, comment out when not necessary.
# IMPORTANT: Do this at the start and whenever there is an update to the db
# OR should you update before every print statment? Because that's only when you see it.
# UPDATE SORT ORDER: update_sort_key(): Orders subscriptions by date starting today.
# Ask user to choose a function:
# PRINT OUT: subscriptions_print_all(cur)
# TODO: Separate real subscriptions (option to life) from monthly costs (necessary for life functions)
# TODO: add an option for monthly total for previous month
# --- But, how will I get those totals w/o going into QUICKEN?
# TODO: Warnings of subscriptions that will renew that should be cancelled
# --- EXAMPLE: ApPlE music: Next bill date: June 30, 2022
# TODO: UPDATE APPLETV $6.99(7.61)/month; RENEWS: Jan 12, 2023
# ================= !!!!!!!!!!!!!!! =================
# TODO: Add function to add or delete subscriptions rather than do it manually
# ================= !!!!!!!!!!!!!!! =================
# ================= (()()()()()()()()()()()()) =================
# REPORT printing section
# ================= (()()()()()()()()()()()()) =================
def select_all_subscriptions():
pass
print("PRINTING ALL ROWS...")
"""Not used at this time but helpful to evaluate entire database"""
""" Use only after updates have been run """
# for row in rows:
# print(row)
# print("TYPE:", type(row))
# print(row[11])
# print("TYPE [11]:", type(row[11]))
# # ELIMINATE THIS PAUSE and MAYBE COMMENT OUT THE function altogether
# input("ANALYZE ROW TYPE...CONT or QUIT")
# print("Done printing rows")
return
# ############### PRINT OUT #####################
""" SORT BEFORE PRINT:
Sort by next_bill_date
THEN by
name
"""
# recurring REPORT:
def recurring_print():
pass
def subs_only_print():
pass
# STREAMING REPORT:
def streaming_print():
pass
total_stream_mo_costs = 0
# Print all subscriptions in order from today
# Eliminate 'cancelled' WHERE next_bill_date<today()
""" === PRINT HEADER === """
msg= "STREAMING/MONTHLY COSTS"
char_pad_num = ((t_width)-len(msg))/2-2
msg_formatted = (f'{"="*int(char_pad_num)} {msg} {"="*int(char_pad_num)}\n')
print(msg_formatted)
print("ROW SPACES:", len('NAME $/MO '))
print(f'{"NAME":12}{"$/MO":^10}{" "*4}{"$/YR":^8}{" "*5}{"OWNER":8}{"ACCT":^8}{" "*4}{"RENEWS":^8}')
# for row in rows:
# total_stream_mo_costs += float(row[2]) # Total cost of monthly subscriptions (price_mo)
# Format date for printing:
# full_date = date_formatted(row[8])
# print(record)
# NAME, PRICE/MO, PRICE/YR, RENEWS, OWNER, ACCT
# 12 7 4(sp)
# print(f'{row[0]:12} ${float(row[2]):>7,.2f}{" "*4}${float(row[4]):>8.2f} {row[1]:>8s} {row[5]:>8s}{" "*4}{full_date:>8}')
print("\n\tWhat to Watch:")
# for row in rows:
# print(row)
# print(f'\n\t{row[0]:7s}:\n\t{row[1]}')
msg = (f'TOTAL: ${total_stream_mo_costs:,.2f} / month')
char_pad_num = ((t_width)-len(msg))/2-2
msg_formatted = (f'\n{"="*int(char_pad_num)} {msg} {"="*int(char_pad_num)}\n')
print(msg_formatted)
return
# SHARED REPORT:
def shared_print():
# TODO: Print programs of interest for streaming and possible dates when the start/end
# TODO: Clarify this
total_shared_mo_costs = 0
""" === PRINT HEADER === """
msg= "SHARED ACCTS :: MONTHLY COSTS"
char_pad_num = ((t_width)-len(msg))/2-2
msg_formatted = (f'{"="*int(char_pad_num)} {msg} {"="*int(char_pad_num)}\n')
print(msg_formatted)
print(f'{"DUE DATE":<20s}{"NAME":22}{"$MONTHLY":^9} {"$ANNUAL":^8} {"PERIOD":<10}{"OWNER":10}{"ACCT":^8} {"NOTES":<22}')
# for row in rows:
# total_shared_mo_costs += float(row[2]) # Total cost of monthly subscriptions (price_mo)
# Format date for printing:
# full_date = date_formatted(row[8])
# print(record)
# print("{:20}{:22}${:>7,.2f} ${:>9.2f} {:>8s} {:>8s} {:>8} {:<22}".format(
# full_date,
# row[0],
# float(row[2]),
# float(row[4]),
# row[9], # period
# row[1], # owner
# row[5], # acct
# row[11])) # Notes
msg = (f'TOTAL: ${total_shared_mo_costs:,.2f}')
char_pad_num = ((t_width)-len(msg))/2-2
msg_formatted = (f'\n{"="*int(char_pad_num)} {msg} {"="*int(char_pad_num)}\n')
print(msg_formatted)
return
# PRINT ALL REPORT:
def subscriptions_date_sorted():
total_sub_mo_costs = 0
col_names = ["full_date", "name", "float(subscr_data[name]['price_mo']","float(subscr_data[name]['price_yr']",
"subscr_data[name]['period']","subscr_data[name]['owner']","subscr_data[name]['status']","subscr_data[name]['notes']"]
subs_df = pd.read_json("data/subs-test_MASTER.json")
df_swapped = subs_df.transpose()
selected_columns = col_names
# df_selected = df_swapped[col_names]
print(df_swapped.head)
df_selected = df_selected[df_selected['status']] != "expired"
df_selected.sort_values(by='full_date', inplace=True)
print("SUBS_DF:")
print(subs_df)
input("CHECK Dataframe")
# Print all subscriptions in order from today
""" === PRINT HEADER === """
print(f'{"="*58}SUBSCRIPTIONS/MONTHLY HARD COSTS{"="*58}')
print("\n")
print(f'{"DUE DATE":<20s}{"NAME":22}{"$MONTHLY":^9} {"$ANNUAL":^8} {"PERIOD":<10}{"OWNER":10}{"ACCT":^8} {"NOTES":<22}')
# for row in rows:
# total_sub_mo_costs += float(row[2]) # Total cost of monthly subscriptions (price_mo)
# Format date for printing:
# full_date = date_formatted(row[8])
# # print(record)
# print("{:20}{:22}${:>7,.2f} ${:>9.2f} {:>8s} {:>8s} {:>8} {:<22}".format(
# full_date,
# row[0],
# float(row[2]),
# float(row[4]),
# row[10], #period
# row[1],
# row[5],
# row[12]))
# print(f'\n{"="*42} ${total_sub_mo_costs:,.2f} {"="*58}')
return
def subscriptions_print_all(): # ADDED 'cur' as TEST using separated Menu_Tree()
total_mo_subs = 0
# Print all subscriptions in order from today
# Eliminate 'cancelled' WHERE next_bill_date<today()
# cur.execute("SELECT * from subscriptions WHERE cancel_chg!='cancelled' ORDER BY next_bill_date asc")
# rows = cur.fetchall()
""" === PRINT HEADER === """
print(f'{"="*58}SUBSCRIPTIONS{"="*58}')
print("\n")
print(f'{"RENEW DATE":<16s}{"NAME":22}{"$/MONTHLY":^9} {"$/ANNUAL":^8} {"PERIOD":<10} {"OWNER":10} {"STATUS":<10} {"NOTES":<22}')
# for name, details in subscr_data.items():
for name in subscr_data.keys():
status_msg_completed = ['cancelled','expired','completed']
# REMOVE CANCELLED SUBSCRIPTIONS:
if subscr_data[name]['status'] == 'expired':
continue
if isinstance(subscr_data[name]['price_mo'], list):
print("LIST:", subscr_data[name]['price_mo'])
total_mo_subs += sum(subscr_data[name]['price_mo']) / len(subscr_data[name]['price_mo'])
else:
total_mo_subs += subscr_data[name]['price_mo'] # Total cost of monthly subscriptions (price_mo)
# Format date for printing:
full_date = date_formatted(subscr_data[name]['next_bill_date'])
# RENEW DATE::NAME::$MONTHLY::$ANNUAL::BILLS::OWNER::NOTES
# print("\nROW:\n",row) TESTING PURPOSES ONLY
# {"RENEW DATE"}{"NAME"}{"$/MONTHLY"}{"$/ANNUAL"}{"PERIOD"}{"OWNER"}{"NOTES"')
print("{:16}{:22}${:>8,.2f} ${:>9.2f} {:<10s} {:<6s} {:<10s} {:<22}".format(
full_date,
name,
float(subscr_data[name]['price_mo']),
float(subscr_data[name]['price_yr']),
subscr_data[name]['period'],
subscr_data[name]['owner'],
subscr_data[name]['status'],
subscr_data[name]['notes']))
# Print Footer:
# print(f'TOTAL MONTHLY COSTS (subscriptions only): ${}')
print(f'\n{"="*42} ${total_mo_subs:,.2f} {"="*58}')
# ================= (()()()()()()()()()()()()) =================
# END REPORT printing section
# ================= (()()()()()()()()()()()()) =================
# ================= (()()()()()()()()()()()()) =================
""" UPDATE RECORDS BEFORE EXECUTING THE REST OF THE PROGRAM: """
# ================= (()()()()()()()()()()()()) =================
# Could this be put into one function: See notes at bottom of update_sort.py
def update_sort_key():
''' Updates the next_bill_date in the db so that subscription refresh stays current'''
# Update annual renewals
maintenance()
# update_annual() # TODO
# update_nyt()
# update_next_bill_date()
# update_quarterly()
def test_outboarding():
print("dateToday:",dateToday)
input("check import date_config")
# update_nyt()
update_quarterly()
# update_next_bill_date()
def dummy_function():
print('\n The dummy function has finished running.')
def get_vend_names(subscr_data):
# Make a list (sorted) of names/vendors in subscriptions:
# This function could be under 'housekeeping'
print("This will be the list of vendors")
# TODO: is this used anymore? There is no 'name' in this dictionary/json
# vendor_data = sorted(subscr_data, key=lambda x: x['name'])
vendor_data = list(subscr_data.keys())
for v in vendor_data:
print(v)
return vendor_data
def vendor_update_mo_yr():
pass
# periodically update the price_mo and price_yr columns if there is no info
# Conversely, you could have this function run when adding or updating a vendor
def vendor_update():
# Ask for Vendor name, compare to vendor list to see if it's valid
vendor_name_inp = input("What vendor do you want to update? ")
# vendor_data = get_vend_names()
is_looping = True
# WHILE loop:
while is_looping == True:
for vendor_name in vendor_data:
if vendor_name_inp.lower()== vendor_name.lower():
# print("MATCH", vendor_name) DELETE WHEN FINISHED
is_looping = False
break
else:
continue
if not is_looping:
break # break out of outer loop
else:
print("Let's try it again.")
vendor_name = input("What vendor do you want to update? ")
print("Updating:", vendor_name)
# TODO: CONTINUE EDITING???
# TODO: if vendor_name == 'Xcel' or vendor_name == 'Verizon':
# print("Adding cost to amt_paid list")
print("Continue Updating? (y/n)")
cont_choice = input("").lower()
is_looping = True
while is_looping == True:
if cont_choice=='y':
loop_choice = update_vendor(vendor_name)
if loop_choice==False:
Menu_Tree()
elif cont_choice=='n':
break
# print(row w/ column names)
# input("CHECK DB for UPDATE...") # DELETE WHEN FINISHED
Menu_Tree()
def vendor_delete():
print("This is where you'll delete an existing vendor")
# vendor_name = input("Delete which Vendor? ")
still_selecting = True
while still_selecting:
vendor_name = input(" Delete which Vendor? ")
# menu_choice = input(' Enter your selection: ')
# if vendor_name.lower() in vendor_data:
if vendor_name in vendor_data:
still_selecting = False
else:
print(f' "{vendor_name}" is not a valid entry.')
print(f' DELETE: {vendor_name}?')
# YES or NO
del_choice = input(" YES (y) or NO(any key)").lower()
if del_choice == "y":
print(f' {vendor_name} DELETED')
else:
print(f' {vendor_name} DELETED')
def vendor_add():
new_vendor_data=[]
print("This is where you'll add a new vendor")
print("Please add the following information:")
print("Press RTN if info isn't handy.")
name = input("Vendor Name?: ")
# TODO-check whether vendor exists
is_looping = True
# WHILE loop:
while is_looping == True:
for vendor in vendor_data:
if name.lower()== vendor.lower():
print("MATCH", name)
continue
else:
is_looping = False
break
if not is_looping:
break # break out of outer loop
else:
print("That vendor is already in the database.")
# Escape to main menu or continue
name = input("Vendor Name? ")
new_vendor_data.append(name)
# Limit range of category list because you already have the 'name'
for i in range(1,len(cats_l_alt_order)):
if i == 3:
var = float(input(cats_l_alt_order[i][2]))
else:
var = input(cats_l_alt_order[i][2])
new_vendor_data.append(var)
print("New Vendor Data list:")
for i, val in enumerate(new_vendor_data):
print(f'{i}: {val}')
if val=="":
if i == 7:
# last_bill_date
new_vendor_data.pop(7)
# copy start_date
new_vendor_data.insert(7, new_vendor_data[6])
if i ==8:
# next_bill_date
if new_vendor_data[4]=='annual':
# Add year to start_date
next_bill = date.isoformat(date.fromisoformat(new_vendor_data[6]) + relativedelta(months=1))
elif new_vendor_data[4]=='monthly':
# Add year to start_date
next_bill = date.isoformat(date.fromisoformat(new_vendor_data[6]) + relativedelta(months=1))
elif new_vendor_data[4]=='monthly':
# This has to be worked out w/ the quarterly dues thing.
next_bill = date.isoformat(date.fromisoformat(new_vendor_data[6]) + relativedelta(months=3))
elif new_vendor_data[4]=='four-wks':
next_bill = date.isoformat(date.fromisoformat(new_vendor_data[6]) + timedelta(28))
new_vendor_data.pop(8)
new_vendor_data.insert(8, next_bill)
period = new_vendor_data[4]
pmt = new_vendor_data[3]
if i == 12:
# price_mo
def price_month(operator, x):
return {
'annual': lambda: x / 12,
'quarterly': lambda: x *4 / 12,
'monthly': lambda: x,
'four-wks': lambda: x / 4 * 52 / 12,
}.get(operator, lambda: None)()
price_mo = price_month(period, pmt)
new_vendor_data.pop(12)
new_vendor_data.insert(12, price_mo)
if i == 13:
# price_yr
def price_year(operator, x):
return {
'annual': lambda: x,
'monthly': lambda: x*12,
'quarterly': lambda: x *4,
'four-wks': lambda: x / 4 * 52,
}.get(operator, lambda: None)()
price_yr = price_year(period, pmt)
new_vendor_data.pop(13)
new_vendor_data.insert(13, price_yr)
def reminder():
pass
def say_goodbye():
f.close()
print('\nExiting through the gift shop.')
exit()
def get_cols(subscrip_data):
# This may be used to determine which columns print to the terminal
col_len={
"acctId": 0,
"amt_paid": 0,
"category": 0,
"expire_notice": 0,
"expires": 0,
"films": 0,
"last_bill_date": 0,
"name": 0,
"next_bill_date": 0,
"notes": 0,
"owner": 0,
"paid_from": 0,
"period": 0,
"price_mo": 0,
"price_yr": 0,
"pwd": 0,
"recur": 0,
"recurring": 0,
"start_date": 0,
"status": 0,
"user": 0,
"watch_now_url": 0
}
# Get a list of column names
# print("RECORDS:") # TROUBLESHOOTING
print("Type subscrip_data:", type(subscrip_data))
for record in subscrip_data:
print(record, type(record))
for col, v in subscrip_data[record].items():
exist_size = len(str(col_len[col]))
new_size = len(str(v))
# print(f'{col}:') # TROUBLESHOOTING
# print(f'Existing:{exist_size}') # TROUBLESHOOTING
# print(f'New.....:{new_size}') # TROUBLESHOOTING
if new_size > exist_size:
col_len[col]=new_size
# print()
print("col_len dict:")
for k,v in col_len.items():
print(f'{k:16s}:{v}')
input("Check col_len dictionary")
# print("col_len", col_len) # TROUBLESHOOTING
return col_len
# TODO:
# Option to print:
# Last month
# YTD
# Last Year
# Totals
# Add columns: Monthly, Yearly
class Menu_Tree():
def __init__(self):
self.all_menus = {
'main_menu': {
"[1] Print Reports": 'print_menu',
'[5] Set reminder cancel/change':reminder,
"[2] Update Vendor": 'update_menu',
"[3] Delete Vendor": vendor_delete,
'[4] Add Vendor': vendor_add,
'[5] Set reminder cancel/change':reminder,
"[0] Exit the program.": say_goodbye},
'print_menu': {
"[1] REPORT: Print ALL (alphabetical)": subscriptions_print_all,
"[2] REPORT: Print ALL (dated)": subscriptions_date_sorted,
"[3] REPORT: Recurring":recurring_print,
"[4] REPORT: Subscriptions ONLY": subs_only_print,
"[5] REPORT: Streaming": streaming_print,
"[6] REPORT: Shared ONLY": shared_print,
"[7] REPORT: Upcoming Cancellations": dummy_function,
"[8] REPORT: Software": dummy_function,
'[9] Go back to main page': 'main_menu',
"[0] Exit the program.": say_goodbye},
# Should iterate through the column names, prompting for input
'add_menu': {
"[1] Add new vendor": vendor_add,
"[2] Vendor update": vendor_update,
"[3] Delete vendor": vendor_delete,
'[4] Go back to main page': 'main_menu',
"[0] Exit the program.": say_goodbye},
# Should ask for a name to delete. If that name isn't present, list the names.
'delete_menu': {
"[1] Search list of: ": dummy_function,
"[2] Search a: ": dummy_function,
'[4] Go back to main page': 'main_menu',
"[0] Exit the program.": say_goodbye},
# Should have a drop down menu of column names
'update_menu': {
"[1] Update a Record: ": vendor_update,
"[2] Update price/mo /yr ": vendor_update_mo_yr,
"[3] Is there anything else? ; dummy_function"
'[4] Return to the main menu': 'main_menu',
"[0] Exit the program.": say_goodbye}}
self.run_the_current_command('main_menu')
def run_the_current_command(self, command):
if type(command) == str:
self.current_menu = command
command = self.run_the_menus(self.all_menus[command])
else:
command()
self.run_the_menus(self.all_menus[self.current_menu])
def run_the_menus(self, menu):
look_up_table = {label[1: 2]: label for label in menu}
# print("Look UP Table:", look_up_table)
# for k,v in look_up_table.items():
# print(f'{k}:{v}')
# input("PAUSE to check out look_up_table")
""" MENU FORMATTING: """
# row_pad = (terminal_rows-(len(menu)+2))/2
# print('\n'* int(row_pad))
print(f'\n{"="* terminal_cols}\n')
lbl_space = int(terminal_cols/10)
""" MENU PRINT: """
for label in menu:
print(f'{"- " * lbl_space}{label}')
# print(' - ', label)
print(f'\n{"="* terminal_cols}')
""" SELECTION LOOP: """
still_selecting = True
while still_selecting:
menu_choice = input(' Enter your selection: ')
if menu_choice != '' and menu_choice in look_up_table:
still_selecting = False
command = menu[look_up_table[menu_choice]]
else:
print(f' "{menu_choice}" is not a valid entry.')
# print('\n'* int(row_pad))
self.run_the_current_command(command)
def json_parse(json_file):
# Opening JSON file
f = open(json_file,'r+')
# return JSON object as LIST of dictionaries:
subscr_data = json.load(f)
# print 1st record for confirmation:
# print(subscr_data[0])
# PRINT A LIST of Vendor Names:
# for i in subscr_data:
# print(i['name'])
# input("PAUSE FOR name list")
# PRINT json data:
# print(json.dumps(subscr_data, indent=2))
# input("CHECK JSON FILE")
return subscr_data, f # json data in list/dict form
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
#-#-#-#-#-#-#-#-#-#-#-#- PROGRAM START #-#-#-#-#-#-#-#-#-#-#-#-
def main():
# DECLARE ALL GLOBAL variables
global f
global json_file
global subscr_data
global vendor_data # Sorted (alphabetically) List of names
global cols_data
global owners
global cats_l
global cats_l_alt_order
global terminal_cols, terminal_rows
owners = ['mk', 'shared', 'mo']
categories = [] # Perhaps isn't used.
# OPEN json for printing, sorting, etc:
# json_file = "data/subscriptions.json"
json_file = "data/subs-test_MASTER.json"
# Get json data in list/dict form:
subscr_data,f = json_parse(json_file)
# UPDATE should happen here:
maintenance(json_file) #sort_maint.py opens, modifies, and closes json file locally
# Get size of terminal window
terminal_cols, terminal_rows = terminal_size() # May be handled separately
print()
print(f'T-Size:{terminal_cols} x {terminal_rows}')
input("Check terminal size")
# Sorted (alphabetically) List of Vendor names
# Used in: vendor_update(), vendor_delete(), vendor_add()--UPDATE
vendor_data = get_vend_names(subscr_data)
cols_data = get_cols(subscr_data)
''' MENU should start here'''
Menu_Tree()
# menu_test()
input("CHECK menu-test")
# PRINT AGAIN TO SEE IF update_sort_key worked:
# select_all_subscriptions()
# update_print(, cur)
subscriptions_print_all()
if __name__ == '__main__':
main()
# KEY: 'columns' are in dictionary form
"""
['acct']
['acct_num']
['amt_paid']
['status']
['category']
['films']
['last_bill_date']
['name']
['next_bill_date']
['notes']
['owner']
['period']
['price_mo']
['price_yr']
['recurring']
['start_date']
"""
# Example of a nice list comprehension
# cols_data= list(map(lambda x: x[0],columns))
# RENAME next_bill_date as "renews"
# CANCELLED Should have date of expiry or ['status'] == "expired"
# RULES:
# if it's an annual sub, it should have a renew/end date
# EXAMPLES OF:
# calendar module:
""" Gets a month name from a date. Pretty simple this time but the module has many more features!"""
# format
# date/datetime
# csv module
# date.fromisoformat
"""The class method fromisoformat() from the date class of Python datetime module, constructs a date object from a string containing date in ISO format. i.e., yyyy-mm-dd."""
# relativedelta(months=n)
# csv.DictReader(data)
# Possible addition:
# Software tracking:
# Separate table
# Company
# Software name
# Incl ser#
# Website
# Last purchased
# Renewal frequency or something like this, see: Quicken, Adobe (monthly, annual, infrequent)
# Up to date?
# Version
import os
def terminal_size():
# print("Terminal size: os:")
size = os.get_terminal_size()
# print("Size:",size, type(size))
# print(f'Height: {size[1]}')
# print(f'Width: {size[0]}')
width=size[0]
height=size[1]
return width, height
t_width = os.get_terminal_size()[0]
t_height = os.get_terminal_size()[1]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment