diff --git a/blocnote/apps/financialInputs/migrations/0013_auto_20170424_2158.py b/blocnote/apps/financialInputs/migrations/0013_auto_20170424_2158.py new file mode 100644 index 0000000000000000000000000000000000000000..781d3464cd0629f108aebe0392a0987857d9ae3b --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0013_auto_20170424_2158.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-24 21:58 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0012_customerpreference'), + ] + + operations = [ + migrations.CreateModel( + name='EstimationAlgorithm', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('algorithm', models.CharField(max_length=100)), + ], + ), + migrations.RemoveField( + model_name='billsoverview', + name='estimation_algorithm', + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0015_auto_20170424_2250.py b/blocnote/apps/financialInputs/migrations/0015_auto_20170424_2250.py new file mode 100644 index 0000000000000000000000000000000000000000..e350b035d62e87880c7bb1b48c93568b0297324e --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0015_auto_20170424_2250.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-24 22:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0013_auto_20170424_2158'), + ] + + operations = [ + migrations.AlterField( + model_name='estimationalgorithm', + name='algorithm', + field=models.CharField(max_length=16), + ), + ] diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 139bfb31abb126c3861ab4384d7e22ef8a989137..14e63987d192abb9720883ac08ba491f9bafbf93 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -42,12 +42,18 @@ class Bills(models.Model): charge = models.DecimalField(max_digits=10, decimal_places=2) +class EstimationAlgorithm(models.Model): + """Estimation algorithm used to project bills for pro forma duration.""" + + algorithm = models.CharField(max_length=16) + building_id = models.IntegerField() + + class BillsOverview(models.Model): """Store annual charge of each utility used for bill projection.""" building_id = models.IntegerField() year = models.DecimalField(max_digits=4, decimal_places=0) - estimation_algorithm = models.CharField(max_length=100) electricity = models.DecimalField(max_digits=10, decimal_places=2) electricity_is_user_input = models.BooleanField(default=False) water = models.DecimalField(max_digits=10, decimal_places=2) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index cfc018772ee7c48ae402e85cb6aec8d297d382e2..4f042d4faf16b607d16f61ac2bdff59e5c56d2fe 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -31,8 +31,6 @@ function billProjectionDatesForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) - }).then(res => { - loadBillsOverview(); }); } return false; @@ -95,19 +93,24 @@ function updateTable(utility, text) { /** Generate html text for a given utility and bills data from backend. */ function getText(result, utility) { + units = {}; + units['electricity'] = `kWh`; + units['gas'] = `mmBTU`; + units['oil'] = `mmBTU`; + units['water'] = `Gallons`; var text = ` - + `; - for(var i =0; i`; - for(var j=0; j` + result[i][j] + ``; } text += ``; @@ -148,39 +151,75 @@ function sendFile(id, content) { }).then(res => { var text = getText(res.payload.result, id); updateTable(id, text); - loadBillsOverview(); }); } +function createEstimateModelForm() { + var estimateModelForm = ``; + estimateModelForm += ` +
+
`; + estimateModelForm += estimationDropDownBox(); + estimateModelForm += createCalculateButton(); + estimateModelForm += ` +
+ `; + return estimateModelForm; +} + /** Create form tag Energy Bills Overview */ -function startOverviewForm() { - var text = `
`; +function startBillsOverviewForm() { + var text = ``; return text; } /** Create dropdown box to select estimation algorithm */ function estimationDropDownBox() { - var text = ``; + var text = ` +
+ +
+ `; return text; } + /** Create table and column headings */ -function createOverviewColumnHeaders(year) { - var text = `
Date From Date ToUsage Usage (${units[utility]}) Bill Charge ($)
- - - - - - - `; - return text; +function createBillsOverviewTable(responseDictionary) { + var tableHTML = ``; + tableHTML = ` + +
User InputUtility/Year${year}
+ + + + + `; + for (var year in responseDictionary['electricity']) { + tableHTML += ` + + `; + } + tableHTML += ` + + + `; + tableHTML += createBillsOverviewRows(responseDictionary); + tableHTML += ` +
User InputUtility/Year${year}
+ `; + tableHTML += createSubmitButton(); + tableHTML += ` + + `; + return tableHTML; } + /** Display if user input is needed or not */ function createFlag(state) { - if(state) { + if (state) { text = `Need User Input`; } else { @@ -194,40 +233,82 @@ function createFlag(state) { * database. If value is not from database, make it a user input. Name * the input along with utility type to uniquely identify it. */ -function createInput(id, state, value) { +function createBillsOverviewTableCellInput(id, state, value, year) { var text = ``; var is_readonly = ``; if (!state) { is_readonly = `readonly`; } - text = ``; + text = ``; return text; } /** - * Add rows to the energy bills table. Currently adds only one entry in - * each column. Will be modified to display entries for 20 years. + * Create rows for each utility from Pro Forma start year for the Pro Forma duration. Show flag if bill present or + * absent. If bill is present, make input a readonly tag else, allow it to take inputs. */ -function createOverviewRows(data) { - +function createBillsOverviewRows(data) { var user_input = ''; - var text = ``; - for (var utility in utilities) { - user_input = utilities[utility] + '_is_user_input'; - text += ` - + var text = ` + + `; + for (var key in utilities) { + utility = utilities[key]; + user_input = utility + '_user'; + text += ` + + ${createFlag(data[user_input])} - - ${utilities[utility]} - - ${createInput(utilities[utility], data[user_input], data[utilities[utility]])} - - `; + + + ${utility} + + `; + for (var year in data[utility]) { + text += ` + + ($)${createBillsOverviewTableCellInput(utility, data[user_input], data[utility][year], year)} + + `; + } + text += ` + + `; } - text += ` - - - `; + text += ` + + + Total Annual Charge + `; + for (var charge in data['total_annual_charge']) { + text += ` + $${data['total_annual_charge'][charge]} + `; + } + text += ` + + + `; + return text; +} + +/**Create the calculate button to project bill with given data. */ +function createCalculateButton() { + text = ` +
+ +
+ `; + return text; +} + +/**Create button to submit the projected and input data. */ +function createSubmitButton() { + text = ` +
+ +
+ `; return text; } @@ -245,22 +326,22 @@ function loadBillsOverview() { }).then(res => { if(!res.err) { const table = document.querySelector('#Energy-Bills-Overview'); - var text = ""; - text += startOverviewForm(); - text += estimationDropDownBox(); - text += createOverviewColumnHeaders(res.payload.instance.year); - text += createOverviewRows(res.payload.instance); + var text = ``; + text += createEstimateModelForm(); + if(res.payload.instance.present) { + text += createBillsOverviewTable(res.payload.instance); + } table.innerHTML = text; } }) } /** - * Handle Submition of energy bills overview form. Get form data and create + * Handle Submission of energy bills overview form. Get form data and create * a dictionary result with the formdata. Send result to backend. This will * be modified to receive all projected rows for ~20 years. */ -function billsOverviewFormSubmit(data) { +function billsOverviewFormCalculate(data) { const formData = new FormData(data); const result = {}; for (const [key, value] of formData.entries()) { @@ -274,6 +355,34 @@ function billsOverviewFormSubmit(data) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + if(res.payload.instance['err']) { + alert(res.payload.instance['msg']); + return false; + } + table = document.querySelector('#Energy-Bills-Overview') + var text = ``; + text += createEstimateModelForm(); + text += createBillsOverviewTable(res.payload.instance) + table.innerHTML = text; + }); + return false; +} + +function billsOverviewFormSubmit(form) { + const formData = new FormData(form); + const result = {}; + for (const [key, value] of formData.entries()) { + result[key] = value; + } + request(`bills-overview/`, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) }); return false; } @@ -314,7 +423,7 @@ function createCustomerPreferenceTable(instance) { var expectedPayback = instance['expected_payback']; var expectedNetNOIDSCR = instance['expected_net_noi_dscr'] } - var text = ` + var text = `
diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 37ca0fd0e47eceb7f6cc6a147240c1adcfaae244..f99a8b55dfb0195e5a6c5bb391651f3a96535323 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -4,7 +4,9 @@ from django.http import JsonResponse from django.db import connections from django.views import View -from .models import FinancingOverview, Bills, BillsOverview, CustomerPreference +from bpfin.utilbills.bill_backend_call import bill_prior_proj_rough_annual + +from .models import FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm from .forms import BlocNoteHeaderForm @@ -289,29 +291,101 @@ class BillsTable(View): return JsonResponse({'result': result, 'present': present}) -def get_total_charge(obj): - """Get total utility charge. +def get_total_charge(utility_type, analysis_date, building_id): + """Get annual total utility charge. + + Obtain annual charge for a utility for every year from the Pro Forma start year till end year. Make calls to bpfin + to perform the tasks. + + Args: + utility_type: Which utility type. + analysis_date: Dictionary containing the Pro Forma start date and duration. + building_id: id of the building. + + Returns: + annual_bill: Dictionary with year as keys and the annual charge for that year as value. + """ + bills_object = Bills.objects.filter( + building_id=building_id, + utility_type=utility_type, + ) + + if bills_object: + raw_bill = {} + raw_bill['utility_type'] = utility_type + raw_bill['date_from'] = [] + raw_bill['date_to'] = [] + raw_bill['charge'] = [] + raw_bill['usage'] = [] + + for bill in bills_object: + raw_bill['date_from'].append(bill.date_from) + raw_bill['date_to'].append(bill.date_to) + raw_bill['usage'].append(float(bill.usage)) + raw_bill['charge'].append(float(bill.charge)) + annual_bill = bill_prior_proj_rough_annual(raw_bill, analysis_date) + else: + annual_bill = {} + objs = BillsOverview.objects.filter( + building_id=building_id, + ) + if objs: + for obj in objs: + store_year = str(obj.year) + if utility_type == 'electricity': + annual_bill[store_year] = obj.electricity + elif utility_type == 'gas': + annual_bill[store_year] = obj.gas + elif utility_type == 'oil': + annual_bill[store_year] = obj.oil + elif utility_type == 'water': + annual_bill[store_year] = obj.water + else: + for year in range(analysis_date['proforma_duration']): + store_year = str(analysis_date['proforma_start'].year + year) + annual_bill[store_year] = 0 + for year in annual_bill: + annual_bill[year] = float("{0:.2f}".format(annual_bill[year])) + annual_bill = {str(key): value for key, value in annual_bill.items()} + return annual_bill + + +def get_if_user_input(building_id): + """Check if utility charge input was user or from bills. - Obtain the total charge for a utility. This function will be replaced by - finance team logic. + Check if the utility charge obtained from bills stored or by user input. Query bills database to check if an + entry has been made for this building. Args: - object: Model object which contains the charge value. + building_id: id of the building. Returns: - total: The sum of all charges for the utility. + e_user, g_user, o_user, w_user: Boolean for each utility. """ - total = 0 - for row in obj: - total += row.charge - return total + electricity_obj = Bills.objects.filter( + building_id=building_id, + utility_type='electricity', + ) + gas_obj = Bills.objects.filter( + building_id=building_id, + utility_type='gas', + ) + oil_obj = Bills.objects.filter( + building_id=building_id, + utility_type='oil', + ) + water_obj = Bills.objects.filter( + building_id=building_id, + utility_type='water', + ) + return not electricity_obj, not gas_obj, not oil_obj, not water_obj class BillsOverviewView(View): """Generate Energy Bills Overview table. - Generate energy bills overview table from the energy bills data. If bill not - available, take user input for calculation purposes. + Generate energy bills overview table from the energy bills data. If bill + not available, take user input for calculation purposes. """ model_bills_overview = BillsOverview @@ -321,9 +395,10 @@ class BillsOverviewView(View): def get(self, request, building_id): """Handle HTTP GET request. - Generate energy bills overview table with bills present in the - database. If a certain utility bill is absent, allow user to input - data. + Generate energy bills overview table with bills present in the database. If a certain utility bill is absent, + allow user to input value manually. Call bpfin functions to project bill charges from the Pro Forma start date + for the Pro Forma period. Store projected values as well as manually inputs. On page load, must fetch actual + and projected values. Args: request: HTTP GET request. @@ -333,124 +408,134 @@ class BillsOverviewView(View): JsonResponse: Returns instance, which is a dictionary containing all the data to be filled in the table. """ - instance = self.get_instance(building_id) - return JsonResponse({'instance': instance}) + objs = self.model_bills_overview.objects.filter(building_id=building_id) + result = {} + total_annual_charge = [] + if objs: + result['present'] = True + else: + result['present'] = False + result['electricity'] = {} + result['gas'] = {} + result['oil'] = {} + result['water'] = {} + result['electricity_user'] = True + result['gas_user'] = True + result['oil_user'] = True + result['water_user'] = True + for obj in objs: + store_year = str(obj.year) + result['electricity'][store_year] = float("{0:.2f}".format(obj.electricity)) + result['gas'][store_year] = float("{0:.2f}".format(obj.gas)) + result['oil'][store_year] = float("{0:.2f}".format(obj.oil)) + result['water'][store_year] = float("{0:.2f}".format(obj.water)) + result['electricity_user'] = obj.electricity_is_user_input + result['gas_user'] = obj.gas_is_user_input + result['oil_user'] = obj.oil_is_user_input + result['water_user'] = obj.water_is_user_input + total_annual_charge.append(obj.electricity + obj.gas + obj.oil + obj.water) + result['total_annual_charge'] = total_annual_charge + + return JsonResponse({'instance': result}) - def get_instance(self, building_id): - """Create dictionary instance. + def put(self, request, building_id): + """Handle HTTP PUT request. - This dictionary is an instance of the data present in the database. It - contains the annual utility charge(for now, this will be replaced by - value obtained from finance logic), Pro Forma start year and flags - indicating if a utility is present in the database. This is to tell the - frontend if an input has to be taken or not. + Take annual utility charge and projection option from frontend and project bills for given Pro Forma start + date and duration. Pass this value as a dictionary to the frontend. Args: + request: HTTP PUT request. building_id: id of the building. Returns: - instance: A dictionary containing the annual utility charge and a - flag indicating if utility bill present or not. + JsonResponse: Returns new instance with the updated values. """ - instance = {} - - try: - obj = FinancingOverview.objects.get(building_id=building_id) - date = obj.pro_forma_start_date - year = date.year - except: - year = 'TBD' - - utility_objects = {} - for util in self.utility: - utility_objects[util] = self.model_bills.objects.filter( + put = json.loads(request.body.decode()) + estimation_algorithm_obj = EstimationAlgorithm.objects.filter(building_id=building_id) + if estimation_algorithm_obj: + estimation_algorithm_obj.update( building_id=building_id, - utility_type=util - ) - - for util in self.utility: - instance[util] = 0 - instance[util+'_is_user_input'] = True - - for util in self.utility: - if utility_objects[util]: - instance[util] = get_total_charge( - utility_objects[util] - ) - instance[util+'_is_user_input'] = False - else: - try: - bills_overview_obj = self.model_bills_overview.objects.get( - building_id=building_id - ) - if util == 'electricity': - instance[util] = bills_overview_obj.electricity - elif util == 'gas': - instance[util] = bills_overview_obj.gas - elif util == 'oil': - instance[util] = bills_overview_obj.oil - elif util == 'water': - instance[util] = bills_overview_obj.water - except: - instance[util] = 0 - instance['year'] = year - - return instance + algorithm=put['Estimation Model'], + ) + else: + EstimationAlgorithm.objects.create( + building_id=building_id, + algorithm=put['Estimation Model'], + ) + projected_bills = {} + analysis_date = {} + pro_forma_object = get_model_object(FinancingOverview, building_id) + if not pro_forma_object: + projected_bills['err'] = True + projected_bills['msg'] = 'Please fill in bill projection date, period and funds form' + return JsonResponse({'instance': projected_bills}) + analysis_date['proforma_start'] = pro_forma_object.pro_forma_start_date + analysis_date['proforma_duration'] = int(pro_forma_object.pro_forma_duration) + if put['Estimation Model'] == 'Rough Estimation': + projected_bills['err'] = False + for util in self.utility: + projected_bills[util] = get_total_charge(util, analysis_date, building_id) + e_user, g_user, o_user, w_user = get_if_user_input(building_id) + projected_bills['electricity_user'] = e_user + projected_bills['gas_user'] = g_user + projected_bills['oil_user'] = o_user + projected_bills['water_user'] = w_user + total_annual_charge = [] + for year in range(analysis_date['proforma_duration']): + total = 0 + store_year = str(analysis_date['proforma_start'].year + year) + total += float("{0:.2f}".format(projected_bills['electricity'][store_year])) + total += float("{0:.2f}".format(projected_bills['gas'][store_year])) + total += float("{0:.2f}".format(projected_bills['oil'][store_year])) + total += float("{0:.2f}".format(projected_bills['water'][store_year])) + total_annual_charge.append(total) + projected_bills['total_annual_charge'] = total_annual_charge + else: + projected_bills['err'] = True + projected_bills['msg'] = 'Only Rough Estimation available at this point' + return JsonResponse({'instance': projected_bills}) - def put(self, request, building_id): - """Handle HTTP PUT request. + def post(self, request, building_id): + """Handle HTTP POST request. - Take annual utility charge and projection option from frontend and - store in the database. This will be modified to call the finance - projection fuction. + Get annual utility charge for the Pro Forma duration from Pro Forma start date and store in the database. + If data for this building is already present, they are deleted before storing new data. Args: - request: HTTP PUT request. + request: HTTP POST request. building_id: id of the building. Returns: - JsonResponse: Returns new instance with the updated values. + JsonResponse: A dictionary with status OK. """ - put = json.loads(request.body.decode()) - - entry = self.model_bills_overview.objects.filter( - building_id=building_id - ) - - instance = self.get_instance(building_id) - - if entry: - entry.update( + post = json.loads(request.body.decode()) + self.model_bills_overview.objects.filter(building_id=building_id).delete() + analysis_date = {} + e_user, g_user, o_user, w_user = get_if_user_input(building_id) + financing_overview_obj = FinancingOverview.objects.get(building_id=building_id) + analysis_date['proforma_start'] = financing_overview_obj.pro_forma_start_date + analysis_date['proforma_duration'] = int(financing_overview_obj.pro_forma_duration) + for i in range(analysis_date['proforma_duration']): + store_year = str(analysis_date['proforma_start'].year + i) + e_search = 'electricity-value-' + store_year + g_search = 'gas-value-' + store_year + o_search = 'oil-value-' + store_year + w_search = 'water-value-' + store_year + self.model_bills_overview.objects.create( building_id=building_id, - year=instance['year'], - electricity=put['electricity-value'], - electricity_is_user_input=instance['electricity_is_user_input'], - gas=put['gas-value'], - gas_is_user_input=instance['gas_is_user_input'], - oil=put['oil-value'], - oil_is_user_input=instance['oil_is_user_input'], - water=put['water-value'], - water_is_user_input=instance['water_is_user_input'], - estimation_algorithm=put['Estimation Model'] - ) - else: - entry = self.model_bills_overview.objects.create( - building_id=building_id, - year=instance['year'], - electricity=put['electricity-value'], - electricity_is_user_input=instance['electricity_is_user_input'], - gas=put['gas-value'], - gas_is_user_input=instance['gas_is_user_input'], - oil=put['oil-value'], - oil_is_user_input=instance['oil_is_user_input'], - water=put['water-value'], - water_is_user_input=instance['water_is_user_input'], - estimation_algorithm=put['Estimation Model'] + year=int(store_year), + electricity=post[e_search], + gas=post[g_search], + oil=post[o_search], + water=post[w_search], + electricity_is_user_input=e_user, + gas_is_user_input=g_user, + oil_is_user_input=o_user, + water_is_user_input=w_user ) - new_instance = self.get_instance(building_id) - - return JsonResponse({'status': new_instance}) + return JsonResponse({'status': 'OK'}) class CustomerPreferenceView(View):
Preference