Compare commits
3 Commits
4b5b713c20
...
89d58e4cd3
| Author | SHA1 | Date | |
|---|---|---|---|
| 89d58e4cd3 | |||
| 65fc68e0bf | |||
| f3b828b051 |
176
bills.py
176
bills.py
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 }}:
|
||||
{{ '$%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'] }}:
|
||||
{{ '$%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 = [];
|
||||
|
||||
Reference in New Issue
Block a user