Compare commits

...

3 Commits

3 changed files with 192 additions and 78 deletions

176
bills.py
View File

@@ -1,12 +1,71 @@
from db import get_bill_data, get_bill_types, get_bill_freqs, set_bill_type_growth, new_bill
from defines import END_YEAR
import datetime
from datetime import date, timedelta
# give a bill dat in format YYYY-MM-DD, return quarter (1-4)
################################################################################
# this finds start and end dates of a quarter for a given date
################################################################################
def quarter_bounds(d):
q = (d.month-1)//3
start = date(d.year, 3*q+1, 1)
# last day of quarter = first day of next quarter minus 1 day
if q == 3:
next_start = date(d.year+1, 1, 1)
else:
next_start = date(d.year, 3*q+4, 1)
end = next_start - timedelta(days=1)
return start, end
################################################################################
# takes a bill and its previous bill, works out days between and adds cost / day
# to each quarter the bill covers from prev. to now. Usually means it splits
# one bill in a previous and this qtr (or just puts it all into the current qtr)
################################################################################
def allocate_by_quarter( bill_info, bill_type, yr, prev_bill, bill):
start = date( int(prev_bill['bill_date'][:4]), int(prev_bill['bill_date'][5:7]), int(prev_bill['bill_date'][8:]))
end = date( int(bill['bill_date'][:4]), int(bill['bill_date'][5:7]), int(bill['bill_date'][8:]))
time_difference = end - start
days = time_difference.days
cost_per_day = bill['amount']/days
if end < start:
return {}
if not 'qtr' in bill_info[bill_type]:
bill_info[bill_type]['qtr'] = {}
q_start, q_end = quarter_bounds(start)
cur = q_start
# walk quarters that might overlap - start from the quarter of `start`, iterate until past `end`
while cur <= end:
q_start, q_end = quarter_bounds(cur)
overlap_start = max(start, q_start)
overlap_end = min(end, q_end)
# only add qtr total for yr being calc'd
if overlap_end >= overlap_start:
days = (overlap_end - overlap_start).days + 1
q = (q_start.month-1)//3 + 1
# initialise arrays if needed
if q_start.year not in bill_info[bill_type]['qtr']:
bill_info[bill_type]['qtr'][q_start.year] = {}
for i in range(1,5):
bill_info[bill_type]['qtr'][q_start.year][i]=0
bill_info[bill_type]['qtr'][q_start.year][q] += days*cost_per_day
# next quarter
cur = q_end + timedelta(days=1)
return
################################################################################
# given a bill date in format YYYY-MM-DD, return quarter (1-4)
################################################################################
def qtr(d):
m = int(d[5:7])
return ( (m-1)//3 + 1 )
################################################################################
# find the bill just after the date given
################################################################################
def find_next_bill( bill_type, bill_info, bill_date ):
wanted_year = int(bill_date[:4])
wanted_mm = int(bill_date[5:7])
@@ -17,6 +76,7 @@ def find_next_bill( bill_type, bill_info, bill_date ):
for yr in range( wanted_year, bill_info[bill_type]['last_bill_year']+1 ):
# start with bills in the year wanted (if any)
if yr in bill_info[bill_type]['year']:
# reverse this list so we can q1 bills before q4
for bill in bill_info[bill_type]['year'][yr][::-1]:
bill_mm = int(bill['bill_date'][5:7])
# if bill is in this year but later OR its a later year, return this bill
@@ -26,6 +86,7 @@ def find_next_bill( bill_type, bill_info, bill_date ):
return None
# find the bill just before the date given
def find_previous_bill( bill_type, bill_info, bill_date ):
wanted_year = int(bill_date[:4])
wanted_mm = int(bill_date[5:7])
@@ -35,7 +96,7 @@ def find_previous_bill( bill_type, bill_info, bill_date ):
# start loop from bill_date, go backwards and find which one it is (same year, should be month-based)
# earlier year, then just last one from the year.
yr_range=range( wanted_year, bill_info[bill_type]['first_bill_year'], -1 )
yr_range=range( wanted_year, bill_info[bill_type]['first_bill_year']-1, -1 )
if wanted_year == int(bill_info[bill_type]['first_bill_year']):
# range of this year with -1, does not return anything, so force this year.
yr_range=[ wanted_year ]
@@ -47,19 +108,21 @@ def find_previous_bill( bill_type, bill_info, bill_date ):
# okay, we have the previous billing year, and we wanted one for a year in the future,
# just return the last one in this year as its the most recent
if wanted_year > yr:
return bill_info[bill_type]['year'][yr][-1]
return bill_info[bill_type]['year'][yr][0]
else:
# lets go through the newest to oldest of these bills
for bill in bill_info[bill_type]['year'][yr][::-1]:
for bill in bill_info[bill_type]['year'][yr]:
bill_mm = int(bill['bill_date'][5:7])
# reversing the bills, means we start with the 'most recent' in this year to the oldest
# if the month we want is after the bill, we are done
if wanted_mm > bill_mm:
return bill
return None
# quick wrapper to add a new estimated bill - new estimates have the flag in
# the DB set, but also we update bill_info to reflect the new bill so future
# growth can build of this esimate too - e.g 2030 can use 2029, etc
def new_estimated_bill( bill_info, yr, bill_type, amt, new_date ):
# add to DB
new_bill( bill_type, amt, new_date, 1 )
@@ -72,7 +135,18 @@ def new_estimated_bill( bill_info, yr, bill_type, amt, new_date ):
bill['amount']=amt
bill['estimated']=1
# need this for find_previous_bill to work but only need the above 3 fields
bill_info[bill_type]['year'][yr].append(bill)
bill_info[bill_type]['year'][yr].insert(0,bill)
if bill_info[bill_type]['num_ann_bills'] == 4:
q = qtr( new_date )
if yr not in bill_info[bill_type]['qtr']:
bill_info[bill_type]['qtr'][yr]={}
pb = find_previous_bill( bill_type, bill_info, new_date )
if pb['estimated'] == 0:
print( f" FIXFIXFIX - have a prev real bill={pb['bill_date']} & this is first est - likely need to better apportion this bill into the quarters" )
allocate_by_quarter( bill_info, bill_type, yr, pb, bill )
bill_info[bill_type]['qtr'][yr][q]=amt
return
@@ -86,32 +160,33 @@ def add_missing_annual_bill_in_yr( bill_type, bill_info, yr ):
for i in range( bill_info[bill_type]['last_bill_year'], yr ):
amt += amt * bill_info[bill_type]['growth']/100
# last param is estimated (and this is an estimate for a future bill / not real)
new_estimated_bill( bill_info, yr, bill_type, amt, f'{yr}-{mm_dd}' )
return
# missing quarterly bill, find date based on MM-DD and ??? - can have missing bilsl in first year
# add growth (based on drop-down) for each future year
def add_missing_quarter_bills_in_yr( bill_type, bill_info, yr ):
# okay we have data for this year but some missing (wouldnt be here otherwise)
# and data from previous year... lets fill in gaps
if yr in bill_info[bill_type]['year'] and yr-1 in bill_info[bill_type]['year']:
# per if above, ONLY get here if we have first few bills of {yr}, cannot be last few
have_q = qtr( bill_info[bill_type]['year'][yr][0]['bill_date'] )
for q in range(have_q+1,5):
# use 5-q, as bills are in descending order in bill_info, e.g. q4 is 1st,
bill=bill_info[bill_type]['year'][yr-1][4-q]
mm_dd= bill['bill_date'][5:]
amt = bill['amount']*(1+bill_info[bill_type]['growth']/100)
new_date = f'{yr}-{mm_dd}'
# okay we have data for last year but some missing (in this year), lets fill in gaps
# could be called if only have data for q2 - q4 in first year and we dont have a previous years q1 data so don't try
if 'qtr' in bill_info[bill_type] and yr-1 in bill_info[bill_type]['qtr']:
# if we do have data in this year, we have q1-q3 only, and want missing qtrs set range appropriately...
if yr in bill_info[bill_type]['qtr']:
# per if above, ONLY get here if we have first few bills of {yr}, cannot be last few
have_q = qtr( bill_info[bill_type]['year'][yr][0]['bill_date'] )
r=range(have_q+1,5)
else:
r=range(1,5)
for q in r:
# amt is total of last year's qtr bill proportion
amt = bill_info[bill_type]['qtr'][yr-1][q]*(1+bill_info[bill_type]['growth']/100)
# just make new bills first of last month of a qtr (good as any date for GAS, they move anyway)
new_date = f'{yr}-{q*3:02d}-01'
# SANITY CHECK: we might be adding a bill estimate we already have (due to stupid gas bills /qtrly code)
if yr in bill_info[bill_type]['year']:
for b in bill_info[bill_type]['year'][yr]:
if b['bill_date'] == new_date:
return
new_estimated_bill( bill_info, yr, bill_type, amt, new_date )
# for now only add full new years based on last year with ann_growth (seasonal)
if yr not in bill_info[bill_type]['year'] and yr-1 in bill_info[bill_type]['year']:
for bill in bill_info[bill_type]['year'][yr-1]:
mm_dd= bill['bill_date'][5:]
amt = bill['amount']*(1+bill_info[bill_type]['growth']/100)
new_estimated_bill( bill_info, yr, bill_type, amt, f'{yr}-{mm_dd}' )
return
# missing monthly bills, find date based on DD and put in each missing month
@@ -159,7 +234,6 @@ def add_missing_monthly_bills_in_yr( bill_type, bill_info, yr ):
if i == int(lb_mm):
amt += amt * bill_info[bill_type]['growth']/100
bill_info[bill_type]['last_bill_amount']=amt
# last param is estimated (and this is an estimate for a future bill / not real)
new_estimated_bill( bill_info, yr, bill_type, amt, new_date )
return
@@ -178,10 +252,12 @@ def get_growth_value( bt, bill_type ):
return el['ann_growth_max']
################################################################################
# go through the bill data from the DB, put it into more friendly formats, then
# work out and then add missing bill data (might be b/c we have monthly bills,
# and I didn't want to input 12 of them at the same price), and it always
# occurs for future bills
################################################################################
def process_bill_data(bd, bt, bf):
# this maps a bill id to a freq id (e.g. bill #34 - has a frequency of #2 (which might be quarterly)
bt_id_freq = {row["id"]: row["freq"] for row in bt}
@@ -232,10 +308,12 @@ def process_bill_data(bd, bt, bf):
yr_min=int(bill_info[bill_type]['first_bill']['bill_date'][:4])
yr_max=int(bill_info[bill_type]['last_bill']['bill_date'][:4])
ProportionQtrlyData( bill_type, bill_info )
# go from first_bill year until reach end year
for yr in range( yr_min, END_YEAR+1 ):
# we have all the bills needed for yr
if yr in bill_info[bill_type]['year'] and len(bill_info[bill_type]['year'][yr]) == bill_info[bill_type]['num_ann_bills']:
# we have all the bills needed for yr - but dont be cute with qtrly, gas bills suck can have missing with 4 bills
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 )
@@ -253,9 +331,36 @@ def add_missing_bills_for_yr( bill_type, bill_info, yr ):
add_missing_monthly_bills_in_yr( bill_type, bill_info, yr )
return
################################################################################
# Takes qtrly bills and start from 2nd year of bills (so we can estimate growth)
# and go through each bill allocating hte proportion of each bill to each
# relevant quarter - to build more accurate totals. Would be mostly marginal
# accept when Gas qtrly bills have 6 per year, and we need to guess say qtr4 in
# the future, we can't easily find corresponding bill form previous year, so
# this allows us to aggregate per quarter and use matching quarter
################################################################################
def ProportionQtrlyData( bill_type, bill_info ):
# just do up to now for the moment so that add_missing_bills later will have qtr data to use
now_yr = datetime.date.today().year
# FIX UP CRAPPY QUARTERLY BILLING PROPORTIONS (only useful as some gas bills are 6 / year!)
if bill_info[bill_type]['num_ann_bills']==4:
for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1):
for b in bill_info[bill_type]['year'][yr]:
pb = find_previous_bill( bill_type, bill_info, b['bill_date'] )
if not pb:
continue
allocate_by_quarter( bill_info, bill_type, yr, pb, b )
return
################################################################################
# function to work out totals per year, and then calcuates annual growth in
# 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 ):
# just do up to now so we stop earlier than looking at other estimated (just an optimisation)
now_yr = datetime.date.today().year
total={}
for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1):
# if not enough bills in this year (or none), then try next year (first year might have not enough bills)
@@ -274,8 +379,17 @@ def derive_ann_growth( bill_type, bill_info ):
total[yr] = 0
for b in bill_info[bill_type]['year'][yr]:
total[yr] += b['amount']
if bill_type == 3:
print( f"total[{yr}]={total[yr]}" )
# crazily we can have more bills in this year than expected, so work out qtrly costs, and patch that back into total array
for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1):
if 'qtr' in bill_info[bill_type] and yr in bill_info[bill_type]['qtr']:
tot=0
for q in range( 1,5 ):
tot += bill_info[bill_type]['qtr'][yr][q]
if yr in total:
# use new derived qtr, slightly more accurate
total[yr]=tot
# once we have all yr totals:
growth = {}
@@ -295,6 +409,8 @@ def derive_ann_growth( bill_type, bill_info ):
if growth > max_growth:
max_growth = growth
if count:
## print( f"Before sanity check, min={min_growth}, avg={avg_growth/count}, max_growth={max_growth}" )
# HACK FOR SANITY SAKE NOW - bills wont decrease normally, and 10% is unlikely for sustained growth
if min_growth< 0: min_growth=0
if avg_growth< 0 or avg_growth > 10: avg_growth = 3*count
if max_growth>10 : max_growth = 9.99

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -23,7 +23,7 @@
<div class="mt-4 col-6">
<div class="row align-items-center">
<button id="new-bill-type-button" class="mb-3 px-0 offset-4 col-2 btn btn-success" onCLick="StartNewBillType()"><span class="bi bi-plus-lg"> New Bill Type</span></button>
<button id="new-bill-type-button" class="mb-3 px-0 offset-4 col-2 btn btn-success bg-success-subtle text-success" onCLick="StartNewBillType()"><span class="bi bi-plus-lg"> New Bill Type</span></button>
<div class="new-bill-type-class px-0 col-2 d-none"> <input type="text" class="form-control text-end float-end border border-primary" id="new-bill-type-name"></div>
<div class="new-bill-type-class px-0 col-2 d-none"><select id="new-bill-type-freq" class="form-select text-center">
{% for bf in bill_freqs %}
@@ -31,21 +31,21 @@
{% endfor %}
</select>
</div>
<button id="save-bill-type" class="new-bill-type-class px-0 col-1 btn btn-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 d-none" onClick="CancelNewBillType()"><span class="bi bi-trash3"> Cancel</span></button>
<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>
</div>
<div class="row">
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-light rounded-0 h-100"><br>Name</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-light rounded-0 h-100"><br>Frequency</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-light rounded-0">Annual Growth Est (min/avg/max)</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-light rounded-0 h-100"><br>Actions</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0 h-100"><br>Name</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0 h-100"><br>Frequency</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Annual Growth Est (min/avg/max)</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0 h-100"><br>Actions</ ></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 bg-white" id="bill-type-name-{{bt.id}}" value="{{ bt.name }}" disabled> </div>
<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>
<!-- 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 bg-white" disabled>
<div class="px-0 col-2"><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 %}
@@ -74,10 +74,10 @@
</label>
</div>
</div>
<button id="bill-type-chg-{{bt.id}}" class="px-0 col-1 btn btn-success" onClick="StartUpdateBillType( {{bt.id}} )">Change</button>
<button id="bill-type-del-{{bt.id}}" class="px-0 col-1 btn btn-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 d-none" onClick="UpdateBillType( {{bt.id}} )">Save</button>
<button id="bill-type-canc-{{bt.id}}" class="px-0 col-1 btn btn-danger d-none" onClick="CancelUpdateBillType({{bt.id}}, '{{bt.name}}')"><span class="bi bi-trash3"> Cancel</button>
<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>
</div>
{% endfor %}
</div>
@@ -86,7 +86,7 @@
<div class="pt-4 col-6">
<div class="row align-items-center">
<button id="new-bill-data-button" class="mb-3 px-0 offset-6 col-2 btn btn-success" onCLick="StartNewBillData()"><span class="bi bi-plus-lg"> New Bill</span></button>
<button id="new-bill-data-button" class="mb-3 px-0 offset-6 col-2 btn btn-success bg-success-subtle text-success" onCLick="StartNewBillData()"><span class="bi bi-plus-lg"> New Bill</span></button>
<div class="new-bill-data-class px-0 col-2 d-none"> <select id="new-bill-data-type" class="form-select text-end float-end border border-primary">
{% for bt in bill_types %}
<option value={{bt.id}}>{{bt.name}}</option>
@@ -95,9 +95,9 @@
</div>
<div class="new-bill-data-class px-0 col-2 d-none"> <input type="date" class="form-control text-end float-end border border-primary" id="new-bill-data-date"> </div>
<div class="new-bill-data-class px-0 col-2 d-none"> <input type="number" class="form-control text-end float-end border border-primary" id="new-bill-data-amount"> </div>
<button id="save-bill" class="new-bill-data-class px-0 col-1 btn btn-success d-none" onClick="NewBill()">
<button id="save-bill" class="new-bill-data-class px-0 col-1 btn btn-success bg-success-subtle text-success d-none" onClick="NewBill()">
<span class="bi bi-floppy"></span> Save </button>
<button class="new-bill-data-class px-0 col-1 btn btn-danger d-none" onClick="CancelNewBill()" ><span class="bi bi-trash3"> Cancel</span> </button>
<button class="new-bill-data-class px-0 col-1 btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelNewBill()" ><span class="bi bi-x"> Cancel</span> </button>
</div>
<!-- create tabbed view for each bill type -->
@@ -121,29 +121,29 @@
{% for bd in bill_data %}
{% if loop.first %}
<div class="row pt-2">
<div class="p-0 col-2"> <label class="form-control text-center border-0 fw-bold bg-light rounded-0">Name</ > </div>
<div class="p-0 col-2"> <label class="form-control text-center border-0 fw-bold bg-light rounded-0">Date</ > </div>
<div class="p-0 col-2"> <label class="form-control text-center border-0 fw-bold bg-light rounded-0">Amount</ > </div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-light rounded-0">Actions</ ></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">Amount</ > </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>
{% endif %}
{% if bd.bill_type_id == bt.id %}
{% if bd.estimated == 1 %}
<div class="row est d-none fst-italic">
{% set classes="fst-italic form-control text-center bg-white" %}
{% set classes="fst-italic form-control text-center" %}
{% else %}
<div class="row">
{% set classes="form-control text-center bg-white" %}
{% set classes="form-control text-center" %}
{% endif %}
<div class="px-0 col-2"> <input type="text" class="{{classes}}" id="bill-data-type-{{bd.id}}" value="{{ bd.name }}" 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>
<div class="px-0 col-2"> <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-1 btn btn-success" onClick="StartUpdateBill( {{bd.id}} )">Change</button>
<button id="bill-data-del-{{bd.id}}" class="px-0 col-1 btn btn-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete
<button id="bill-data-save-{{bd.id}}" class="px-0 col-1 btn btn-success d-none" onClick="UpdateBill( {{bd.id}} )">Save</button>
<button id="bill-data-canc-{{bd.id}}" class="px-0 col-1 btn btn-danger d-none"
onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-trash3"> Cancel</button>
<button id="bill-data-chg-{{bd.id}}" class="px-0 col-1 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-1 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-1 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-1 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>
{% endif %}
</div>
@@ -229,7 +229,6 @@
function CancelUpdateBill( id, bill_type, bill_date, amount )
{
// fix-up type, date and amount fields
$('#bill-data-type-'+id).addClass('bg-white')
$('#bill-data-date-'+id).prop('disabled', true )
$('#bill-data-amount-'+id).prop('disabled', true )
// alter change/delete buttons to be save/cancel
@@ -301,10 +300,8 @@
function CancelUpdateBillType(id,orig_name)
{
// "re-enable" the freq & growth
$('.bill-type-'+id).removeClass('bg-light text-secondary').addClass('bg-white')
// "disable" name for edits
$('#bill-type-name-'+id).prop('disabled', true)
$('.bill-type-'+id).prop('disabled', true)
// alter change/delete buttons to be save/cancel
$('#bill-type-chg-'+id).removeClass('d-none')
$('#bill-type-del-'+id).removeClass('d-none')

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -11,6 +11,7 @@
<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>
<script src="https://code.highcharts.com/themes/adaptive.js"></script>
<style>
.col-form-label { width:140px; }
html { font-size: 80%; }
@@ -62,7 +63,7 @@
class="col-form-label me-2 text-end float-end {{extra}}">{{el.label}}
</label>
{% if el.display== "readonly" %}
{% set bg="bg-light" %}
{% set bg="bg-body-tertiary" %}
{% set bd="" %}
{% else %}
{% set bg="" %}
@@ -91,7 +92,7 @@
</h5>
<div class="row">
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">2025</div>
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">2025</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 ) %}
@@ -105,14 +106,14 @@
{% if yr|int > first_yr|int and mon == '01' and day|int <= 14 %}
</div><div class="col-auto">
<div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">{{yr}}</div>
<div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">{{yr}}</div>
{% endif %}
<font color="black">
<font class="text-secondary">
{{ date }}:&nbsp;
{{ '$%0.2f' % dollars|float }}<br>
</font>
{% if comp_done.val == 0 and yr == comp_yr and mon == comp_mon and day|int >= comp_day|int %}
<font color="blue">
<font class="text-info">
{{ COMP['date'] }}:&nbsp;
{{ '$%0.2f' % COMP['amount']|float }}<br>
</font>
@@ -151,7 +152,7 @@
</div>
</div>
</form>
<div class="row mt-4" id="container" style="width:100%; height:400px;"></div>
<div class="row mt-4 highcharts-dark" id="container" style="width:100%; height:400px;"></div>
<script type="text/javascript">
// make these global so we can also use them in the /save route (via modal)
const savingsData = JSON.parse('{{ savings | tojson }}');
@@ -165,17 +166,17 @@
if( $("#Ioniq6_future option:selected"). text() == 'lease' )
{
// disable buyout
$('#lbl-Car_buyout').addClass('bg-light text-secondary border-secondary')
$('#Car_buyout').addClass('bg-light text-secondary border-secondary').attr('readonly', 'readonly' )
$('#Car_buyout_date').addClass('bg-light text-secondary border-secondary').attr('readonly', 'readonly' )
$('#lbl-Car_buyout').addClass('bg-body-tertiary border-secondary')
$('#Car_buyout').addClass('bg-body-tertiary border-secondary').attr('readonly', 'readonly' )
$('#Car_buyout_date').addClass('bg-body-tertiary border-secondary').attr('readonly', 'readonly' )
}
else
{
// disable lease
$('#lbl-Car_loan').addClass('bg-light text-secondary')
$('#Car_loan').addClass('bg-light text-secondary')
$('#lbl-Car_balloon').addClass('bg-light text-secondary')
$('#Car_balloon').addClass('bg-light text-secondary')
$('#lbl-Car_loan').addClass('bg-body-tertiary')
$('#Car_loan').addClass('bg-body-tertiary')
$('#lbl-Car_balloon').addClass('bg-body-tertiary')
$('#Car_balloon').addClass('bg-body-tertiary')
}
var tooltipTriggerList = [].slice.call(document.querySelectorAll("[data-bs-toggle='tooltip']"))
@@ -215,7 +216,7 @@
plotBands.push({
from: start,
to: end,
color: year % 2 === 0 ? 'white' : 'lemonchiffon' // Alternate colors
color: year % 2 === 0 ? '#101010' : 'black' // Alternate colors
});
year = currentYear;
start = new Date(`${year}-01-01`).getTime();
@@ -226,7 +227,7 @@
plotBands.push({
from: start,
to: end,
color: year % 2 === 0 ? 'white' : 'lemonchiffon'
color: year % 2 === 0 ? 'charcoal' : 'black'
});
const annotations = [];