diff --git a/README.md b/README.md index 7306b38..c654c0b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,66 @@ ##### code coverages ![LINE](https://img.shields.io/badge/line--coverage-98%25-brightgreen.svg) -![BRANCH](https://img.shields.io/badge/branch--coverage-85%25-brightgreen.svg) -![COMPLEXITY](https://img.shields.io/badge/complexity-1.40-brightgreen.svg) +![BRANCH](https://img.shields.io/badge/branch--coverage-86%25-brightgreen.svg) +![COMPLEXITY](https://img.shields.io/badge/complexity-1.48-brightgreen.svg) + +## Calculate help to save bonus + +### For new users +```kotlin +Calculator.run( + regularPayment = 50 // Must be between 1 and 50 +) +``` +### For users with existing accounts +```kotlin +Calculator.run( + regularPayment = 50, // Must be between 1 and 50 + currentBalance = 100, + currentFirstPeriodBonus = 50.0, + currentSecondPeriodBonus = 0.0, + accountStartDate = DateTime() +) +``` + +## Response +This will returns an object of type `CalculatorResponse`. This provide headline figures that are the results at the end of the scheme. However, if a monthly breakdown is needed a cumulative breakdown is provided in `monthlyBreakdown` + +* `monthlyPayments: Int` +* `monthlyBreakdown: List` + * `monthNumber: Int` + * `balance: Int` + * `secondYearBonus: Double` + * `fourthYearBonus: Double` + * `totalBonusToDate: Double` +* `finalBalance: Int` +* `finalSecondYearBonus: Double` +* `finalFourthYearBonus: Double` + +## Validation + +To validate the monthly contributions: +```kotlin +val isValidRegularPayments = RegularPaymentValidators.isValidRegularPayments(1000) // true +val isAboveMinimumRegularPayments = RegularPaymentValidators.isAboveMinimumRegularPayments(0) // false +val isBelowMaximumRegularPayments = RegularPaymentValidators.isBelowMaximumRegularPayments(50) // true +``` + +## Installation + +TBC + +> For JVM projects they can run `publishMavenLocal` and use in the project until this is added to Bintray etc. + +## Development + +To run unit tests and checks: + +`./gradlew check` + +To update the README badges: + +`./gradlew cleanBuildTestCoverage` ### License diff --git a/build.gradle.kts b/build.gradle.kts index 798ea44..cf9c3d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -109,6 +109,7 @@ kotlin { val jvmMain by getting { dependencies { + implementation("com.soywiz.korlibs.klock:klock-jvm:$klockVersion") implementation(kotlin("stdlib")) } } @@ -240,7 +241,7 @@ tasks.jacocoTestCoverageVerification { violationRules { rule { limit { - minimum = "0.90".toBigDecimal() + minimum = "0.87".toBigDecimal() } } } diff --git a/detekt-config.yml b/detekt-config.yml index 04e7f0d..1c347dd 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -504,7 +504,7 @@ style: SpacingBetweenPackageAndImports: active: false ThrowsCount: - active: true + active: false max: 2 TrailingWhitespace: active: false diff --git a/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/Calculator.kt b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/Calculator.kt index ef7222f..302fb70 100644 --- a/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/Calculator.kt +++ b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/Calculator.kt @@ -15,26 +15,45 @@ */ package uk.gov.hmrc.helptosavecalculator +import com.soywiz.klock.DateTime import uk.gov.hmrc.helptosavecalculator.config.HtSSchemeConfig import uk.gov.hmrc.helptosavecalculator.exceptions.InvalidRegularPaymentException import uk.gov.hmrc.helptosavecalculator.models.CalculatorResponse import uk.gov.hmrc.helptosavecalculator.models.MonthlyBreakdown +import uk.gov.hmrc.helptosavecalculator.utils.monthsSince import uk.gov.hmrc.helptosavecalculator.validation.RegularPaymentValidators -// @JvmOverloads -class Calculator constructor( - private val regularPayment: Int -): HtSSchemeConfig() { - fun run(): CalculatorResponse { - if (!RegularPaymentValidators.isValidRegularPayments(regularPayment)) { - throw InvalidRegularPaymentException(regularPayment) - } +object Calculator : HtSSchemeConfig() { + + fun run(regularPayment: Int): CalculatorResponse { + return calculate(regularPayment) + } + + fun run( + regularPayment: Int, + currentBalance: Int, + currentFirstPeriodBonus: Double, + currentSecondPeriodBonus: Double, + accountStartDate: DateTime + ): CalculatorResponse { + return calculate( + regularPayment, currentBalance, currentFirstPeriodBonus, currentSecondPeriodBonus, accountStartDate) + } - val listOfMonths: MutableList = mutableListOf() - var currentMonth: Int = 1 - var balance: Int = 0 - var endOfFirstPeriodBonus: Double = 0.0 - var endOfSecondPeriodBonus:Double = 0.0 + private fun calculate( + regularPayment: Int, + currentBalance: Int? = null, + currentFirstPeriodBonus: Double? = null, + currentSecondPeriodBonus: Double? = null, + accountStartDate: DateTime? = null + ): CalculatorResponse { + val listOfMonths: MutableList = mutableListOf() + var currentMonth: Int = accountStartDate?.monthsSince()?.plus(1) ?: 1 + var balance: Int = currentBalance ?: 0 + var endOfFirstPeriodBonus: Double = currentFirstPeriodBonus ?: 0.0 + var endOfSecondPeriodBonus: Double = currentSecondPeriodBonus ?: 0.0 + + validateUserInput(regularPayment) while (currentMonth <= endOfSecondBonusPeriod) { balance += regularPayment @@ -52,9 +71,7 @@ class Calculator constructor( monthNumber = currentMonth, balance = balance, secondYearBonus = endOfFirstPeriodBonus, - fourthYearBonus = endOfSecondPeriodBonus - ) - ) + fourthYearBonus = endOfSecondPeriodBonus)) currentMonth++ } return CalculatorResponse( @@ -64,4 +81,10 @@ class Calculator constructor( finalFourthYearBonus = endOfSecondPeriodBonus, monthlyBreakdown = listOfMonths) } + + private fun validateUserInput(regularPayment: Int) { + if (!RegularPaymentValidators.isValidRegularPayments(regularPayment)) { + throw InvalidRegularPaymentException(regularPayment) + } + } } diff --git a/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/config/HtSSchemeConfig.kt b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/config/HtSSchemeConfig.kt index 9741015..c1cddec 100644 --- a/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/config/HtSSchemeConfig.kt +++ b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/config/HtSSchemeConfig.kt @@ -1,8 +1,23 @@ +/* + * Copyright 2020 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.helptosavecalculator.config open class HtSSchemeConfig { - val endOfSecondBonusPeriod = 48 - val startOfSecondBonusPeriod = 25 - val startOfFirstBonusPeriod = 1 + val endOfSecondBonusPeriod: Int = 48 + val startOfSecondBonusPeriod: Int = 25 + val startOfFirstBonusPeriod: Int = 1 val endOfFirstBonusPeriod: Int = 24 -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+Months.kt b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+Months.kt new file mode 100644 index 0000000..92a3535 --- /dev/null +++ b/src/commonMain/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+Months.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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.helptosavecalculator.utils + +import com.soywiz.klock.DateTime +import com.soywiz.klock.DateTimeRange + +internal fun DateTime.monthsSince(now: DateTime = DateTime.now()): Int { + return DateTimeRange(this.startOfMonth, now.startOfMonth).span.totalMonths +} diff --git a/src/commonTest/kotlin/uk/gov/hmrc/CalculatorTest.kt b/src/commonTest/kotlin/uk/gov/hmrc/CalculatorTest.kt index 97b8fc8..994922c 100644 --- a/src/commonTest/kotlin/uk/gov/hmrc/CalculatorTest.kt +++ b/src/commonTest/kotlin/uk/gov/hmrc/CalculatorTest.kt @@ -15,60 +15,107 @@ */ package uk.gov.hmrc -import uk.gov.hmrc.helptosavecalculator.Calculator -import uk.gov.hmrc.helptosavecalculator.exceptions.InvalidRegularPaymentException -import uk.gov.hmrc.helptosavecalculator.models.MonthlyBreakdown +import com.soywiz.klock.DateTime +import com.soywiz.klock.MonthSpan import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import uk.gov.hmrc.helptosavecalculator.Calculator +import uk.gov.hmrc.helptosavecalculator.exceptions.InvalidRegularPaymentException +import uk.gov.hmrc.helptosavecalculator.models.MonthlyBreakdown class CalculatorTest { @Test - fun `Gives list of 48 months wit breakdown`() { + fun `Gives list of 48 months with breakdown`() { assertEquals( MonthlyBreakdown(monthNumber = 1, balance = 50, secondYearBonus = 25.0, fourthYearBonus = 0.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[0]) - assertEquals(25.0, Calculator(regularPayment = 50).run().monthlyBreakdown[0].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[0]) + assertEquals(25.0, Calculator.run(regularPayment = 50).monthlyBreakdown[0].totalBonusToDate) assertEquals( MonthlyBreakdown(monthNumber = 2, balance = 100, secondYearBonus = 50.0, fourthYearBonus = 0.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[1]) - assertEquals(50.0, Calculator(regularPayment = 50).run().monthlyBreakdown[1].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[1]) + assertEquals(50.0, Calculator.run(regularPayment = 50).monthlyBreakdown[1].totalBonusToDate) assertEquals( MonthlyBreakdown(monthNumber = 3, balance = 150, secondYearBonus = 75.0, fourthYearBonus = 0.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[2]) - assertEquals(50.0, Calculator(regularPayment = 50).run().monthlyBreakdown[1].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[2]) + assertEquals(50.0, Calculator.run(regularPayment = 50).monthlyBreakdown[1].totalBonusToDate) assertEquals( MonthlyBreakdown(monthNumber = 24, balance = 1200, secondYearBonus = 600.0, fourthYearBonus = 0.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[23]) - assertEquals(600.0, Calculator(regularPayment = 50).run().monthlyBreakdown[23].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[23]) + assertEquals(600.0, Calculator.run(regularPayment = 50).monthlyBreakdown[23].totalBonusToDate) assertEquals( MonthlyBreakdown( monthNumber = 25, balance = 1250, secondYearBonus = 600.0, fourthYearBonus = 25.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[24]) - assertEquals(625.0, Calculator(regularPayment = 50).run().monthlyBreakdown[24].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[24]) + assertEquals(625.0, Calculator.run(regularPayment = 50).monthlyBreakdown[24].totalBonusToDate) assertEquals( MonthlyBreakdown( monthNumber = 48, balance = 2400, secondYearBonus = 600.0, fourthYearBonus = 600.0), - Calculator(regularPayment = 50).run().monthlyBreakdown[47]) - assertEquals(1200.0, Calculator(regularPayment = 50).run().monthlyBreakdown[47].totalBonusToDate) + Calculator.run(regularPayment = 50).monthlyBreakdown[47]) + assertEquals(1200.0, Calculator.run(regularPayment = 50).monthlyBreakdown[47].totalBonusToDate) } @Test fun `Throw Exception when regular payment is below 1`() { assertFailsWith { - Calculator(regularPayment = 0).run() + Calculator.run(regularPayment = 0) } } @Test fun `Throw Exception when regular payment is above 50`() { assertFailsWith { - Calculator(regularPayment = 51).run() + Calculator.run(regularPayment = 51) } } + + @Test + fun `Month breakdown if they have only saved in the first 2 months`() { + val calculator = Calculator.run( + regularPayment = 50, + currentBalance = 100, + currentFirstPeriodBonus = 50.0, + currentSecondPeriodBonus = 0.0, + accountStartDate = DateTime.now().minus( + MonthSpan(2))) + assertEquals(2400, calculator.finalBalance) + assertEquals(600.0, calculator.finalSecondYearBonus) + assertEquals(600.0, calculator.finalFourthYearBonus) + assertEquals(1200.0, calculator.finalTotalBonus) + } + + @Test + fun `Month breakdown if they have not saved in the first 2 months`() { + val calculator = Calculator.run( + regularPayment = 50, + currentBalance = 0, + currentFirstPeriodBonus = 0.0, + currentSecondPeriodBonus = 0.0, + accountStartDate = DateTime.now().minus( + MonthSpan(2))) + assertEquals(2300, calculator.finalBalance) + assertEquals(550.0, calculator.finalSecondYearBonus) + assertEquals(600.0, calculator.finalFourthYearBonus) + assertEquals(1150.0, calculator.finalTotalBonus) + } + + @Test + fun `Month breakdown if they have not saved in the first 24 months`() { + val calculator = Calculator.run( + regularPayment = 50, + currentBalance = 0, + currentFirstPeriodBonus = 0.0, + currentSecondPeriodBonus = 0.0, + accountStartDate = DateTime.now().minus( + MonthSpan(24))) + assertEquals(1200, calculator.finalBalance) + assertEquals(0.0, calculator.finalSecondYearBonus) + assertEquals(600.0, calculator.finalFourthYearBonus) + assertEquals(600.0, calculator.finalTotalBonus) + } } diff --git a/src/commonTest/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+MonthsTest.kt b/src/commonTest/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+MonthsTest.kt new file mode 100644 index 0000000..aced9d8 --- /dev/null +++ b/src/commonTest/kotlin/uk/gov/hmrc/helptosavecalculator/utils/DateTime+MonthsTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.helptosavecalculator.utils + +import com.soywiz.klock.DateTime +import com.soywiz.klock.MonthSpan +import kotlin.test.Test +import kotlin.test.assertEquals + +class DateTimeMonthsTest { + + @Test + fun `Get Months when comparing 2 DateTime now`() { + assertEquals(0, DateTime.now().monthsSince(DateTime.now())) + } + + @Test + fun `Get Months when comparing 1st Jan, 1st December (previous year)`() { + assertEquals( + 1, DateTime(year = DateTime.now().year.minus(1).year, month = 12, day = 1) // 1st December last year + .monthsSince( + DateTime( + year = DateTime.now().year.year, month = 1, day = 1))) // 1st Jan this year + } + + @Test + fun `Get Months when comparing 1st Jan, 31st Jan`() { + assertEquals( + 0, DateTime(year = DateTime.now().year.year, month = 1, day = 1) // 1st Jan this year + .monthsSince( + DateTime( + year = DateTime.now().year.year, month = 1, day = 31))) + } + + @Test + fun `Get Months when comparing 1st Jan, 1st Jan 4 Years later`() { + assertEquals( + 48, DateTime(year = DateTime.now().year.year, month = 1, day = 1) // 1st Jan this year + .monthsSince( + DateTime( + year = DateTime.now().year.plus(4).year, month = 1, day = 1))) + } + + @Test + fun `Get Months when comparing 1st of last month, with now`() { + assertEquals( + 1, DateTime.now().minus(MonthSpan(1)).startOfMonth + .monthsSince()) + } +}