From 7c852c80c9f5925f50f709c18bf144b7efedc562 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 27 Apr 2017 12:19:09 -0400 Subject: [PATCH 1/6] Loan loan options table column headers. --- .../static/financialInputs/scripts/app.js | 43 +++++++++++++++++++ .../templates/financialInputs/index.html | 3 ++ .../financialInputs/loanOptions.html | 28 ++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index c42b171..bffa92e 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -5,6 +5,7 @@ for (var utility_index in utilities) { } loadBillsOverview(); getIncomeStatementTable(); +loadLoanOptionsComponent(); getCustomerPreferenceTable(); getLiabilitiesTable(); getCashBalanceForm(); @@ -973,3 +974,45 @@ function updateIncomeStatementTable(rowIndex, cellIndex, value) { var body = document.querySelector('#income-statement-table-body'); body.rows[rowIndex].cells[cellIndex].innerHTML = value; } + +function loadLoanOptionsComponent() { + loadLoanOptionsTextBox('Required NOI DSCR', 'required-noi-dscr'); + loadLoanOptionsTextBox('Required Cash DSCR', 'required-cash-dscr'); + loadLoanOptionsColumnHeadings(); + loadLoanOptionsBody(); +} + +function loadLoanOptionsTextBox(field, id) { + inputDiv = document.querySelector('#'+id); + inputDiv.innerHTML = ` + ${field} + + `; +} + +function loadLoanOptionsColumnHeadings() { + head = document.querySelector('#loan-options-table-head'); + head.innerHTML = ` + + Loan Options + Lender + Interest Rate + Duration + Maximum Loan Amount + + `; +} + +function loadLoanOptionsBody() { + body = document.querySelector('#loan-options-table-body'); +} + +function lenderOptions(num) { + return ` + + `; +} diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index b3d50c3..b266a65 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -31,6 +31,9 @@
{% 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 0000000..de0f663 --- /dev/null +++ b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html @@ -0,0 +1,28 @@ +
+

Loan Options

+
+
+
+
+
+
+
+
+
+ Required Cash DSCR +
+
+
+
+
+
+
+ + + + + +
+
+
+
-- GitLab From 4076a297f9d6382011520551cf80e388bf2f3f33 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 27 Apr 2017 15:13:42 -0400 Subject: [PATCH 2/6] Implement add row function. --- .../static/financialInputs/scripts/app.js | 50 +++++++++++++++++-- .../financialInputs/loanOptions.html | 2 + 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index bffa92e..4817d6d 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -10,7 +10,7 @@ getCustomerPreferenceTable(); getLiabilitiesTable(); getCashBalanceForm(); var rowCounter = 0; - +var loanOptionsCounter = 0; /** * Handle submition of the header form. Validate commissioning date is greater * than construction start date. Create result dictionary containing the form @@ -979,7 +979,7 @@ function loadLoanOptionsComponent() { loadLoanOptionsTextBox('Required NOI DSCR', 'required-noi-dscr'); loadLoanOptionsTextBox('Required Cash DSCR', 'required-cash-dscr'); loadLoanOptionsColumnHeadings(); - loadLoanOptionsBody(); + addLoanOptionsRow(1,'',0,0,0) } function loadLoanOptionsTextBox(field, id) { @@ -999,17 +999,61 @@ function loadLoanOptionsColumnHeadings() { Interest Rate Duration Maximum Loan Amount + Option `; } function loadLoanOptionsBody() { body = document.querySelector('#loan-options-table-body'); + body.innerHTML = ` + + Loan 1 + ${lenderOptions(1)} + % + Months + $ + + `; +} + +function addLoanOptionsRow(lender, interestRate, duration, maxLoanAmount) { + body = document.querySelector('#loan-options-table-body') + var rowCount = body.rows.length; + var row = body.insertRow(rowCount); + var cell = row.insertCell(0); + cell.innerHTML = `Loan ${rowCount+1}`; + cell = row.insertCell(1); + cell.innerHTML = `${lenderOptions(loanOptionsCounter)}`; + cell = row.insertCell(2); + cell.innerHTML = `%`; + cell = row.insertCell(3); + cell.innerHTML = `months`; + cell = row.insertCell(4); + cell.innerHTML = `$`; + cell = row.insertCell(5); + cell.innerHTML = ` +
+ +
+ `; + loanOptionsCounter += 1; + return false; +} + +function deleteLoanOptionsRow(row) { + table = document.querySelector('#loan-options-table'); + 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; } function lenderOptions(num) { return ` - diff --git a/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html index de0f663..9de25f0 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html @@ -16,6 +16,7 @@
+
@@ -23,6 +24,7 @@
+
-- GitLab From 2111d22d547387739d99d2835c421bd360d7f67a Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Mon, 1 May 2017 09:11:21 -0400 Subject: [PATCH 3/6] Loan options component. Show Lenders in dropdown box based on the fund. Load default loan options when lender is chosen. Default loan options must be able to modify. Store rows in the database. Add and delete row functionality. --- blocnote/apps/financialInputs/models.py | 20 +++ .../static/financialInputs/scripts/app.js | 146 ++++++++++++++++-- .../financialInputs/loanOptions.html | 7 +- blocnote/apps/financialInputs/urls.py | 1 + blocnote/apps/financialInputs/views.py | 86 ++++++++++- 5 files changed, 241 insertions(+), 19 deletions(-) diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 6d6bd79..0a9f5f7 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -95,3 +95,23 @@ class IncomeStatement(models.Model): utility_expense = models.DecimalField(max_digits=10, decimal_places=2) other_utility_expense = models.DecimalField(max_digits=10, decimal_places=2) non_utility_operating_expense = models.DecimalField(max_digits=10, decimal_places=2) + + +class Lender(models.Model): + name = models.CharField(max_length=200) + + +class DefaultLoan(models.Model): + lender = models.ForeignKey(Lender, on_delete=models.CASCADE, blank=True, null=True) + fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) + interest_rate = models.DecimalField(max_digits=5, decimal_places=3) + duration = models.DecimalField(max_digits=3, decimal_places=0) + max_loan_amount = models.DecimalField(max_digits=10, decimal_places=2) + + +class LoanOptions(models.Model): + building_id = models.IntegerField() + lender = models.ForeignKey(Lender, on_delete=models.CASCADE, blank=True, null=True) + interest_rate = models.DecimalField(max_digits=5, decimal_places=3) + duration = models.DecimalField(max_digits=3, decimal_places=0) + max_loan_amount = 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 4817d6d..4ef14d0 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -9,6 +9,7 @@ loadLoanOptionsComponent(); getCustomerPreferenceTable(); getLiabilitiesTable(); getCashBalanceForm(); +getLoanOptionsTable(); var rowCounter = 0; var loanOptionsCounter = 0; /** @@ -37,6 +38,8 @@ function billProjectionDatesForm(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + getLoanOptionsTable(); }); } return false; @@ -976,17 +979,14 @@ function updateIncomeStatementTable(rowIndex, cellIndex, value) { } function loadLoanOptionsComponent() { - loadLoanOptionsTextBox('Required NOI DSCR', 'required-noi-dscr'); - loadLoanOptionsTextBox('Required Cash DSCR', 'required-cash-dscr'); - loadLoanOptionsColumnHeadings(); - addLoanOptionsRow(1,'',0,0,0) + } -function loadLoanOptionsTextBox(field, id) { +function loadLoanOptionsTextBox(field, id, value) { inputDiv = document.querySelector('#'+id); inputDiv.innerHTML = ` ${field} - + `; } @@ -1017,14 +1017,14 @@ function loadLoanOptionsBody() { `; } -function addLoanOptionsRow(lender, interestRate, duration, maxLoanAmount) { +function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAmount) { body = document.querySelector('#loan-options-table-body') var rowCount = body.rows.length; var row = body.insertRow(rowCount); var cell = row.insertCell(0); cell.innerHTML = `Loan ${rowCount+1}`; cell = row.insertCell(1); - cell.innerHTML = `${lenderOptions(loanOptionsCounter)}`; + cell.innerHTML = `${lenderOptions(lenderList, loanOptionsCounter, lender)}`; cell = row.insertCell(2); cell.innerHTML = `%`; cell = row.insertCell(3); @@ -1051,12 +1051,130 @@ function deleteLoanOptionsRow(row) { return false; } -function lenderOptions(num) { - return ` - + `; + var isSelected = ``; + for (var index = 0; index < lenderList.length; index++) { + if (lender === lenderList[index]) { + isSelected = `selected`; + } + else { + isSelected = ``; + } + text += ` + + `; + } + text += ` `; + return text; +} + +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; + addLoanOptionsRow(lenders,defaultLoansList[0].lender, defaultLoansList[0].interest_rate,defaultLoansList[0].duration,defaultLoansList[0].max_loan_amount); + }) + return false; +} + +function updateLoanOptionsCell(body, rowIndex, cellIndex, value) { + body.rows[rowIndex].cells[cellIndex].innerHTML = value; +} + +function loadDefaultLoan(data) { + body = document.querySelector('#loan-options-table-body'); + 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, `%`); + updateLoanOptionsCell(body, data - 1, 4, `%`); + }); +} + +function submitLoanOptionsForm(form) { + var formData = new FormData(form); + var body = document.querySelector('#loan-options-table-body') + 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; +} + +function getLoanOptionsTable() { + request(`loan-options/?loans=all-loans`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + 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/loanOptions.html b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html index 9de25f0..d1ced60 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/loanOptions.html @@ -9,22 +9,21 @@
- Required Cash DSCR
- -
+ +
- +
diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index 8856aed..ce55b44 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 8682082..f4ebc0b 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -11,7 +11,7 @@ from bpfin.financials.financial_lib import organize_bill_overview from bpfin.financials.income_statement_form_hist import form_income_statement_hist from bpfin.financials.income_statement_next import income_statement_next -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 @@ -806,3 +806,87 @@ class IncomeStatementTable(View): result.append(record) instance['result'] = result return JsonResponse({'instance': instance}) + + +class LoanOptionsTable(View): + model = LoanOptions + + def get_default_loans(self, default_loan_options_objs): + 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): + result = [] + lenders = [] + get_type = request.GET.get('loans') + print(get_type) + loan_options_objs = self.model.objects.filter( + building_id=building_id, + ) + financing_overview_obj = FinancingOverview.objects.filter( + building_id=building_id, + ) + if financing_overview_obj: + fund_id = financing_overview_obj[0].fund.id + else: + return JsonResponse({'status': []}) + default_loan_options_objs = DefaultLoan.objects.filter( + fund=fund_id, + ) + for obj in default_loan_options_objs: + lenders.append(obj.lender.name) + if get_type == 'default': + result = self.get_default_loans(default_loan_options_objs) + return JsonResponse({'status': result, 'lenders': lenders}) + else: + 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) + return JsonResponse({'present': True, 'status': result, 'lenders': lenders, 'noi': noi_dscr, 'cash': cash_dscr}) + else: + result = self.get_default_loans(default_loan_options_objs) + return JsonResponse({'status': result, 'lenders': lenders}) + + def put(self, request, building_id): + put = json.loads(request.body.decode()) + print(put) + financing_overview_obj = FinancingOverview.objects.filter( + building_id=building_id, + ) + print(financing_overview_obj) + 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']: + print(record) + lender_id = Lender.objects.filter( + name=record['lender'], + )[0].id + print(lender_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'}) -- GitLab From 34cda2be4d28d2f4f2a8accad4ceb9a60042bf9a Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 10:14:29 -0400 Subject: [PATCH 4/6] Update loan options table to make inputs number only and required. Remove redundant function. --- .../migrations/0003_auto_20170503_2200.py | 49 +++++++++++++++ .../static/financialInputs/scripts/app.js | 63 ++++++++----------- 2 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 blocnote/apps/financialInputs/migrations/0003_auto_20170503_2200.py diff --git a/blocnote/apps/financialInputs/migrations/0003_auto_20170503_2200.py b/blocnote/apps/financialInputs/migrations/0003_auto_20170503_2200.py new file mode 100644 index 0000000..867ed62 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0003_auto_20170503_2200.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-05-03 22:00 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0002_incomestatement'), + ] + + operations = [ + migrations.CreateModel( + name='DefaultLoan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('interest_rate', models.DecimalField(decimal_places=3, max_digits=5)), + ('duration', models.DecimalField(decimal_places=0, max_digits=3)), + ('max_loan_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('fund', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund')), + ], + ), + migrations.CreateModel( + name='Lender', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ], + ), + migrations.CreateModel( + name='LoanOptions', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('interest_rate', models.DecimalField(decimal_places=3, max_digits=5)), + ('duration', models.DecimalField(decimal_places=0, max_digits=3)), + ('max_loan_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('lender', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Lender')), + ], + ), + migrations.AddField( + model_name='defaultloan', + name='lender', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Lender'), + ), + ] diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 6b077db..2e065ac 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -4,13 +4,18 @@ for (var utility_index in utilities) { } loadBillsOverview(); getIncomeStatementTable(); -loadLoanOptionsComponent(); getCustomerPreferenceTable(); getLiabilitiesTable(); getCashBalanceForm(); getLoanOptionsTable(); var loanOptionsCounter = 0; + +var fund = document.querySelector('#id_fund'); +fund.onchange = function() { + getLoanOptionsTable(); +} + /** * Handle submition of the header form. Validate commissioning date is greater * than construction start date. Create result dictionary containing the form @@ -1056,20 +1061,16 @@ function updateIncomeStatementTable(rowIndex, cellIndex, value) { body.rows[rowIndex].cells[cellIndex].innerHTML = value; } -function loadLoanOptionsComponent() { - -} - function loadLoanOptionsTextBox(field, id, value) { inputDiv = document.querySelector('#'+id); inputDiv.innerHTML = ` ${field} - + `; } function loadLoanOptionsColumnHeadings() { - head = document.querySelector('#loan-options-table-head'); + head = document.querySelector('#loan-options-table thead'); head.innerHTML = ` Loan Options @@ -1082,33 +1083,20 @@ function loadLoanOptionsColumnHeadings() { `; } -function loadLoanOptionsBody() { - body = document.querySelector('#loan-options-table-body'); - body.innerHTML = ` - - Loan 1 - ${lenderOptions(1)} - % - Months - $ - - `; -} - function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAmount) { - body = document.querySelector('#loan-options-table-body') + body = document.querySelector('#loan-options-table tbody') var rowCount = body.rows.length; var row = body.insertRow(rowCount); var cell = row.insertCell(0); cell.innerHTML = `Loan ${rowCount+1}`; cell = row.insertCell(1); - cell.innerHTML = `${lenderOptions(lenderList, loanOptionsCounter, lender)}`; + cell.innerHTML = `${lenderOptions(lenderList, lender)}`; cell = row.insertCell(2); - cell.innerHTML = `%`; + cell.innerHTML = `%`; cell = row.insertCell(3); - cell.innerHTML = `months`; + cell.innerHTML = `months`; cell = row.insertCell(4); - cell.innerHTML = `$`; + cell.innerHTML = `$`; cell = row.insertCell(5); cell.innerHTML = `
@@ -1121,17 +1109,20 @@ function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAm function deleteLoanOptionsRow(row) { table = document.querySelector('#loan-options-table'); - table.deleteRow(row); - for (rowInd = 1; rowInd < table.rows.length; rowInd++) { - row = table.rows.item(rowInd).cells; - row.item(0).innerHTML = `Loan ${rowInd}`; + 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; } -function lenderOptions(lenderList, num, lender) { +function lenderOptions(lenderList, lender) { var text = ` - `; var isSelected = ``; for (var index = 0; index < lenderList.length; index++) { @@ -1171,7 +1162,7 @@ function updateLoanOptionsCell(body, rowIndex, cellIndex, value) { } function loadDefaultLoan(data) { - body = document.querySelector('#loan-options-table-body'); + body = document.querySelector('#loan-options-table tbody'); lender = body.rows[data-1].cells[1].children[0].value; request(`loan-options/?loans=default`, { method: 'GET', @@ -1186,15 +1177,15 @@ function loadDefaultLoan(data) { defaultLoan = defaultLoanList[index]; } } - updateLoanOptionsCell(body, data - 1, 2, `%`); - updateLoanOptionsCell(body, data - 1, 3, `%`); - updateLoanOptionsCell(body, data - 1, 4, `%`); + updateLoanOptionsCell(body, data - 1, 2, `%`); + updateLoanOptionsCell(body, data - 1, 3, `months`); + updateLoanOptionsCell(body, data - 1, 4, `$`); }); } function submitLoanOptionsForm(form) { var formData = new FormData(form); - var body = document.querySelector('#loan-options-table-body') + 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 -- GitLab From 7cbb4031e474778dc2b36f8302c13148ac8b3315 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 12:23:19 -0400 Subject: [PATCH 5/6] Fix forms.py to display selected fund correctly. Implement loan options table. If loan options not saved, loan empty table with a list of lenders for the fund and their default loan options. If loan options previosly stored, fetch and display that data. If fund is changed after initial selection, previous loan options will be deleted and new empty table is displayed. --- blocnote/apps/financialInputs/forms.py | 1 - .../static/financialInputs/scripts/app.js | 99 ++++++++++++++----- .../financialInputs/loanOptions.html | 4 +- blocnote/apps/financialInputs/views.py | 92 ++++++++++++++--- 4 files changed, 154 insertions(+), 42 deletions(-) diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py index 584eda4..64f7084 100644 --- a/blocnote/apps/financialInputs/forms.py +++ b/blocnote/apps/financialInputs/forms.py @@ -49,4 +49,3 @@ class BlocNoteHeaderForm(ModelForm): self.fields['fund'].widget.attrs.update({ 'class': 'custom-select' }) - self.initial['fund'] = 1 diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 6c62136..964c36c 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -15,13 +15,10 @@ getLiabilitiesTable(); getCashBalanceForm(); getLoanOptionsTable(); -var loanOptionsCounter = 0; - -var fund = document.querySelector('#id_fund'); -fund.onchange = function() { - getLoanOptionsTable(); -} - +/** + * The following 2 functions display a warning message saying if fund is changed, it will affect the loan options. + * This message is displayed when the mouse is over the fund select box. + */ var fund = document.querySelector('#id_fund'); fund.onmouseenter = function() { var errorDiv = document.querySelector('#show-error'); @@ -76,7 +73,22 @@ function billProjectionDatesForm(form) { 'X-CSRFToken': Cookies.get('csrftoken') }) }).then(res => { - getLoanOptionsTable(); + /** + * 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. + */ + 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(); + }); }); } return false; @@ -589,7 +601,6 @@ function liabilitiesSubmitForm(form) { 'year': Number(table.rows[rowInd].cells[4].children[2].value), } if (!validateDate(date, todaysDate)) { - console.log('Invalid date'); alert('Invalid date'); return false; } @@ -696,7 +707,7 @@ function addCashBalanceRow(balance, month, day, year, isFromBalanceSheet) { var rowCount = table.rows.length; var row = table.insertRow(rowCount); var cell = row.insertCell(0); - cell.innerHTML += `${rowCount}`; + cell.innerHTML += `${rowCount + 1}`; cell = row.insertCell(1); cell.innerHTML += ` @@ -1113,6 +1124,9 @@ function updateIncomeStatementTable(rowIndex, cellIndex, value) { body.rows[rowIndex].cells[cellIndex].innerHTML = value; } +/** + * This loads the NOI DSCR and Cash DSCR inputs. + */ function loadLoanOptionsTextBox(field, id, value) { inputDiv = document.querySelector('#'+id); inputDiv.innerHTML = ` @@ -1121,6 +1135,9 @@ function loadLoanOptionsTextBox(field, id, value) { `; } +/** + * Load the column heading for loan options. + */ function loadLoanOptionsColumnHeadings() { head = document.querySelector('#loan-options-table thead'); head.innerHTML = ` @@ -1135,6 +1152,9 @@ function loadLoanOptionsColumnHeadings() { `; } +/** + * Add a new row to the loan options table. + */ function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAmount) { body = document.querySelector('#loan-options-table tbody') var rowCount = body.rows.length; @@ -1155,10 +1175,12 @@ function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAm
`; - loanOptionsCounter += 1; 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?"); @@ -1172,6 +1194,9 @@ function deleteLoanOptionsRow(row) { return false; } +/** + * Generate the lender dropdown box given the list of lenders. + */ function lenderOptions(lenderList, lender) { var text = ` - + - +
diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 03e6046..905c7b2 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -955,9 +955,19 @@ class IncomeStatementTable(View): 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 = {} @@ -969,29 +979,63 @@ class LoanOptionsTable(View): 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') - print(get_type) - loan_options_objs = self.model.objects.filter( - building_id=building_id, - ) + + # 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: - return JsonResponse({'status': []}) + 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) - return JsonResponse({'status': result, 'lenders': lenders}) + 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, @@ -1005,29 +1049,53 @@ class LoanOptionsTable(View): record['duration'] = obj.duration record['max_loan_amount'] = obj.max_loan_amount result.append(record) - return JsonResponse({'present': True, 'status': result, 'lenders': lenders, 'noi': noi_dscr, 'cash': cash_dscr}) + + # 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) - return JsonResponse({'status': result, 'lenders': lenders}) + 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()) - print(put) financing_overview_obj = FinancingOverview.objects.filter( building_id=building_id, ) - print(financing_overview_obj) 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']: - print(record) lender_id = Lender.objects.filter( name=record['lender'], )[0].id - print(lender_id) self.model.objects.create( building_id=building_id, lender_id=lender_id, -- GitLab From b04e8907807c95f6fd67b8d12834fd2209937068 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 12:28:01 -0400 Subject: [PATCH 6/6] Add comments in models.py. --- blocnote/apps/financialInputs/models.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 20a7518..36dbbdb 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -111,10 +111,17 @@ class IncomeStatement(models.Model): class Lender(models.Model): + """Lenders for the different funds.""" + name = models.CharField(max_length=200) class DefaultLoan(models.Model): + """Default loan options. + + Each fund-lender combination has default loan terms which will be used as template. + """ + lender = models.ForeignKey(Lender, on_delete=models.CASCADE, blank=True, null=True) fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) interest_rate = models.DecimalField(max_digits=5, decimal_places=3) @@ -123,6 +130,11 @@ class DefaultLoan(models.Model): class LoanOptions(models.Model): + """Loan options for a building. + + This could be the same as the default options present or modified. + """ + building_id = models.IntegerField() lender = models.ForeignKey(Lender, on_delete=models.CASCADE, blank=True, null=True) interest_rate = models.DecimalField(max_digits=5, decimal_places=3) -- GitLab