money: count real Joint recurring items before the move instead of a flat €900/mo
Some checks failed
Deploy production / deploy (push) Has been cancelled
Some checks failed
Deploy production / deploy (push) Has been cancelled
This commit is contained in:
@@ -177,8 +177,8 @@
|
|||||||
Edits recalculate the chart right away and are not saved. Fixed for every scenario: contract
|
Edits recalculate the chart right away and are not saved. Fixed for every scenario: contract
|
||||||
gross {cents(scenarioFacts.grossCents)}/mo until 30 Sep 2027, net salary and all costs from
|
gross {cents(scenarioFacts.grossCents)}/mo until 30 Sep 2027, net salary and all costs from
|
||||||
the recurring list, cash buffer {cents(scenarioFacts.bufferCents)} then everything is invested,
|
the recurring list, cash buffer {cents(scenarioFacts.bufferCents)} then everything is invested,
|
||||||
Joint contribution {cents(scenarioFacts.preMoveHouseholdCents)}/mo until the rent starts,
|
Joint items from the recurring list in full until the rent starts, then your share, while your
|
||||||
your partner’s income pays her share, DUO {cents(scenarioFacts.duoCents)}/mo from {scenarioFacts.duoFrom}
|
partner’s income pays hers, DUO {cents(scenarioFacts.duoCents)}/mo from {scenarioFacts.duoFrom}
|
||||||
to {scenarioFacts.duoUntil}. Prices, benefits, pension, the buffer and the salary plateau
|
to {scenarioFacts.duoUntil}. Prices, benefits, pension, the buffer and the salary plateau
|
||||||
grow with inflation; spending grows at the spending rate for ten years, then with inflation.
|
grow with inflation; spending grows at the spending rate for ten years, then with inflation.
|
||||||
Box 3 keeps taxing gains (losses carried forward). Salary bands (base per year, excl. 8%
|
Box 3 keeps taxing gains (losses carried forward). Salary bands (base per year, excl. 8%
|
||||||
|
|||||||
@@ -98,23 +98,23 @@ describe('runScenario', () => {
|
|||||||
});
|
});
|
||||||
it('pays salary on payday, sweeps the excess the day after and counts recurring income and costs', () => {
|
it('pays salary on payday, sweeps the excess the day after and counts recurring income and costs', () => {
|
||||||
const points = runScenario(inputs, flat).points;
|
const points = runScenario(inputs, flat).points;
|
||||||
// Without history the contract payday (the 25th) is used; €900 pre-move household is
|
// Without history the contract payday (the 25th) is used. Before the move only the Joint
|
||||||
// spread at €30 a day in November.
|
// recurring items count: €50 groceries (living with parents), spread at €1.67 a day.
|
||||||
expect(on(points, '2026-11-25').total - on(points, '2026-11-24').total).toBeCloseTo(
|
expect(on(points, '2026-11-25').total - on(points, '2026-11-24').total).toBeCloseTo(
|
||||||
300000 + 51250 - 3000,
|
300000 + 51250 - 5000 / 30,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
// The benefit (paid on the 1st, a Sunday) moves to Monday.
|
// The benefit (paid on the 1st, a Sunday) moves to Monday.
|
||||||
expect(on(points, '2026-11-02').total - on(points, '2026-11-01').total).toBeCloseTo(
|
expect(on(points, '2026-11-02').total - on(points, '2026-11-01').total).toBeCloseTo(
|
||||||
10000 - 3000,
|
10000 - 5000 / 30,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
expect(on(points, '2026-11-25').cash).toBeGreaterThan(500000);
|
expect(on(points, '2026-11-25').cash).toBeGreaterThan(500000);
|
||||||
expect(on(points, '2026-11-26').cash).toBe(500000);
|
expect(on(points, '2026-11-26').cash).toBe(500000);
|
||||||
// Rest of October: 7/31 of the €900 pre-move household. November: 300k salary + 10k benefit
|
// Rest of October: 7/31 of the €50 groceries. November: 300k salary + 10k benefit
|
||||||
// + 28k one-off + 13th month 3/12 − 3k gym − 90k pre-move household.
|
// + 28k one-off + 13th month 3/12 − 3k gym − 5k groceries. No flat Joint contribution.
|
||||||
expect(on(points, '2026-11-30').total).toBeCloseTo(
|
expect(on(points, '2026-11-30').total).toBeCloseTo(
|
||||||
350000 - (90000 * 7) / 31 + 300000 + 10000 + 28000 + 51250 - 3000 - 90000,
|
350000 - (5000 * 7) / 31 + 300000 + 10000 + 28000 + 51250 - 3000 - 5000,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -149,10 +149,7 @@ describe('runScenario', () => {
|
|||||||
const points = runScenario(withHistory, flat).points;
|
const points = runScenario(withHistory, flat).points;
|
||||||
// 21 November 2026 is a Saturday, so the salary lands on Friday the 20th.
|
// 21 November 2026 is a Saturday, so the salary lands on Friday the 20th.
|
||||||
expect(on(points, '2026-11-20').total - on(points, '2026-11-19').total).toBeGreaterThan(300000);
|
expect(on(points, '2026-11-20').total - on(points, '2026-11-19').total).toBeGreaterThan(300000);
|
||||||
expect(on(points, '2026-12-21').total - on(points, '2026-12-20').total).toBeCloseTo(
|
expect(on(points, '2026-12-21').total - on(points, '2026-12-20').total).toBeCloseTo(300000, 0);
|
||||||
300000 - 90000 / 31,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('orders the default scenarios from pessimistic to optimistic', () => {
|
it('orders the default scenarios from pessimistic to optimistic', () => {
|
||||||
const [pessimistic, realistic, optimistic] = (
|
const [pessimistic, realistic, optimistic] = (
|
||||||
|
|||||||
@@ -220,8 +220,6 @@ export const scenarioFacts = {
|
|||||||
sideTaxRate: 0.4,
|
sideTaxRate: 0.4,
|
||||||
/** Cash buffer in today's euros (grows with inflation); everything above it is invested. */
|
/** Cash buffer in today's euros (grows with inflation); everything above it is invested. */
|
||||||
bufferCents: 500000,
|
bufferCents: 500000,
|
||||||
/** Joint household contribution before the move, per month (Apr–Oct 2026 average). */
|
|
||||||
preMoveHouseholdCents: 90000,
|
|
||||||
/** DUO study-debt repayment, per month, for at most 35 years from 2029. */
|
/** DUO study-debt repayment, per month, for at most 35 years from 2029. */
|
||||||
duoFrom: 2029,
|
duoFrom: 2029,
|
||||||
duoUntil: 2063,
|
duoUntil: 2063,
|
||||||
@@ -691,8 +689,9 @@ export function runScenario(inputs: ScenarioInputs, params: ScenarioParams): Sce
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first < moveMonth) {
|
if (first < moveMonth) {
|
||||||
spending += facts.preMoveHouseholdCents * remaining;
|
// Before the move the Joint account only pays the Joint items on the recurring list
|
||||||
spreadMonth(-facts.preMoveHouseholdCents);
|
// (Netflix, groceries while living with my parents, …), in full: no partner income yet.
|
||||||
|
for (const item of inputs.household) addItem(item, spendGrowth, false);
|
||||||
} else {
|
} else {
|
||||||
const share = pct(params.householdSharePercent) * spendGrowth;
|
const share = pct(params.householdSharePercent) * spendGrowth;
|
||||||
const extra = params.householdExtraEuros * 100 * share;
|
const extra = params.householdExtraEuros * 100 * share;
|
||||||
|
|||||||
Reference in New Issue
Block a user