-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add calculatePensionAnnualAllowance including support for Pension Tap…
…ering calculations (#23) * Initial implementation * Add tests and tips * Update docs, expose a param * Run prettier
- Loading branch information
Showing
7 changed files
with
303 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { calculatePensionAnnualAllowance } from "./pensionAnnualAllowance"; | ||
|
||
describe("calculatePensionAnnualAllowance (24/25)", () => { | ||
test("Fidelity Example 1", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 210_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 20_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 230_000, | ||
thresholdIncome: 210_000, | ||
reduction: 0, | ||
allowance: 60_000, | ||
}); | ||
}); | ||
|
||
test("Fidelity Example 2", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 235_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 60_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 295_000, | ||
thresholdIncome: 235_000, | ||
reduction: 17_500, | ||
allowance: 42_500, | ||
}); | ||
}); | ||
|
||
test("AJ Bell Example 1", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 200_000, | ||
retrospectivePensionPaymentsTaxRelief: 20_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 20_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 220_000, | ||
thresholdIncome: 180_000, | ||
reduction: 0, | ||
allowance: 60_000, | ||
}); | ||
}); | ||
|
||
test("AJ Bell Example 2", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 230_000, | ||
retrospectivePensionPaymentsTaxRelief: 10_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 50_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 280_000, | ||
thresholdIncome: 220_000, | ||
reduction: 10_000, | ||
allowance: 50_000, | ||
}); | ||
}); | ||
|
||
describe("Quilter Examples", () => { | ||
test("Example A", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 265_000, | ||
retrospectivePensionPaymentsTaxRelief: 0, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 0, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 265_000, | ||
thresholdIncome: 265_000, | ||
reduction: 2500, | ||
allowance: 57_500, | ||
}); | ||
}); | ||
|
||
test("Example B", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 330_000, | ||
retrospectivePensionPaymentsTaxRelief: 20_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 0, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 330_000, | ||
thresholdIncome: 310_000, | ||
reduction: 35_000, | ||
allowance: 25_000, | ||
}); | ||
}); | ||
|
||
test("Example C", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 245_000, | ||
retrospectivePensionPaymentsTaxRelief: 0, | ||
employeeDcPensionContributions: 20_000, | ||
employerDcPensionContributions: 0, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 265_000, | ||
thresholdIncome: 265_000, | ||
reduction: 2_500, | ||
allowance: 57_500, | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Royal London Examples", () => { | ||
// Examples that use carry forward or DB pensions are not included | ||
test("Example 1", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 284_000, | ||
retrospectivePensionPaymentsTaxRelief: 15_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 30_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 314_000, | ||
thresholdIncome: 269_000, | ||
reduction: 27_000, | ||
allowance: 33_000, | ||
}); | ||
}); | ||
|
||
test("Example 5", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 270_000, | ||
retrospectivePensionPaymentsTaxRelief: 0, | ||
employeeDcPensionContributions: 20_000, | ||
employerDcPensionContributions: 20_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 310_000, | ||
thresholdIncome: 290_000, | ||
reduction: 25_000, | ||
allowance: 35_000, | ||
}); | ||
}); | ||
}); | ||
|
||
test("HL example", () => { | ||
const result = calculatePensionAnnualAllowance({ | ||
totalAnnualIncome: 250_000, | ||
retrospectivePensionPaymentsTaxRelief: 24_000, | ||
employeeDcPensionContributions: 0, | ||
employerDcPensionContributions: 36_000, | ||
}); | ||
|
||
expect(result).toEqual({ | ||
adjustedIncome: 286_000, | ||
thresholdIncome: 226_000, | ||
reduction: 13_000, | ||
allowance: 47_000, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { getHmrcRates } from "./hmrc"; | ||
|
||
import type { TaxYear } from "./types"; | ||
|
||
interface Args { | ||
taxYear?: TaxYear; | ||
totalAnnualIncome: number; | ||
retrospectivePensionPaymentsTaxRelief?: number; | ||
employeeDcPensionContributions?: number; | ||
employerDcPensionContributions?: number; | ||
lumpSumDeathBenefits?: number; | ||
} | ||
|
||
// Calculates an individual's annual pension contributions allowance | ||
// UNSUPPORTED: DB Pensions | ||
// UNSUPPORTED: Carry forward allowances from previous years | ||
// UNSUPPORTED: Paying into overseas pension schemes | ||
// Tips: | ||
// Use `retrospectivePensionPaymentsTaxRelief` for personal contributions (or any other form of relief at source contribution) | ||
// Use `employeeDcPensionContributions` for post-2015 Salary Sacrifice schemes | ||
// Use `retrospectivePensionPaymentsTaxRelief` for pre-2015 Salary Sacrifice schemes | ||
export const calculatePensionAnnualAllowance = ({ | ||
taxYear, | ||
totalAnnualIncome, | ||
retrospectivePensionPaymentsTaxRelief, | ||
employeeDcPensionContributions, | ||
employerDcPensionContributions, | ||
lumpSumDeathBenefits, | ||
}: Args) => { | ||
const { | ||
PENSION_ANNUAL_ALLOWANCE, // 60k | ||
PENSION_MINIMUM_ANNUAL_ALLOWANCE, // 10k | ||
PENSION_ADJUSTED_LIMIT, // 260k | ||
} = getHmrcRates({ taxYear }); | ||
|
||
const pensionSavings = | ||
(employeeDcPensionContributions ?? 0) + | ||
(employerDcPensionContributions ?? 0) + | ||
(retrospectivePensionPaymentsTaxRelief ?? 0); | ||
|
||
const adjustedIncome = | ||
totalAnnualIncome + | ||
pensionSavings - | ||
(retrospectivePensionPaymentsTaxRelief ?? 0) - | ||
(lumpSumDeathBenefits ?? 0); | ||
|
||
const thresholdIncome = | ||
totalAnnualIncome + | ||
(employeeDcPensionContributions ?? 0) - | ||
(retrospectivePensionPaymentsTaxRelief ?? 0) - | ||
(lumpSumDeathBenefits ?? 0); | ||
|
||
const amountOver = adjustedIncome - PENSION_ADJUSTED_LIMIT; | ||
|
||
// Reduction is £1 per £2 over the limit | ||
// Also, reductions are rounded down to the nearest £1 | ||
const reduction = amountOver > 0 ? Math.floor(amountOver / 2) : 0; | ||
|
||
let newAllowance = PENSION_ANNUAL_ALLOWANCE; | ||
|
||
if (reduction > 0) { | ||
const updated = PENSION_ANNUAL_ALLOWANCE - reduction; | ||
newAllowance = | ||
updated < PENSION_MINIMUM_ANNUAL_ALLOWANCE | ||
? PENSION_MINIMUM_ANNUAL_ALLOWANCE | ||
: updated; | ||
} | ||
|
||
return { | ||
adjustedIncome, | ||
thresholdIncome, | ||
reduction, | ||
allowance: newAllowance, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters