redid bills UI, you can now choose CPI or FLAT X% as well, via a drop-down not a set of buttons. The changing of inflation also tweaks any bills using CPI, all works

This commit is contained in:
2025-12-23 18:35:52 +11:00
parent ce20c57d11
commit f309dfa947
7 changed files with 220 additions and 75 deletions

2
BUGS
View File

@@ -1,2 +1,4 @@
* kayo bills are wrong in between normal bills
* added an electricity bill by accident for 2018, that kills lots :( * 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 - something to do with missing year of data in quarterly bills - still an issue

17
TODO
View File

@@ -1,11 +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: CALC:
* if I quit at different times of the financial year, technically the amount I earn will be taxed * 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 differently, but hard to calc - slides around with tax brackets in future
UI: UI:
* allow changing comparison when in tab with graph of comparisons - remember to force redraw page and then come to graph again * to allow <yr> total on bill summary, need to re-work annual growth to a drop-down
* I *COULD* have a basic date check/workflow, e.g. it now>last_update+14 (and its more than a paydate <- need to think that bit through) - [DONE] mock-up
then the question mark can be more prominent -> use an <alert> that suggests updating as per ? - 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: For bills:
* might need to be able to mark a specific bill as an outlier: * might need to be able to mark a specific bill as an outlier:

View File

@@ -2,6 +2,7 @@ from db import set_bill_type_growth, new_bill, deleteFutureEstimates, get_financ
from calc import calc_key_dates from calc import calc_key_dates
from defines import END_YEAR from defines import END_YEAR
import datetime import datetime
import re
from datetime import date, timedelta from datetime import date, timedelta
@@ -238,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 # 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 ): 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 # 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 # 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:] dd = bill_info[bill_type]['first_bill']['bill_date'][8:]
@@ -295,8 +298,18 @@ def get_growth_value( bt, bill_type ):
return el['ann_growth_min'] return el['ann_growth_min']
elif which == 'simple': elif which == 'simple':
return el['ann_growth_simple'] return el['ann_growth_simple']
else: elif which == 'max':
return el['ann_growth_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
################################################################################ ################################################################################
@@ -405,7 +418,7 @@ def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ):
amt=fb['amount'] amt=fb['amount']
bt=fb['bill_type'] bt=fb['bill_type']
# factor in growth for next bill # 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}" new_date=f"{yr}-{car_mmdd}"
# if we dont already have an annual bill for this year (all car bills are annual) # if we dont already have an annual bill for this year (all car bills are annual)
if yr not in bill_info[bt]['year']: if yr not in bill_info[bt]['year']:
@@ -428,7 +441,7 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ):
bt=fb['bill_type'] bt=fb['bill_type']
if bill_info[bt]['num_ann_bills'] == 1: if bill_info[bt]['num_ann_bills'] == 1:
# factor in growth for next bill # 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}" new_date=f"{yr}-{dq_mm}-{dq_dd}"
# if we dont already have an annual bill for this year # if we dont already have an annual bill for this year
if not find_this_bill( bt, bill_info, new_date ): if not find_this_bill( bt, bill_info, new_date ):
@@ -440,7 +453,7 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ):
new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}"
if not find_this_bill( bt, bill_info, new_date ): if not find_this_bill( bt, bill_info, new_date ):
new_estimated_bill( bill_info, yr, bt, amt, 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 amt += amt * bill_info[bt]['growth']/100
for m in range( 1, 13): for m in range( 1, 13):
new_date=f"{yr}-{m:02d}-{dq_dd}" new_date=f"{yr}-{m:02d}-{dq_dd}"
@@ -614,7 +627,6 @@ def recalcFutureBills():
print("Recalculating future bills as we changed a key date" ) print("Recalculating future bills as we changed a key date" )
finance_data = get_finance_data() finance_data = get_finance_data()
key_dates = calc_key_dates( finance_data ) key_dates = calc_key_dates( finance_data )
finance_data = get_finance_data()
bill_data = get_bill_data("order_by_date_only") bill_data = get_bill_data("order_by_date_only")
bill_types = get_bill_types() bill_types = get_bill_types()
bill_freqs = get_bill_freqs() bill_freqs = get_bill_freqs()
@@ -623,7 +635,6 @@ def recalcFutureBills():
# this maps freq to bills per annum (e.g. id=2 to 4 bills per annum) # 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} 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) getFutureBills(bill_data, bill_types, future_car_bills, future_D_quit_bills)
deleteFutureEstimates() deleteFutureEstimates()
# deal with future car bills # deal with future car bills
@@ -635,7 +646,7 @@ def recalcFutureBills():
# only can use simple growth as its a future bill # only can use simple growth as its a future bill
growth=bill_types[bt]['ann_growth_simple'] growth=bill_types[bt]['ann_growth_simple']
# factor in growth for next bills # factor in growth for next bills
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}" new_date=f"{yr}-{car_mmdd}"
new_bill( fb['bill_type'], amt, new_date, 1 ) new_bill( fb['bill_type'], amt, new_date, 1 )
amt += amt * growth/100 amt += amt * growth/100
@@ -654,7 +665,7 @@ def recalcFutureBills():
num_ann_bills= bf_id_num[bt_id_freq[bt]] num_ann_bills= bf_id_num[bt_id_freq[bt]]
if num_ann_bills == 1: if num_ann_bills == 1:
# factor in growth for next bill # 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}" new_date=f"{yr}-{dq_mm}-{dq_dd}"
# if we dont already have an annual bill for this year # if we dont already have an annual bill for this year
new_bill( fb['bill_type'], amt, new_date, 1 ) new_bill( fb['bill_type'], amt, new_date, 1 )
@@ -664,7 +675,7 @@ def recalcFutureBills():
for m in range( int(dq_mm), 13): for m in range( int(dq_mm), 13):
new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}"
new_bill( fb['bill_type'], amt, new_date, 1 ) new_bill( fb['bill_type'], amt, new_date, 1 )
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 * growth/100 amt += amt * growth/100
for m in range( 1, 13): for m in range( 1, 13):
new_date=f"{yr}-{m:02d}-{dq_dd}" new_date=f"{yr}-{m:02d}-{dq_dd}"

67
db.py
View File

@@ -84,6 +84,12 @@ def init_db():
FOREIGN KEY(comparison_set_id) REFERENCES comparison_set(id) 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 ( cur.execute('''CREATE TABLE IF NOT EXISTS bill_type (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
freq INTEGER, freq INTEGER,
@@ -95,12 +101,6 @@ def init_db():
FOREIGN KEY(freq) REFERENCES bill_freq(id) 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 ( cur.execute('''CREATE TABLE IF NOT EXISTS bill_data (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
bill_type INTEGER, bill_type INTEGER,
@@ -116,19 +116,56 @@ def init_db():
show_estimated INTEGER 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') cur.execute('SELECT COUNT(*) FROM finance')
if cur.fetchone()[0] == 0: if cur.fetchone()[0] == 0:
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, 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) 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', 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)) (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 ( 1, 'Annual', 1 )" )
cur.execute( "INSERT INTO bill_freq values ( 2, 'Quarterly', 4 )" ) 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 ( 3, 'Quarterly (forced)', 4 )" )
cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" ) cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" )
# start with no specific Tab/bill_type to show, and dont show_estimated # 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 )" ) 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.commit()
conn.close() conn.close()
@@ -367,6 +404,14 @@ def set_bill_type_growth( id, min_g, avg_g, max_g, simple_g ):
conn.close() conn.close()
return 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(): def get_bill_ui():
conn = connect_db(True) conn = connect_db(True)
cur = conn.cursor() cur = conn.cursor()
@@ -404,6 +449,14 @@ def delete_estimated_bills():
conn.close() conn.close()
return 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): def delete_cset(id):
conn = connect_db(False) conn = connect_db(False)
cur = conn.cursor() cur = conn.cursor()

28
main.py
View File

@@ -1,9 +1,11 @@
# main.py # main.py
from flask import Flask, render_template, request, redirect, url_for, Response, jsonify from flask import Flask, render_template, request, redirect, url_for, Response, jsonify
from calc import calculate_savings_depletion, calc_key_dates 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 init_db, get_finance_data, update_finance, get_budget_data
from db import get_bill_data, new_bill, update_bill_data, delete_bill, delete_estimated_bills from db import insert_cset, get_comp_set_data, get_comp_set_options, delete_cset
from db import get_bill_ui, save_ui, 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 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, recalcFutureBills from bills import process_bill_data, calc_future_totals, set_bill_type_growth, recalcFutureBills
from defines import END_YEAR from defines import END_YEAR
@@ -11,6 +13,7 @@ from collections import defaultdict, Counter
from datetime import datetime, date from datetime import datetime, date
import csv import csv
import io import io
import requests
from disp import FP_VAR from disp import FP_VAR
app = Flask(__name__) app = Flask(__name__)
@@ -148,6 +151,21 @@ def update():
# changed Ioniq6_future, Car_buyout_date or D_Num_fortnights_pay, so lets force recalc key_dates, and therefore estimated bills # 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']: 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() 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')) return redirect(url_for('index'))
@@ -160,13 +178,14 @@ def DisplayBillData():
bill_types = get_bill_types() bill_types = get_bill_types()
bill_freqs = get_bill_freqs() bill_freqs = get_bill_freqs()
bill_ui = get_bill_ui() 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, # 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) 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) # get an array of the total costs of bills each year - purely cosmetic (using bill_info)
total=calc_future_totals(bill_info, bill_types) total=calc_future_totals(bill_info, bill_types)
# update/re-get bill_data now that new estimated bills have been added # update/re-get bill_data now that new estimated bills have been added
bill_data = get_bill_data("order_by_bill_type_then_date") 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']) @app.route('/newbilltype', methods=['POST'])
def InsertBillType(): def InsertBillType():
@@ -224,6 +243,7 @@ def SaveUI():
@app.route('/force_recalc_bills', methods=['POST']) @app.route('/force_recalc_bills', methods=['POST'])
def force_recalc_bills(): def force_recalc_bills():
delete_estimated_bills() delete_estimated_bills()
recalcFutureBills()
return "200" return "200"
@app.route('/cset') @app.route('/cset')

View File

@@ -36,7 +36,7 @@
{% endfor %} {% endfor %}
</table> </table>
#} #}
<div class="col-7"> <div class="col-8">
<div class="row"> <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">Bill Type</div>
<div class="col-2 form-control-inline d-none new-bill-type-class">Frequency</div> <div class="col-2 form-control-inline d-none new-bill-type-class">Frequency</div>
@@ -55,66 +55,92 @@
<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> <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>
<div class="row"> <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"><label class="form-control d-flex
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></div> align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></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"><label class="form-control d-flex
<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> align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></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">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> </div>
{% for bt in bill_types %} {% for bt in bill_types %}
<div class="row"> <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 --> <!-- 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> <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 %} {% for bf in bill_freqs %}
<option value={{bf.id}}>{{bf.name}}</option> <option value={{bf.id}}>{{bf.name}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<script>$('#bill-type-freq-{{bt.id}}').val( {{bt.freq}} );</script> <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"> <div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="min-{{bt.id}}" autocomplete="off" <select id="{{bt.id}}_growth" class="form-select col" onChange="UseGrowth({{bt.id}})">
onChange="UseGrowth({{bt.id}}, 'min')" {% if bt.which_growth == 'min' %}checked{% endif %}> {% for gt in growth %}
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="min-{{bt.id}}" style="width: 6ch;"> {% if gt.name == 'Min' %}
{% if bt.ann_growth_min> 0 and bt.ann_growth_min < 10 %} &nbsp; {% endif %} <option value='min'
{{'%.2f'|format(bt.ann_growth_min)}} {% if bt.which_growth == 'min' %}selected{% endif %}
</label> >{{'%.2f'|format(bt.ann_growth_min)}}% {{gt.name}}</option>
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="avg-{{bt.id}}" autocomplete="off" {% elif gt.name == 'Avg' %}
onChange="UseGrowth({{bt.id}}, 'avg')" {% if bt.which_growth == 'avg' %}checked{% endif %}> <option value='avg'
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="avg-{{bt.id}}" style="width: 6ch;"> {% if bt.which_growth == 'avg' %}selected{% endif %}
{% if bt.ann_growth_avg < 10 %} &nbsp; {% endif %} >{{'%.2f'|format(bt.ann_growth_avg)}}% {{gt.name}}</option>
{{'%.2f'|format(bt.ann_growth_avg)}} {% elif gt.name == 'Max' %}
</label> <option value='max'
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="max-{{bt.id}}" autocomplete="off" {% if bt.which_growth == 'max' %}selected{% endif %}
onChange="UseGrowth({{bt.id}}, 'max')" {% if bt.which_growth == 'max' %}checked{% endif %}> >{{'%.2f'|format(bt.ann_growth_max)}}% {{gt.name}}</option>
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="max-{{bt.id}}" style="width: 6ch;"> {% elif gt.name == 'Simple' %}
{% if bt.ann_growth_max < 10 %} &nbsp; {% endif %} <option value='simple'
{{'%.2f'|format(bt.ann_growth_max)}} {% if bt.which_growth == 'simple' %}selected{% endif %}
</label> >{{'%.2f'|format(bt.ann_growth_simple)}}% {{gt.name}}</option>
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="simple-{{bt.id}}" autocomplete="off" {% elif gt.name == 'CPI' %}
onChange="UseGrowth({{bt.id}}, 'simple')" {% if bt.which_growth == 'simple' %}checked{% endif %}> <option value='cpi'
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="simple-{{bt.id}}" style="width: 6ch;"> {% if bt.which_growth == 'cpi' %}selected{% endif %}
{% if bt.ann_growth_simple < 10 %} &nbsp; {% endif %} >{{'%.2f'|format(cpi)}}% {{gt.name}}</option>
{{'%.2f'|format(bt.ann_growth_simple)}} {% else %}
</label> <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> </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> {% for yr in range( 2025, 2032 ) %}
<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> <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>
<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> {% endfor %}
<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-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-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> <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> </div>
{% endfor %} {% 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 ) %} {% set tot=namespace( sum=0 ) %}
{% for bt in bill_types %} {% for bt in bill_types %}
{% if bt.id in total %} {% if bt.id in total %}
{% set tot.sum = tot.sum + total[bt.id][yr] %} {% set tot.sum = tot.sum + total[bt.id][yr] %}
{% endif %} {% endif %}
{% endfor %} {% 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" %} {% set markup="h5" %}
{% if yr == this_year %} {% if yr == this_year %}
{% set markup="h4 pt-4" %} {% set markup="h4 pt-4" %}
@@ -127,12 +153,16 @@
${{'%.2f'|format(tot.sum)}} ${{'%.2f'|format(tot.sum)}}
</div> </div>
</div> </div>
#}
{% endfor %} {% endfor %}
<div class="px-0 col"></div>
<div class="px-0 col"></div>
</div>
</div> </div>
<!-- right-hand-side, bill types (e.g. gas, phone, etc.) --> <!-- right-hand-side, bill types (e.g. gas, phone, etc.) -->
<div class="col-5"> <div class="col-4">
<div class="row"> <div class="row">
<div class="col-2 form-control-inline d-none new-bill-data-class">Bill Type</div> <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> <div id="new-bill-data-date-label" class="col-4 form-control-inline d-none new-bill-data-class">Date</div>
@@ -186,8 +216,9 @@
<div class="row pt-2"> <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">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">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="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-4"><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">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> </div>
{% endif %} {% endif %}
{% if bd.bill_type == bt.id %} {% if bd.bill_type == bt.id %}
@@ -203,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> <div class="px-0 col-2"> <input type="text" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div>
{% else %} {% else %}
<div class="px-0 col-2"> <input type="date" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div> <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 %} {% 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 %} {% 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-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-2 btn btn-danger bg-danger-subtle text-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete <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-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-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-2 btn btn-danger bg-danger-subtle text-danger d-none" <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> onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-x"> Cancel</button>
</button> </button>
{% else %}
<div class="px-0 col"></div>
<div class="px-0 col"></div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -403,10 +443,11 @@
data: JSON.stringify( { 'id': id } ), success: function() { window.location='bills' } } ) 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', $.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 ) function SaveTab( last_tab )
@@ -472,6 +513,13 @@
$.ajax( { type: 'POST', url: '/force_recalc_bills', contentType: 'application/json', success: function() { window.location='bills' } } ) $.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> </script>
</body> </body>
</html> </html>

View File

@@ -33,7 +33,7 @@
<ul> <ul>
<li>Savings (<a href='https://online.macquarie.com.au/personal/#/login'>Macquarie</a> <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.mebank.com.au/authR5/ib/login.jsp'>ME bank</a>
+<a href='https://ib.nab.com.au/login'>NAB</a>) -- noting ME bank is: $2001.19</li> +<a href='https://ib.nab.com.au/login'>NAB</a>) -- noting ME bank is: $955.83</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><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>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><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>