UNPKG

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
(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); }; } }());