+ `;
+ return false;
+}
+
+/**
+ * Delete a row from loan options table.
+ */
+function deleteLoanOptionsRow(row) {
+ table = document.querySelector('#loan-options-table');
+ var result = confirm("Are you sure you want to delete this row?");
+ if (result) {
+ table.deleteRow(row);
+ for (rowInd = 1; rowInd < table.rows.length; rowInd++) {
+ row = table.rows.item(rowInd).cells;
+ row.item(0).innerHTML = `Loan ${rowInd}`;
+ }
+ }
+ return false;
+}
+
+/**
+ * Generate the lender dropdown box given the list of lenders.
+ */
+function lenderOptions(lenderList, lender) {
+ var text = `
+
+ `;
+ return text;
+}
+
+/**
+ * Implement add row button.
+ */
+function addRowButtonFunction() {
+ request(`loan-options/?loans=default`, {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ }).then(res => {
+ var lenders = res.payload.lenders;
+ var defaultLoansList = res.payload.status;
+ var lender = defaultLoansList[0].lender;
+ var interestRate = defaultLoansList[0].interest_rate;
+ var duration = defaultLoansList[0].duration;
+ var maxLoanAmount = defaultLoansList[0].max_loan_amount;
+ addLoanOptionsRow(lenders, lender, interestRate, duration, maxLoanAmount);
+ })
+ return false;
+}
+
+/**
+ * Update a cell in the loan options table given the row and cell index and the value.
+ */
+function updateLoanOptionsCell(body, rowIndex, cellIndex, value) {
+ body.rows[rowIndex].cells[cellIndex].innerHTML = value;
+}
+
+/**
+ * request backend for list of lenders and default loan options.
+ */
+function loadDefaultLoan(data) {
+ body = document.querySelector('#loan-options-table tbody');
+ lender = body.rows[data-1].cells[1].children[0].value;
+ request(`loan-options/?loans=default`, {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ }).then(res => {
+ var defaultLoanList = res.payload.status;
+ for (var index = 0; index < defaultLoanList.length; index++) {
+ if (lender === defaultLoanList[index].lender) {
+ defaultLoan = defaultLoanList[index];
+ }
+ }
+ updateLoanOptionsCell(body, data - 1, 2, `%`);
+ updateLoanOptionsCell(body, data - 1, 3, `months`);
+ updateLoanOptionsCell(body, data - 1, 4, `$`);
+ });
+}
+
+/**
+ * Save loan options table. Send the the rows in the form of a dictionary to the backend to store in the database.
+ */
+function submitLoanOptionsForm(form) {
+ var body = document.querySelector('#loan-options-table tbody')
+ var rowCount = body.rows.length;
+ var result = {};
+ result['noi-dscr'] = document.querySelector('#required-noi-dscr-input').value
+ result['cash-dscr'] = document.querySelector('#required-cash-dscr-input').value
+ if (result['noi-dscr'] === '') {
+ alert('Please enter NOI DSCR');
+ return false;
+ }
+ if (result['cash-dscr'] === '') {
+ alert('Please enter Cash DSCR');
+ return false;
+ }
+ var instance = []
+ for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) {
+ var record = {};
+ record['lender'] = body.rows[rowIndex].cells[1].children[0].value;
+ record['interest-rate'] = body.rows[rowIndex].cells[2].children[0].value;
+ record['duration'] = body.rows[rowIndex].cells[3].children[0].value;
+ record['maximum-loan-amount'] = body.rows[rowIndex].cells[4].children[0].value;
+ instance.push(record);
+ }
+ result['instance'] = instance;
+ request('loan-options/', {
+ method: 'PUT',
+ credentials: 'same-origin',
+ body: JSON.stringify(result),
+ headers: new Headers({
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': Cookies.get('csrftoken')
+ })
+ });
+ return false;
+}
+
+/**
+ * HTTP GET request to get the loan options table if present. If not, get default loan options and lenders list.
+ */
+function getLoanOptionsTable() {
+ request(`loan-options/?loans=all-loans`, {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ }).then(res => {
+ if (res.payload.load) {
+ loadLoanOptionsColumnHeadings();
+ if (res.payload.status.length > 0) {
+ var lenders = res.payload.lenders;
+ var LoansList = res.payload.status;
+ if (res.payload.present) {
+ loadLoanOptionsTextBox('Required NOI DSCR', 'required-noi-dscr', res.payload.noi);
+ loadLoanOptionsTextBox('Required Cash DSCR', 'required-cash-dscr', res.payload.cash);
+ for (var index = 0; index < LoansList.length; index++) {
+ addLoanOptionsRow(lenders,LoansList[index].lender,LoansList[index].interest_rate,LoansList[index].duration,LoansList[index].max_loan_amount);
+ }
+ }
+ else {
+ loadLoanOptionsTextBox('Required NOI DSCR', 'required-noi-dscr', '');
+ loadLoanOptionsTextBox('Required Cash DSCR', 'required-cash-dscr', '');
+ addLoanOptionsRow(lenders,LoansList[0].lender,LoansList[0].interest_rate,LoansList[0].duration,LoansList[0].max_loan_amount);
+ }
+ }
+ }
+ });
+}
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html
index 1cd3039f5a1f30b7552c28f584dce1dd8877ee8f..11d2565f02f74eadccf89eee1e5ee029050a0211 100644
--- a/blocnote/apps/financialInputs/templates/financialInputs/index.html
+++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html
@@ -39,6 +39,11 @@
{% include "financialInputs/liabilities.html" %}
+
+
+ {% include "financialInputs/loanOptions.html" %}
+
+
{% include "financialInputs/customerPreference.html" %}
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html
new file mode 100644
index 0000000000000000000000000000000000000000..08bddccfd144d1b85e1d698fb9ea77ff4a45c8d1
--- /dev/null
+++ b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html
@@ -0,0 +1,29 @@
+
+
Loan Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py
index 8856aed8d065ed1e90ccf7034c71e7d213bb24e7..ce55b448c3ba52ec85c79e4b01602416bed513a1 100644
--- a/blocnote/apps/financialInputs/urls.py
+++ b/blocnote/apps/financialInputs/urls.py
@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^liabilities/$', views.LiabilitiesTable.as_view(), name='liabilities'),
url(r'^cash-balance/$', views.CashBalanceView.as_view(), name='cash_balance'),
url(r'^income-statement/$', views.IncomeStatementTable.as_view(), name='income_statement'),
+ url(r'^loan-options/$', views.LoanOptionsTable.as_view(), name='loan_options'),
]
diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py
index b909292d6b7fa60662cd5d1d2a0afe9e2234ed0a..905c7b25475ece6d45f4f31deebb47c602928b1d 100644
--- a/blocnote/apps/financialInputs/views.py
+++ b/blocnote/apps/financialInputs/views.py
@@ -9,7 +9,7 @@ from bpfin.utilbills.bill_backend_call import bill_prior_proj_rough_annual
from bpfin.financials.financial_lib import organize_bill_overview
from bpfin.financials.financial_lib import Income_Statement_Table
-from .models import FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm, Liabilities, CashBalance, IncomeStatement
+from .models import Fund, FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm, Liabilities, CashBalance, IncomeStatement, LoanOptions, DefaultLoan, Lender
from .forms import BlocNoteHeaderForm
@@ -952,3 +952,155 @@ class IncomeStatementTable(View):
result.append(record)
instance['result'] = result
return JsonResponse({'instance': instance})
+
+
+class LoanOptionsTable(View):
+ """Select loan options for the building."""
+
+ model = LoanOptions
+
+ def get_default_loans(self, default_loan_options_objs):
+ """Return the default loans given model object.
+
+ Args:
+ default_loan_options_objs: List of default loan objects for the stored fund.
+
+ Returns:
+ result: Dictionary with loan terms for the fund.
+ """
+ result = []
+ for obj in default_loan_options_objs:
+ record = {}
+ record['lender'] = obj.lender.name
+ record['interest_rate'] = obj.interest_rate
+ record['duration'] = obj.duration
+ record['max_loan_amount'] = obj.max_loan_amount
+ result.append(record)
+ return result
+
+ def get(self, request, building_id):
+ """Handle HTTP GET request.
+
+ Args:
+ request: HTTP GET request. Request has 3 options- delete-loan-options, default and all-loans.
+ building_id: id of the building.
+
+ Returns:
+ JsonResponse: response is a dictionary. Detailed explanation is provided below.
+ """
+ result = []
+ lenders = []
+ response = {}
+ # Obtain the type of request.
+ get_type = request.GET.get('loans')
+
+ # delete-loan-options request deletes existing loan options stored. This happens when the user changes the fund
+ # for the building. Since fund and lender loans go in pair, anything stored prior to this is not needed.
+ if get_type == 'delete-loan-options':
+ self.model.objects.filter(building_id=building_id).delete()
+ return JsonResponse(response)
+
+ # Fetch the fund selected for this building. If fund not selected yet, return load as false to indicate the
+ # frontend not to load the loan options table.
+ financing_overview_obj = FinancingOverview.objects.filter(
+ building_id=building_id,
+ )
+ if financing_overview_obj:
+ fund_id = financing_overview_obj[0].fund.id
+ else:
+ response = {
+ 'load': False,
+ }
+ return JsonResponse(response)
+
+ # Retreive the default loan options objects for the fund selected.
+ default_loan_options_objs = DefaultLoan.objects.filter(
+ fund=fund_id,
+ )
+
+ # Retrieve the list of lenders for the fund.
+ for obj in default_loan_options_objs:
+ lenders.append(obj.lender.name)
+
+ # default request sends the list of lenders and default loan terms to the frontend.
+ if get_type == 'default':
+ result = self.get_default_loans(default_loan_options_objs)
+ response = {
+ 'status': result,
+ 'lenders': lenders,
+ }
+ return JsonResponse(response)
+
+ # The other request is all-loans which fetches the loan options stored for this building id.
+ else:
+ loan_options_objs = self.model.objects.filter(
+ building_id=building_id,
+ )
+ if loan_options_objs:
+ financing_overview_obj = FinancingOverview.objects.get(
+ building_id=building_id,
+ )
+ noi_dscr = financing_overview_obj.required_noi_dscr
+ cash_dscr = financing_overview_obj.requrired_cash_dscr
+ for obj in loan_options_objs:
+ record = {}
+ record['lender'] = obj.lender.name
+ record['interest_rate'] = obj.interest_rate
+ record['duration'] = obj.duration
+ record['max_loan_amount'] = obj.max_loan_amount
+ result.append(record)
+
+ # load tells the frontend that a fund has been selected for this building and it must load the loan
+ # options table. present tells the frontend that loan options have previosly been saved and to load
+ # them. If present is not true, the frontend knows to load one of the default loan options.
+ response = {
+ 'load': True,
+ 'present': True,
+ 'status': result,
+ 'lenders': lenders,
+ 'noi': noi_dscr,
+ 'cash': cash_dscr,
+ }
+ return JsonResponse(response)
+ else:
+ result = self.get_default_loans(default_loan_options_objs)
+ response = {
+ 'load': True,
+ 'status': result,
+ 'lenders': lenders,
+ }
+ return JsonResponse(response)
+
+ def put(self, request, building_id):
+ """Handle HTTP PUT request.
+
+ fetch the fund from the database.Delete all existing loan options for the biulding and store the new data.
+
+ Args:
+ request: HTTP PUT request with the loan options rows in the body.
+ building_id: id of the building.
+
+ Returns:
+ JsonResponse: status OK.
+ """
+ put = json.loads(request.body.decode())
+ financing_overview_obj = FinancingOverview.objects.filter(
+ building_id=building_id,
+ )
+ financing_overview_obj.update(
+ required_noi_dscr=float(put['noi-dscr']),
+ requrired_cash_dscr=float(put['cash-dscr']),
+ )
+ self.model.objects.filter(building_id=building_id).delete()
+ for record in put['instance']:
+ lender_id = Lender.objects.filter(
+ name=record['lender'],
+ )[0].id
+ self.model.objects.create(
+ building_id=building_id,
+ lender_id=lender_id,
+ interest_rate=record['interest-rate'],
+ duration=record['duration'],
+ max_loan_amount=record['maximum-loan-amount']
+ )
+ return JsonResponse({'status': 'OK'})