moneyfunx 3.0.10 → 3.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../../src/lib/debt/payments.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EAEb,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EAAE,EACd,OAAO,EAAE,MAAM,GACd,MAAM,CAYR;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,OAAO,GACrB,MAAM,CAOR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,GAAE,MAAU,EACvB,SAAS,GAAE,MAAU,GACpB,aAAa,EAAE,CA2BjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,OAAe,GAC7B,oBAAoB,
|
|
1
|
+
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../../src/lib/debt/payments.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EAEb,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EAAE,EACd,OAAO,EAAE,MAAM,GACd,MAAM,CAYR;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,OAAO,GACrB,MAAM,CAOR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,GAAE,MAAU,EACvB,SAAS,GAAE,MAAU,GACpB,aAAa,EAAE,CA2BjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,OAAe,GAC7B,oBAAoB,CA6ItB"}
|
|
@@ -102,58 +102,76 @@ export function payLoans(loans, payment, reduceMinimum = false) {
|
|
|
102
102
|
let totalLifetimePrincipal = 0;
|
|
103
103
|
let totalAmortizationSchedule = [];
|
|
104
104
|
while (paidLoans < loans.length) {
|
|
105
|
-
const
|
|
105
|
+
const activeLoans = loans.slice(paidLoans);
|
|
106
|
+
const firstLoan = activeLoans[0];
|
|
107
|
+
// 1. Determine the "Driver" parameters (the loan being paid off in this chunk)
|
|
106
108
|
const firstLoanPayment = (firstLoan.minPayment +
|
|
107
|
-
determineExtraPayment(
|
|
109
|
+
determineExtraPayment(activeLoans, monthlyPayment));
|
|
108
110
|
const firstLoanPrincipalRemaining = loanPrincipalsRemaining[firstLoan.id];
|
|
111
|
+
// How long does this chunk last?
|
|
109
112
|
const periodsToPay = firstLoan.numPaymentsToZero(firstLoanPayment, firstLoanPrincipalRemaining);
|
|
110
|
-
|
|
111
|
-
//
|
|
113
|
+
// 2. Calculate the Final Payment specifics for the driver loan
|
|
114
|
+
// (Needed for carryover calculation)
|
|
112
115
|
const finalPrincipal = firstLoan.principalRemaining(periodsToPay - 1, firstLoanPayment, firstLoanPrincipalRemaining);
|
|
113
116
|
const firstLoanFinalPayment = finalPrincipal + firstLoan.accrueInterest(finalPrincipal);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
];
|
|
118
|
-
// Merge into totals
|
|
119
|
-
totalAmortizationSchedule = [
|
|
120
|
-
...totalAmortizationSchedule,
|
|
121
|
-
...firstLoanAmortizedPayments
|
|
122
|
-
];
|
|
123
|
-
paidLoans += 1;
|
|
124
|
-
if (reduceMinimum) {
|
|
125
|
-
monthlyPayment -= firstLoan.minPayment;
|
|
126
|
-
}
|
|
127
|
-
;
|
|
128
|
-
// handle calculating information for the rest of the loans
|
|
129
|
-
loans.slice(paidLoans).forEach((loan, index) => {
|
|
117
|
+
// 3. Process ALL active loans for this chunk of time
|
|
118
|
+
activeLoans.forEach((loan, index) => {
|
|
119
|
+
const isDriverLoan = index === 0;
|
|
130
120
|
const loanPrincipalRemaining = loanPrincipalsRemaining[loan.id];
|
|
131
|
-
|
|
121
|
+
// Determine payment amount: Driver gets extra, others get minimum
|
|
122
|
+
const paymentAmount = isDriverLoan ? firstLoanPayment : loan.minPayment;
|
|
123
|
+
// Determine duration: Others are capped by the Driver's time-to-zero
|
|
124
|
+
const duration = isDriverLoan
|
|
125
|
+
? periodsToPay
|
|
126
|
+
: Math.min(periodsToPay, loan.numPaymentsToZero(loan.minPayment, loanPrincipalRemaining));
|
|
127
|
+
// Determine carryover: Only applies to the SECOND loan (index 1), derived from Driver
|
|
128
|
+
const carryoverAmount = (index === 1)
|
|
129
|
+
? determineCarryover(firstLoan, firstLoanPayment, firstLoanFinalPayment, reduceMinimum)
|
|
130
|
+
: 0;
|
|
131
|
+
const loanAmortizedPayments = amortizePayments(loan, loanPrincipalRemaining, paymentAmount, duration, periodsElapsed, carryoverAmount);
|
|
132
|
+
// A. Append to individual loan schedule
|
|
132
133
|
paymentSchedule[loan.id].amortizationSchedule = [
|
|
133
134
|
...paymentSchedule[loan.id].amortizationSchedule,
|
|
134
135
|
...loanAmortizedPayments,
|
|
135
136
|
];
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
// B. Merge into Total Schedule
|
|
138
|
+
if (isDriverLoan) {
|
|
139
|
+
// The Driver extends the timeline. We append its records to initialize the new period slots in Totals.
|
|
140
|
+
totalAmortizationSchedule = [
|
|
141
|
+
...totalAmortizationSchedule,
|
|
142
|
+
...loanAmortizedPayments
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Followers merge into the existing slots created by the Driver.
|
|
147
|
+
// We only map over the NEW segment of the total schedule to avoid re-scanning history (optional optimization),
|
|
148
|
+
// but for safety/simplicity we map the whole structure and match by period.
|
|
149
|
+
totalAmortizationSchedule = totalAmortizationSchedule.map((element) => {
|
|
150
|
+
const matchedInnerElement = loanAmortizedPayments.find((innerElement) => innerElement.period === element.period);
|
|
151
|
+
return (matchedInnerElement != null)
|
|
152
|
+
? {
|
|
153
|
+
period: element.period,
|
|
154
|
+
principal: element.principal + matchedInnerElement.principal,
|
|
155
|
+
interest: element.interest + matchedInnerElement.interest,
|
|
156
|
+
principalRemaining: element.principalRemaining + matchedInnerElement.principalRemaining
|
|
157
|
+
}
|
|
158
|
+
: element;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// Update tracking state for next iteration
|
|
162
|
+
if (paymentSchedule[loan.id].amortizationSchedule.length > 0) {
|
|
163
|
+
const fullSchedule = paymentSchedule[loan.id].amortizationSchedule;
|
|
164
|
+
loanPrincipalsRemaining[loan.id] = fullSchedule[fullSchedule.length - 1].principalRemaining;
|
|
153
165
|
}
|
|
154
166
|
});
|
|
167
|
+
paidLoans += 1;
|
|
155
168
|
periodsElapsed += periodsToPay;
|
|
169
|
+
if (reduceMinimum) {
|
|
170
|
+
monthlyPayment -= firstLoan.minPayment;
|
|
171
|
+
}
|
|
172
|
+
;
|
|
156
173
|
}
|
|
174
|
+
// Final Totals Calculation
|
|
157
175
|
for (const loan of loans) {
|
|
158
176
|
const loanLifetimeInterest = (paymentSchedule[loan.id].amortizationSchedule.reduce((lifetimeInterest, record) => lifetimeInterest + record.interest, 0));
|
|
159
177
|
paymentSchedule[loan.id].lifetimeInterest = loanLifetimeInterest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contributions.d.ts","sourceRoot":"","sources":["../../../src/lib/investment/contributions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"contributions.d.ts","sourceRoot":"","sources":["../../../src/lib/investment/contributions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EACV,kBAAkB,EAClB,+BAA+B,EAGhC,MAAM,oCAAoC,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,UAAU,EAAE,EACzB,YAAY,EAAE,MAAM,GACnB,MAAM,CAMR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,MAAU,EACvB,wBAAwB,GAAE,OAAc,GACvC,kBAAkB,CAkBpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,WAAW,GAAE,MAAU,EACvB,wBAAwB,GAAE,OAAc,GACvC,kBAAkB,EAAE,CAmBtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,UAAU,EAAE,EACzB,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,wBAAwB,GAAE,OAAc,GACvC,+BAA+B,CAyGjC"}
|
|
@@ -68,8 +68,7 @@ export function amortizeContributions(instrument, initialBalance, contribution,
|
|
|
68
68
|
const periodicContribution = instrument.validateContribution(contribution, ytd);
|
|
69
69
|
const record = amortizeContribution(instrument, currentBalance, periodicContribution, period + startPeriod, accrueBeforeContribution);
|
|
70
70
|
currentBalance = record.currentBalance;
|
|
71
|
-
// Reset YTD every 12 periods
|
|
72
|
-
// NOTE: This logic assumes period 0 is the start of a year.
|
|
71
|
+
// Reset YTD every 12 periods
|
|
73
72
|
period % 12 === 0 ? ytd = 0 : ytd += periodicContribution;
|
|
74
73
|
contributionSchedule.push(record);
|
|
75
74
|
}
|
|
@@ -130,21 +129,35 @@ export function contributeInstruments(instruments, contribution, numContribution
|
|
|
130
129
|
contributionSchedules[instrument.id].lifetimeGrowth = instrumentLifetimeGrowth;
|
|
131
130
|
totalLifetimeContribution += instrumentLifetimeContribution;
|
|
132
131
|
totalLifetimeGrowth += instrumentLifetimeGrowth;
|
|
133
|
-
// Merge totals safely
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
132
|
+
// Merge totals safely handling varying lengths
|
|
133
|
+
if (totalAmortizationSchedule.length === 0) {
|
|
134
|
+
// Initialize with the first instrument's schedule
|
|
135
|
+
totalAmortizationSchedule = [...instrumentSchedule];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// We need to merge. If schedules are different lengths, we map over the longer one.
|
|
139
|
+
const maxLen = Math.max(totalAmortizationSchedule.length, instrumentSchedule.length);
|
|
140
|
+
const newTotal = [];
|
|
141
|
+
for (let i = 0; i < maxLen; i++) {
|
|
142
|
+
const totalRecord = totalAmortizationSchedule[i];
|
|
143
|
+
const instrumentRecord = instrumentSchedule[i];
|
|
144
|
+
if (totalRecord && instrumentRecord) {
|
|
145
|
+
newTotal.push({
|
|
146
|
+
period: totalRecord.period,
|
|
147
|
+
contribution: totalRecord.contribution + instrumentRecord.contribution,
|
|
148
|
+
growth: totalRecord.growth + instrumentRecord.growth,
|
|
149
|
+
currentBalance: totalRecord.currentBalance + instrumentRecord.currentBalance
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (totalRecord) {
|
|
153
|
+
newTotal.push(totalRecord);
|
|
154
|
+
}
|
|
155
|
+
else if (instrumentRecord) {
|
|
156
|
+
newTotal.push(instrumentRecord);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
totalAmortizationSchedule = newTotal;
|
|
160
|
+
}
|
|
148
161
|
}
|
|
149
162
|
contributionSchedules[TOTALS] = {
|
|
150
163
|
lifetimeContribution: totalLifetimeContribution,
|