Skip to content

Commit

Permalink
Existing customers - no withdrawals *from this point onwards* (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
georgeherby authored Jan 10, 2020
1 parent c271dc0 commit 7cd7724
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 42 deletions.
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonthlyBreakdown>`
* `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

Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ kotlin {

val jvmMain by getting {
dependencies {
implementation("com.soywiz.korlibs.klock:klock-jvm:$klockVersion")
implementation(kotlin("stdlib"))
}
}
Expand Down Expand Up @@ -240,7 +241,7 @@ tasks.jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = "0.90".toBigDecimal()
minimum = "0.87".toBigDecimal()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ style:
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
active: false
max: 2
TrailingWhitespace:
active: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonthlyBreakdown> = mutableListOf<MonthlyBreakdown>()
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<MonthlyBreakdown> = 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
Expand All @@ -52,9 +71,7 @@ class Calculator constructor(
monthNumber = currentMonth,
balance = balance,
secondYearBonus = endOfFirstPeriodBonus,
fourthYearBonus = endOfSecondPeriodBonus
)
)
fourthYearBonus = endOfSecondPeriodBonus))
currentMonth++
}
return CalculatorResponse(
Expand All @@ -64,4 +81,10 @@ class Calculator constructor(
finalFourthYearBonus = endOfSecondPeriodBonus,
monthlyBreakdown = listOfMonths)
}

private fun validateUserInput(regularPayment: Int) {
if (!RegularPaymentValidators.isValidRegularPayments(regularPayment)) {
throw InvalidRegularPaymentException(regularPayment)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 65 additions & 18 deletions src/commonTest/kotlin/uk/gov/hmrc/CalculatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<InvalidRegularPaymentException> {
Calculator(regularPayment = 0).run()
Calculator.run(regularPayment = 0)
}
}

@Test
fun `Throw Exception when regular payment is above 50`() {
assertFailsWith<InvalidRegularPaymentException> {
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)
}
}
Loading

0 comments on commit 7cd7724

Please sign in to comment.