diff --git a/.gitignore b/.gitignore index 8310350f1b71b65fdaaf5a8b6d2d92a7ed48bf28..32b1e5a16a36f3d59c0566fb584e5e9f6c6a9f23 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ node_modules # misc .idea +.vscode/ diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..584eda407a4749a7625223279e4fce023af0f1fb --- /dev/null +++ b/blocnote/apps/financialInputs/forms.py @@ -0,0 +1,52 @@ +import datetime + +from django.forms import ModelForm +from django import forms +from django.forms.extras import SelectDateWidget + +from .models import FinancingOverview + + +class BlocNoteHeaderForm(ModelForm): + """Header Form. + + Form that appears at the top of the page. This class specifies the fields + and sets the form field types for dates, sets the range of dates to display + and adds attributes to some of the fields. + """ + + years_to_display = range(datetime.datetime.now().year - 15, + datetime.datetime.now().year + 50) + anticipated_construction_start_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + anticipated_commissioning_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + pro_forma_start_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + analysis_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + + class Meta: + model = FinancingOverview + fields = ('anticipated_construction_start_date', + 'anticipated_commissioning_date', + 'anticipated_construction_period', + 'pro_forma_start_date', + 'pro_forma_duration', + 'analysis_date', + 'fund') + + def __init__(self, *args, **kwargs): + super(BlocNoteHeaderForm, self).__init__(*args, **kwargs) + for field in iter(self.fields): + self.fields[field].widget.attrs.update({ + 'class': 'form-control' + }) + self.fields['fund'].widget.attrs.update({ + 'class': 'custom-select' + }) + self.initial['fund'] = 1 diff --git a/blocnote/apps/financialInputs/migrations/0001_initial.py b/blocnote/apps/financialInputs/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..934afed6c8470922d73f813e67723908b2d77ff9 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 15:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='FinancingOverview', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('required_noi_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('requrired_cash_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('anticipated_construction_start_date', models.DateField()), + ('anticipated_commissioning_date', models.DateField()), + ('anticipated_construction_period', models.DecimalField(decimal_places=0, max_digits=2)), + ], + ), + migrations.CreateModel( + name='Fund', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('Name', models.CharField(max_length=200)), + ], + ), + migrations.AddField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py b/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py new file mode 100644 index 0000000000000000000000000000000000000000..bb6bee6ae8aae9983dd72ac6ec54d9b55b0d6c5a --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 20:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + migrations.AlterField( + model_name='financingoverview', + name='required_noi_dscr', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + migrations.AlterField( + model_name='financingoverview', + name='requrired_cash_dscr', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py b/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py new file mode 100644 index 0000000000000000000000000000000000000000..29295423a4aae5474ae5b5b171f05fc9cf7ebc29 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 21:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0002_auto_20170412_2043'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py b/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py new file mode 100644 index 0000000000000000000000000000000000000000..ca3454003527e023f27ee4850f5fc0fc35c44237 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 21:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0003_auto_20170412_2143'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='anticipated_construction_period', + field=models.DecimalField(decimal_places=0, max_digits=4), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py b/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py new file mode 100644 index 0000000000000000000000000000000000000000..3086180e64810a89ca2dddfcf4620f742331c8b9 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-13 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0004_auto_20170412_2153'), + ] + + operations = [ + migrations.AddField( + model_name='financingoverview', + name='analysis_date', + field=models.DateField(default='2017-01-01'), + preserve_default=False, + ), + migrations.AddField( + model_name='financingoverview', + name='pro_forma_duration', + field=models.DecimalField(decimal_places=0, default=2, max_digits=2), + preserve_default=False, + ), + migrations.AddField( + model_name='financingoverview', + name='pro_forma_start_date', + field=models.DateField(default='2017-01-01'), + preserve_default=False, + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py b/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py new file mode 100644 index 0000000000000000000000000000000000000000..a10168597e492fc49dafc85978c7e816be95b0e4 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-13 15:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0005_auto_20170413_1530'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='anticipated_construction_period', + field=models.DecimalField(decimal_places=0, max_digits=3), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0007_bills.py b/blocnote/apps/financialInputs/migrations/0007_bills.py new file mode 100644 index 0000000000000000000000000000000000000000..cf6002808452df06658696372ffd4dbe8fee51af --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0007_bills.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 15:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0006_auto_20170413_1533'), + ] + + operations = [ + migrations.CreateModel( + name='Bills', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('date_from', models.DateField()), + ('date_to', models.DateField()), + ('utility_type', models.CharField(max_length=11)), + ('usage', models.DecimalField(decimal_places=2, max_digits=6)), + ('charge', models.DecimalField(decimal_places=2, max_digits=10)), + ], + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py b/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py new file mode 100644 index 0000000000000000000000000000000000000000..3ecb611b8ef94452420c6e61c5a5611fda2608dc --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 20:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0007_bills'), + ] + + operations = [ + migrations.AlterField( + model_name='bills', + name='usage', + field=models.DecimalField(decimal_places=2, max_digits=10), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0009_billsoverview.py b/blocnote/apps/financialInputs/migrations/0009_billsoverview.py new file mode 100644 index 0000000000000000000000000000000000000000..6f0b789ce5f777dfe621e5a07a64cabe604531fe --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0009_billsoverview.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 23:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0008_auto_20170417_2026'), + ] + + operations = [ + migrations.CreateModel( + name='BillsOverview', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('year', models.DecimalField(decimal_places=0, max_digits=4)), + ('estimation_algorithm', models.CharField(choices=[('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior')], max_length=100)), + ], + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py b/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py new file mode 100644 index 0000000000000000000000000000000000000000..5a4c4fd2850f30f2dcd8f59bd9f8ec9a01278fd5 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 23:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0009_billsoverview'), + ] + + operations = [ + migrations.AlterField( + model_name='billsoverview', + name='estimation_algorithm', + field=models.CharField(max_length=100), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py b/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py new file mode 100644 index 0000000000000000000000000000000000000000..8198ef55d1219d378f4dabb3d10a0da9892d922f --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-18 02:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0010_auto_20170417_2338'), + ] + + operations = [ + migrations.AddField( + model_name='billsoverview', + name='electricity', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='electricity_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='gas', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='gas_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='oil', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='oil_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='water', + field=models.DecimalField(decimal_places=2, default=0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='water_is_user_input', + field=models.BooleanField(default=False), + ), + ] diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 71a836239075aa6e6e4ecb700e9c42c95c022d91..b10647c3055533d0d0f66255ec4a9bd5b48c8e83 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -1,3 +1,58 @@ from django.db import models -# Create your models here. + +class Fund(models.Model): + """Model that represents which fund is the money from.""" + + Name = models.CharField(max_length=200) + + def __str__(self): + """Return fund name in string format.""" + return self.Name + + +class FinancingOverview(models.Model): + """Basic dates and fund. + + Store necessary dates, fund name and relevant time periods. This is used in + Bill Projection in bpfin. + """ + + building_id = models.IntegerField() + fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) + required_noi_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) + requrired_cash_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) + pro_forma_start_date = models.DateField() + pro_forma_duration = models.DecimalField(max_digits=2, decimal_places=0) + analysis_date = models.DateField() + anticipated_construction_start_date = models.DateField() + anticipated_commissioning_date = models.DateField() + anticipated_construction_period = models.DecimalField(max_digits=3, + decimal_places=0) + + +class Bills(models.Model): + """Utility bill information obtained from an external file.""" + + building_id = models.IntegerField() + date_from = models.DateField() + date_to = models.DateField() + utility_type = models.CharField(max_length=11) + usage = models.DecimalField(max_digits=10, decimal_places=2) + charge = models.DecimalField(max_digits=10, decimal_places=2) + + +class BillsOverview(models.Model): + """Store annual charge of each utility used for bill projection.""" + + building_id = models.IntegerField() + year = models.DecimalField(max_digits=4, decimal_places=0) + estimation_algorithm = models.CharField(max_length=100) + electricity = models.DecimalField(max_digits=10, decimal_places=2) + electricity_is_user_input = models.BooleanField(default=False) + water = models.DecimalField(max_digits=10, decimal_places=2) + water_is_user_input = models.BooleanField(default=False) + oil = models.DecimalField(max_digits=10, decimal_places=2) + oil_is_user_input = models.BooleanField(default=False) + gas = models.DecimalField(max_digits=10, decimal_places=2) + gas_is_user_input = models.BooleanField(default=False) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js new file mode 100644 index 0000000000000000000000000000000000000000..9f076c4b68a695e03d3bd55c485ab0ec93ead38e --- /dev/null +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -0,0 +1,274 @@ +utilities = ['electricity', 'gas', 'oil', 'water']; +for (var utility_index in utilities) { + loadInitialBillsTable(utilities[utility_index]); +} +loadBillsOverview(); + +function billProjectionDatesForm(form) { + /* + * 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. + */ + const formData = new FormData(form); + var validDate = validateDate(formData); + if (!validDate) { + alert("Anticipated Commissioning date has to be after Anticipated Construction start date"); + } + else { + const result = {}; + for (const [key, value] of formData.entries()) { + result[key] = value; + } + 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 => { + loadBillsOverview(); + }); + } + return false; +} + +function validateDate(data) { + /* Validate that commissioning date is after the construction start date. */ + var startDateYear = data.get('anticipated_construction_start_date_year'); + var startDateMonth = data.get('anticipated_construction_start_date_month'); + var startDateDay = data.get('anticipated_construction_start_date_day'); + var endDateYear = data.get('anticipated_commissioning_date_year'); + var endDateMonth = data.get('anticipated_commissioning_date_month'); + var endDateDay = data.get('anticipated_commissioning_date_day'); + if (endDateYear < startDateYear) { + return false; + } + else if (endDateYear === startDateYear) { + if (endDateMonth < startDateMonth) { + return false; + } + else if (endDateMonth === startDateMonth) { + if (endDateDay <= startDateDay) { + return false; + } + } + } + return true; +} + +function loadInitialBillsTable(utility) { + /* + * Load initial bills table given a utility. Send GET request to obtain + * bill information and call updateTable function to update the bills table + * with the data obtained from backend. + */ + const table = document.querySelector('#'+utility); + + request(`bills/?utility_type=${utility}`, { + method: 'get', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + if (res.payload.present) { + var text = getText(res.payload.result, utility); + updateTable(utility, text); + } + }) +} + + +function updateTable(utility, text) { + /* Update the Bills table with the data. Function queries to get the table + * component and then updates the component with the text. + */ + table = document.querySelector('#'+utility); + table.innerHTML = text; +} + +function getText(result, utility) { + /* Generate html text for a given utility and bills data from backend. */ + var text = `
| Date From | +Date To | +Usage | +Bill Charge ($) | +
|---|