moneyfunx 3.0.9 → 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.
- package/build/lib/debt/payments.d.ts +2 -2
- package/build/lib/debt/payments.d.ts.map +1 -1
- package/build/lib/debt/payments.js +71 -47
- package/build/lib/investment/contributions.d.ts +7 -7
- package/build/lib/investment/contributions.d.ts.map +1 -1
- package/build/lib/investment/contributions.js +45 -27
- package/package.json +1 -1
|
@@ -32,8 +32,8 @@ export declare function determineCarryover(loan: Loan, loanPayment: number, loan
|
|
|
32
32
|
*
|
|
33
33
|
* @param {Loan} loan The loan to amortize payments for
|
|
34
34
|
* @param {number} principal The amount borrowed
|
|
35
|
-
* @param {number} payment The amount to pay to the loan's balance each period
|
|
36
|
-
* @param {number} numPayments The number of periods to make payments to the loan
|
|
35
|
+
* @param {number|null} payment The amount to pay to the loan's balance each period
|
|
36
|
+
* @param {number|null} numPayments The number of periods to make payments to the loan
|
|
37
37
|
* @param {number} startPeriod An initial offset of periods to 'fast-forward' the state of the loan to prior to calculation of each period
|
|
38
38
|
* @param {number} carryover An additional amount to pay towards a loan, used when a residual amount is available from paying off the previous loan this period
|
|
39
39
|
* @returns {PaymentRecord[]} The amortization schedule for the number of payments of payment made to the loan from the provided start period
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../../src/lib/debt/payments.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
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"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import * as errors from '../errors.js';
|
|
7
|
+
import { TOTALS } from '../constants.js';
|
|
7
8
|
/**
|
|
8
9
|
*
|
|
9
10
|
* Calculates the extra amount in a payment after all loans' minimum payments are met
|
|
@@ -45,27 +46,24 @@ export function determineCarryover(loan, loanPayment, loanFinalPayment, reduceMi
|
|
|
45
46
|
*
|
|
46
47
|
* @param {Loan} loan The loan to amortize payments for
|
|
47
48
|
* @param {number} principal The amount borrowed
|
|
48
|
-
* @param {number} payment The amount to pay to the loan's balance each period
|
|
49
|
-
* @param {number} numPayments The number of periods to make payments to the loan
|
|
49
|
+
* @param {number|null} payment The amount to pay to the loan's balance each period
|
|
50
|
+
* @param {number|null} numPayments The number of periods to make payments to the loan
|
|
50
51
|
* @param {number} startPeriod An initial offset of periods to 'fast-forward' the state of the loan to prior to calculation of each period
|
|
51
52
|
* @param {number} carryover An additional amount to pay towards a loan, used when a residual amount is available from paying off the previous loan this period
|
|
52
53
|
* @returns {PaymentRecord[]} The amortization schedule for the number of payments of payment made to the loan from the provided start period
|
|
53
54
|
*/
|
|
54
55
|
export function amortizePayments(loan, principal, payment, numPayments, startPeriod = 0, carryover = 0) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (numPayments === null) {
|
|
60
|
-
numPayments = loan.numPaymentsToZero(payment);
|
|
61
|
-
}
|
|
56
|
+
// Strict null check handling
|
|
57
|
+
let actualPayment = (payment !== null) ? payment : loan.minPayment;
|
|
58
|
+
actualPayment = loan.validatePayment(actualPayment);
|
|
59
|
+
let actualNumPayments = (numPayments !== null) ? numPayments : loan.numPaymentsToZero(actualPayment);
|
|
62
60
|
const amortizationSchedule = [];
|
|
63
61
|
let principalRemaining = principal;
|
|
64
|
-
for (let period = 0; period <
|
|
62
|
+
for (let period = 0; period < actualNumPayments; period++) {
|
|
65
63
|
const interestThisPeriod = loan.accrueInterest(principalRemaining);
|
|
66
|
-
const principalThisPeriod = Math.min((period ===
|
|
67
|
-
?
|
|
68
|
-
:
|
|
64
|
+
const principalThisPeriod = Math.min((period === actualNumPayments - 1
|
|
65
|
+
? actualPayment + carryover
|
|
66
|
+
: actualPayment) - interestThisPeriod, principalRemaining);
|
|
69
67
|
principalRemaining -= principalThisPeriod;
|
|
70
68
|
amortizationSchedule.push({
|
|
71
69
|
period: startPeriod + period + 1,
|
|
@@ -104,50 +102,76 @@ export function payLoans(loans, payment, reduceMinimum = false) {
|
|
|
104
102
|
let totalLifetimePrincipal = 0;
|
|
105
103
|
let totalAmortizationSchedule = [];
|
|
106
104
|
while (paidLoans < loans.length) {
|
|
107
|
-
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)
|
|
108
108
|
const firstLoanPayment = (firstLoan.minPayment +
|
|
109
|
-
determineExtraPayment(
|
|
109
|
+
determineExtraPayment(activeLoans, monthlyPayment));
|
|
110
110
|
const firstLoanPrincipalRemaining = loanPrincipalsRemaining[firstLoan.id];
|
|
111
|
+
// How long does this chunk last?
|
|
111
112
|
const periodsToPay = firstLoan.numPaymentsToZero(firstLoanPayment, firstLoanPrincipalRemaining);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...totalAmortizationSchedule,
|
|
120
|
-
...firstLoanAmortizedPayments
|
|
121
|
-
];
|
|
122
|
-
paidLoans += 1;
|
|
123
|
-
if (reduceMinimum) {
|
|
124
|
-
monthlyPayment -= firstLoan.minPayment;
|
|
125
|
-
}
|
|
126
|
-
;
|
|
127
|
-
// handle calculating information for the rest of the loans
|
|
128
|
-
loans.slice(paidLoans).forEach((loan, index) => {
|
|
113
|
+
// 2. Calculate the Final Payment specifics for the driver loan
|
|
114
|
+
// (Needed for carryover calculation)
|
|
115
|
+
const finalPrincipal = firstLoan.principalRemaining(periodsToPay - 1, firstLoanPayment, firstLoanPrincipalRemaining);
|
|
116
|
+
const firstLoanFinalPayment = finalPrincipal + firstLoan.accrueInterest(finalPrincipal);
|
|
117
|
+
// 3. Process ALL active loans for this chunk of time
|
|
118
|
+
activeLoans.forEach((loan, index) => {
|
|
119
|
+
const isDriverLoan = index === 0;
|
|
129
120
|
const loanPrincipalRemaining = loanPrincipalsRemaining[loan.id];
|
|
130
|
-
|
|
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
|
|
131
133
|
paymentSchedule[loan.id].amortizationSchedule = [
|
|
132
134
|
...paymentSchedule[loan.id].amortizationSchedule,
|
|
133
135
|
...loanAmortizedPayments,
|
|
134
136
|
];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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;
|
|
165
|
+
}
|
|
148
166
|
});
|
|
167
|
+
paidLoans += 1;
|
|
149
168
|
periodsElapsed += periodsToPay;
|
|
169
|
+
if (reduceMinimum) {
|
|
170
|
+
monthlyPayment -= firstLoan.minPayment;
|
|
171
|
+
}
|
|
172
|
+
;
|
|
150
173
|
}
|
|
174
|
+
// Final Totals Calculation
|
|
151
175
|
for (const loan of loans) {
|
|
152
176
|
const loanLifetimeInterest = (paymentSchedule[loan.id].amortizationSchedule.reduce((lifetimeInterest, record) => lifetimeInterest + record.interest, 0));
|
|
153
177
|
paymentSchedule[loan.id].lifetimeInterest = loanLifetimeInterest;
|
|
@@ -155,7 +179,7 @@ export function payLoans(loans, payment, reduceMinimum = false) {
|
|
|
155
179
|
totalLifetimeInterest += loanLifetimeInterest;
|
|
156
180
|
totalLifetimePrincipal += loan.currentBalance;
|
|
157
181
|
}
|
|
158
|
-
paymentSchedule
|
|
182
|
+
paymentSchedule[TOTALS] = {
|
|
159
183
|
lifetimeInterest: totalLifetimeInterest,
|
|
160
184
|
lifetimePrincipal: totalLifetimePrincipal,
|
|
161
185
|
amortizationSchedule: totalAmortizationSchedule,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import type { Instrument } from '../investment/instrument.js';
|
|
7
|
-
import { ContributionRecord, InstrumentsContributionSchedule } from '../investment/contributionTypes.js';
|
|
7
|
+
import type { ContributionRecord, InstrumentsContributionSchedule } from '../investment/contributionTypes.js';
|
|
8
8
|
/**
|
|
9
9
|
*
|
|
10
10
|
* @param {IInstrument[]} instruments The instruments to allocate maximum contributions
|
|
@@ -19,8 +19,8 @@ export declare function determineExtraContribution(instruments: Instrument[], co
|
|
|
19
19
|
* @param {number} contribution The amount to contribute to the instrument's balance
|
|
20
20
|
* @param {number[int]} startPeriod The initial offset for period values
|
|
21
21
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* true: A -> C
|
|
23
|
+
* false: C -> A
|
|
24
24
|
* @returns {ContributionRecord} The amortized contribution
|
|
25
25
|
*/
|
|
26
26
|
export declare function amortizeContribution(instrument: Instrument, currentBalance: number, contribution: number, startPeriod?: number, accrueBeforeContribution?: boolean): ContributionRecord;
|
|
@@ -34,8 +34,8 @@ export declare function amortizeContribution(instrument: Instrument, currentBala
|
|
|
34
34
|
* @param {number} numContributions The number of periods to make contributions to the instrument
|
|
35
35
|
* @param {number[int]} startPeriod The inital offset for period values
|
|
36
36
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* true: A -> C
|
|
38
|
+
* false: C -> A
|
|
39
39
|
* @returns {ContributionRecord[]} The amortized contributions
|
|
40
40
|
*/
|
|
41
41
|
export declare function amortizeContributions(instrument: Instrument, initialBalance: number, contribution: number, numContributions: number, startPeriod?: number, accrueBeforeContribution?: boolean): ContributionRecord[];
|
|
@@ -45,8 +45,8 @@ export declare function amortizeContributions(instrument: Instrument, initialBal
|
|
|
45
45
|
* @param {number} contribution The total amount to contirbute each period
|
|
46
46
|
* @param {number[int]} numContributions The number of periods to contribute
|
|
47
47
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* true: A -> C
|
|
49
|
+
* false: C -> A
|
|
50
50
|
* @returns {InstrumentsContributionSchedule} The amortized contributions for all instruments
|
|
51
51
|
*/
|
|
52
52
|
export declare function contributeInstruments(instruments: Instrument[], contribution: number, numContributions: number, accrueBeforeContribution?: boolean): InstrumentsContributionSchedule;
|
|
@@ -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"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* This file containts functions for computing detailed information on contributing to investments
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
+
import { TOTALS } from '../constants.js';
|
|
6
7
|
/**
|
|
7
8
|
*
|
|
8
9
|
* @param {IInstrument[]} instruments The instruments to allocate maximum contributions
|
|
@@ -21,26 +22,27 @@ export function determineExtraContribution(instruments, contribution) {
|
|
|
21
22
|
* @param {number} contribution The amount to contribute to the instrument's balance
|
|
22
23
|
* @param {number[int]} startPeriod The initial offset for period values
|
|
23
24
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* true: A -> C
|
|
26
|
+
* false: C -> A
|
|
26
27
|
* @returns {ContributionRecord} The amortized contribution
|
|
27
28
|
*/
|
|
28
29
|
export function amortizeContribution(instrument, currentBalance, contribution, startPeriod = 0, accrueBeforeContribution = true) {
|
|
29
30
|
let interestThisPeriod;
|
|
31
|
+
let newBalance = currentBalance;
|
|
30
32
|
if (accrueBeforeContribution) {
|
|
31
|
-
interestThisPeriod = instrument.accrueInterest(
|
|
32
|
-
|
|
33
|
+
interestThisPeriod = instrument.accrueInterest(newBalance);
|
|
34
|
+
newBalance += contribution + interestThisPeriod;
|
|
33
35
|
}
|
|
34
36
|
else {
|
|
35
|
-
|
|
36
|
-
interestThisPeriod = instrument.accrueInterest(
|
|
37
|
-
|
|
37
|
+
newBalance += contribution;
|
|
38
|
+
interestThisPeriod = instrument.accrueInterest(newBalance);
|
|
39
|
+
newBalance += interestThisPeriod;
|
|
38
40
|
}
|
|
39
41
|
return {
|
|
40
42
|
period: startPeriod + 1,
|
|
41
43
|
contribution: contribution,
|
|
42
44
|
growth: interestThisPeriod,
|
|
43
|
-
currentBalance,
|
|
45
|
+
currentBalance: newBalance,
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
;
|
|
@@ -54,8 +56,8 @@ export function amortizeContribution(instrument, currentBalance, contribution, s
|
|
|
54
56
|
* @param {number} numContributions The number of periods to make contributions to the instrument
|
|
55
57
|
* @param {number[int]} startPeriod The inital offset for period values
|
|
56
58
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* true: A -> C
|
|
60
|
+
* false: C -> A
|
|
59
61
|
* @returns {ContributionRecord[]} The amortized contributions
|
|
60
62
|
*/
|
|
61
63
|
export function amortizeContributions(instrument, initialBalance, contribution, numContributions, startPeriod = 0, accrueBeforeContribution = true) {
|
|
@@ -66,6 +68,7 @@ export function amortizeContributions(instrument, initialBalance, contribution,
|
|
|
66
68
|
const periodicContribution = instrument.validateContribution(contribution, ytd);
|
|
67
69
|
const record = amortizeContribution(instrument, currentBalance, periodicContribution, period + startPeriod, accrueBeforeContribution);
|
|
68
70
|
currentBalance = record.currentBalance;
|
|
71
|
+
// Reset YTD every 12 periods
|
|
69
72
|
period % 12 === 0 ? ytd = 0 : ytd += periodicContribution;
|
|
70
73
|
contributionSchedule.push(record);
|
|
71
74
|
}
|
|
@@ -78,8 +81,8 @@ export function amortizeContributions(instrument, initialBalance, contribution,
|
|
|
78
81
|
* @param {number} contribution The total amount to contirbute each period
|
|
79
82
|
* @param {number[int]} numContributions The number of periods to contribute
|
|
80
83
|
* @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
|
|
81
|
-
*
|
|
82
|
-
*
|
|
84
|
+
* true: A -> C
|
|
85
|
+
* false: C -> A
|
|
83
86
|
* @returns {InstrumentsContributionSchedule} The amortized contributions for all instruments
|
|
84
87
|
*/
|
|
85
88
|
export function contributeInstruments(instruments, contribution, numContributions, accrueBeforeContribution = true) {
|
|
@@ -126,22 +129,37 @@ export function contributeInstruments(instruments, contribution, numContribution
|
|
|
126
129
|
contributionSchedules[instrument.id].lifetimeGrowth = instrumentLifetimeGrowth;
|
|
127
130
|
totalLifetimeContribution += instrumentLifetimeContribution;
|
|
128
131
|
totalLifetimeGrowth += instrumentLifetimeGrowth;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
161
|
}
|
|
144
|
-
contributionSchedules
|
|
162
|
+
contributionSchedules[TOTALS] = {
|
|
145
163
|
lifetimeContribution: totalLifetimeContribution,
|
|
146
164
|
lifetimeGrowth: totalLifetimeGrowth,
|
|
147
165
|
amortizationSchedule: totalAmortizationSchedule,
|