diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 3622a0acd2c502dcf349394d493351735ba01399..fd36648b3eb45d838d4e142ae334b5c9b3094632 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -91,10 +91,10 @@ function billProjectionDatesForm(form) { var proFormaDuration = document.querySelector('#pro-forma-duration-input').value; var analysisDate = document.querySelector('#analysis-date-input').value; var fund = document.querySelector('#id_fund-select').value; - var anticipatedConstructionStarDate = document.querySelector('#anticipated-construction-start-date-input').value; + var anticipatedConstructionStartDate = document.querySelector('#anticipated-construction-start-date-input').value; var anticipatedCommissioningDate = document.querySelector('#anticipated-commissioning-date-input').value; var anticipatedConstructionPeriod = document.querySelector('#anticipated-construction-period-input').value; - startYear = anticipatedConstructionStarDate.split('-') + startYear = anticipatedConstructionStartDate.split('-') endDate = anticipatedCommissioningDate.split('-'); var anticipatedConstructionStartYear = startYear[0]; var anticipatedConstructionStartMonth = startYear[1]; @@ -102,7 +102,7 @@ function billProjectionDatesForm(form) { var anticipatedCommissioningStartYear = endDate[0]; var anticipatedCommissioningStartMonth = endDate[1]; var anticipatedCommissioningStartDay = endDate[2]; - var anticipatedConstructionStarDateDict = { + var anticipatedConstructionStartDateDict = { 'day': anticipatedConstructionStartDay, 'month': anticipatedConstructionStartMonth, 'year': anticipatedConstructionStartYear, @@ -112,57 +112,80 @@ function billProjectionDatesForm(form) { 'month': anticipatedCommissioningStartMonth, 'year': anticipatedCommissioningStartYear, } - // Ensure that commissioning date is after start date. - var validDate = validateDate(anticipatedConstructionStarDateDict, anticipatedCommissioningDateDict); - if (!validDate) { - alert("Anticipated Commissioning date has to be after Anticipated Construction start date"); + var proFormaStartYear = Number(proFormaStartDate.split('-')[0]); + var isProFormaDateCorrect = true; + + /** + * Usually proforma start year is 3 years before current year. If the entered date is less than 3, confirm with + * user if that value is correct. + */ + if (todaysDate.year - proFormaStartYear < 3) { + isProFormaDateCorrect = confirm('ARE YOU SURE THE PROFORMA DATE IS CORRECT?'); } - else { - result = { - 'pro_forma_start_date': proFormaStartDate, - 'pro_forma_duration': proFormaDuration, - 'analysis_date': analysisDate, - 'fund': fund, - 'anticipated_construction_start_date': anticipatedConstructionStarDate, - 'anticipated_commissioning_date': anticipatedCommissioningDate, - 'anticipated_construction_period': anticipatedConstructionPeriod, + if (isProFormaDateCorrect) { + // Ensure that commissioning date is after start date. + var validDate = validateDate(anticipatedConstructionStartDateDict, anticipatedCommissioningDateDict); + if (!validDate) { + document.querySelector('#pro-forma-form-save-msg').innerHTML = ` + + Anticipated Commissioning date has to be after Anticipated Construction start date + + `; } - request('finance-overview/', { - method: 'PUT', - credentials: 'same-origin', - body: JSON.stringify(result), - headers: new Headers({ - 'Content-Type': 'application/json', - 'X-CSRFToken': Cookies.get('csrftoken') - }) - }).then(res => { - // Display response message. Add that loan options changed if fund was changed. Reset didFundChange to false. - responseMessage = res.payload.msg; - if (didFundChange) { - responseMessage += ' Loan Options table is reloded.'; - } - var resMsg = document.querySelector('#pro-forma-form-save-msg'); - resMsg.innerHTML = `${responseMessage}`; - /** - * delete-loan-options request to the backend to delete loan options for this building id if previosly stored. - * This ensures that the loan options table is up to date with the latest fund. Clear the loan options table - * and reload with new fund's default loan options. - */ - if (didFundChange) { - didFundChange = false; - request(`loan-options/?loans=delete-loan-options`, { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - }).then(res => { - var tableBody = document.querySelector('#loan-options-table tbody'); - tableBody.innerHTML = ``; - }); + else { + result = { + 'pro_forma_start_date': proFormaStartDate, + 'pro_forma_duration': proFormaDuration, + 'analysis_date': analysisDate, + 'fund': fund, + 'anticipated_construction_start_date': anticipatedConstructionStartDate, + 'anticipated_commissioning_date': anticipatedCommissioningDate, + 'anticipated_construction_period': anticipatedConstructionPeriod, } - getLoanOptionsTable(); - }); + request('finance-overview/', { + method: 'PUT', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) + }).then(res => { + if (!res.payload.err) { + // Display response message. Add that loan options changed if fund was changed. Reset didFundChange to false. + responseMessage = 'Saved'; + if (didFundChange) { + responseMessage += ' Loan Options table is reloaded.'; + } + var resMsg = document.querySelector('#pro-forma-form-save-msg'); + resMsg.innerHTML = `${responseMessage}`; + /** + * delete-loan-options request to the backend to delete loan options for this building id if previosly stored. + * This ensures that the loan options table is up to date with the latest fund. Clear the loan options table + * and reload with new fund's default loan options. + */ + if (didFundChange) { + didFundChange = false; + request(`loan-options/?loans=delete-loan-options`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + var tableBody = document.querySelector('#loan-options-table tbody'); + tableBody.innerHTML = ``; + }); + } + getLoanOptionsTable(); + } + else { + // Display error message. + var resMsg = document.querySelector('#pro-forma-form-save-msg'); + resMsg.innerHTML = `${res.payload.err}`; + } + }); + } } return false; } @@ -289,7 +312,7 @@ function createEstimateModelForm() { var estimateModelForm = ``; estimateModelForm += `
-
`; +
`; estimateModelForm += estimationDropDownBox(); estimateModelForm += createCalculateButton(); estimateModelForm += ` @@ -421,7 +444,7 @@ function createBillsOverviewRows(data) { function createCalculateButton() { text = `
- +
`; return text; @@ -431,7 +454,7 @@ function createCalculateButton() { function createSubmitButton() { text = `
- +
`; return text; @@ -482,7 +505,7 @@ function billsOverviewFormCalculate(data) { }) }).then(res => { if (res.payload.instance['err']) { - alert(res.payload.instance['msg']); + alert(res.payload.instance['err']); return false; } table = document.querySelector('#Energy-Bills-Overview') @@ -494,12 +517,23 @@ function billsOverviewFormCalculate(data) { return false; } +// Watch bills overview table to display/erase error/success messages. +var billsOverview = document.querySelector('#Energy-Bills-Overview'); +billsOverview.onchange = function() { + document.querySelector('#bills-overview-warning-message').innerHTML = ''; +} + function billsOverviewFormSubmit(form) { const formData = new FormData(form); const result = {}; for (const [key, value] of formData.entries()) { result[key] = value; } + // Check the estimation model. Since only rough estimation is implemented, allow only that. + if (!(document.querySelector('#estimation-model').value === 'Rough Estimation')) { + alert('Please recalculate the projection with rough estimation and try to save again'); + return false; + } request(`bills-overview/`, { method: 'POST', credentials: 'same-origin', @@ -508,10 +542,42 @@ function billsOverviewFormSubmit(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + // Display error/success message. + var message = ''; + if (!res.payload.err) { + message = 'Saved'; + } + else { + message = res.payload.err; + } + document.querySelector('#bills-overview-warning-message').innerHTML = ` + ${message} + `; + // Update the total once manual inputs are saved. + var totalCharge = [] + var billsOverviewTable = document.querySelector('#table-bills-overview tbody'); + var columnLength = billsOverviewTable.rows[0].cells.length - 2; + for (var cellIndex = 2; cellIndex < columnLength; cellIndex++) { + val = 0; + for(var rowIndex = 0; rowIndex < 4; rowIndex++) { + val += Number(billsOverviewTable.rows[rowIndex].cells[cellIndex].children[0].value); + } + totalCharge.push(val); + } + for (var cellIndex = 2; cellIndex < columnLength; cellIndex++) { + billsOverviewTable.rows[4].cells[cellIndex].innerHTML = totalCharge[cellIndex - 2]; + } }); return false; } +// Watch customer preference to display/erase error/success mesaages. +var customerPreference = document.querySelector('#Customer-Preference'); +customerPreference.onchange = function() { + document.querySelector('#customer-preference-warning-message').innerHTML = ''; +} + /** * Handle customer preference form submit. Make a HTTP PUT request. */ @@ -530,6 +596,12 @@ function customerPreferenceForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + if (!res.payload.err) { + document.querySelector('#customer-preference-warning-message').innerHTML = ` + Saved + `; + } }); return false; } @@ -543,7 +615,7 @@ function createCustomerPreferenceTable(instance) { var expectedPayback = 999; var expectedNetNOIDSCR = 1.15 - if (instance.status) { + if (instance) { var downpayment = instance['downpayment']; var expectedPayback = instance['expected_payback']; var expectedNetNOIDSCR = instance['expected_net_noi_dscr'] @@ -559,15 +631,15 @@ function createCustomerPreferenceTable(instance) { Affordable Downpayment - + Expected Payback - Months + Months Expected Net NOI DSCR - + @@ -632,7 +704,7 @@ function addLiabilitiesRow(lender, service, term, date) { `; cell = row.insertCell(4); cell.innerHTML = ` - + `; cell = row.insertCell(5); cell.innerHTML = ` @@ -657,6 +729,11 @@ function deleteLiabilitiesRow(rowIndex) { return false; } +var liabilitiesForm = document.querySelector('#Liabilities'); +liabilitiesForm.onchange = function() { + document.querySelector('#liabilities-warning-message').innerHTML = ''; +} + /**HTTP PUT request with the user inputs for Mortgage and Liability. */ function liabilitiesSubmitForm(form) { table = document.querySelector('#liabilities-table'); @@ -689,6 +766,12 @@ function liabilitiesSubmitForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + if (!res.payload.err) { + document.querySelector('#liabilities-warning-message').innerHTML = ` + Saved! + `; + } }); return false; } @@ -722,7 +805,7 @@ function getCashBalanceForm() { /**Load cash balance table with initial values. Takes response from GET request to construct the table. */ function loadCashBalanceTable(instance) { table = document.querySelector('#cash-balance-table'); - if (instance.status) { + if (instance) { for (var record=0; record < instance.result.length; record++) { balanceAmount = Number(instance.result[record].balance_amount); date = instance.result[record].date; @@ -731,7 +814,7 @@ function loadCashBalanceTable(instance) { } } else { - addCashBalanceRow(0, '', false); + addCashBalanceRow('', '', false); } } @@ -758,6 +841,11 @@ function deleteCashBalanceRow(row) { var result = confirm('ARE YOU SURE YOU WANT TO DELETE THIS ROW?'); if (result) { table = document.querySelector('#cash-balance-table'); + var rowCount = table.rows.length; + if (rowCount === 2) { + alert('YOU MUST HAVE ATLEAST ONE CASH BALANCE ENTRY!'); + return false; + } table.deleteRow(row); for (rowInd = 1; rowInd < table.rows.length; rowInd++) { row = table.rows.item(rowInd).cells; @@ -779,11 +867,11 @@ function addCashBalanceRow(balance, date, isFromBalanceSheet) { cell.innerHTML = `${rowCount + 1}`; cell = row.insertCell(1); cell.innerHTML = ` - + `; cell = row.insertCell(2); cell.innerHTML = ` - + `; cell = row.insertCell(3); cell.innerHTML = getCashBalanceCheckBox(isFromBalanceSheet); @@ -794,6 +882,12 @@ function addCashBalanceRow(balance, date, isFromBalanceSheet) { return false; } +// Watch cash balance to display/erase error/success messages. +var cashBalanceForm = document.querySelector('#cash-balance-form'); +cashBalanceForm.onchange = function() { + document.querySelector('#cash-balance-warning-message').innerHTML = ''; +} + /**Make PUT request with data from the cash balance table. */ function submitCashBalanceForm(form) { var myResult = []; @@ -810,7 +904,9 @@ function submitCashBalanceForm(form) { 'year': Number(dateSplit[0]), } if (!validateDate(dateDict, todaysDate)) { - alert('Statement Date cannot be in the future'); + document.querySelector('#cash-balance-warning-message').innerHTML = ` + Statement date cannot be in the future! + `; return false; } record['statement-date'] = date; @@ -831,6 +927,12 @@ function submitCashBalanceForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + if (!res.payload.err) { + document.querySelector('#cash-balance-warning-message').innerHTML = ` + Saved + `; + } }); return false; } @@ -853,9 +955,9 @@ function loadIncomeStatemenHeading() { heading.innerHTML = ` Year - - - + + + Next Year Average @@ -870,17 +972,17 @@ function loadIncomeStatemenBody() { body.innerHTML = ` Revenue - - - + + + Utility Expense - - - + + + @@ -934,9 +1036,9 @@ function loadIncomeStatemenBody() { Non-Utility Operating Expense - - - + + + @@ -976,7 +1078,7 @@ function loadIncomeStatementDropdownBox(cagr) { var text = ``; text += ` Please select Growth Rate - `; @@ -1036,6 +1138,11 @@ function getIncomeStatementTable() { }); } +var incomeStatementForm = document.querySelector('#income-statement-form'); +incomeStatementForm.onchange = function() { + document.querySelector('#income-statement-error-msg').innerHTML = ``; +} + /** * Fire up when the calculate button is hit. Get all the input data from the table and the growth rate value * and make HTTP PUT request to the backend. Update the table with the calculated data received from the backend. @@ -1065,65 +1172,75 @@ function onCalculateIncomeStatement(data) { 'X-CSRFToken': Cookies.get('csrftoken') }) }).then(res => { - const REVENUE_ROW = 0; - const UTILITY_EXPENSE_ROW = 1; - const ENERGY_EXPENSE_ROW = 2; - const ELECTRICITY_ROW = 3; - const GAS_ROW = 4; - const OIL_ROW = 5; - const WATER_ROW = 6; - const OTHER_UTILITY_EXPENSE_ROW = 7; - const NON_UTILITY_OPERATING_EXPENSE_ROW = 8; - const NET_NON_ENERGY_EXPENSE_ROW = 9; - const TOTAL_EXPENSE_ROW = 10; - const NET_OPERATING_EXPENSE = 11; - var incomeStatementFull = res.payload.instance; - for (key in incomeStatementFull) { - cellIndex = Number(key)+1; - updateIncomeStatementTable(ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].energy_opex); - updateIncomeStatementTable(ELECTRICITY_ROW, cellIndex, incomeStatementFull[key].electricity_opex); - updateIncomeStatementTable(GAS_ROW, cellIndex, incomeStatementFull[key].gas_opex); - updateIncomeStatementTable(OIL_ROW, cellIndex, incomeStatementFull[key].oil_opex); - updateIncomeStatementTable(WATER_ROW, cellIndex, incomeStatementFull[key].water_opex); - updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].other_utility); - updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].net_non_energy_opex); - updateIncomeStatementTable(TOTAL_EXPENSE_ROW, cellIndex, incomeStatementFull[key].total_opex); - updateIncomeStatementTable(NET_OPERATING_EXPENSE, cellIndex, incomeStatementFull[key].noi); + if (!res.payload.err) { + const REVENUE_ROW = 0; + const UTILITY_EXPENSE_ROW = 1; + const ENERGY_EXPENSE_ROW = 2; + const ELECTRICITY_ROW = 3; + const GAS_ROW = 4; + const OIL_ROW = 5; + const WATER_ROW = 6; + const OTHER_UTILITY_EXPENSE_ROW = 7; + const NON_UTILITY_OPERATING_EXPENSE_ROW = 8; + const NET_NON_ENERGY_EXPENSE_ROW = 9; + const TOTAL_EXPENSE_ROW = 10; + const NET_OPERATING_EXPENSE = 11; + var incomeStatementFull = res.payload.instance; + for (key in incomeStatementFull) { + cellIndex = Number(key)+1; + updateIncomeStatementTable(ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].energy_opex); + updateIncomeStatementTable(ELECTRICITY_ROW, cellIndex, incomeStatementFull[key].electricity_opex); + updateIncomeStatementTable(GAS_ROW, cellIndex, incomeStatementFull[key].gas_opex); + updateIncomeStatementTable(OIL_ROW, cellIndex, incomeStatementFull[key].oil_opex); + updateIncomeStatementTable(WATER_ROW, cellIndex, incomeStatementFull[key].water_opex); + updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].other_utility); + updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].net_non_energy_opex); + updateIncomeStatementTable(TOTAL_EXPENSE_ROW, cellIndex, incomeStatementFull[key].total_opex); + updateIncomeStatementTable(NET_OPERATING_EXPENSE, cellIndex, incomeStatementFull[key].noi); + } + var future = res.payload.future; + const FUTURE_INDEX = 4; + heading = document.querySelector('#income-statement-table thead'); + heading.rows[0].cells[FUTURE_INDEX].innerHTML = future.year; + updateIncomeStatementTable(REVENUE_ROW,FUTURE_INDEX,future.revenue); + updateIncomeStatementTable(UTILITY_EXPENSE_ROW,FUTURE_INDEX,future.utility_expense); + updateIncomeStatementTable(ENERGY_EXPENSE_ROW,FUTURE_INDEX,future.energy_opex); + updateIncomeStatementTable(ELECTRICITY_ROW,FUTURE_INDEX,future.electricity_opex); + updateIncomeStatementTable(GAS_ROW,FUTURE_INDEX,future.gas_opex); + updateIncomeStatementTable(OIL_ROW,FUTURE_INDEX,future.oil_opex); + updateIncomeStatementTable(WATER_ROW,FUTURE_INDEX,future.water_opex); + updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW,FUTURE_INDEX,future.other_utility); + updateIncomeStatementTable(NON_UTILITY_OPERATING_EXPENSE_ROW,FUTURE_INDEX,future.non_utility_expense); + updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW,FUTURE_INDEX,future.net_non_energy_opex); + updateIncomeStatementTable(TOTAL_EXPENSE_ROW,FUTURE_INDEX,future.total_opex); + updateIncomeStatementTable(NET_OPERATING_EXPENSE,FUTURE_INDEX,future.noi); + + var averageDict = res.payload.average; + const AVERAGE_DICT_INDEX = 5; + updateIncomeStatementTable(REVENUE_ROW,AVERAGE_DICT_INDEX,averageDict.revenue); + updateIncomeStatementTable(UTILITY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.utility_expense); + updateIncomeStatementTable(ENERGY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.energy_opex); + updateIncomeStatementTable(ELECTRICITY_ROW,AVERAGE_DICT_INDEX,averageDict.electricity_opex); + updateIncomeStatementTable(GAS_ROW,AVERAGE_DICT_INDEX,averageDict.gas_opex); + updateIncomeStatementTable(OIL_ROW,AVERAGE_DICT_INDEX,averageDict.oil_opex); + updateIncomeStatementTable(WATER_ROW,AVERAGE_DICT_INDEX,averageDict.water_opex); + updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.other_utility); + updateIncomeStatementTable(NON_UTILITY_OPERATING_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.non_utility_expense); + updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.net_non_energy_opex); + updateIncomeStatementTable(TOTAL_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.total_opex); + updateIncomeStatementTable(NET_OPERATING_EXPENSE,AVERAGE_DICT_INDEX,averageDict.noi); + + var box = document.querySelector('#growth-rate option'); + box.innerHTML = `CAGR ${res.payload.CAGR}`; + document.querySelector('#income-statement-error-msg').innerHTML = ` + Saved Successfully. + `; + } + else { + document.querySelector('#income-statement-error-msg').innerHTML = ` + ${res.payload.err} + `; } - var future = res.payload.future; - const FUTURE_INDEX = 4; - heading = document.querySelector('#income-statement-table thead'); - heading.rows[0].cells[FUTURE_INDEX].innerHTML = future.year; - updateIncomeStatementTable(REVENUE_ROW,FUTURE_INDEX,future.revenue); - updateIncomeStatementTable(UTILITY_EXPENSE_ROW,FUTURE_INDEX,future.utility_expense); - updateIncomeStatementTable(ENERGY_EXPENSE_ROW,FUTURE_INDEX,future.energy_opex); - updateIncomeStatementTable(ELECTRICITY_ROW,FUTURE_INDEX,future.electricity_opex); - updateIncomeStatementTable(GAS_ROW,FUTURE_INDEX,future.gas_opex); - updateIncomeStatementTable(OIL_ROW,FUTURE_INDEX,future.oil_opex); - updateIncomeStatementTable(WATER_ROW,FUTURE_INDEX,future.water_opex); - updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW,FUTURE_INDEX,future.other_utility); - updateIncomeStatementTable(NON_UTILITY_OPERATING_EXPENSE_ROW,FUTURE_INDEX,future.non_utility_expense); - updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW,FUTURE_INDEX,future.net_non_energy_opex); - updateIncomeStatementTable(TOTAL_EXPENSE_ROW,FUTURE_INDEX,future.total_opex); - updateIncomeStatementTable(NET_OPERATING_EXPENSE,FUTURE_INDEX,future.noi); - - var averageDict = res.payload.average; - const AVERAGE_DICT_INDEX = 5; - updateIncomeStatementTable(REVENUE_ROW,AVERAGE_DICT_INDEX,averageDict.revenue); - updateIncomeStatementTable(UTILITY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.utility_expense); - updateIncomeStatementTable(ENERGY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.energy_opex); - updateIncomeStatementTable(ELECTRICITY_ROW,AVERAGE_DICT_INDEX,averageDict.electricity_opex); - updateIncomeStatementTable(GAS_ROW,AVERAGE_DICT_INDEX,averageDict.gas_opex); - updateIncomeStatementTable(OIL_ROW,AVERAGE_DICT_INDEX,averageDict.oil_opex); - updateIncomeStatementTable(WATER_ROW,AVERAGE_DICT_INDEX,averageDict.water_opex); - updateIncomeStatementTable(OTHER_UTILITY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.other_utility); - updateIncomeStatementTable(NON_UTILITY_OPERATING_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.non_utility_expense); - updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.net_non_energy_opex); - updateIncomeStatementTable(TOTAL_EXPENSE_ROW,AVERAGE_DICT_INDEX,averageDict.total_opex); - updateIncomeStatementTable(NET_OPERATING_EXPENSE,AVERAGE_DICT_INDEX,averageDict.noi); - - var box = document.querySelector('#growth-rate option'); - box.innerHTML = `CAGR ${res.payload.CAGR}`; }) return false; } @@ -1197,6 +1314,11 @@ function deleteLoanOptionsRow(row) { table = document.querySelector('#loan-options-table'); var result = confirm("Are you sure you want to delete this row?"); if (result) { + var rowCount = table.rows.length; + if (rowCount === 2) { + alert('YOU NEED ATLEAST ONE LOAN OPTION!'); + return false; + } table.deleteRow(row); for (rowInd = 1; rowInd < table.rows.length; rowInd++) { row = table.rows.item(rowInd).cells; @@ -1285,6 +1407,19 @@ function loadDefaultLoan(data) { }); } +var saveLoanOptions = document.querySelector('#loan-options-table-form'); +var NoiDscr = document.querySelector('#required-noi-dscr'); +var CashDscr = document.querySelector('#required-cash-dscr'); +NoiDscr.onchange = function() { + document.querySelector('#no-fund-msg').innerHTML = ''; +} +CashDscr.onchange = function() { + document.querySelector('#no-fund-msg').innerHTML = ''; +} +saveLoanOptions.onchange = function() { + document.querySelector('#no-fund-msg').innerHTML = ''; +} + /** * Save loan options table. Send the the rows in the form of a dictionary to the backend to store in the database. */ @@ -1320,6 +1455,12 @@ function submitLoanOptionsForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + if (!res.payload.err) { + document.querySelector('#no-fund-msg').innerHTML = ` + Saved + `; + } }); return false; } diff --git a/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html b/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html index a1e6de82bbf54cee31ddbe6129b0ea8325bd33e7..ffc70efa8c5e797e179e90d80d8098a46f1709c4 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html @@ -3,3 +3,5 @@
+
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/cashBalance.html b/blocnote/apps/financialInputs/templates/financialInputs/cashBalance.html index 0a3dd92df7e25e4b415eb5b6a02e2cf1602d5d19..e383f8b9ac5721bb0a5246a25db06d6d59e5aed9 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/cashBalance.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/cashBalance.html @@ -2,7 +2,11 @@ Cash Balance - +
+ +
+
+
@@ -17,6 +21,6 @@
-
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/customerPreference.html b/blocnote/apps/financialInputs/templates/financialInputs/customerPreference.html index ec52556d2192ba158afadf777a1482e381300846..c4188f40774be0558c320623349321393de7180f 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/customerPreference.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/customerPreference.html @@ -3,4 +3,6 @@
+
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/incomeStatement.html b/blocnote/apps/financialInputs/templates/financialInputs/incomeStatement.html index f1ce56c4d34af8a8def9b171f847819e387f9bdf..5b2d0c056ceae5100155965700ebacaa5a11c419 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/incomeStatement.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/incomeStatement.html @@ -2,11 +2,13 @@

Income Statements

-
+
-
- +
+ +
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/liabilities.html b/blocnote/apps/financialInputs/templates/financialInputs/liabilities.html index 5b0b087984be836d7fbfbca05f698bf08f6be2cf..1ab73ebeaa041d7a0e48e2e33f615b08f99a10ae 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/liabilities.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/liabilities.html @@ -3,6 +3,8 @@
+
+
diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index d6a160ad12a0f545d4329833fe5ff0d0fa7c1944..904837b2e967b54cab18a4155a9f5c5fdb1da70c 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -1,5 +1,6 @@ import json from datetime import date +import datetime from django.shortcuts import render from django.http import JsonResponse @@ -15,6 +16,16 @@ from .models import Fund, FinancingOverview, Bills, BillsOverview, CustomerPrefe from .models import Liabilities, CashBalance, IncomeStatement, LoanOptions, DefaultLoan, Lender, GrowthRate +def get_analysis_date(building_id): + """Fetch the proforma start date and duration from the database.""" + financing_overview_obj = FinancingOverview.objects.get(building_id=building_id) + analysis_date = { + 'proforma_start': financing_overview_obj.pro_forma_start_date, + 'proforma_duration': int(financing_overview_obj.pro_forma_duration), + } + return analysis_date + + def get_model_object(model, building_id): """Function that returns a model object. @@ -34,7 +45,7 @@ def get_model_object(model, building_id): return 0 -def change_date_format(date): +def change_date_format(dates): """Change date format. The CSV file contains date in MM/DD/YYYY format. The database needs it @@ -46,7 +57,7 @@ def change_date_format(date): Returns: new_date: The date in YYYY-MM-DD format. """ - date_split = date.split('/') + date_split = dates.split('/') new_date = date_split[2] + '-' + date_split[0] + '-' + date_split[1] return new_date @@ -137,9 +148,8 @@ class BlocNoteHeader(View): result = {} try: self.handle_form(put, building_id) - result['msg'] = 'The data was saved.' except: - result['msg'] = 'Sorry, the data could not be saved.' + result['err'] = 'Sorry, the data could not be saved.' return JsonResponse(result) def get(self, request, building_id): @@ -156,12 +166,15 @@ class BlocNoteHeader(View): JsonResponse: Result with the financing overview data. """ financing_overview_objs = self.model.objects.filter(building_id=building_id) + + # Fetch all the funds. funds_obj = Fund.objects.all() funds = [] for fund_obj in funds_obj: funds.append((fund_obj.id, fund_obj.Name)) result = {} - result['present'] = False + + # Check if the financing overview entry exists in the database. if financing_overview_objs: financing_overview_obj = financing_overview_objs[0] result = { @@ -478,16 +491,12 @@ class BillsOverviewView(View): 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' + projected_bills['err'] = '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) + analysis_date = get_analysis_date(building_id) 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) @@ -506,8 +515,7 @@ class BillsOverviewView(View): 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' + projected_bills['err'] = 'Only Rough Estimation available at this point' return JsonResponse({'instance': projected_bills}) def post(self, request, building_id): @@ -525,31 +533,30 @@ class BillsOverviewView(View): """ 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=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 - ) - - return JsonResponse({'status': 'OK'}) + analysis_date = get_analysis_date(building_id) + try: + 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=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 + ) + except: + return JsonResponse({'err': 'Could not save table!'}) + return JsonResponse({}) class CustomerPreferenceView(View): @@ -586,7 +593,7 @@ class CustomerPreferenceView(View): expected_payback=put['Expected-Payback'], expected_net_noi_dscr=put['Expected-NOI-DSCR'] ) - return JsonResponse({'status': 'ok'}) + return JsonResponse({}) def get(self, request, building_id): """Fetch customer preference table. @@ -611,10 +618,8 @@ class CustomerPreferenceView(View): instance['downpayment'] = obj.downpayment instance['expected_payback'] = obj.expected_payback instance['expected_net_noi_dscr'] = obj.expected_net_noi_dscr - instance['status'] = True - else: - instance['status'] = False - return JsonResponse({'instance': instance}) + return JsonResponse({'instance': instance}) + return JsonResponse({}) class LiabilitiesTable(View): @@ -671,7 +676,7 @@ class LiabilitiesTable(View): monthly_service=float(record['monthly-service']), remaining_term=float(record['remaining-term']), ) - return JsonResponse({'status': 'OK'}) + return JsonResponse({}) class CashBalanceView(View): @@ -690,18 +695,16 @@ class CashBalanceView(View): """ objs = self.model.objects.filter(building_id=building_id) instance = {} - instance['result'] = [] if objs: - instance['status'] = True + instance['result'] = [] for obj in objs: temp = {} temp['date'] = obj.statement_date temp['is_from_balance_sheet'] = obj.is_from_balance_sheet temp['balance_amount'] = obj.balance_amount instance['result'].append(temp) - else: - instance['status'] = False - return JsonResponse({'instance': instance}) + return JsonResponse({'instance': instance}) + return JsonResponse({}) def put(self, request, building_id): """Handle HTTP PUT request. @@ -726,7 +729,7 @@ class CashBalanceView(View): is_from_balance_sheet=record['is-from-balance-sheet'], balance_amount=record['balance'], ) - return JsonResponse({'instance': 'OK'}) + return JsonResponse({}) class IncomeStatementTable(View): @@ -738,7 +741,7 @@ class IncomeStatementTable(View): model = IncomeStatement - def convert_response_format(self, put): + def convert_response_format(self, put, pro_forma_year): """Convert PUT body format. Convert the data in HTTP PUT request into format accepted by bpfin. Obtain the growth rate as well. @@ -750,16 +753,24 @@ class IncomeStatementTable(View): result: Dictionary with year as key and income statement information dictionary as value. growth rate: The selected growth rate to project the future income statement. """ + now_year = int(datetime.datetime.now().year) result = {} - for record in put: - if 'growth-rate' not in record: - value = {} - value['revenue'] = float(record['revenue']) - value['utility_expense'] = float(record['utility-expense']) - value['non_utility_expense'] = float(record['non-utility-operating-expense']) - result[int(record['year'])] = value - else: - growth_rate = float(record['growth-rate']) + prev_year = int(put[0]['year']) + + # Make sure the historical years are before current year, after proforma start year and in increasing order. + for index in range(3): + cur_year = int(put[index]['year']) + if cur_year < pro_forma_year: + raise ValueError('A year before proforma start year was entered in column %s!' % (index + 1)) + if (index > 0 and prev_year >= cur_year) or (cur_year >= now_year): + raise ValueError('Year in column %s should be greater than year in column %s!' % ((index + 1), index)) + value = {} + value['revenue'] = float(put[index]['revenue']) + value['utility_expense'] = float(put[index]['utility-expense']) + value['non_utility_expense'] = float(put[index]['non-utility-operating-expense']) + result[int(put[index]['year'])] = value + prev_year = cur_year + growth_rate = float(put[3]['growth-rate']) return result, growth_rate def get_bills_overview_data(self, building_id): @@ -793,7 +804,9 @@ class IncomeStatementTable(View): 'oil': [oil, not obj.oil_is_user_input], 'water': [water, not obj.water_is_user_input], } - return bill_overview + return bill_overview + else: + raise ValueError def save(self, result, building_id): """Save income statement data in database. @@ -830,23 +843,33 @@ class IncomeStatementTable(View): """ put = json.loads(request.body.decode()) + # Get bills overview data from the database. + try: + bill_overview = self.get_bills_overview_data(building_id) + except: + return JsonResponse({'err': 'There are no bills projected for this building'}) + + # Get the analysis date from the financing overview model. + try: + analysis_date = get_analysis_date(building_id) + except: + return JsonResponse({'err': 'Please fill in the proforma information form at the top!'}) + # Convert the response body to the format bpfin accepts. - raw_income_statement_input, growth_rate = self.convert_response_format(put) + try: + raw_income_statement_input, growth_rate = self.convert_response_format( + put, + int(analysis_date['proforma_start'].year) + ) + except ValueError as err: + return JsonResponse({'err': err.args[0]}) + GrowthRate.objects.filter(building_id=building_id).delete() GrowthRate.objects.create( building_id=building_id, growth_rate=float("{0:.2f}".format(growth_rate)), ) - # Get bills overview data from the database. - bill_overview = self.get_bills_overview_data(building_id) - - # Get the analysis date from the financing overview model. - financing_overview_obj = FinancingOverview.objects.get(building_id=building_id) - analysis_date = { - 'proforma_start': financing_overview_obj.pro_forma_start_date, - 'proforma_duration': financing_overview_obj.pro_forma_duration, - } # organize_bill_overview takes in the bills overview and analysis date and fills in average values in places # where there are no values. bill_overview_organized = organize_bill_overview(bill_overview, analysis_date) @@ -902,7 +925,7 @@ class IncomeStatementTable(View): average_dict['total_opex'] = float("{0:.2f}".format(average_dict['total_opex'])) average_dict['noi'] = float("{0:.2f}".format(average_dict['noi'])) - cagr = float("{0:.2f}".format(cagr)) + cagr = ('= ' + "{0:.2f}".format(cagr * 100) + '%') return JsonResponse({'instance': result, 'future': result_dict, 'average': average_dict, 'CAGR': cagr}) def get(self, request, building_id): @@ -1084,4 +1107,4 @@ class LoanOptionsTable(View): duration=record['duration'], max_loan_amount=record['maximum-loan-amount'] ) - return JsonResponse({'status': 'OK'}) + return JsonResponse({}) diff --git a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js index 3028a456d95ea2414a775720889756fabc1143ce..f1de81d1b9b54a09011c66b646544de293f8a077 100644 --- a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js +++ b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js @@ -47,7 +47,15 @@ function costEstimationTableBody() { */ function deleteCostEstimationRow(rowIndex) { table = document.querySelector('#cost-estimation-table tbody'); - body.deleteRow(rowIndex-1); + var result = confirm('ARE YOU SURE YOU WANT TO DELETE THIS ROW?'); + if (result) { + var rowCount = table.rows.length; + if (rowCount === 1) { + alert('YOU NEED ATLEAST ONE ITEM WITH ITS COST ESTIMATION!'); + return false; + } + body.deleteRow(rowIndex-1); + } return false; } @@ -262,8 +270,12 @@ function addPERow(body, economicsOverview) { * Load the Energy Expense Savings Projection stacked bar graph. */ function loadGraph(res) { - var graph = document.querySelector('#myChart'); - graph.innerHTML = ``; + var graph = document.querySelector('#savings-schedule'); + graph.innerHTML = ` +

+

+ + `; var config = { type: 'bar', data: { diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index 78560ec24186b2d442d73e684e73291042649632..1531d439335858f2558162339e30897de3689d04 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -309,8 +309,8 @@ class Scenarios(View): raw_bill['date_to'].append(bill.date_to) raw_bill['usage'].append(float(bill.usage)) raw_bill['charge'].append(float(bill.charge)) - month_rough = prior_proj_rough_month(raw_bill, analysis_date) - prior_month_bill[utility_type] = month_rough + month_rough = prior_proj_rough_month(raw_bill, analysis_date) + prior_month_bill[utility_type] = month_rough return prior_month_bill def put(self, request, building_id):