money scenarios: contract timing for salary extras (13th month in Nov, holiday pay in May, bonus in Aug, net estimates from the contract)
All checks were successful
Deploy production / deploy (push) Successful in 2m32s
All checks were successful
Deploy production / deploy (push) Successful in 2m32s
This commit is contained in:
@@ -33,7 +33,7 @@ export type ScenarioParams = {
|
||||
raisePercent: number;
|
||||
raiseMonth: number;
|
||||
raiseFromYear: number;
|
||||
/** Share of the 5% target bonus actually paid each September, 0–100. */
|
||||
/** Share of the contract's bonus estimate actually paid each August, 0–100. */
|
||||
bonusPercentOfTarget: number;
|
||||
/** Side income, gross euros per month, ramping linearly to the end value. */
|
||||
sideStartEuros: number;
|
||||
@@ -95,7 +95,7 @@ export const defaultScenarios: Record<ScenarioId, ScenarioParams> = {
|
||||
raisePercent: 3.5,
|
||||
raiseMonth: 10,
|
||||
raiseFromYear: 2028,
|
||||
bonusPercentOfTarget: 50,
|
||||
bonusPercentOfTarget: 100,
|
||||
sideStartEuros: 250,
|
||||
sideEndEuros: 500,
|
||||
spendGrowthPercent: 2.5,
|
||||
@@ -141,10 +141,19 @@ export const scenarioFacts = {
|
||||
contractEnd: '2027-09-30',
|
||||
/** Extra net per extra gross euro: about 50% marginal tax after 2.65% pension. */
|
||||
marginalNet: (1 - 0.0265) * 0.5,
|
||||
/** Holiday pay (8%) and 13th month accrue monthly; paid half net in May and December. */
|
||||
holidayPayRate: 0.08,
|
||||
/** Target bonus as a share of yearly gross (12.96 months), paid half net in September. */
|
||||
bonusTarget: 0.05,
|
||||
/**
|
||||
* Contract extras, net per full year at the contract gross (scaled with later pay): holiday
|
||||
* pay paid in May and the 13th month in November, both accrued per month worked. The first
|
||||
* ones are pro rata (13th month 3/12 in Nov 2026, holiday pay 8/12 in May 2027). The bonus
|
||||
* is paid in August after the fiscal year, only while employed: once €850 (FY27), then €1,300.
|
||||
*/
|
||||
holidayNetYearCents: 195000,
|
||||
thirteenthNetYearCents: 205000,
|
||||
holidayMonthsAccrued: 1,
|
||||
thirteenthMonthsAccrued: 2,
|
||||
firstBonusCents: 85000,
|
||||
firstBonusYear: 2027,
|
||||
bonusNetYearCents: 130000,
|
||||
sideTaxRate: 0.4,
|
||||
bufferCents: 500000,
|
||||
/** Joint household contribution before the move, per month (Apr–Oct 2026 average). */
|
||||
@@ -301,8 +310,8 @@ export function runScenario(inputs: ScenarioInputs, params: ScenarioParams): Sce
|
||||
let cash = inputs.spendableCents;
|
||||
let invested = inputs.investedCents;
|
||||
let gross = baseGross;
|
||||
let accruedHoliday = baseGross * facts.holidayPayRate;
|
||||
let accrued13 = baseGross / 12;
|
||||
let accruedHoliday = (facts.holidayNetYearCents / 12) * facts.holidayMonthsAccrued;
|
||||
let accrued13 = (facts.thirteenthNetYearCents / 12) * facts.thirteenthMonthsAccrued;
|
||||
let investedAtYearStart = invested;
|
||||
let contributedThisYear = 0;
|
||||
let box3Due = 0;
|
||||
@@ -357,25 +366,29 @@ export function runScenario(inputs: ScenarioInputs, params: ScenarioParams): Sce
|
||||
gross *= 1 + pct(params.raisePercent);
|
||||
let income = employed ? netFromGross(gross) : params.gapNetEuros * 100;
|
||||
if (employed) {
|
||||
accruedHoliday += gross * facts.holidayPayRate;
|
||||
accrued13 += gross / 12;
|
||||
accruedHoliday += (facts.holidayNetYearCents / 12) * (gross / baseGross);
|
||||
accrued13 += (facts.thirteenthNetYearCents / 12) * (gross / baseGross);
|
||||
}
|
||||
if (month === 5) {
|
||||
income += accruedHoliday * 0.5;
|
||||
income += accruedHoliday;
|
||||
accruedHoliday = 0;
|
||||
}
|
||||
if (month === 12) {
|
||||
income += accrued13 * 0.5;
|
||||
if (month === 11) {
|
||||
income += accrued13;
|
||||
accrued13 = 0;
|
||||
}
|
||||
// A job change pays out what is still accrued when the contract ends.
|
||||
if (params.gapMonths > 0 && first === addMonths(facts.contractEnd, 0)) {
|
||||
income += (accruedHoliday + accrued13) * 0.5;
|
||||
income += accruedHoliday + accrued13;
|
||||
accruedHoliday = 0;
|
||||
accrued13 = 0;
|
||||
}
|
||||
if (month === 9 && first >= addMonths(facts.contractEnd, 0) && employed)
|
||||
income += pct(params.bonusPercentOfTarget) * facts.bonusTarget * gross * 12.96 * 0.5;
|
||||
if (month === 8 && year >= facts.firstBonusYear && employed)
|
||||
income +=
|
||||
pct(params.bonusPercentOfTarget) *
|
||||
(year === facts.firstBonusYear
|
||||
? facts.firstBonusCents
|
||||
: facts.bonusNetYearCents * (gross / baseGross));
|
||||
const ramp = Math.min(1, Math.max(0, (index - 3) / Math.max(1, totalMonths - 4)));
|
||||
const sideGross = params.sideStartEuros + (params.sideEndEuros - params.sideStartEuros) * ramp;
|
||||
income += sideGross * 100 * (1 - facts.sideTaxRate);
|
||||
|
||||
Reference in New Issue
Block a user