diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..bca424db326cd75f84eee2d40b93206690ad012c --- /dev/null +++ b/blocnote/apps/financialInputs/forms.py @@ -0,0 +1,21 @@ +"""Forms to render and validate for financial-inputs endpoint.""" +from django.forms import ModelForm +from blocnote.apps.financialInputs.models import FinancingOverview + + +class ProformaInputsForm(ModelForm): + """Define the form to input/display proforma information.""" + + class Meta: + """Choose the model and the relevant fields for the form.""" + + model = FinancingOverview + fields = [ + 'fund', + 'pro_forma_start_date', + 'pro_forma_duration', + 'analysis_date', + 'anticipated_construction_start_date', + 'anticipated_commissioning_date', + 'anticipated_construction_period', + ] diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/old_views.py similarity index 92% rename from blocnote/apps/financialInputs/views.py rename to blocnote/apps/financialInputs/old_views.py index 86193167b84cf64fd104c91ff32a7c5cd5b6284e..c2a86ae9b4c89b96919e5e2bafec6427ff9c9607 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/old_views.py @@ -145,95 +145,6 @@ class Index(View): return render(request, 'financialInputs/index.html', context=context) -class BlocNoteHeader(View): - """This class handles header creation and update.""" - - model = FinancingOverview - - def handle_form(self, put, building_id): - """Handle form submit. - - Take the header form passed by the frontend and make an entry in the database. - - Args: - put: This is the data sent in the PUT request by frontend. - building_id: id of the building. - """ - self.model.objects.filter(building_id=building_id).delete() - self.model.objects.create( - building_id=building_id, - fund_id=put['fund'], - pro_forma_start_date=put['pro_forma_start_date'], - pro_forma_duration=put['pro_forma_duration'], - analysis_date=put['analysis_date'], - anticipated_construction_start_date=put['anticipated_construction_start_date'], - anticipated_commissioning_date=put['anticipated_commissioning_date'], - anticipated_construction_period=put['anticipated_construction_period'], - ) - - def put(self, request, building_id): - """PUT route for header. - - Handle the PUT request for the header form. Store all the data received into the database. If storing was - successfull, send success message else send a failure message. - - Args: - request: HTTP PUT request. - building_id: id of the building. - - Returns: - JsonResponse: If success, returns a message saying successful else return message saying it failed. - """ - put = json.loads(request.body.decode()) - result = {} - try: - self.handle_form(put, building_id) - except Exception as err: - return JsonResponse( - { - 'error': 'Sorry, something went wrong. Please try again.', - }, status=400) - return JsonResponse(result) - - def get(self, request, building_id): - """HTTP GET request. - - Fetch the financing overview data from the database and send to the frontend. Send the list of lenders - regardless of whether a financing overview entry has been made or not. - - Args: - request: HTTP GET request. - building_id: id of the building. - - Returns: - 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 = {} - - # Check if the financing overview entry exists in the database. - if financing_overview_objs: - financing_overview_obj = financing_overview_objs[0] - result = { - 'present': True, - 'fund': financing_overview_obj.fund.id, - 'pro_forma_start_date': financing_overview_obj.pro_forma_start_date, - 'pro_forma_duration': financing_overview_obj.pro_forma_duration, - 'analysis_date': financing_overview_obj.analysis_date, - 'anticipated_construction_start_date': financing_overview_obj.anticipated_construction_start_date, - 'anticipated_commissioning_date': financing_overview_obj.anticipated_commissioning_date, - 'anticipated_construction_period': financing_overview_obj.anticipated_construction_period, - } - result['funds'] = funds - return JsonResponse({'result': result}) - - class BillsTable(View): """Create the Energy Bills tables. diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index d2d9204e6fed0e86f03ecff56532d4b7437edf00..c679f63f565bd025f39d56d915e6b73df1e40458 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -8,7 +8,6 @@ const todaysDate = { for (var utility_index in utilities) { loadInitialBillsTable(utilities[utility_index]); } -getBillProjectionDatesForm(); loadBillsOverview(); getIncomeStatementTable(); getCustomerPreferenceTable(); @@ -16,182 +15,6 @@ getLiabilitiesTable(); getCashBalanceForm(); getLoanOptionsTable(); -/** - * proformaForm will watch the form and if it changes, it will remove the response message is it was displayed. - */ -var proformaForm = document.querySelector('#pro-forma-form'); -proformaForm.onchange = function() { - var resMsg = document.querySelector('#pro-forma-form-save-msg'); - resMsg.innerHTML = ``; -} - -/** - * 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'); -/** - * This is to watch if fund value is changed. If it did, it would affect loan options. - */ -var didFundChange = false; -fund.onmouseenter = function() { - var errorDiv = document.querySelector('#show-error'); - errorDiv.innerHTML = `Changing fund will affect loan options`; -} - -fund.onmouseleave = function() { - var errorDiv = document.querySelector('#show-error'); - errorDiv.innerHTML = ""; -} - -fund.onchange = function() { - didFundChange = true; -} - -/** - * HTTP GET request to fetch the proforma form information from the database if preent. It would receive the list of - * funds along with. - */ -function getBillProjectionDatesForm() { - request(`finance-overview`, { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - }).then(res => { - fundDropBox = document.querySelector('#id_fund-select'); - var funds = res.payload.result.funds; - for (var fundIndex = 0; fundIndex < funds.length; fundIndex++) { - fundDropBox.innerHTML += ` - - `; - if (res.payload.result.present) { - document.querySelector('#pro-forma-date-input').value = res.payload.result.pro_forma_start_date; - document.querySelector('#pro-forma-duration-input').value = res.payload.result.pro_forma_duration; - document.querySelector('#analysis-date-input').value = res.payload.result.analysis_date; - document.querySelector('#id_fund-select').value = res.payload.result.fund; - document.querySelector('#anticipated-construction-start-date-input').value = res.payload.result.anticipated_construction_start_date; - document.querySelector('#anticipated-commissioning-date-input').value = res.payload.result.anticipated_commissioning_date; - document.querySelector('#anticipated-construction-period-input').value = res.payload.result.anticipated_construction_period; - } - } - }); -} - -/** - * Handle submition of the header form. Validate commissioning date is greater - * than construction start date. Create result dictionary containing the form - * data and convert into JSON to send to the backend. Upon success, load the - * Bills overview table as the Pro Forma year changes the start year of the - * Bills overview table. - */ -function billProjectionDatesForm(form) { - var proFormaStartDate = document.querySelector('#pro-forma-date-input').value; - 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 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 = anticipatedConstructionStartDate.split('-') - endDate = anticipatedCommissioningDate.split('-'); - var anticipatedConstructionStartYear = startYear[0]; - var anticipatedConstructionStartMonth = startYear[1]; - var anticipatedConstructionStartDay = startYear[2]; - var anticipatedCommissioningStartYear = endDate[0]; - var anticipatedCommissioningStartMonth = endDate[1]; - var anticipatedCommissioningStartDay = endDate[2]; - var anticipatedConstructionStartDateDict = { - 'day': anticipatedConstructionStartDay, - 'month': anticipatedConstructionStartMonth, - 'year': anticipatedConstructionStartYear, - } - var anticipatedCommissioningDateDict = { - 'day': anticipatedCommissioningStartDay, - 'month': anticipatedCommissioningStartMonth, - 'year': anticipatedCommissioningStartYear, - } - 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?'); - } - 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 - - `; - } - 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, - } - 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.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'); - res.err.responseBody.then((error) => { - resMsg.innerHTML = `${error.error}`; - }) - } - }); - } - } - return false; -} - /** Validate that commissioning date is after the construction start date. */ function validateDate(startDate, endDate) { var startDateYear = startDate.year; diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/proformaInputs.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/proformaInputs.js new file mode 100644 index 0000000000000000000000000000000000000000..dbdc284c87df042b39c4ef369c1cd34a2759c942 --- /dev/null +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/proformaInputs.js @@ -0,0 +1,162 @@ +'use strict'; + +const ERROR_TAGS = [ + 'pro_forma_start_date-error', + 'pro_forma_duration-error', + 'analysis_date-error', + 'fund-error', + 'anticipated_construction_start_date-error', + 'anticipated_commissioning_date-error', + 'anticipated_construction_period-error', +]; +/** + * proformaForm will watch the form and if it changes, it will remove the response message is it + * was displayed. + */ +const proformaForm = document.querySelector('#pro-forma-form'); +proformaForm.onchange = function resetErrors() { + document.querySelector('#pro-forma-form-save-msg').innerHTML = ''; + ERROR_TAGS.forEach((tag) => { + document.querySelector(`#${tag}`).innerHTML = ''; + }); +}; + +/** + * 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. + */ +const fund = document.querySelector('#id_fund'); +/** + * This is to watch if fund value is changed. If it did, it would affect loan options. + */ +let didFundChange = false; +fund.onmouseenter = function showFundWarning() { + document.querySelector('#show-error').innerHTML = ` + Changing fund will affect loan options + `; +}; + +fund.onmouseleave = function clearFundWarning() { + document.querySelector('#show-error').innerHTML = ''; +}; + +fund.onchange = function checkIfFundChanged() { + didFundChange = true; +}; + +/** + * HTTP GET request to fetch the proforma form information from the database if preent. It would + * receive the list of funds along with. + */ +function getBillProjectionDatesForm() { + request('finance-overview', { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => { + if (!res.err) { + const fundDropBox = document.querySelector('#fund_id'); + const funds = res.payload.instance.funds; + // Load fund dropdown box with the list of funds received. + funds.forEach((thisFund) => { + fundDropBox.innerHTML += ` + + `; + }); + // Load all the values if previously filled. + if (res.payload.instance.financing_overview_data) { + const financingOverviewData = res.payload.instance.financing_overview_data; + Object.keys(financingOverviewData).forEach((key) => { + const data = financingOverviewData[key]; + proformaForm.querySelector(`#${key}`).value = data; + }); + } + } else { + // Display error message if error on load. + document.querySelector('#pro-forma-form-save-msg').innerHTML = ` + Sorry, there was an error loading. + `; + } + }); +} + +getBillProjectionDatesForm(); + +/** + * Handle submition of the Proforma form. Validate commissioning date is greater than construction + * start date. Create result dictionary containing the form data and convert into JSON to send to + * the backend. Upon success, load the loan options table with the saved fund if fund changed or + * saving this form for the first time. + * + * @param {any} form : Proforma inputs form filled. + * @returns : False so that page does not reload. + */ +function billProjectionDatesForm(form) { + const formData = new FormData(form); + const result = {}; + formData.forEach((value, field) => { + result[field] = value; + }); + const constructionStartDate = new Date(result.anticipated_construction_start_date); + const constructionCommissioningDate = new Date(result.anticipated_commissioning_date); + // Make sure construction commision date is afer construction start date. + if (constructionStartDate > constructionCommissioningDate) { + document.querySelector('#pro-forma-form-save-msg').innerHTML = ` + + Anticipated Commissioning date has to be after Anticipated Construction start date + + `; + return false; + } + 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) => { + const displayMessage = document.querySelector('#pro-forma-form-save-msg'); + if (!res.err) { + // Display response message. Add that loan options changed if fund was changed. Reset + // didFundChange to false. + let responseMessage = 'Saved. '; + if (didFundChange) { + responseMessage += 'Loan Options table is reloaded.'; + } + displayMessage.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(() => { + const tableBody = document.querySelector('#loan-options-table tbody'); + tableBody.innerHTML = ''; + }); + getLoanOptionsTable(); + } + } else { + res.err.responseBody.then((error) => { + // Display error message. + Object.keys(error).forEach((key) => { + document.querySelector(`#${key}-error`).innerHTML = ` + ${error[key][0]} + `; + }); + }); + } + }); + return false; +} diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html deleted file mode 100644 index 51a687a6529c72c918100ee0eeea59f961041121..0000000000000000000000000000000000000000 --- a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html +++ /dev/null @@ -1,51 +0,0 @@ -
- {% csrf_token %} -
-
-
- {{ form.pro_forma_start_date.label_tag }} -
-
- {{ form.pro_forma_start_date }} -
-
- {{ form.pro_forma_duration.label_tag }} -
-
- {{ form.pro_forma_duration }} (years) -
-
-
-
-
-
- {{ form.analysis_date.label_tag }} -
-
- {{ form.analysis_date }} -
-
- Select Fund: {{ form.fund }} -
-
-
-
-
-
-
- {{ form.anticipated_construction_start_date.label_tag }} - {{ form.anticipated_construction_start_date }} - - {{ form.anticipated_commissioning_date.label_tag }} - {{ form.anticipated_commissioning_date }} -
-
-
-
- {{ form.anticipated_construction_period.label_tag }} - {{ form.anticipated_construction_period }} weeks -
- -
-
-
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index a1e800b4c78eb12dcbee7a6eb97927e32836cdc0..064a91370da324dd5c9e636745eb7de9fa07962e 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -55,4 +55,5 @@ + {% endblock %} diff --git a/blocnote/apps/financialInputs/templates/financialInputs/proFormaForm.html b/blocnote/apps/financialInputs/templates/financialInputs/proFormaForm.html index f3d71224eddeb63249bef928b267fc2269805acb..ccc94e43bb4ed034beca7c103900256c5d36bf6c 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/proFormaForm.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/proFormaForm.html @@ -4,11 +4,15 @@
Pro Forma Start date - + +
+
Pro Forma Duration (years) - + +
+
@@ -16,12 +20,16 @@
Analysis Date - + +
+
Fund - +
+
@@ -31,11 +39,15 @@
Anticipated Construction Start Date - + +
+
Anticipated Commissioning Date - + +
+
@@ -43,7 +55,9 @@
Anticipated Construction Period (weeks) - + +
+
diff --git a/blocnote/apps/financialInputs/tests.py b/blocnote/apps/financialInputs/tests.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..7d6d6c030b1545aa9fbd5995c280d0d44b897428 100644 --- a/blocnote/apps/financialInputs/tests.py +++ b/blocnote/apps/financialInputs/tests.py @@ -1,3 +1,241 @@ +"""Tests for all the views in financialInputs.""" +import json + from django.test import TestCase -# Create your tests here. +from .models import Fund, FinancingOverview + +PROFORMA_START_DATE = '2014-01-01' +ANALYSIS_DATE = '2017-06-06' +CONSTRUCTION_START_DATE = '2017-07-01' +COMMISSIONING_DATE = '2017-08-01' + + +class ProformaInputsViewTest(TestCase): + """Test the proforma_inputs view for GET and PUT routes.""" + + def setUp(self): + """Create 3 funds as it is building independent.""" + Fund.objects.create(id=1, Name='Fund1') + Fund.objects.create(id=2, Name='Fund2') + Fund.objects.create(id=3, Name='Fund3') + + def test_finance_overview_get_1(self): + """Test the GET route when database is empty. + + Test GET route when database is completely empty for a building except for the funds. This should return + a response with just the funds as a list of tuples with its id and name. This funds is in a dictionary called + instance. + """ + response = self.client.get('/buildings/3000/financial-inputs/finance-overview/') + response_content = response.json() + expected_response_content = { + 'instance': { + 'funds': [ + [1, 'Fund1'], + [2, 'Fund2'], + [3, 'Fund3'], + ], + }, + } + self.assertEqual(response.status_code, 200) + self.assertTrue('instance' in response_content) + self.assertEqual(response_content, expected_response_content) + + def test_finance_overview_get_2(self): + """Test GET route with proper financing overview data. + + Create financing overview record for the building and then perform a GET request. This should return a response + as a dictionary with funds as a list of tuples with fund id and name and the rest of the data for a key called + financing_overview_data. + """ + fund = Fund.objects.get(Name='Fund2') + FinancingOverview.objects.create( + building_id=3000, + fund=fund, + pro_forma_start_date=PROFORMA_START_DATE, + pro_forma_duration=25, + analysis_date=ANALYSIS_DATE, + anticipated_construction_start_date=CONSTRUCTION_START_DATE, + anticipated_commissioning_date=COMMISSIONING_DATE, + anticipated_construction_period=4 + ) + response = self.client.get('/buildings/3000/financial-inputs/finance-overview/') + response_content = response.json() + expected_response_content = { + 'instance': { + 'funds': [ + [1, 'Fund1'], + [2, 'Fund2'], + [3, 'Fund3'], + ], + 'financing_overview_data': { + 'fund_id': 2, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': '25', + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': '4', + }, + }, + } + self.assertEqual(response.status_code, 200) + self.assertTrue('instance' in response_content) + self.assertEqual(response_content, expected_response_content) + + def test_finance_overview_put_1(self): + """Test the PUT route when empty content is sent. + + Test PUT route when empty content is sent. This should do nothing. + """ + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps({})) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('error' in response_content) + + def test_finance_overview_put_2(self): + """Test the PUT route when correct content is sent. + + Test PUT route when correct content is sent. This should do nothing. + """ + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 200) + self.assertEqual(response_content, {}) + + def test_finance_overview_put_3(self): + """Test PUT route when construction start date is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': '', + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('anticipated_construction_start_date' in response_content) + + def test_finance_overview_put_4(self): + """Test PUT route when commissioning date is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': '', + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('anticipated_commissioning_date' in response_content) + + def test_finance_overview_put_5(self): + """Test PUT route when proforma start date is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': '', + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('pro_forma_start_date' in response_content) + + def test_finance_overview_put_6(self): + """Test PUT route when proforma duration is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': '', + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('pro_forma_duration' in response_content) + + def test_finance_overview_put_7(self): + """Test PUT route when analysis date is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': '', + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('analysis_date' in response_content) + + def test_finance_overview_put_8(self): + """Test PUT route when construction period is empty.""" + content = { + 'fund_id': 1, + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': '', + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('anticipated_construction_period' in response_content) + + def test_finance_overview_put_9(self): + """Test PUT route with fund as empty.""" + content = { + 'fund_id': '', + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('fund' in response_content) + + def test_finance_overview_put_10(self): + """Test PUT route when fund is missing.""" + content = { + 'pro_forma_start_date': PROFORMA_START_DATE, + 'pro_forma_duration': 25, + 'analysis_date': ANALYSIS_DATE, + 'anticipated_construction_start_date': CONSTRUCTION_START_DATE, + 'anticipated_commissioning_date': COMMISSIONING_DATE, + 'anticipated_construction_period': 4, + } + response = self.client.put('/buildings/3000/financial-inputs/finance-overview/', json.dumps(content)) + response_content = response.json() + self.assertEqual(response.status_code, 400) + self.assertTrue('error' in response_content) diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index e668ab6afc0940f56db14ae625d6b88371fb8fd9..cf298f6e6286245998e7498c284ea2ab9b44427f 100644 --- a/blocnote/apps/financialInputs/urls.py +++ b/blocnote/apps/financialInputs/urls.py @@ -1,16 +1,17 @@ from django.conf.urls import url -from . import views +from . import old_views +from .views import proforma_input app_name = 'financial-inputs' urlpatterns = [ - url(r'^$', views.Index.as_view(), name='index'), - url(r'^finance-overview/$', views.BlocNoteHeader.as_view(), name='header'), - url(r'^bills/$', views.BillsTable.as_view(), name='bills'), - url(r'^bills-overview/$', views.BillsOverviewView.as_view(), name='bills_overview'), - url(r'^customer-preference/$', views.CustomerPreferenceView.as_view(), name='customer_preference'), - 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'), + url(r'^$', old_views.Index.as_view(), name='index'), + url(r'^finance-overview/$', proforma_input.ProformaInputs.as_view(), name='header_new'), + url(r'^bills/$', old_views.BillsTable.as_view(), name='bills'), + url(r'^bills-overview/$', old_views.BillsOverviewView.as_view(), name='bills_overview'), + url(r'^customer-preference/$', old_views.CustomerPreferenceView.as_view(), name='customer_preference'), + url(r'^liabilities/$', old_views.LiabilitiesTable.as_view(), name='liabilities'), + url(r'^cash-balance/$', old_views.CashBalanceView.as_view(), name='cash_balance'), + url(r'^income-statement/$', old_views.IncomeStatementTable.as_view(), name='income_statement'), + url(r'^loan-options/$', old_views.LoanOptionsTable.as_view(), name='loan_options'), ] diff --git a/blocnote/apps/financialInputs/views/__init__.py b/blocnote/apps/financialInputs/views/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/blocnote/apps/financialInputs/views/proforma_input.py b/blocnote/apps/financialInputs/views/proforma_input.py new file mode 100644 index 0000000000000000000000000000000000000000..ddedc1e0c77703379d69d39a7f64f505de9189a4 --- /dev/null +++ b/blocnote/apps/financialInputs/views/proforma_input.py @@ -0,0 +1,105 @@ +"""Route for Proforma Inputs.""" +import json +import copy +from django.http import JsonResponse +from django.views import View + +from blocnote.apps.financialInputs import forms +from blocnote.apps.financialInputs.models import FinancingOverview, Fund + + +class ProformaInputs(View): + """This class handles Proforma inputs storing/updating. + + Proforma or projection information consists of the start date from which projection should begin and a duration, + which determines how far to project from the start date. The start date is typically 3-5 years before current. + Apart from this, other relevant information are analysis date which is the current date, fund which basically + indicates what fund would be used for this building, anticipated construction start date, anticipated commissioni- + ng date and anticipated construction period which may or may not be the difference between the latter two inputs. + """ + + fund_model = Fund + financing_model = FinancingOverview + proforma_inputs_form = forms.ProformaInputsForm + form_length = 7 + + def put(self, request, building_id): + """HTTP PUT request. + + Receive the financing overview inputs from the frontend. Validate this information and then create new entry + if not already existing for this building or update existing entry. + + Args: + resquest: HTTP PUT request. + building_id: id of the building. + + Returns: + error: error message with status 400 if form validation fails. + Otherwise a status 200. + """ + put = json.loads(request.body.decode()) + # Django form validation does not care if foreign key is missing so this check is done here. + if 'fund_id' not in put: + return JsonResponse({'error': 'Fund missing from form'}, status=400) + if put['fund_id'] == '': + return JsonResponse({'fund': 'Fund cannot be empty.'}, status=400) + + # Create form from the inputs received. + form = self.proforma_inputs_form(put) + + # Validate form. + if form.is_valid(): + put['building_id'] = building_id + self.financing_model.objects.update_or_create( + building_id=building_id, + defaults=put, + ) + else: + # construct an error dictionary to send to frontend if form is not valid. + error_dict = {} + for field, error in form.errors.items(): + error_dict[field] = error + return JsonResponse(error_dict, status=400) + return JsonResponse({}) + + def get(self, request, building_id): + """HTTP GET request. + + Fetch the financing overview data from the database and send to the frontend. Send the list of lenders + regardless of whether a financing overview entry has been made or not. + + Args: + request: HTTP GET request. + building_id: id of the building. + + Returns: + JsonResponse: Result with the financing overview data. + """ + financing_overview_objs = self.financing_model.objects.filter(building_id=building_id) + + unwanted_keys = [ + '_state', + 'id', + 'building_id', + 'required_noi_dscr', + 'requrired_cash_dscr', + ] + + # Fetch all the funds. + funds_obj = self.fund_model.objects.all() + funds = [] + for fund_obj in funds_obj: + funds.append((fund_obj.id, fund_obj.Name)) + instance = {} + + # Check if the financing overview entry exists in the database. + if financing_overview_objs: + financing_overview_obj = financing_overview_objs[0] + financing_overview_data = {} + obj_dict = financing_overview_obj.__dict__ + for key in obj_dict: + if key not in unwanted_keys: + financing_overview_data[key] = obj_dict[key] + instance['financing_overview_data'] = financing_overview_data + instance['funds'] = funds + return JsonResponse({'instance': instance}) diff --git a/blocnote/settings.py b/blocnote/settings.py index bed7ab25edab224514f93c8fd5b2fa280d51b3a0..91909c036045cc98de803ce9765587f12fd40cf5 100644 --- a/blocnote/settings.py +++ b/blocnote/settings.py @@ -82,7 +82,7 @@ TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' # Tell nose to measure coverage on the 'foo' and 'bar' apps NOSE_ARGS = [ '--with-coverage', - '--cover-package=blocnote.apps.budgetSimulator', + '--cover-package=blocnote.apps.budgetSimulator,blocnote.apps.financialInputs', ] WSGI_APPLICATION = 'blocnote.wsgi.application'