Skip to content

Commit

Permalink
Merge pull request #69 from hmrc/HMA-5744
Browse files Browse the repository at this point in the history
[HMA-5744] - Update the National Insurance Primary Threshold
  • Loading branch information
aerodigi authored May 25, 2022
2 parents 63b929e + 91f3ff1 commit 8c6a70f
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 34 deletions.
16 changes: 6 additions & 10 deletions src/commonMain/kotlin/uk/gov/hmrc/calculator/Calculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Calculator @JvmOverloads constructor(
private val payPeriod: PayPeriod,
private val isPensionAge: Boolean = false,
private val howManyAWeek: Double? = null,
private val taxYear: Int = TaxYear.currentTaxYearInt
private val taxYear: TaxYear = TaxYear.currentTaxYear
) {

private val bandBreakdown: MutableList<BandBreakdown> = mutableListOf()
Expand Down Expand Up @@ -153,7 +153,7 @@ class Calculator @JvmOverloads constructor(
return when (taxCode) {
is StandardTaxCode, is AdjustedTaxFreeTCode, is EmergencyTaxCode, is MarriageTaxCodes -> {
val taxBands = TaxBands.getBands(
taxYearType,
taxYear,
taxCode.country
)
getTotalFromTaxBands(
Expand All @@ -167,7 +167,7 @@ class Calculator @JvmOverloads constructor(
)
is SingleBandTax -> {
val taxBands = TaxBands.getBands(
taxYearType,
taxYear,
taxCode.country
)
getTotalFromSingleBand(
Expand All @@ -177,7 +177,7 @@ class Calculator @JvmOverloads constructor(
}
is KTaxCode -> {
val taxBands = TaxBands.getBands(
taxYearType,
taxYear,
taxCode.country
)
getTotalFromTaxBands(
Expand Down Expand Up @@ -205,13 +205,13 @@ class Calculator @JvmOverloads constructor(

private fun employerNIToPay(yearlyWages: Double) =
if (isPensionAge) 0.0 else getTotalFromNIBands(
EmployerNIBands(taxYearType).bands,
EmployerNIBands(taxYear).bands,
yearlyWages
)

private fun employeeNIToPay(yearlyWages: Double) =
if (isPensionAge) 0.0 else getTotalFromNIBands(
EmployeeNIBands(taxYearType).bands,
EmployeeNIBands(taxYear).bands,
yearlyWages
)

Expand Down Expand Up @@ -265,10 +265,6 @@ class Calculator @JvmOverloads constructor(
return amount
}

private val taxYearType: TaxYear by lazy {
TaxYear.fromInt(taxYear)
}

private val taxCodeType: TaxCode by lazy {
this.taxCode.toTaxCode()
}
Expand Down
28 changes: 12 additions & 16 deletions src/commonMain/kotlin/uk/gov/hmrc/calculator/model/TaxYear.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,35 @@
package uk.gov.hmrc.calculator.model

import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
import uk.gov.hmrc.calculator.annotations.Throws
import uk.gov.hmrc.calculator.exception.InvalidTaxYearException
import uk.gov.hmrc.calculator.services.DateService

internal enum class TaxYear(private val value: Int) {
enum class TaxYear(private val value: Int) {
TWENTY_TWENTY(2020),
TWENTY_TWENTY_ONE(2021),
TWENTY_TWENTY_TWO(2022);
TWENTY_TWENTY_TWO(2022),
TWENTY_TWENTY_TWO_REVISED(2022);

companion object {

@Throws(InvalidTaxYearException::class)
fun fromInt(value: Int): TaxYear =
values()
.firstOrNull { it.value == value }
?: throw InvalidTaxYearException("$value")

val currentTaxYearInt: Int =
DateTime
.nowLocal()
.let {
if (it < firstDayOfTaxYear(it.yearInt)) it.yearInt - 1 else it.yearInt
if (it < DateService.firstDayOfTaxYear(it.yearInt)) it.yearInt - 1 else it.yearInt
}

val currentTaxYear: TaxYear =
values()
.first { it.value == currentTaxYearInt }

private fun firstDayOfTaxYear(year: Int): DateTimeTz =
DateTime(
year = year,
month = 4,
day = 6
).local
val currentTaxYear: TaxYear
get() {
val year = values().first { it.value == currentTaxYearInt }
return if (year == TWENTY_TWENTY_TWO && DateService.isIn2022RevisedPeriod) {
TWENTY_TWENTY_TWO_REVISED
} else year
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ internal class EmployeeNIBands(taxYear: TaxYear) {
EmployeeNIBand(50270.0, -1.0, 0.0325)
)

private val employeeNIBands2022Revised: List<EmployeeNIBand> = listOf(
EmployeeNIBand(12570.0, 50270.00, 0.1325),
EmployeeNIBand(50270.0, -1.0, 0.0325)
)

internal val bands: List<EmployeeNIBand> = when (taxYear) {
TaxYear.TWENTY_TWENTY -> employeeNIBands2020
TaxYear.TWENTY_TWENTY_ONE -> employeeNIBands2021
TaxYear.TWENTY_TWENTY_TWO -> employeeNIBands2022
TaxYear.TWENTY_TWENTY_TWO_REVISED -> employeeNIBands2022Revised
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ internal class EmployerNIBands(taxYear: TaxYear) {
internal val bands: List<EmployerNIBand> = when (taxYear) {
TaxYear.TWENTY_TWENTY -> employerNIBands2020
TaxYear.TWENTY_TWENTY_ONE -> employerNIBands2021
TaxYear.TWENTY_TWENTY_TWO -> employerNIBands2022
TaxYear.TWENTY_TWENTY_TWO, TaxYear.TWENTY_TWENTY_TWO_REVISED -> employerNIBands2022
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ internal object TaxBands {
fun getBands(taxYear: TaxYear, country: Country) = when (taxYear) {
TaxYear.TWENTY_TWENTY -> if (country == SCOTLAND) scottish2020Bands() else restOfUK2020Bands()
TaxYear.TWENTY_TWENTY_ONE -> if (country == SCOTLAND) scottish2021Bands() else restOfUK2021Bands()
TaxYear.TWENTY_TWENTY_TWO -> if (country == SCOTLAND) scottish2022Bands() else restOfUK2022Bands()
TaxYear.TWENTY_TWENTY_TWO, TaxYear.TWENTY_TWENTY_TWO_REVISED ->
if (country == SCOTLAND) scottish2022Bands() else restOfUK2022Bands()
}

private fun scottish2020Bands() = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ internal object TaxFreeAllowance {
fun getAllowance(taxYear: TaxYear): Double =
when (taxYear) {
TaxYear.TWENTY_TWENTY -> 12500.00
TaxYear.TWENTY_TWENTY_ONE, TaxYear.TWENTY_TWENTY_TWO -> 12570.00
TaxYear.TWENTY_TWENTY_ONE, TaxYear.TWENTY_TWENTY_TWO, TaxYear.TWENTY_TWENTY_TWO_REVISED -> 12570.00
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2022 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.gov.hmrc.calculator.services

import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz

interface DateService {
val isIn2022RevisedPeriod: Boolean
fun firstDayOfTaxYear(year: Int): DateTimeTz

companion object {
fun firstDayOfTaxYear(year: Int): DateTimeTz =
DateServiceImpl().firstDayOfTaxYear(year)

val isIn2022RevisedPeriod: Boolean =
DateServiceImpl().isIn2022RevisedPeriod
}
}
class DateServiceImpl(
private val dateTimeService: DateTimeService = DateTimeServiceImpl()
) : DateService {

override val isIn2022RevisedPeriod: Boolean =
dateTimeService.now().local > DateTime(
year = 2022,
month = 7,
day = 5
).local

override fun firstDayOfTaxYear(year: Int): DateTimeTz =
DateTime(
year = year,
month = 4,
day = 6
).local
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2022 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.gov.hmrc.calculator.services

import com.soywiz.klock.DateTime

interface DateTimeService {
fun now(): DateTime
}

class DateTimeServiceImpl : DateTimeService {
override fun now(): DateTime = DateTime.now()
}
112 changes: 109 additions & 3 deletions src/commonTest/kotlin/uk/gov/hmrc/calculator/CalculatorTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class CalculatorTests {
taxCode = "1257L",
wages = 40000.0,
payPeriod = PayPeriod.YEARLY,
taxYear = 2021
taxYear = TaxYear.TWENTY_TWENTY_ONE
).run()

assertEquals(Country.ENGLAND, result.country)
Expand Down Expand Up @@ -85,7 +85,7 @@ internal class CalculatorTests {
taxCode = "1257L",
wages = 40000.0,
payPeriod = PayPeriod.YEARLY,
taxYear = 2022
taxYear = TaxYear.TWENTY_TWENTY_TWO
).run()

assertEquals(Country.ENGLAND, result.country)
Expand Down Expand Up @@ -132,6 +132,112 @@ internal class CalculatorTests {
assertTrue(yearly.taxBreakdown!!.isNotEmpty())
}

@Test
fun `GIVEN 2022_REVISED WHEN all supplied data valid THEN calculates response`() {
val result = Calculator(
taxCode = "1257L",
wages = 40000.0,
payPeriod = PayPeriod.YEARLY,
taxYear = TaxYear.TWENTY_TWENTY_TWO_REVISED
).run()

assertEquals(Country.ENGLAND, result.country)
assertFalse(result.isKCode)

val weekly = result.weekly
assertEquals(PayPeriod.WEEKLY, weekly.payPeriod)
assertEquals(69.89, weekly.employeesNI)
assertEquals(89.43, weekly.employersNI)
assertEquals(769.23, weekly.wages)
assertEquals(241.73, weekly.taxFree)
assertEquals(105.47, weekly.taxToPay)
assertEquals(593.87, weekly.takeHome)
assertTrue(weekly.taxBreakdown!!.isNotEmpty())

val fourWeekly = result.fourWeekly
assertEquals(PayPeriod.FOUR_WEEKLY, fourWeekly.payPeriod)
assertEquals(279.58, fourWeekly.employeesNI)
assertEquals(357.73, fourWeekly.employersNI)
assertEquals(3076.92, fourWeekly.wages)
assertEquals(966.92, fourWeekly.taxFree)
assertEquals(421.86, fourWeekly.taxToPay)
assertEquals(2375.48, fourWeekly.takeHome)
assertTrue(fourWeekly.taxBreakdown!!.isNotEmpty())

val monthly = result.monthly
assertEquals(PayPeriod.MONTHLY, monthly.payPeriod)
assertEquals(302.87, monthly.employeesNI)
assertEquals(387.54, monthly.employersNI)
assertEquals(3333.33, monthly.wages)
assertEquals(1047.5, monthly.taxFree)
assertEquals(457.02, monthly.taxToPay)
assertEquals(2573.44, monthly.takeHome)
assertTrue(monthly.taxBreakdown!!.isNotEmpty())

val yearly = result.yearly
assertEquals(PayPeriod.YEARLY, yearly.payPeriod)
assertEquals(3634.48, yearly.employeesNI)
assertEquals(4650.45, yearly.employersNI)
assertEquals(40000.00, yearly.wages)
assertEquals(12570.00, yearly.taxFree)
assertEquals(5484.20, yearly.taxToPay)
assertEquals(30881.33, yearly.takeHome)
assertTrue(yearly.taxBreakdown!!.isNotEmpty())
}

@Test
fun `GIVEN 2022_REVISED WHEN all supplied data valid and 60000 salary THEN calculates response`() {
val result = Calculator(
taxCode = "1257L",
wages = 60000.0,
payPeriod = PayPeriod.YEARLY,
taxYear = TaxYear.TWENTY_TWENTY_TWO_REVISED
).run()

assertEquals(Country.ENGLAND, result.country)
assertFalse(result.isKCode)

val weekly = result.weekly
assertEquals(PayPeriod.WEEKLY, weekly.payPeriod)
assertEquals(102.14, weekly.employeesNI)
assertEquals(147.32, weekly.employersNI)
assertEquals(1153.85, weekly.wages)
assertEquals(241.73, weekly.taxFree)
assertEquals(219.78, weekly.taxToPay)
assertEquals(831.93, weekly.takeHome)
assertTrue(weekly.taxBreakdown!!.isNotEmpty())

val fourWeekly = result.fourWeekly
assertEquals(PayPeriod.FOUR_WEEKLY, fourWeekly.payPeriod)
assertEquals(408.58, fourWeekly.employeesNI)
assertEquals(589.27, fourWeekly.employersNI)
assertEquals(4615.38, fourWeekly.wages)
assertEquals(966.92, fourWeekly.taxFree)
assertEquals(879.11, fourWeekly.taxToPay)
assertEquals(3327.7, fourWeekly.takeHome)
assertTrue(fourWeekly.taxBreakdown!!.isNotEmpty())

val monthly = result.monthly
assertEquals(PayPeriod.MONTHLY, monthly.payPeriod)
assertEquals(442.62, monthly.employeesNI)
assertEquals(638.37, monthly.employersNI)
assertEquals(5000.00, monthly.wages)
assertEquals(1047.5, monthly.taxFree)
assertEquals(952.37, monthly.taxToPay)
assertEquals(3605.01, monthly.takeHome)
assertTrue(monthly.taxBreakdown!!.isNotEmpty())

val yearly = result.yearly
assertEquals(PayPeriod.YEARLY, yearly.payPeriod)
assertEquals(5311.48, yearly.employeesNI)
assertEquals(7660.45, yearly.employersNI)
assertEquals(60000.00, yearly.wages)
assertEquals(12570.00, yearly.taxFree)
assertEquals(11428.40, yearly.taxToPay)
assertEquals(43260.12, yearly.takeHome)
assertTrue(yearly.taxBreakdown!!.isNotEmpty())
}

@Test
fun `GIVEN hours is zero and pay period hour WHEN calculate THEN exception`() {
assertFailsWith<InvalidHoursException> {
Expand Down Expand Up @@ -195,7 +301,7 @@ internal class CalculatorTests {
taxCode = "1257L",
wages = 40000.0,
payPeriod = PayPeriod.YEARLY,
taxYear = 2040
taxYear = TaxYear.fromInt(2040)
).run()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ class EmployeeNIBandsTests {
assertEquals(true, band0.inBand(12000.0))
assertEquals(false, band0.inBand(650000.0))

val band1 = bands[1]
assertEquals(0.0325, band1.percentageAsDecimal)
assertEquals(true, band1.inBand(650000.0))
assertEquals(false, band1.inBand(1000.0))
}
@Test
fun `WHEN year is 2022Revised THEN band ranges correct`() {
val bands = EmployeeNIBands(TaxYear.TWENTY_TWENTY_TWO_REVISED).bands

val band0 = bands[0]
assertEquals(0.1325, band0.percentageAsDecimal)
assertEquals(true, band0.inBand(12571.0))
assertEquals(false, band0.inBand(650000.0))

val band1 = bands[1]
assertEquals(0.0325, band1.percentageAsDecimal)
assertEquals(true, band1.inBand(650000.0))
Expand Down
Loading

0 comments on commit 8c6a70f

Please sign in to comment.