client-ui
Version:
Testing implementation of nodeJs Backend, angular frontend, and hopefully in a way that this can be deployed to s3/cloudfront
470 lines (407 loc) • 17.3 kB
JavaScript
(function () {
'use strict';
angular.module(moduleName).controller('loanCalculatorController', loanCalculatorController);
loanCalculatorController.$inject = ['$scope'];
function loanCalculatorController($scope){
$scope.loanAmountEntry = null;
$scope.interestRateEntry = null;
$scope.termEntry = null;
$scope.pmtPerMonthEntry = null;
$scope.originFeeEntry = null;
$scope.totalOutput;
$scope.APROutput;
$scope.interestRatePayment;
$scope.principalOutput;
$scope.validNumberError;
$scope.calculated = false;
$scope.validation = function() {
if($scope.loanAmountEntry) {
$scope.loanAmountEntry = Number($scope.loanAmountEntry.toString().replace(",", ""));
};
if($scope.originFeeEntry) {
$scope.originFeeEntry = Number($scope.originFeeEntry.toString().replace(",", ""));
}
if($scope.validNumInputs() && $scope.validValueInputs()) {
$scope.proceed();
$scope.generateChart();
$scope.calculated = true;
};
};
$scope.validNumInputs = function() {
var P = +!!$scope.loanAmountEntry,
R = +!!$scope.interestRateEntry,
T = +!!$scope.termEntry,
A = +!!$scope.pmtPerMonthEntry,
O = +!!$scope.originFeeEntry;
var validNumber = P + R + T + A;
if(validNumber !== 3) {
$scope.validNumberError = "Please input three of the four inputs.";
return false;
};
return true;
};
$scope.validValueInputs = function() {
var P = $scope.loanAmountEntry,
R = $scope.interestRateEntry,
T = $scope.termEntry,
A = $scope.pmtPerMonthEntry,
O = $scope.originFeeEntry;
if ((P < 0 || (P && isNaN(P)) || !(Number(P) == Number(P).toFixed(2)) && !isNaN(P))||
(R < 0 || (R && isNaN(R)) || R > 100) ||
(T < 0 || (T && isNaN(T)) || (T && !(T % 1 === 0))) ||
(A < 0 || (A && isNaN(A)) || !(Number(A) == Number(A).toFixed(2) && !isNaN(A)) || (Number(P) > 0 && Number(A) > Number(P)))||
(O < 0 || (O && isNaN(O)) || !(Number(O) == Number(O).toFixed(2) && !isNaN(O)) || (Number(P) > 0 && Number(O) > Number(P)))){
if (P < 0 || (P && isNaN(P)) || !(Number(P) == Number(P).toFixed(2)) && !isNaN(P)) {
$scope.loanAmountError = "Please enter a valid number.";
};
if (R < 0 || (R && isNaN(R)) || R > 100) {
$scope.interestRateError = "Please enter a valid number.";
};
if (T < 0 || (T && isNaN(T)) || (T && !(T % 1 === 0))) {
$scope.termEntryError = "Please enter a valid number.";
};
if (A < 0 || (A && isNaN(A)) || !(Number(A) == Number(A).toFixed(2) && !isNaN(A)) || (Number(A) > 0 && Number(A) > Number(P))) {
$scope.pmtPerMonthError = "Please enter a valid number.";
};
if (O < 0 || (O && isNaN(O)) || !(Number(O) == Number(O).toFixed(2) && !isNaN(P)) || (Number(P) > 0 && Number(O) > Number(P))) {
$scope.originFeeError = "Please enter a valid number.";
};
return false;
};
return true;
};
$scope.proceed = function() {
$scope.validNumberError = "";
$scope.loanAmountError = "";
$scope.interestRateError = "";
$scope.termEntryError = "";
$scope.pmtPerMonthError = "";
$scope.originFeeError = "";
var table = document.getElementById("amort"),
numRows = table.rows.length,
i;
table.style.display = "table";
for(i = 0; i < numRows - 1; i++) {
table.deleteRow(1);
};
$scope.amortizeLoan($scope.getLoanData());
};
$scope.getLoanData = function() {
var P = $scope.loanAmountEntry,
R = $scope.interestRateEntry / 1200,
T = $scope.termEntry,
O = $scope.originFeeEntry,
A = $scope.pmtPerMonthEntry,
B;
if(!O) { O = 0; };
if(!A) {
A = P * ((R * Math.pow(1 + R, T)) / (Math.pow(1 + R, T) - 1));
B = A;
};
if(!P) {
P = A * ((Math.pow(1 + R, T) - 1)/(R * Math.pow(1 + R, T)));
B = P;
};
if(!T) {
T = Math.floor($scope.getBaseLog(1 + R, 1 / (1 - (R * (P / A)))));
B = T;
};
if(!R) {
R = $scope.loanMath.calculateRatePerPeriod(P, 0, T, A, 1, 8)
B = R;
if(R<0) {
$scope.interestRateEntry = R;
$scope.validValueInputs();
return false;
}
};
var totalPmt = A * T,
roundedA = $scope.roundUp(A, 0.01),
//aprPrecision = 0.001,
//apr = $scope.roundDecimal($scope.calculateAPR(P, T, R * 12, O), aprPrecision),
apr = ($scope.loanMath.calculateAPR(P, O, T, A, R, 8) * 100).toFixed(3),
I = $scope.roundDecimal((totalPmt - P), 0.01);
if(P % 1 === 0) { P = P + ".00" };
$scope.APROutput = apr;
$scope.principalOutput = P;
$scope.totalPmt = I + P;
switch(B) {
case A:
$scope.pmtPerMonthEntry = Number(A.toFixed(2));
break;
case P:
$scope.loanAmountEntry = Number($scope.roundUp(P, 0.01));
$scope.principalOutput = Number($scope.roundUp(P, 0.01));
break;
case T:
$scope.termEntry = T;
break;
case R:
$scope.interestRateEntry = (R * 1200).toFixed(3);
break;
default:
break;
};
var loanData = [Number(R * 12), Number(A)];
return loanData;
};
$scope.roundUp = function(num, precision) { //doesnt actually always round up
return (Math.ceil(num / precision) * precision).toFixed(2);
};
$scope.roundDown = function(num, precision) {//doesnt actually always round down
return (Math.floor(num / precision) * precision).toFixed(2);
};
$scope.roundDecimal = function(num, precision) {
return (Math.ceil(num / precision) * precision).toFixed(Math.abs(Math.round($scope.getBaseLog(10, precision))));
};
$scope.getBaseLog = function(base, argument) {
return Math.log(argument) / Math.log(base);
};
$scope.getMonthlyRate = function(A, P, T) { //function from https://groups.google.com/forum/#!msg/sci.math/hop5mBJX1TA/suyDwCZA1ZEJ
var q = $scope.getBaseLog(2, 1 + 1/T),
R = Math.pow((Math.pow(((A/P) + 1), (1/q)) - 1), q) - 1;
// //R = Monthly interest rate (approximation)
return R;
};
$scope.loanMath = {
maxIteration: 100,
defaultPrecision: 8,
calculatePayment: function (amount, rate, term) {
var output = false;
if (rate > 0) {
output = amount * rate / (1 - Math.pow(1 / (1 + rate), term));
}
return output;
},
calculateAmount: function (payment, rate, term) {
var output = false;
if (rate > 0) {
output = payment / (rate / (1 - (Math.pow(1 / (1 + rate), term))));
}
return output;
},
findRateUpperBound: function(guess, monthlyPayment, term, amount) {
var iteration = 0;
var paymentCalc = $scope.loanMath.calculatePayment(amount, guess, term);
while (paymentCalc <= monthlyPayment) {
if (++iteration >= $scope.loanMath.maxIteration) {
return false;
}
guess *= 2;
paymentCalc = $scope.loanMath.calculatePayment(amount, guess, term);
}
return guess;
},
findRateLowerBound: function(guess, monthlyPayment, term, amount) {
var iteration = 0;
var paymentCalc = $scope.loanMath.calculatePayment(amount, guess, term);
while (paymentCalc >= monthlyPayment) {
if (++iteration >= $scope.loanMath.maxIteration) {
return false;
}
guess /= 2;
paymentCalc = $scope.loanMath.calculatePayment(amount, guess, term);
}
return guess;
},
calculateRatePerPeriod: function (amount, fee, term, monthlyPayment, guess, precision){
if (typeof precision === 'undefined') {
precision = $scope.loanMath.defaultPrecision;
}
var amountFinanced = amount - fee;
var precisionTest = Math.pow(10, (-1 * precision));
var paymentTest;
var upperBound = $scope.loanMath.findRateUpperBound(guess, monthlyPayment, term, amountFinanced);
var lowerBound = $scope.loanMath.findRateLowerBound(guess, monthlyPayment, term, amountFinanced);
var middle = (upperBound + lowerBound) / 2;
var iteration = 0;
while (upperBound - lowerBound > precisionTest) {
if (++iteration > $scope.loanMath.maxIteration) {
return false;
}
middle = (upperBound + lowerBound) / 2;
paymentTest = $scope.loanMath.calculatePayment(amountFinanced, middle, term);
if (paymentTest === false || isNaN(paymentTest)) {
return false;
} else if (paymentTest > monthlyPayment) {
upperBound = middle;
} else if (paymentTest < monthlyPayment) {
lowerBound = middle;
} else {
return middle;
}
}
return middle;
},
calculateAPR: function (amount, fee, term, monthlyPayment, guess, numDigits){
if (typeof numDigits === 'undefined') {
numDigits = 3;
}
return parseFloat(Number(12 * $scope.loanMath.calculateRatePerPeriod(amount, fee, term, monthlyPayment, guess, numDigits + 2)).toFixed(numDigits || 2));
}
};
$scope.calculateAPR = function(loanAmount, numPayments, baseRate, fees) {
/*
By Paul Cormier - Sep 10, 2010 - http://webmasterymadesimple.com
loanAmount = the amount borrowed
numPayments = number of monthly payments e.g. 30 years = 360
baseRate = the base percentage rate of the loan. A 5.25% Annual Rate should be passed in as 0.0525 NOT 5.25
fees = the loan closing costs e.g. origination fee, broker fees, etc.
*/
if(! fees){
var fees=0;
}
var loanAmount=parseFloat(loanAmount);
var numPayments=parseFloat(numPayments);
var baseRate=parseFloat(baseRate);
var fees=parseFloat(fees);
var rate = baseRate / 12;
var totalmonthlypayment = ((loanAmount+fees) * rate * Math.pow(1+rate,numPayments)) / (Math.pow(1+rate, numPayments)-1);
var testrate = rate;
var iteration = 1;
var testresult = 0;
//iterate until result = 0
var testdiff = testrate;
while (iteration <= 100) {
testresult = ((testrate * Math.pow(1 + testrate, numPayments)) / (Math.pow(1 + testrate, numPayments) - 1)) - (totalmonthlypayment / loanAmount);
if (Math.abs(testresult) < 0.0000001) break;
if (testresult < 0) testrate += testdiff;
else testrate -= testdiff;
testdiff = testdiff / 2;
iteration++;
}
testrate = testrate * 12 * 100;
return testrate;
};
Number.prototype.formatMoney = function(c, d, t){
var n = this,
c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d == undefined ? "." : d,
t = t == undefined ? "," : t,
s = n < 0 ? "-" : "",
i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
};
$scope.generateChart = function() {
var I = $scope.interestRatePayment,
P = $scope.principalOutput,
A = 360,
T = I + P;
var data = [
{
value : I,
color : "#0c4569",
highlight : "#3d6a87",
label : "Interest"
},
{
value : P,
color : "#C3D3E3",
highlight : "#dbe4ee",
label : "Principal"
}
];
var options = {
nimationEasing : "easeInOutQuart",
animationSteps : 35,
responsive : true,
tooltipTemplate: "<%if (label){%><%=label%>: <%}%>$<%= value %>"
};
var ctx = document.getElementById("pieChart").getContext("2d"),
pieChart = new Chart(ctx).Doughnut(data, options);
};
$scope.amortizeLoan = function(loanData) {
//Grab current date -- FOR PRODUCTION --> MUST USE DATE OF LOAN SERVICING
var currentDate = new Date();
//Month & yeat used for payment date presentation
var currentMN = currentDate.getMonth();
var currentYR = currentDate.getFullYear();
//Original Day of month (1-31)
var currentDayOG = currentDate.getDate();
//Current Day of month (1-31) (to be later used for date rollback)
var currentDay = currentDate.getDate();
//Grabbed interest rate -- adjusted to monthly (was annual)
var monthlyINT = loanData[0]/12;
//Grabbed standard payment amount
var monthlyPMT = loanData[1];
//Grabbed loan amount
var remainingBLN = $scope.loanAmountEntry;
//Calculate interest payment
var amtToINT = monthlyINT * remainingBLN;
//Tracking Total Interest
var totalINT = amtToINT;
//Calculate amount toward principal
var amtToPPL = monthlyPMT - amtToINT;
//Grab Additional Payment info
// var addtlPayments = document.getElementsByClassName("addtlPayments");
// var addtlDates = document.getElementsByClassName("addtlDates");
//Grab table element from page
var table = document.getElementById("amort");
var pmtCounter = 1;
while (remainingBLN > 0) {
//Decrement Remaining Balance
remainingBLN -= amtToPPL;
//Adjusted rounding for final payment
if (remainingBLN < amtToPPL) {
amtToPPL += remainingBLN;
remainingBLN = 0;
};
//Rollback for loans with payment dates of 31st of the month
if (currentDay < currentDayOG) {
var diff = currentDayOG - currentDay;
currentDate.setDate(diff);
currentDate.setMonth(currentMN);
};
///Create new row
var row = table.insertRow(-1);
//Create data cells for new row
// var pmt = row.insertCell(0); //new row for payment number
var mn = row.insertCell(0);
var pa = row.insertCell(1);
var pr = row.insertCell(2);
var i = row.insertCell(3);
var ti = row.insertCell(4);
var b = row.insertCell(5);
//Date string formatting
var formattedDate = (currentDate.getUTCMonth() + 1) + '/' + currentDate.getUTCDate() + '/' + currentDate.getUTCFullYear();
//Populate data cells
// pmt.innerHTML = pmtCounter;
mn.innerHTML = formattedDate;
pa.innerHTML = "$" + monthlyPMT.formatMoney(2,'.',',');
pr.innerHTML = "$" + amtToPPL.formatMoney(2,'.',',');
i.innerHTML = "$" + amtToINT.formatMoney(2,'.',',');
ti.innerHTML = "$" + totalINT.formatMoney(2,'.',',');
b.innerHTML = "$" + remainingBLN.formatMoney(2,'.',',');
//increment counter for next row
pmtCounter += 1;
//Update month values
currentMN += 1;
//Account for year changes (+1 yr reset month to jan)
if (currentMN > 11) {
currentYR += 1;
currentMN = 0;
};
//Adjust date variable for changes
currentDate.setFullYear(currentYR);
currentDate.setMonth(currentMN);
if (currentMN !== 1 && currentMN !== 3 && currentMN !== 5 && currentMN !== 8 && currentMN !== 10) {
currentDate.setDate(currentDayOG);
};
//Update current day value
currentDay = currentDate.getDate();
//Update Interest payment
amtToINT = monthlyINT * remainingBLN;
//Update payment toward principal
amtToPPL = monthlyPMT - amtToINT;
//Updating total Interest
totalINT += amtToINT;
}
$scope.interestRatePayment = Number(totalINT.toFixed(2)); //correct interest in box/interest in table differnce (matches table)
$scope.pmtPerMonthEntry = Number(monthlyPMT.toFixed(2));
$scope.principalOutput = Number($scope.loanAmountEntry).toFixed(2);
$scope.totalOutput = (Number($scope.principalOutput) + Number($scope.interestRatePayment)).toFixed(2);
$scope.termEntry = Number(pmtCounter - 1);
};
}
}());