Skip to content

Commit

Permalink
several improvements in calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
robhicks committed Jun 14, 2014
1 parent 1f3dada commit dc9f0f6
Show file tree
Hide file tree
Showing 3 changed files with 1,496 additions and 72 deletions.
175 changes: 103 additions & 72 deletions finance.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
aggregateLateFees: aggregateLateFees,
interestAllocationForPayment: interestAllocationForPayment,
paymentAllocations: paymentAllocations,
addLateFees: addLateFees
addLateFees: addLateFees,
pastDueAmount: pastDueAmount
};
var _, moment;

Expand All @@ -36,6 +37,30 @@
window.finance = this;
}

function pastDueAmount(loan) {
var d = Q.defer();
var paymentsDue = loan.amortizationTable.filter(function(tx) {
return moment(tx.txDate).isBefore(moment());
});
var paymentsMade = loan.transactions.filter(function(tx){
return (moment(tx.txDate).isBefore(moment()) && tx.type !== 'Late Fee');
});
var sumPaymentsDue = 0, sumPaymentsMade = 0;

paymentsDue.forEach(function(pmt){
sumPaymentsDue += Number(pmt.amount);
});

paymentsMade.forEach(function(pmt){
sumPaymentsMade += Number(pmt.amount);
});

loan.pastDueAmount = sumPaymentsDue - sumPaymentsMade;
d.resolve(loan);

return d.promise;
}

/*
presentValueOfLumpSum
-----------
Expand Down Expand Up @@ -211,12 +236,12 @@
*/
function addAmorizationTable(loan, cb) {
var d = Q.defer();
if(!loan) d.reject('loan object not provided');
else if(!loan.loanAmount) d.reject('loan amount missing');
else if(!loan.term) d.reject('loan term missing');
else if(!loan.interestRate) d.reject('interest rate not provided');
else if(!loan.firstPaymentDate) d.reject('first payment date missing');
else if(!loan.paymentAmount) d.reject('missing loan payment amount');
if (!loan) d.reject('loan object not provided');
else if (!loan.loanAmount) d.reject('loan amount missing');
else if (!loan.term) d.reject('loan term missing');
else if (!loan.interestRate) d.reject('interest rate not provided');
else if (!loan.firstPaymentDate) d.reject('first payment date missing');
else if (!loan.paymentAmount) d.reject('missing loan payment amount');
else {
var loanAmount = Number(loan.loanAmount);
var term = Number(loan.term);
Expand Down Expand Up @@ -355,11 +380,10 @@
*/
function isLoanPastDue(loan, cb) {
var d = Q.defer();
if (!loan && !loan.transactions) d.reject('loan object not provided');
var lateFeeTxs = loan.transactions.filter(function(tx){
return tx.type === 'Late Fee';
});
loan.pastDue = lateFeeTxs.length > 0;
if (!loan && !loan.transactions) return d.reject('loan object not provided');

loan.pastDue = loan.pastDueAmount > 0;
console.log('isLoanPastDue');
d.resolve(loan);
if (cb) return cb(loan);
return d.promise;
Expand Down Expand Up @@ -549,11 +573,11 @@
d.reject('required parameters for dateLastPaymentWasReceived not provided');
}

var paymentTxs = loan.transactions.filter(function(tx){
var paymentTxs = loan.transactions.filter(function (tx) {
return tx.type !== 'Late Fee';
});

last = _.max(paymentTxs, function(rec){
last = _.max(paymentTxs, function (rec) {
return rec.txDate;
});

Expand All @@ -576,8 +600,9 @@
if (!loan || !loan.loanAmount || !loan.transactions) {
d.reject(new Error('required parameters for outstandingPrincipal not provided'))
} else {
loan.transactions.forEach(function (tx) {
sumOfPayments += typeof tx.principal === "number" ? tx.principal : 0;
var paymentTxs = loan.transactions.filter(function(tx){ return tx.type !== "Late Fee"});
paymentTxs.forEach(function (tx) {
sumOfPayments += tx.principal;
});
loan.loanBalance = loan.loanAmount - sumOfPayments;
d.resolve(loan);
Expand All @@ -586,6 +611,19 @@
return d.promise;
}

function remaininPrincipal(loan) {
var sumOfPayments = 0;
if (!loan || !loan.loanAmount || !loan.transactions) {
return new Error("required loan parameters not provided");
} else {
var paymentTxs = loan.transactions.filter(function(tx){ return tx.type !== "Late Fee"});
paymentTxs.forEach(function (tx) {
sumOfPayments += tx.principal;
});
return loan.loanAmount - sumOfPayments;
}
}

/**
*
* @param loan
Expand All @@ -601,14 +639,14 @@
if (!loan || !loan.transactions) {
d.reject(new Error('required parameters for aggregateLateFees not provided'));
} else {
lateFeeTxs = loan.transactions.filter(function(tx){
lateFeeTxs = loan.transactions.filter(function (tx) {
return moment(tx.txDate).isBefore(determinationDate) && tx.type === "Late Fee";
});
lateFeeTxs.forEach(function(tx){
if(!isNaN(tx.amount)) temp += tx.amount;
lateFeeTxs.forEach(function (tx) {
if (!isNaN(tx.amount)) temp += tx.amount;
});
loan.aggregateLateFees = temp;
// console.log(loan);
console.log('aggregateLateFees');
d.resolve(loan);
if (cb) return cb(loan);
}
Expand All @@ -628,63 +666,53 @@
* - daysInYear
* @returns {exports.pending.promise|*|promise|Q.defer.promise|Deferred.promise|Pending.promise}
*/
function interestAllocationForPayment(loan, cb) {
function interestAllocationForPayment(loan, tx, cb) {
var d = Q.defer();
var days = 0;
var daysInYear = loan.daysInYear || 365;
var determinationDate = loan.determinationDate ? moment(loan.determinationDate) : moment();
var closingDate;
var rate;
var interestPaid = 0;
if (!loan || !loan.closingDate || !loan.loanAmount || !loan.interestRate || !loan.paymentAmount) {
d.reject(new Error('required parameters for accruedInterestForLoanTx not provided'));
} else {
closingDate = moment(loan.closingDate);
days = determinationDate.diff(closingDate, 'days');
rate = loan.interestRate / 100 / daysInYear;
var accruedInterest = loan.loanAmount * days * rate;
loan.transactions.forEach(function (tx) {
var paymentDate = moment(tx.date);
if (paymentDate.isBefore(determinationDate)) interestPaid += tx.interest;
});
loan.allocationToInterest = (accruedInterest - interestPaid) < loan.paymentAmount
? accruedInterest - interestPaid
: loan.paymentAmount;
d.resolve(loan);
}
d.resolve(paymentInterest(loan, tx));
if (cb) return cb(loan);
return d.promise;
}

function paymentInterest(loan, tx) {
var accruedInterest = 0;
var daysInYear = loan.daysInYear || 365;
var txDate = moment(tx.txDate);
var previousTxs = loan.transactions.filter(function (ttx) {
return moment(ttx.txDate).isBefore(txDate) && ttx.type !== "Late Fee";
});
var lastTx = previousTxs.length > 0 ? _.max(previousTxs, function (rec) {
return rec.txDate
}) : null;
var previousPaymentDate = lastTx ? moment(lastTx.txDate) : moment(loan.closingDate);
var paymentDate = moment(tx.txDate);
var rate = loan.interestRate / 100 / daysInYear;
var elapsedDays = txDate.diff(previousPaymentDate, 'days');
loan.loanBalance = getLoanBalance(loan, txDate);
return elapsedDays * rate * loan.loanBalance;
}

function paymentAllocations(loan, cb) {
var d = Q.defer();
var daysInYear = loan.daysInYear || 365;
var interestPaid = 0;
if (!loan || !loan.closingDate || !loan.loanAmount || !loan.interestRate || !loan.paymentAmount) {
d.reject(new Error('required parameters for paymentAllocations not provided'));
} else {
var rate = loan.interestRate / 100 / daysInYear;
var closingDate = moment(loan.closingDate);
var txDate, days, accruedInterest;

//Calculate parameters of payment transactions
loan.transactions.forEach(function (tx, i) {
if (tx.type !== "Late Fee") {
txDate = moment(tx.txDate);
days = txDate.diff(closingDate, 'days');
accruedInterest = loan.loanAmount * days * rate;
interestPaid = calcInterestPaid(loan, tx.txDate);
tx.interest = (accruedInterest - interestPaid) < tx.amount
? accruedInterest - interestPaid
: tx.amount;
tx.principal = tx.amount - tx.interest;
// console.log(i, tx.paymentNumber);
tx.paymentDueDate = i > 0 && loan.amortizationTable[tx.paymentNumber] ? loan.amortizationTable[tx.paymentNumber - 1].txDate : loan.firstPaymentDate;
tx.loanBalance = getLoanBalance(loan, txDate) - tx.principal;
}

var paymentTxs = loan.transactions.filter(function(tx) { return tx.type !== "Late Fee"});

console.log(loan.transactions);
paymentTxs.forEach(function(tx, i){
tx.interest = paymentInterest(loan, tx);
tx.principal = tx.amount - tx.interest;
// console.log(i, 'interest: ', tx.interest, ' principal: ', tx.principal);
tx.loanBalance = getLoanBalance(loan, tx.txDate) - tx.principal;
});

console.log(loan.transactions);

d.resolve(loan);
console.log('paymentAllocations');
}
if (cb) return cb(loan);
return d.promise;
Expand All @@ -696,7 +724,7 @@
* @param cb
* @returns {*}
*/
function addLateFees(loan, cb){
function addLateFees(loan, cb) {

var d = Q.defer();
if (!loan || !loan.closingDate || !loan.loanAmount || !loan.interestRate || !loan.paymentAmount) {
Expand All @@ -710,7 +738,7 @@
return tx.type === 'Late Fee';
});
//remove all existing late fees
lateFeeTxs.forEach(function(tx){
lateFeeTxs.forEach(function (tx) {
loan.transactions.id(tx._id).remove();
});

Expand All @@ -721,12 +749,13 @@
//until the determination date
elapsedMonths = determinationDate.diff(moment(loan.firstPaymentDate), 'months') + 1;

for(i = 0; i < elapsedMonths; i++){
for (i = 0; i < elapsedMonths; i++) {
pmtDate = moment(loan.firstPaymentDate).add('months', i);
graceDate = moment(loan.firstPaymentDate).add('months', i).add('days', loan.daysUntilLate);
graceDate = moment(loan.firstPaymentDate).add('months', i).add('days', loan.daysUntilLate);
if (!paymentMadeOnTime(pmtTransactions, pmtDate, graceDate, loan.paymentAmount)
&& pmtDate.isBefore(determinationDate) && !lateFeeAppliedForDate(loan, graceDate)) {
txDate = graceDate.toISOString();
console.log('loan balance in late fees: ', getLoanBalance(loan, txDate));
loan.transactions.push({
txDate: txDate,
type: "Late Fee",
Expand All @@ -738,13 +767,13 @@
}
}


console.log('addLateFees');
d.resolve(loan);
if (cb) return cb(loan);
return d.promise;
}

function paymentMadeOnTime(pmtTransactions, paymentDate, graceDate, amount){
function paymentMadeOnTime(pmtTransactions, paymentDate, graceDate, amount) {
var result = false;
var tx, txDate;
for (var i = 0, len = pmtTransactions.length; i < len; i++) {
Expand All @@ -758,12 +787,14 @@
return result;
}

function lateFeeAppliedForDate(loan, txDate){
function lateFeeAppliedForDate(loan, txDate) {
txDate = txDate._isAMomentObject ? txDate : moment(txDate);
var result = false;
var lateFeeTxs = loan.transactions.filter(function(tx){return tx.type === "Late Fee"});
lateFeeTxs.forEach(function(tx){
if(moment(tx.txDate).isSame(txDate)) result = true;
var lateFeeTxs = loan.transactions.filter(function (tx) {
return tx.type === "Late Fee"
});
lateFeeTxs.forEach(function (tx) {
if (moment(tx.txDate).isSame(txDate)) result = true;
});
return result;
}
Expand Down
Loading

0 comments on commit dc9f0f6

Please sign in to comment.