Compare commits
12 Commits
252dc23364
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b33390c3e | |||
| f309dfa947 | |||
| ce20c57d11 | |||
| d4662b9051 | |||
| 0a9a50f9a1 | |||
| 9cc907fb62 | |||
| 4bb336645a | |||
| 227e95cab7 | |||
| bf66e9fa7c | |||
| 5ce614ed28 | |||
| b6b396342f | |||
| fb2fffea7b |
2
BUGS
2
BUGS
@@ -1,2 +1,4 @@
|
||||
* kayo bills are wrong in between normal bills
|
||||
|
||||
* added an electricity bill by accident for 2018, that kills lots :(
|
||||
- something to do with missing year of data in quarterly bills - still an issue
|
||||
|
||||
26
TODO
26
TODO
@@ -1,12 +1,22 @@
|
||||
bills html, and growth types are very lame... could I do something more like:
|
||||
{% for gt in growth %}
|
||||
{% if gt.name == 'Min' %}
|
||||
<option value='min'
|
||||
{% if bt.which_growth == gt.name %}selected{% endif %}
|
||||
>{{'%.2f'|format(bt.ann_growth_min)}}% {{gt.name}}</option>
|
||||
|
||||
|
||||
CALC:
|
||||
* if I quit at different times of the financial year, technically the amount I earn will be taxed
|
||||
differently, but hard to calc - slides around with tax brackets in future
|
||||
|
||||
UI:
|
||||
* do I want to save the front tab?
|
||||
|
||||
* have a comparison management capability - somehow, at least be able to delete csets (can I put an x inside the menu? OR, have a manage button and pop-up?)
|
||||
|
||||
* maybe a help/note/set of links somehow, to common things: e.g. macquarie int rate page, inflation source, etc.
|
||||
- also then could effectively take those "notes" out of db.py and incorporate, so that when I update stuff it gets done right
|
||||
- in fact, I *COULD* have a basic date check/workflow, e.g. its been more than X days since Y was updated, click here
|
||||
and it walks me through the manual update steps and when I finish its datestamped until next time
|
||||
* to allow <yr> total on bill summary, need to re-work annual growth to a drop-down
|
||||
- [DONE] mock-up
|
||||
- need to change 'which growth' to accommodate new growth models (cpi, override*)
|
||||
- [DONE] do this in DB with new table - then don't do crazy pattern matching/making up overrides in the UI code
|
||||
- get UI to use bill_growth_types table
|
||||
- get UseGrowth() to use bill_growth_types table
|
||||
|
||||
For bills:
|
||||
* might need to be able to mark a specific bill as an outlier:
|
||||
|
||||
121
bills.py
121
bills.py
@@ -1,6 +1,8 @@
|
||||
from db import set_bill_type_growth, new_bill
|
||||
from db import set_bill_type_growth, new_bill, deleteFutureEstimates, get_finance_data, get_bill_data, get_bill_types, get_bill_freqs
|
||||
from calc import calc_key_dates
|
||||
from defines import END_YEAR
|
||||
import datetime
|
||||
import re
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
@@ -237,6 +239,8 @@ def actually_add_estimated_new_quarter_bill_forced( bill_type, bill_info, yr, q
|
||||
# NOTE: ALWAYS called for first year - don't always add bills/see below
|
||||
def add_missing_monthly_bills_in_yr( bill_type, bill_info, yr ):
|
||||
|
||||
print( f"add_missing_monthly_bills_in_yr for ( bt={bill_type} -- yr={yr} )" )
|
||||
|
||||
# start date arithmetic from first bill (this is possibly an issue if monthly is not
|
||||
# really perfectly the same each month, but its only for an estimate so should be ok
|
||||
dd = bill_info[bill_type]['first_bill']['bill_date'][8:]
|
||||
@@ -294,8 +298,18 @@ def get_growth_value( bt, bill_type ):
|
||||
return el['ann_growth_min']
|
||||
elif which == 'simple':
|
||||
return el['ann_growth_simple']
|
||||
else:
|
||||
elif which == 'max':
|
||||
return el['ann_growth_max']
|
||||
elif which == 'cpi':
|
||||
finance_data = get_finance_data()
|
||||
return finance_data['Inflation']
|
||||
else:
|
||||
match = re.match("flat-(\d+)", which )
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
else:
|
||||
print( f"FAILED TO GET_GROWTH_VALUE --> which={which}" )
|
||||
return 0
|
||||
|
||||
|
||||
################################################################################
|
||||
@@ -310,7 +324,6 @@ def process_bill_data(bd, bt, bf, key_dates):
|
||||
bt_id_ann_growth_avg = {row["id"]: row["ann_growth_avg"] for row in bt}
|
||||
bt_id_name = {row["id"]: row["name"] for row in bt}
|
||||
|
||||
|
||||
# this maps freq to bills per annum (e.g. id=2 to 4 bills per annum)
|
||||
bf_id_num = {row["id"]: row["num_bills_per_annum"] for row in bf}
|
||||
# and allows me a way to see if the bill is quarterly but also fixed or seasonal
|
||||
@@ -386,7 +399,7 @@ def process_bill_data(bd, bt, bf, key_dates):
|
||||
if yr in bill_info[bill_type]['year'] and len(bill_info[bill_type]['year'][yr]) >= bill_info[bill_type]['num_ann_bills'] and bill_info[bill_type]['num_ann_bills'] !=4:
|
||||
continue
|
||||
add_missing_bills_for_yr( bill_type, bill_info, yr )
|
||||
derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills )
|
||||
derive_ann_growth( bill_type, bill_info, key_dates )
|
||||
|
||||
deal_with_future_car_bills( key_dates, future_car_bills, bill_info )
|
||||
deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info )
|
||||
@@ -405,11 +418,10 @@ def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ):
|
||||
amt=fb['amount']
|
||||
bt=fb['bill_type']
|
||||
# factor in growth for next bill
|
||||
for yr in range( int(car_yr), END_YEAR ):
|
||||
for yr in range( int(car_yr), END_YEAR+1 ):
|
||||
new_date=f"{yr}-{car_mmdd}"
|
||||
# if we dont already have an annual bill for this year (all car bills are annual)
|
||||
if yr not in bill_info[bt]['year']:
|
||||
print( f"amt of a car fb is: {amt}, car_yr={car_yr}, yr={yr}, nd={new_date}")
|
||||
new_estimated_bill( bill_info, yr, fb['bill_type'], amt, new_date )
|
||||
amt += amt * bill_info[bt]['growth']/100
|
||||
|
||||
@@ -429,27 +441,23 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ):
|
||||
bt=fb['bill_type']
|
||||
if bill_info[bt]['num_ann_bills'] == 1:
|
||||
# factor in growth for next bill
|
||||
for yr in range( int(D_quit_yr), END_YEAR ):
|
||||
for yr in range( int(D_quit_yr), END_YEAR+1 ):
|
||||
new_date=f"{yr}-{dq_mm}-{dq_dd}"
|
||||
# if we dont already have an annual bill for this year
|
||||
if not find_this_bill( bt, bill_info, new_date ):
|
||||
print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, yr={yr}, nd={new_date}")
|
||||
new_estimated_bill( bill_info, yr, bt, amt, new_date )
|
||||
amt += amt * bill_info[bt]['growth']/100
|
||||
elif bill_info[bt]['num_ann_bills'] == 12:
|
||||
print( f"should be adding monthly future bill" )
|
||||
# do rest of this year, then next years
|
||||
for m in range( int(dq_mm), 13):
|
||||
new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}"
|
||||
if not find_this_bill( bt, bill_info, new_date ):
|
||||
print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, nd={new_date}")
|
||||
new_estimated_bill( bill_info, yr, bt, amt, new_date )
|
||||
for yr in range( int(D_quit_yr)+1, END_YEAR ):
|
||||
for yr in range( int(D_quit_yr)+1, END_YEAR+1 ):
|
||||
amt += amt * bill_info[bt]['growth']/100
|
||||
for m in range( 1, 13):
|
||||
new_date=f"{yr}-{m:02d}-{dq_dd}"
|
||||
if not find_this_bill( bt, bill_info, new_date ):
|
||||
print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, yr={yr}, nd={new_date}")
|
||||
new_estimated_bill( bill_info, yr, bt, amt, new_date )
|
||||
|
||||
################################################################################
|
||||
@@ -492,7 +500,7 @@ def ProportionQtrlyData( bill_type, bill_info ):
|
||||
# terms of min/avg/max - uses qtr data for qtrly bills, or just normal totals
|
||||
# for other bill types
|
||||
################################################################################
|
||||
def derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills ):
|
||||
def derive_ann_growth( bill_type, bill_info, key_dates ):
|
||||
# just do up to now so we stop earlier than looking at other estimated (just an optimisation)
|
||||
now_yr = datetime.date.today().year
|
||||
|
||||
@@ -586,3 +594,90 @@ def calc_future_totals(bill_info, bill_types):
|
||||
# had to round to 2 decimal here to get sensible totals
|
||||
total[bt['id']][yr] = round( total[bt['id']][yr], 2 )
|
||||
return total
|
||||
|
||||
|
||||
################################################################################
|
||||
# When we change the day D_quits, or we buyout the car, then future bills need
|
||||
# to change/rebuild estimates, convenience routine used to find future bills -
|
||||
# rather than go through them as we render /bills
|
||||
################################################################################
|
||||
def getFutureBills(bd,bt,future_car_bills, future_D_quit_bills):
|
||||
# this maps a bill id to a name
|
||||
bt_id_name = {row["id"]: row["name"] for row in bt}
|
||||
|
||||
for bill in bd:
|
||||
bill_type = bill['bill_type']
|
||||
if bill['bill_date'] == 'future':
|
||||
# Future bills, deal with them at the end - they have dynamic start dates
|
||||
if 'Hyundai' in bt_id_name[bill_type]:
|
||||
future_car_bills.insert( 0, bill )
|
||||
else:
|
||||
future_D_quit_bills.insert( 0, bill )
|
||||
return
|
||||
|
||||
|
||||
################################################################################
|
||||
# When we change the day D_quits, or we buyout the car, then future bills need
|
||||
# to change/rebuild estimates, convenience routine used to handle this
|
||||
################################################################################
|
||||
def recalcFutureBills():
|
||||
future_car_bills=[]
|
||||
future_D_quit_bills=[]
|
||||
|
||||
print("Recalculating future bills as we changed a key date" )
|
||||
finance_data = get_finance_data()
|
||||
key_dates = calc_key_dates( finance_data )
|
||||
bill_data = get_bill_data("order_by_date_only")
|
||||
bill_types = get_bill_types()
|
||||
bill_freqs = get_bill_freqs()
|
||||
|
||||
bt_id_freq = {row["id"]: row["freq"] for row in bill_types}
|
||||
# this maps freq to bills per annum (e.g. id=2 to 4 bills per annum)
|
||||
bf_id_num = {row["id"]: row["num_bills_per_annum"] for row in bill_freqs}
|
||||
|
||||
getFutureBills(bill_data, bill_types, future_car_bills, future_D_quit_bills)
|
||||
deleteFutureEstimates()
|
||||
# deal with future car bills
|
||||
car_yr=key_dates['D_hyundai_owned'][0:4]
|
||||
car_mmdd=key_dates['D_hyundai_owned'][5:]
|
||||
for fb in future_car_bills:
|
||||
amt=fb['amount']
|
||||
bt=fb['bill_type']
|
||||
# only can use simple growth as its a future bill
|
||||
growth=bill_types[bt]['ann_growth_simple']
|
||||
# factor in growth for next bills
|
||||
for yr in range( int(car_yr), END_YEAR+1 ):
|
||||
new_date=f"{yr}-{car_mmdd}"
|
||||
new_bill( fb['bill_type'], amt, new_date, 1 )
|
||||
amt += amt * growth/100
|
||||
|
||||
# deal with future D_Quit bills
|
||||
D_quit_yr = key_dates['D_quit_date'][0:4]
|
||||
dq_mm=key_dates['D_quit_date'][5:7]
|
||||
dq_dd=key_dates['D_quit_date'][8:]
|
||||
# avoid feb 29+ :)
|
||||
if int(dq_dd) > 28: dq_dd=28
|
||||
for fb in future_D_quit_bills:
|
||||
# deal with future bills due to their starting dates being dynamic
|
||||
amt=fb['amount']
|
||||
bt=fb['bill_type']
|
||||
growth=bill_types[bt]['ann_growth_simple']
|
||||
num_ann_bills= bf_id_num[bt_id_freq[bt]]
|
||||
if num_ann_bills == 1:
|
||||
# factor in growth for next bill
|
||||
for yr in range( int(D_quit_yr), END_YEAR+1 ):
|
||||
new_date=f"{yr}-{dq_mm}-{dq_dd}"
|
||||
# if we dont already have an annual bill for this year
|
||||
new_bill( fb['bill_type'], amt, new_date, 1 )
|
||||
amt += amt * growth/100
|
||||
elif num_ann_bills == 12:
|
||||
# do rest of this year, then next years
|
||||
for m in range( int(dq_mm), 13):
|
||||
new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}"
|
||||
new_bill( fb['bill_type'], amt, new_date, 1 )
|
||||
for yr in range( int(D_quit_yr)+1, END_YEAR+1 ):
|
||||
amt += amt * growth/100
|
||||
for m in range( 1, 13):
|
||||
new_date=f"{yr}-{m:02d}-{dq_dd}"
|
||||
new_bill( fb['bill_type'], amt, new_date, 1 )
|
||||
return
|
||||
|
||||
68
calc.py
68
calc.py
@@ -6,13 +6,11 @@ from defines import END_YEAR
|
||||
LEASE = 0
|
||||
|
||||
# Dates that don't change
|
||||
car_balloon_date = datetime(2026, 11, 15)
|
||||
new_fin_year_25 = datetime(2025, 7, 1)
|
||||
new_fin_year_26 = datetime(2026, 7, 1)
|
||||
end_date = datetime(END_YEAR, 4, 15)
|
||||
school_fees_date = datetime(2025, 12, 5)
|
||||
mich_present_date = datetime(2026,10,15)
|
||||
first_pay_date = datetime(2025,1,8)
|
||||
school_fees_date = datetime(2025, 12, 5)
|
||||
car_balloon_date = datetime(2026, 11, 15)
|
||||
mich_present_date = datetime(2026,10,15)
|
||||
end_date = datetime(END_YEAR, 4, 15)
|
||||
|
||||
def bill_amount_today(finance, day, bill_data, bt_id_name, total ):
|
||||
amt=0
|
||||
@@ -24,8 +22,8 @@ def bill_amount_today(finance, day, bill_data, bt_id_name, total ):
|
||||
amt += b['amount']
|
||||
if b['amount'] > 1000:
|
||||
n=bt_id_name[ b['bill_type'] ]
|
||||
print( f"bill_amt_today {n} for {day_str} has amt={amt}" )
|
||||
add_annotation(finance, day, total-amt, -amt, f"Pay {n}" )
|
||||
print( f"bill_amt_today {n} for {day_str} has amt={b['amount']}" )
|
||||
add_annotation(finance, day, total-b['amount'], -b['amount'], f"Pay {n}" )
|
||||
# bills are desc order so if the bill is before the day we are after then stop looking
|
||||
if b['bill_date'] < day_str:
|
||||
return amt
|
||||
@@ -84,22 +82,18 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
payout = 83115.84
|
||||
print( f"leave payout gross={payout}" )
|
||||
|
||||
# as the leave is just on top of my existing earnings and if in 2024 fin year, just take tax at 37% for the extra leave amount
|
||||
# hardcoded 6 represents the 12 weeks or 6 fornights of pay owed to me when I give notice or they sack me
|
||||
D_leave_after_tax = payout * (1-0.37)
|
||||
|
||||
# However, if I quit in the next fin year - tax for 2025 will be: $4,288 plus 30c for each $1 over $45,000
|
||||
# (assuming the 7830.42 * ~90/bus_days_in_fortnight = ~ $64k - > 45k and < $135k bracket is 30%)
|
||||
# Given, I probably can't stop Deakin doing PAYG deductions, I won't get
|
||||
# the tax back until the end of the financial year, so work out the
|
||||
# amount of tax I will get back info: tax_diff_D_leave
|
||||
tax_on_leave = (payout - 45000)*.37 + 4288
|
||||
D_leave_after_tax_new_fin_year = payout - tax_on_leave
|
||||
D_leave_after_tax = payout - tax_on_leave
|
||||
|
||||
# just use redunancy calc...
|
||||
D_leave_after_tax_new_fin_year = 56518.77
|
||||
D_leave_after_tax = 56518.77
|
||||
|
||||
tax_diff_D_leave = payout - D_leave_after_tax_new_fin_year
|
||||
tax_diff_D_leave = payout - D_leave_after_tax
|
||||
|
||||
print( f"tax_diff_D_leave: {tax_diff_D_leave}")
|
||||
|
||||
@@ -107,7 +101,6 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
|
||||
# convenience vars to make it easier to read conditional leave tax/payment logic below
|
||||
D_has_quit = False
|
||||
D_quit_year = 0
|
||||
claim_tax_on_leave = False
|
||||
|
||||
# Constants for interest calculations
|
||||
@@ -118,7 +111,7 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
current_date = datetime.today()
|
||||
|
||||
|
||||
# TODO: need to refactor Living_Expenses to exclude bills
|
||||
# refactor Living_Expenses to exclude bills (as we have detailed future projections for them that usually exceed inflation)
|
||||
total=0
|
||||
yr=str(current_date.year)
|
||||
for b in bill_data:
|
||||
@@ -131,6 +124,8 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
|
||||
# Calculate daily living expenses
|
||||
daily_living_expenses = Living_Expenses / 365
|
||||
print( f"daily LE starts at={daily_living_expenses}" )
|
||||
print( f"fortnightly LE starts at={daily_living_expenses*14}" )
|
||||
|
||||
# Start the calculation
|
||||
current_savings = Savings
|
||||
@@ -181,31 +176,17 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
current_savings -= Car_loan_via_pay
|
||||
print( f"{current_date}: making car loan pay as pre-tax lease: ${Car_loan_via_pay}" )
|
||||
|
||||
# no more pay and if leave after tax > 0 this is the day I quit
|
||||
if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0:
|
||||
D_has_quit = True
|
||||
D_quit_date = current_date
|
||||
D_quit_year = current_date.year
|
||||
# okay, if we leave before Jun 30th 2024, then I pay full tax, otherwise I get 'extra', but have to await end of next fin year
|
||||
if current_date > new_fin_year_25:
|
||||
claim_tax_on_leave = True
|
||||
print(f"{current_date}: D has resigned in new year- get paid out my 12 weeks + remaining leave and lose some to tax - ${D_leave_after_tax_new_fin_year}" )
|
||||
current_savings += D_leave_after_tax_new_fin_year
|
||||
add_annotation(finance, current_date, current_savings, D_leave_after_tax_new_fin_year, "D quit" )
|
||||
else:
|
||||
claim_tax_on_leave = False
|
||||
print(f"{current_date}: D has resigned - get paid out my 12 weeks + remaining leave and lose some to tax - ${D_leave_after_tax}" )
|
||||
current_savings += D_leave_after_tax
|
||||
add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" )
|
||||
# going to pay tax on payout, so claim it back next year
|
||||
claim_tax_on_leave = True
|
||||
print(f"{current_date}: D has resigned in new year- get paid out my 12 weeks + remaining leave and lose some to tax - ${D_leave_after_tax}" )
|
||||
current_savings += D_leave_after_tax
|
||||
add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" )
|
||||
D_leave_after_tax = 0
|
||||
|
||||
# its end of 'next' fin year, if tax_diff > 0, then ddp quit after new tax year and gets back the overpaid tax
|
||||
if current_date > new_fin_year_26 and claim_tax_on_leave:
|
||||
current_savings += tax_diff_D_leave
|
||||
print( f"I quit last fin year, so now its 1st July {current_date.year}, get tax back of {tax_diff_D_leave}" )
|
||||
add_annotation(finance, current_date, current_savings, tax_diff_D_leave, "D quit - tax back" )
|
||||
# can only claim the tax back once :)
|
||||
claim_tax_on_leave=False
|
||||
|
||||
if fortnight_income:
|
||||
print(f"{current_date}: salary paid by Deakin - adding: {fortnight_income}" )
|
||||
current_savings += fortnight_income
|
||||
@@ -213,6 +194,14 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
|
||||
savings_per_fortnight.append((current_date.strftime("%Y-%m-%d"), round(current_savings, 2)))
|
||||
|
||||
# its end of fin year, if claim_tax_on_leave > 0 then get tax back
|
||||
if current_date.month == 7 and current_date.day == 1 and claim_tax_on_leave:
|
||||
current_savings += tax_diff_D_leave
|
||||
print( f"I quit last fin year, so now its 1st July {current_date.year}, get tax back of {tax_diff_D_leave}" )
|
||||
add_annotation(finance, current_date, current_savings, tax_diff_D_leave, "D quit - tax back" )
|
||||
# can only claim the tax back once :)
|
||||
claim_tax_on_leave=False
|
||||
|
||||
# if I have quit, then car lease payments are made on the 15th of the month for full Car_loan
|
||||
if D_has_quit and current_date.day == 15:
|
||||
if Ioniq6_future == LEASE and current_date <= car_balloon_date:
|
||||
@@ -230,7 +219,7 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
# monthly increase living expenses by a monthly inflation multiplier
|
||||
Living_Expenses += (Inflation/100.0)/12 * Living_Expenses
|
||||
daily_living_expenses = Living_Expenses / 365
|
||||
#print(f"{current_date}: Living Exp inceased - ${Living_Expenses}")
|
||||
# print(f"{current_date}: Living Exp inceased - ${Living_Expenses}")
|
||||
|
||||
if current_date.date() == school_fees_date.date():
|
||||
current_savings -= School_Fees
|
||||
@@ -269,7 +258,7 @@ def calculate_savings_depletion(finance, bill_data, bill_type):
|
||||
# if selling shares, and its 1st of July...
|
||||
# BUT not if D quits before end of financial year - as I won't be able to sell CBA shares for no cap gains
|
||||
# so wait until the following year
|
||||
if current_date.month == 7 and current_date.day == 1 and D_has_quit and Sell_shares>0 and (current_date.year > D_quit_year or current_date.year == D_quit_year and claim_tax_on_leave == False):
|
||||
if current_date.month == 7 and current_date.day == 1 and D_has_quit and Sell_shares>0 and (D_quit_date.month<7 or D_quit_date.year < current_date.year ):
|
||||
# 2024 Govt. value
|
||||
tax_threshold = 18200
|
||||
# cap-gains is 50% of profit (lazy profit calc here, just assume its all profit)
|
||||
@@ -321,6 +310,5 @@ def calc_key_dates( finance ):
|
||||
else:
|
||||
key_dates['D_hyundai_owned'] = finance['Car_buyout_date']
|
||||
|
||||
print( f"kd={key_dates}" )
|
||||
return key_dates
|
||||
|
||||
|
||||
101
db.py
101
db.py
@@ -84,6 +84,12 @@ def init_db():
|
||||
FOREIGN KEY(comparison_set_id) REFERENCES comparison_set(id)
|
||||
)''')
|
||||
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS bill_freq (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name STRING,
|
||||
num_bills_per_annum INTEGER
|
||||
)''')
|
||||
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS bill_type (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
freq INTEGER,
|
||||
@@ -95,12 +101,6 @@ def init_db():
|
||||
FOREIGN KEY(freq) REFERENCES bill_freq(id)
|
||||
)''')
|
||||
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS bill_freq (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name STRING,
|
||||
num_bills_per_annum INTEGER
|
||||
)''')
|
||||
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS bill_data (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
bill_type INTEGER,
|
||||
@@ -116,28 +116,56 @@ def init_db():
|
||||
show_estimated INTEGER
|
||||
)''')
|
||||
|
||||
# Check if table is empty, if so insert default values
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS bill_growth_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name string,
|
||||
value INTEGER
|
||||
)''')
|
||||
|
||||
# Check if finance table is empty, if so insert default values
|
||||
cur.execute('SELECT COUNT(*) FROM finance')
|
||||
if cur.fetchone()[0] == 0:
|
||||
###
|
||||
# For now manually update below on the fortnight of the original pay shcedule to compare saved version vs. our reality. Update:
|
||||
# Savings (Macq+me bank) -- noting ME bank is: $2001.19, nab is -5200
|
||||
# TLS/CBA prices
|
||||
# Interest rate
|
||||
# D_leave_owed_in_days
|
||||
# maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation:
|
||||
# I've decided to use RBA Trimmed Mean CPI YoY -- https://tradingeconomics.com/australia/inflation-cpi
|
||||
###
|
||||
cur.execute('''INSERT INTO finance (D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, Car_loan, Car_balloon, Car_buyout, Living_Expenses, Savings, Interest_Rate,
|
||||
Inflation, Mich_present, Overseas_trip, Mark_reno, D_leave_owed_in_days, D_TLS_shares, M_TLS_shares, D_CBA_shares, TLS_price, CBA_price, Overseas_trip_date, Mark_reno_date, Car_buyout_date, Sell_shares, compare_to, Ioniq6_future)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
||||
(4762.29, 10, 24000, 620, 2412, 45824.68, 83738.74, 80000, 424875.26, 4.75, 2.4, 10000, 50000, 10000, 76.85, 1000, 750, 1111, 4.52, 163.32, '2025-06-01', '2025-09-01', '2025-02-20', 4, 0, 0))
|
||||
|
||||
# Check if bill_freq table is empty, if so insert default values
|
||||
cur.execute('SELECT COUNT(*) FROM bill_freq')
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute( "INSERT INTO bill_freq values ( 1, 'Annual', 1 )" )
|
||||
cur.execute( "INSERT INTO bill_freq values ( 2, 'Quarterly', 4 )" )
|
||||
cur.execute( "INSERT INTO bill_freq values ( 3, 'Quarterly (forced)', 4 )" )
|
||||
cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" )
|
||||
# start with no specific Tab/bill_type to show, and dont show_estimated
|
||||
|
||||
# Check if bill_ui table is empty, if so insert default values
|
||||
cur.execute('SELECT COUNT(*) FROM bill_ui')
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute( "INSERT INTO bill_ui values ( 1, null, 0 )" )
|
||||
|
||||
# Check if bill_growth_types table is empty, if so insert default values
|
||||
cur.execute('SELECT COUNT(*) FROM bill_growth_types')
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Min', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Avg', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Max', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Simple', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'CPI', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 0', 0 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 1', 1 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 2', 2 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 3', 3 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 4', 4 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 5', 5 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 6', 6 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 7', 7 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 8', 8 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 9', 9 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 10', 10 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 12', 12 )" )
|
||||
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 15', 15 )" )
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -376,6 +404,14 @@ def set_bill_type_growth( id, min_g, avg_g, max_g, simple_g ):
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def get_bill_growth_types():
|
||||
conn = connect_db(True)
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT * FROM bill_growth_types')
|
||||
growth = cur.fetchall()
|
||||
conn.close()
|
||||
return growth
|
||||
|
||||
def get_bill_ui():
|
||||
conn = connect_db(True)
|
||||
cur = conn.cursor()
|
||||
@@ -395,3 +431,36 @@ def save_ui(data):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
|
||||
def deleteFutureEstimates():
|
||||
conn = connect_db(False)
|
||||
cur = conn.cursor()
|
||||
cur.execute( "delete from bill_data where bill_date != 'future' and bill_type in ( select bill_type from bill_data where bill_date='future')" )
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def delete_estimated_bills():
|
||||
conn = connect_db(False)
|
||||
cur = conn.cursor()
|
||||
cur.execute( "delete from bill_data where estimated=1" )
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def delete_estimated_bills_for(bt_id):
|
||||
conn = connect_db(False)
|
||||
cur = conn.cursor()
|
||||
cur.execute( f"delete from bill_data where estimated=1 and bill_type = {bt_id}" )
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def delete_cset(id):
|
||||
conn = connect_db(False)
|
||||
cur = conn.cursor()
|
||||
cur.execute( f"delete from comparison_set where id = '{id}'" )
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
70
main.py
70
main.py
@@ -1,16 +1,19 @@
|
||||
# main.py
|
||||
from flask import Flask, render_template, request, redirect, url_for, Response, jsonify
|
||||
from calc import calculate_savings_depletion, calc_key_dates
|
||||
from db import init_db, get_finance_data, update_finance, get_budget_data, insert_cset, get_comp_set_data, get_comp_set_options, get_bill_freqs
|
||||
from db import get_bill_data, new_bill, update_bill_data, delete_bill
|
||||
from db import init_db, get_finance_data, update_finance, get_budget_data
|
||||
from db import insert_cset, get_comp_set_data, get_comp_set_options, delete_cset
|
||||
from db import get_bill_freqs, get_bill_growth_types
|
||||
from db import get_bill_data, new_bill, update_bill_data, delete_bill, delete_estimated_bills, delete_estimated_bills_for
|
||||
from db import get_bill_ui, save_ui
|
||||
from db import get_bill_types, insert_bill_type, update_bill_type, delete_bill_type, use_growth
|
||||
from bills import process_bill_data, calc_future_totals, set_bill_type_growth
|
||||
from bills import process_bill_data, calc_future_totals, set_bill_type_growth, recalcFutureBills
|
||||
from defines import END_YEAR
|
||||
from collections import defaultdict, Counter
|
||||
from datetime import datetime, date
|
||||
import csv
|
||||
import io
|
||||
import requests
|
||||
from disp import FP_VAR
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -113,6 +116,8 @@ def save():
|
||||
@app.route('/update', methods=['POST'])
|
||||
def update():
|
||||
|
||||
old_finance_data = get_finance_data()
|
||||
|
||||
finance_data = (
|
||||
request.form['D_Salary'],
|
||||
request.form['D_Num_fortnights_pay'],
|
||||
@@ -142,6 +147,26 @@ def update():
|
||||
request.form['Ioniq6_future']
|
||||
)
|
||||
update_finance(finance_data)
|
||||
new_finance_data = get_finance_data()
|
||||
# changed Ioniq6_future, Car_buyout_date or D_Num_fortnights_pay, so lets force recalc key_dates, and therefore estimated bills
|
||||
if old_finance_data['D_Num_fortnights_pay'] != new_finance_data['D_Num_fortnights_pay'] or old_finance_data['Ioniq6_future'] != new_finance_data['Ioniq6_future'] or old_finance_data['Car_buyout_date'] != new_finance_data['Car_buyout_date']:
|
||||
recalcFutureBills()
|
||||
if old_finance_data['Inflation'] != new_finance_data['Inflation']:
|
||||
# need to check if any bill type is using CPI, if so, force those future bills to be recalculated
|
||||
bill_types = get_bill_types()
|
||||
for bt in bill_types:
|
||||
if bt['which_growth'] == 'cpi':
|
||||
print( f"OK, changed inflation and need to redo bills for bt_id={bt['id']}" )
|
||||
delete_estimated_bills_for( bt['id'] )
|
||||
#recalc_estimated_bills_for( bt['id'] )
|
||||
# okay, now go through code to recalc bills...
|
||||
base=request.url_root
|
||||
response = requests.get(f"{base}/bills")
|
||||
if response.status_code == 200:
|
||||
print("ALL GOOD")
|
||||
else:
|
||||
print("FFS")
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/bills')
|
||||
@@ -153,13 +178,14 @@ def DisplayBillData():
|
||||
bill_types = get_bill_types()
|
||||
bill_freqs = get_bill_freqs()
|
||||
bill_ui = get_bill_ui()
|
||||
bill_growth_types = get_bill_growth_types()
|
||||
# take bill data, AND work out estimated future bills - process this into the bill_info array,
|
||||
bill_info=process_bill_data(bill_data, bill_types, bill_freqs, key_dates)
|
||||
# get an array of the total costs of bills each year - purely cosmetic (using bill_info)
|
||||
total=calc_future_totals(bill_info, bill_types)
|
||||
# update/re-get bill_data now that new estimated bills have been added
|
||||
bill_data = get_bill_data("order_by_bill_type_then_date")
|
||||
return render_template('bills.html', bill_data=bill_data, bill_types=bill_types, bill_freqs=bill_freqs, bill_ui=bill_ui, this_year=datetime.today().year, END_YEAR=END_YEAR, total=total, key_dates=key_dates )
|
||||
return render_template('bills.html', bill_data=bill_data, bill_types=bill_types, bill_freqs=bill_freqs, bill_ui=bill_ui, this_year=datetime.today().year, END_YEAR=END_YEAR, total=total, key_dates=key_dates, growth=bill_growth_types, cpi=finance_data['Inflation'] )
|
||||
|
||||
@app.route('/newbilltype', methods=['POST'])
|
||||
def InsertBillType():
|
||||
@@ -214,6 +240,42 @@ def SaveUI():
|
||||
save_ui( data )
|
||||
return "200"
|
||||
|
||||
@app.route('/force_recalc_bills', methods=['POST'])
|
||||
def force_recalc_bills():
|
||||
delete_estimated_bills()
|
||||
recalcFutureBills()
|
||||
return "200"
|
||||
|
||||
@app.route('/cset')
|
||||
def cset():
|
||||
finance_data = get_finance_data()
|
||||
get_comp_set_options(finance_data)
|
||||
comp_data={}
|
||||
for el in finance_data['COMP_SETS']:
|
||||
comp_data[el[0]] = get_comp_set_data( el[0] )
|
||||
# delete items not that helpful (same for all, not that interesting)
|
||||
if el[0]:
|
||||
del comp_data[el[0]]['vars']['Car_loan_via_pay']
|
||||
del comp_data[el[0]]['vars']['Mark_reno']
|
||||
del comp_data[el[0]]['vars']['Mark_reno_date']
|
||||
del comp_data[el[0]]['vars']['Overseas_trip_date']
|
||||
del comp_data[el[0]]['vars']['Car_balloon']
|
||||
del comp_data[el[0]]['vars']['Mich_present']
|
||||
del comp_data[el[0]]['vars']['D_TLS_shares']
|
||||
del comp_data[el[0]]['vars']['M_TLS_shares']
|
||||
|
||||
return render_template('cset.html', finance=finance_data, comp_data=comp_data )
|
||||
|
||||
@app.route('/delcset', methods=['POST'])
|
||||
def DeleteCSet():
|
||||
data = request.get_json()
|
||||
delete_cset( data['id'] )
|
||||
return "200"
|
||||
|
||||
# quick health route so traefik knows we are up
|
||||
@app.route('/health')
|
||||
def health():
|
||||
return {"status": "ok"}, 200
|
||||
|
||||
# Main program
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -8,4 +8,4 @@ pysqlite3
|
||||
Werkzeug
|
||||
flask-compress
|
||||
gunicorn
|
||||
|
||||
requests
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
#}
|
||||
<div class="col-7">
|
||||
<div class="col-8">
|
||||
<div class="row">
|
||||
<div class="col-2 form-control-inline d-none new-bill-type-class">Bill Type</div>
|
||||
<div class="col-2 form-control-inline d-none new-bill-type-class">Frequency</div>
|
||||
@@ -52,68 +52,95 @@
|
||||
</div>
|
||||
<button id="save-bill-type" class="new-bill-type-class px-0 col-1 btn btn-success bg-success-subtle text-success d-none" onClick="NewBillType()"><span class="bi bi-floppy"></span> Save</button>
|
||||
<button id="canc-bill-type" class="new-bill-type-class px-0 col-1 btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelNewBillType()"><span class="bi bi-x"> Cancel</span></button>
|
||||
<button id="recalc-bills" class="mt-4 col-2 offset-3 btn btn-warning bg-warning-subtle text-warning" onClick="ForceRecalcBills()"><span class="bi bi-repeat"> Recalculate</span></button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div>
|
||||
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></div>
|
||||
<div class="px-0 col-4"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Annual Growth Est (min/avg/max/simple)</ ></div>
|
||||
<div class="px-0 col-1"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">{{this_year}} Total</ ></div>
|
||||
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div>
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></div>
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0 fw-bold
|
||||
bg-body-tertiary rounded-0">Annual Growth</ ></div>
|
||||
{% for yr in range( 2025, 2032 ) %}
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0
|
||||
fw-bold bg-body-tertiary rounded-0">{{yr}} Total</ ></div>
|
||||
{% endfor %}
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
|
||||
{# spacer to get header line right now we don't use forced col widths #}
|
||||
<div class="px-0 col"><label class="form-control d-flex
|
||||
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0"> </ ></div>
|
||||
</div>
|
||||
{% for bt in bill_types %}
|
||||
<div class="row">
|
||||
<div class="px-0 col-2"><input type="text" class="bill-type-{{bt.id}} form-control text-center" id="bill-type-name-{{bt.id}}" value="{{ bt.name }}" disabled> </div>
|
||||
<div class="px-0 col-1"><input type="text" class="bill-type-{{bt.id}} form-control text-center" id="bill-type-name-{{bt.id}}" value="{{ bt.name }}" disabled> </div>
|
||||
<!-- bind Enter to save this bill-type -->
|
||||
<script>$("#bill-type-name-{{bt.id}}").keyup(function(event){ if(event.which == 13){ $('#bill-type-save-{{bt.id}}').click(); } event.preventDefault(); });</script>
|
||||
<div class="px-0 col-2"><select id="bill-type-freq-{{bt.id}}" class="bill-type-{{bt.id}} form-select text-center" disabled>
|
||||
<div class="px-0 col-1"><select id="bill-type-freq-{{bt.id}}" class="bill-type-{{bt.id}} form-select text-center" disabled>
|
||||
{% for bf in bill_freqs %}
|
||||
<option value={{bf.id}}>{{bf.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<script>$('#bill-type-freq-{{bt.id}}').val( {{bt.freq}} );</script>
|
||||
<div class="px-0 col-4">
|
||||
<div class="px-0 col">
|
||||
<div class="btn-group w-100" role="group">
|
||||
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="min-{{bt.id}}" autocomplete="off"
|
||||
onChange="UseGrowth({{bt.id}}, 'min')" {% if bt.which_growth == 'min' %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="min-{{bt.id}}" style="width: 6ch;">
|
||||
{% if bt.ann_growth_min> 0 and bt.ann_growth_min < 10 %} {% endif %}
|
||||
{{'%.2f'|format(bt.ann_growth_min)}}
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="avg-{{bt.id}}" autocomplete="off"
|
||||
onChange="UseGrowth({{bt.id}}, 'avg')" {% if bt.which_growth == 'avg' %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="avg-{{bt.id}}" style="width: 6ch;">
|
||||
{% if bt.ann_growth_avg < 10 %} {% endif %}
|
||||
{{'%.2f'|format(bt.ann_growth_avg)}}
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="max-{{bt.id}}" autocomplete="off"
|
||||
onChange="UseGrowth({{bt.id}}, 'max')" {% if bt.which_growth == 'max' %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="max-{{bt.id}}" style="width: 6ch;">
|
||||
{% if bt.ann_growth_max < 10 %} {% endif %}
|
||||
{{'%.2f'|format(bt.ann_growth_max)}}
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="simple-{{bt.id}}" autocomplete="off"
|
||||
onChange="UseGrowth({{bt.id}}, 'simple')" {% if bt.which_growth == 'simple' %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="simple-{{bt.id}}" style="width: 6ch;">
|
||||
{% if bt.ann_growth_simple < 10 %} {% endif %}
|
||||
{{'%.2f'|format(bt.ann_growth_simple)}}
|
||||
</label>
|
||||
<select id="{{bt.id}}_growth" class="form-select col" onChange="UseGrowth({{bt.id}})">
|
||||
{% for gt in growth %}
|
||||
{% if gt.name == 'Min' %}
|
||||
<option value='min'
|
||||
{% if bt.which_growth == 'min' %}selected{% endif %}
|
||||
>{{'%.2f'|format(bt.ann_growth_min)}}% {{gt.name}}</option>
|
||||
{% elif gt.name == 'Avg' %}
|
||||
<option value='avg'
|
||||
{% if bt.which_growth == 'avg' %}selected{% endif %}
|
||||
>{{'%.2f'|format(bt.ann_growth_avg)}}% {{gt.name}}</option>
|
||||
{% elif gt.name == 'Max' %}
|
||||
<option value='max'
|
||||
{% if bt.which_growth == 'max' %}selected{% endif %}
|
||||
>{{'%.2f'|format(bt.ann_growth_max)}}% {{gt.name}}</option>
|
||||
{% elif gt.name == 'Simple' %}
|
||||
<option value='simple'
|
||||
{% if bt.which_growth == 'simple' %}selected{% endif %}
|
||||
>{{'%.2f'|format(bt.ann_growth_simple)}}% {{gt.name}}</option>
|
||||
{% elif gt.name == 'CPI' %}
|
||||
<option value='cpi'
|
||||
{% if bt.which_growth == 'cpi' %}selected{% endif %}
|
||||
>{{'%.2f'|format(cpi)}}% {{gt.name}}</option>
|
||||
{% else %}
|
||||
<option value='flat-{{gt.value}}'
|
||||
{% if bt.which_growth == 'flat-'+gt.value|string %}selected{% endif %}
|
||||
>{{'%.2f'|format(gt.value)}}% {{gt.name}}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-0 col-1"><input type="text" class="bill-type-total-{{bt.id}} form-control text-center" id="bill-type-total-{{bt.id}}" value="${{'%.2f'|format(total[bt.id][this_year])}}" disabled> </div>
|
||||
<button id="bill-type-chg-{{bt.id}}" class="px-0 col-1 btn btn-success bg-success-subtle text-success" onClick="StartUpdateBillType( {{bt.id}} )"><span class="bi bi-pencil-square"> Change</button>
|
||||
<button id="bill-type-del-{{bt.id}}" class="px-0 col-1 btn btn-danger bg-danger-subtle text-danger" onClick="DelBillType({{bt.id}})"><span class="bi bi-trash3"> Delete</button>
|
||||
<button id="bill-type-save-{{bt.id}}" class="px-0 col-1 btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBillType( {{bt.id}} )"><spam class="bi bi-floppy"> Save</button>
|
||||
<button id="bill-type-canc-{{bt.id}}" class="px-0 col-1 btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelUpdateBillType({{bt.id}}, '{{bt.name}}')"><span class="bi bi-x"> Cancel</button>
|
||||
{% for yr in range( 2025, 2032 ) %}
|
||||
<div class="px-0 col"><input type="text" class="bill-type-total-{{bt.id}} form-control text-center" id="bill-type-total-{{bt.id}}" value="${{'%.2f'|format(total[bt.id][yr])}}" disabled> </div>
|
||||
{% endfor %}
|
||||
<button id="bill-type-chg-{{bt.id}}" class="px-0 col btn btn-success bg-success-subtle text-success" onClick="StartUpdateBillType( {{bt.id}} )"><span class="bi bi-pencil-square"> Change</button>
|
||||
<button id="bill-type-del-{{bt.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger" onClick="DelBillType({{bt.id}})"><span class="bi bi-trash3"> Delete</button>
|
||||
<button id="bill-type-save-{{bt.id}}" class="px-0 col btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBillType( {{bt.id}} )"><spam class="bi bi-floppy"> Save</button>
|
||||
<button id="bill-type-canc-{{bt.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelUpdateBillType({{bt.id}}, '{{bt.name}}')"><span class="bi bi-x"> Cancel</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for yr in range( this_year, END_YEAR) %}
|
||||
<div class="row">
|
||||
<div class="px-0 col"></div>
|
||||
<div class="px-0 col"></div>
|
||||
<div class="px-0 col"><input type="text" class="form-control text-end text-primary fs-5" value="TOTAL:"></div>
|
||||
{% for yr in range( this_year, END_YEAR+1) %}
|
||||
{% set tot=namespace( sum=0 ) %}
|
||||
{% for bt in bill_types %}
|
||||
{% if bt.id in total %}
|
||||
{% set tot.sum = tot.sum + total[bt.id][yr] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="px-0 col"><input type="text" class="form-control text-center text-primary bg-dark fs-5" value="${{'%.2f'|format(tot.sum)}}" disabled></div>
|
||||
{#
|
||||
{% set markup="h5" %}
|
||||
{% if yr == this_year %}
|
||||
{% set markup="h4 pt-4" %}
|
||||
@@ -126,12 +153,16 @@
|
||||
${{'%.2f'|format(tot.sum)}}
|
||||
</div>
|
||||
</div>
|
||||
#}
|
||||
{% endfor %}
|
||||
<div class="px-0 col"></div>
|
||||
<div class="px-0 col"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- right-hand-side, bill types (e.g. gas, phone, etc.) -->
|
||||
|
||||
<div class="pt-4 col-5">
|
||||
<div class="col-4">
|
||||
<div class="row">
|
||||
<div class="col-2 form-control-inline d-none new-bill-data-class">Bill Type</div>
|
||||
<div id="new-bill-data-date-label" class="col-4 form-control-inline d-none new-bill-data-class">Date</div>
|
||||
@@ -185,8 +216,9 @@
|
||||
<div class="row pt-2">
|
||||
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div>
|
||||
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Date</ ></div>
|
||||
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Amount</ ></div>
|
||||
<div class="px-0 col-4"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
|
||||
<div class="p-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Amount</ ></div>
|
||||
<div class="px-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
|
||||
<div class="px-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0 h-100"></ ></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if bd.bill_type == bt.id %}
|
||||
@@ -202,15 +234,24 @@
|
||||
<div class="px-0 col-2"> <input type="text" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div>
|
||||
{% else %}
|
||||
<div class="px-0 col-2"> <input type="date" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div>
|
||||
<script>
|
||||
if( typeof future_id !== 'undefined' && future_id>0) {
|
||||
first_col_id={{bd.id}}
|
||||
future_id=0
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
<div class="px-0 col-2"> <input type="number" class="{{classes}}" id="bill-data-amount-{{bd.id}}" value="{{ bd.amount }}" disabled> </div>
|
||||
<div class="px-0 col"> <input type="number" class="{{classes}}" id="bill-data-amount-{{bd.id}}" value="{{ bd.amount }}" disabled> </div>
|
||||
{% if bd.estimated == 0 %}
|
||||
<button id="bill-data-chg-{{bd.id}}" class="px-0 col-2 btn btn-success bg-success-subtle text-success" onClick="StartUpdateBill( {{bd.id}} )"><span class="bi bi-pencil-square"> Change</button>
|
||||
<button id="bill-data-del-{{bd.id}}" class="px-0 col-2 btn btn-danger bg-danger-subtle text-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete
|
||||
<button id="bill-data-save-{{bd.id}}" class="px-0 col-2 btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBill( {{bd.id}} )"><span class="bi bi-floppy"> Save</button>
|
||||
<button id="bill-data-canc-{{bd.id}}" class="px-0 col-2 btn btn-danger bg-danger-subtle text-danger d-none"
|
||||
onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-x"> Cancel</button>
|
||||
</button>
|
||||
<button id="bill-data-chg-{{bd.id}}" class="px-0 col btn btn-success bg-success-subtle text-success" onClick="StartUpdateBill( {{bd.id}} )"><span class="bi bi-pencil-square"> Change</button>
|
||||
<button id="bill-data-del-{{bd.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete
|
||||
<button id="bill-data-save-{{bd.id}}" class="px-0 col btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBill( {{bd.id}} )"><span class="bi bi-floppy"> Save</button>
|
||||
<button id="bill-data-canc-{{bd.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger d-none"
|
||||
onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-x"> Cancel</button>
|
||||
</button>
|
||||
{% else %}
|
||||
<div class="px-0 col"></div>
|
||||
<div class="px-0 col"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -402,10 +443,11 @@
|
||||
data: JSON.stringify( { 'id': id } ), success: function() { window.location='bills' } } )
|
||||
}
|
||||
|
||||
function UseGrowth( bt, which )
|
||||
function UseGrowth( bt_id )
|
||||
{
|
||||
which = $('#'+bt_id+ '_growth option:selected').val()
|
||||
$.ajax( { type: 'POST', url: '/usegrowth', contentType: 'application/json',
|
||||
data: JSON.stringify( { 'bill_type': bt, 'which_growth': which } ), success: function() { window.location='bills' } } )
|
||||
data: JSON.stringify( { 'bill_type': bt_id, 'which_growth': which } ), success: function() { window.location='bills' } } )
|
||||
}
|
||||
|
||||
function SaveTab( last_tab )
|
||||
@@ -466,7 +508,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
function ForceRecalcBills()
|
||||
{
|
||||
$.ajax( { type: 'POST', url: '/force_recalc_bills', contentType: 'application/json', success: function() { window.location='bills' } } )
|
||||
}
|
||||
|
||||
/*
|
||||
$(document.ready() {
|
||||
for( bt in future_ids ) {
|
||||
$('#bill-data-date-'+future_ids[bt]).width( $('#bill-data-date-'+first_col_id[bt]).width() )
|
||||
}
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
66
templates/cset.html
Normal file
66
templates/cset.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
|
||||
<title>Finance Form (Comparison Sets)</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||
<script src="https://code.highcharts.com/highcharts.js"></script>
|
||||
<script src="https://code.highcharts.com/modules/annotations.js"></script>
|
||||
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
|
||||
<style>
|
||||
.col-form-label { width:140px; }
|
||||
html { font-size: 80%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pt-2 mx-2 container-fluid row">
|
||||
<h3 align="center">Comparison Sets (go to <a href="/">Finance Tracker</a>)</h3>
|
||||
|
||||
<div class="row">
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">Action</th>
|
||||
{% for d in comp_data[finance['COMP_SETS'][1][0]]['vars'] %}
|
||||
{% if d != 'id' %}
|
||||
<th class="text-center">{{ d|replace('_', '<br>')|safe }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% for c in finance['COMP_SETS'] %}
|
||||
{% if c[0] != 0 %}
|
||||
<tr>
|
||||
<td><button class="btn btn-danger bg-danger-subtle text-danger"
|
||||
onClick="DelCSet({{c[0]}})"><span class="bi bi-trash3"> Delete</button></td>
|
||||
{% for d in comp_data[c[0]]['vars'] %}
|
||||
{% if d != 'id' %}
|
||||
{% if d == 'name' %}
|
||||
<td class="align-middle">{{comp_data[c[0]]['vars'][d]}}</td>
|
||||
{% else %}
|
||||
<td class="text-center align-middle">{{comp_data[c[0]]['vars'][d]}}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<script>
|
||||
function DelCSet( id )
|
||||
{
|
||||
// POST to a delete, success should just reload this page
|
||||
$.ajax( { type: 'POST', url: '/delcset', contentType: 'application/json', data: JSON.stringify( { 'id': id } ), success: function() { window.location='cset' } } )
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
|
||||
|
||||
<title>Finance Form</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@@ -19,7 +21,30 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<h3 align="center">Finance Tracker (go to <a href="bills">Bills</a>)</h3>
|
||||
<div class="d-flex align-items-center justify-content-center position-relative">
|
||||
<h3 align="center">Finance Tracker (go to <a href="bills">Bills</a> or <a href="cset">Comparison Sets</a>)</h3>
|
||||
<!-- Clickable Question Mark Icon -->
|
||||
<a href="#" tabindex="0"
|
||||
class="position-absolute end-0 me-3"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="click"
|
||||
data-bs-placement="right"
|
||||
data-bs-content="For now manually update the itmes below on day aftter original pay shcedule to compare saved version vs. our reality:
|
||||
<ul>
|
||||
<li>Savings (<a href='https://online.macquarie.com.au/personal/#/login'>Macquarie</a>
|
||||
+<a href='https://ib.mebank.com.au/authR5/ib/login.jsp'>ME bank</a>
|
||||
+<a href='https://ib.nab.com.au/login'>NAB</a>) -- noting ME bank is: $1000</li>
|
||||
<li><a href='https://www.google.com/search?q=asx+tls'>TLS</a>/<a href='https://www.google.com/search?q=asx+cba'>CBA</a> prices</li>
|
||||
<li>Macq <a href='https://www.macquarie.com.au/everyday-banking/savings-account.html'>Interest rate</a></li>
|
||||
<li><a href='https://deakinpeople.deakin.edu.au/psc/HCMP/EMPLOYEE/HRMS/c/NUI_FRAMEWORK.PT_AGSTARTPAGE_NUI.GBL?CONTEXTIDPARAMS=TEMPLATE_ID%3aPTPPNAVCOL&scname=ADMN_LEAVE&PTPPB_GROUPLET_ID=DU_LEAVE&CRefName=ADMN_NAVCOLL_3'>D_leave_owed_in_days</a> by: {{key_dates['D_quit_date']}}</li>
|
||||
<li>update Inflation - using <a href='https://tradingeconomics.com/australia/core-inflation-rate'>RBA Trimmed Mean CPI YoY</a></li></li>
|
||||
</ul>"
|
||||
data-bs-html="true">
|
||||
<i class="bi bi-question-circle" style="font-size: 2.0rem;"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<form id="vals_form" class="ms-3 mt-3" action="/update" method="POST">
|
||||
{% for r in DISP %}
|
||||
@@ -105,7 +130,7 @@
|
||||
</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">2025</div>
|
||||
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">{{savings[0][0][:4]}}</div>
|
||||
|
||||
{# inside started if below, we add blank lines to the start of the year so the dates line up #}
|
||||
{% for _ in range( 0, padding ) %}
|
||||
@@ -198,6 +223,7 @@
|
||||
const savingsData = JSON.parse('{{ savings | tojson }}');
|
||||
const vars = JSON.parse('{{ finance | tojson }}');
|
||||
|
||||
$(function() { $('[data-bs-toggle="popover"]').popover(); });
|
||||
window.onload = function() {
|
||||
$('#Sell_shares').val( {{finance['Sell_shares']}} )
|
||||
$('#compare_to').val( {{finance['compare_to']}} )
|
||||
@@ -389,4 +415,3 @@
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user