Skip to content

Commit

Permalink
Merge pull request #599 from hmrc/CVSRP-4764
Browse files Browse the repository at this point in the history
[CVSRP-4764] Updating the averaging calculation to cater for Statutory Leave periods
  • Loading branch information
LadonJackson authored Mar 25, 2021
2 parents 890a608 + ccdb755 commit 47617c7
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 41 deletions.
2 changes: 1 addition & 1 deletion app/forms/NumberOfStatLeaveDaysFormProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ class NumberOfStatLeaveDaysFormProvider @Inject() extends Mappings {
)

private[forms] def daysBetween(boundaryStart: LocalDate, boundaryEnd: LocalDate): Int =
Duration.between(boundaryStart.atStartOfDay(), boundaryEnd.atStartOfDay()).toDays.toInt + 1
Duration.between(boundaryStart.atStartOfDay(), boundaryEnd.atStartOfDay()).toDays.toInt

}
22 changes: 14 additions & 8 deletions app/handlers/DataExtractor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@

package handlers

import java.time.LocalDate

import cats.data.Validated.{Invalid, Valid}
import cats.implicits._
import models.UserAnswers.AnswerV
import models._
import pages._
import services.{FurloughPeriodExtractor, PeriodHelper}
import cats.syntax.apply._
import cats.syntax.validated._
import cats.syntax.semigroupk._
import utils.LocalDateHelpers._

import java.time.LocalDate

trait DataExtractor extends FurloughPeriodExtractor with PeriodHelper {

def extractPriorFurloughPeriodV(userAnswers: UserAnswers): AnswerV[Period] = {
Expand Down Expand Up @@ -127,11 +125,19 @@ trait DataExtractor extends FurloughPeriodExtractor with PeriodHelper {
ReferencePayData(furloughPeriod, assigned, frequency)
}

def extractStatutoryLeaveData(userAnswers: UserAnswers): AnswerV[Option[StatutoryLeaveData]] = {
for {
pay <- userAnswers.getO(StatutoryLeavePayPage)
days <- userAnswers.getO(NumberOfStatLeaveDaysPage)
} yield (pay, days).mapN { case (amount, days) => StatutoryLeaveData(days, amount.value) }
}.sequence

def extractPhaseTwoReferencePayDataV(userAnswers: UserAnswers): AnswerV[PhaseTwoReferencePayData] =
(
extractFurloughWithinClaimV(userAnswers),
extractPaymentFrequencyV(userAnswers)
).mapN { (furloughPeriod, frequency) =>
extractPaymentFrequencyV(userAnswers),
extractStatutoryLeaveData(userAnswers)
).mapN { (furloughPeriod, frequency, statLeave) =>
val payDates = userAnswers.getList(PayDatePage)
val actuals = userAnswers.getList(PartTimeHoursPage)
val usuals: Seq[UsualHours] = userAnswers.getList(PartTimeNormalHoursPage)
Expand All @@ -140,7 +146,7 @@ trait DataExtractor extends FurloughPeriodExtractor with PeriodHelper {
val assigned = assignPayDates(frequency, periods, lastPayDay)
val phaseTwo: Seq[PhaseTwoPeriod] = assignPartTimeHours(assigned, actuals, usuals)

PhaseTwoReferencePayData(furloughPeriod, phaseTwo, frequency)
PhaseTwoReferencePayData(furloughPeriod, phaseTwo, frequency, statLeave)
}

def determineLastPayDay(userAnswers: UserAnswers, periods: Seq[Periods]): LocalDate =
Expand Down
14 changes: 10 additions & 4 deletions app/models/Journey.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,20 @@ case object PhaseTwoRegularPay extends PhaseTwoJourney
case object PhaseTwoVariablePay extends PhaseTwoJourney
case object PhaseTwoVariablePayWithCylb extends PhaseTwoJourney

case class PhaseTwoReferencePayData(furloughPeriod: FurloughWithinClaim, periods: Seq[PhaseTwoPeriod], frequency: PaymentFrequency)
case class StatutoryLeaveData(days: Int, pay: BigDecimal)

case class PhaseTwoReferencePayData(furloughPeriod: FurloughWithinClaim,
periods: Seq[PhaseTwoPeriod],
frequency: PaymentFrequency,
statutoryLeave: Option[StatutoryLeaveData] = None)

sealed trait PhaseTwoReferencePay {
val referencePayData: PhaseTwoReferencePayData

def furloughPeriod: FurloughWithinClaim = referencePayData.furloughPeriod
def periods: Seq[PhaseTwoPeriod] = referencePayData.periods
def frequency: PaymentFrequency = referencePayData.frequency
def furloughPeriod: FurloughWithinClaim = referencePayData.furloughPeriod
def periods: Seq[PhaseTwoPeriod] = referencePayData.periods
def frequency: PaymentFrequency = referencePayData.frequency
def statutoryLeave: Option[StatutoryLeaveData] = referencePayData.statutoryLeave
}

case class PhaseTwoRegularPayData(referencePayData: PhaseTwoReferencePayData, wage: Amount) extends PhaseTwoReferencePay
Expand Down
40 changes: 30 additions & 10 deletions app/services/AveragePayCalculator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package services

import models.NonFurloughPay.determineNonFurloughPay
import models.{Amount, AveragePayment, AveragePaymentWithFullPeriod, AveragePaymentWithPartialPeriod, AveragePaymentWithPhaseTwoPeriod, FullPeriodWithPaymentDate, NonFurloughPay, PartialPeriodWithPaymentDate, Period, PeriodWithPaymentDate, PhaseTwoPeriod}
import models.{Amount, AveragePayment, AveragePaymentWithFullPeriod, AveragePaymentWithPartialPeriod, AveragePaymentWithPhaseTwoPeriod, FullPeriodWithPaymentDate, NonFurloughPay, PartialPeriodWithPaymentDate, Period, PeriodWithPaymentDate, PhaseTwoPeriod, StatutoryLeaveData}
import services.Calculators._

trait AveragePayCalculator extends Calculators {
Expand All @@ -28,19 +28,31 @@ trait AveragePayCalculator extends Calculators {
annualPay: Amount): Seq[AveragePayment] =
periods map {
case fp: FullPeriodWithPaymentDate =>
AveragePaymentWithFullPeriod(daily(fp.period.period, priorFurloughPeriod, annualPay), fp, annualPay, priorFurloughPeriod)
AveragePaymentWithFullPeriod(
referencePay = daily(fp.period.period, priorFurloughPeriod, annualPay, None),
periodWithPaymentDate = fp,
annualPay = annualPay,
priorFurloughPeriod = priorFurloughPeriod
)
case pp: PartialPeriodWithPaymentDate =>
val nfp = determineNonFurloughPay(pp.period, nonFurloughPay)
AveragePaymentWithPartialPeriod(nfp, daily(pp.period.partial, priorFurloughPeriod, annualPay), pp, annualPay, priorFurloughPeriod)
AveragePaymentWithPartialPeriod(
nonFurloughPay = nfp,
referencePay = daily(pp.period.partial, priorFurloughPeriod, annualPay, None),
periodWithPaymentDate = pp,
annualPay = annualPay,
priorFurloughPeriod = priorFurloughPeriod
)
}

def phaseTwoAveragePay(annualPay: Amount,
priorFurloughPeriod: Period,
periods: Seq[PhaseTwoPeriod]): Seq[AveragePaymentWithPhaseTwoPeriod] =
periods: Seq[PhaseTwoPeriod],
statutoryLeaveData: Option[StatutoryLeaveData]): Seq[AveragePaymentWithPhaseTwoPeriod] =
periods.map { phaseTwoPeriod =>
val basedOnDays = phaseTwoPeriod.periodWithPaymentDate match {
case fp: FullPeriodWithPaymentDate => daily(fp.period.period, priorFurloughPeriod, annualPay)
case pp: PartialPeriodWithPaymentDate => daily(pp.period.partial, priorFurloughPeriod, annualPay)
case fp: FullPeriodWithPaymentDate => daily(fp.period.period, priorFurloughPeriod, annualPay, statutoryLeaveData)
case pp: PartialPeriodWithPaymentDate => daily(pp.period.partial, priorFurloughPeriod, annualPay, statutoryLeaveData)
}

val referencePay = if (phaseTwoPeriod.isPartTime) {
Expand All @@ -52,9 +64,17 @@ trait AveragePayCalculator extends Calculators {
AveragePaymentWithPhaseTwoPeriod(referencePay, annualPay, priorFurloughPeriod, phaseTwoPeriod)
}

protected def averageDailyCalculator(period: Period, amount: Amount): Amount =
Amount(amount.value / period.countDays).halfUp
protected def averageDailyCalculator(period: Period, amount: Amount, statLeaveAmount: BigDecimal, statLeaveDays: Int): Amount =
Amount((amount.value - statLeaveAmount) / (period.countDays - statLeaveDays)).halfUp

private def daily(period: Period, priorFurloughPeriod: Period, annualPay: Amount): Amount =
Amount(period.countDays * averageDailyCalculator(priorFurloughPeriod, annualPay).value)
private def daily(period: Period,
priorFurloughPeriod: Period,
annualPay: Amount,
statutoryLeaveData: Option[StatutoryLeaveData]): Amount = {

val statLeaveAmount: BigDecimal = statutoryLeaveData.fold[BigDecimal](0)(_.pay)
val statLeaveDays: Int = statutoryLeaveData.fold[Int](0)(_.days)

Amount(period.countDays * averageDailyCalculator(priorFurloughPeriod, annualPay, statLeaveAmount, statLeaveDays).value)
}
}
7 changes: 4 additions & 3 deletions app/services/ReferencePayCalculator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ trait ReferencePayCalculator extends RegularPayCalculator with AveragePayCalcula
}

def phaseTwoReferencePay(data: PhaseTwoReferencePay): Seq[PaymentWithPhaseTwoPeriod] = data match {
case rpd: PhaseTwoRegularPayData => phaseTwoRegularPay(rpd.wage, rpd.referencePayData.periods)
case vpd: PhaseTwoVariablePayData => phaseTwoAveragePay(vpd.annualPay, vpd.priorFurlough, vpd.referencePayData.periods)
case rpd: PhaseTwoRegularPayData => phaseTwoRegularPay(rpd.wage, rpd.referencePayData.periods)
case vpd: PhaseTwoVariablePayData =>
phaseTwoAveragePay(vpd.annualPay, vpd.priorFurlough, vpd.referencePayData.periods, vpd.referencePayData.statutoryLeave)
case lbd: PhaseTwoVariablePayWithCylbData => {
val avg = phaseTwoAveragePay(lbd.annualPay, lbd.priorFurlough, lbd.referencePayData.periods)
val avg = phaseTwoAveragePay(lbd.annualPay, lbd.priorFurlough, lbd.referencePayData.periods, lbd.referencePayData.statutoryLeave)

phaseTwoWithCylb(avg, lbd)
}
Expand Down
45 changes: 44 additions & 1 deletion it/controllers/scenarios/MayConfirmationScenarios.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,49 @@ object MayConfirmationScenarios extends IntegrationSpecBase with CreateRequestHe
.withPayDate(List("2021-04-30", "2021-05-07", "2021-05-14"))
.withUsualHours(List(UsualHours("2021-05-07".toLocalDate, Hours(40.0)), UsualHours("2021-05-14".toLocalDate, Hours(50.0))))
.withPartTimeHours(List(PartTimeHours("2021-05-07".toLocalDate, Hours(14.0)), PartTimeHours("2021-05-14".toLocalDate, Hours(15.0))))
-> 413.08
-> 413.08,
emptyUserAnswers
.withFurloughStatus(FurloughStatus.FurloughEnded)
.withEmployeeStartDate("2020-12-24")
.withFurloughEndDate("2021-05-14")
.withPreviousFurloughedPeriodsAnswer(false)
.withPaymentFrequency(Weekly)
.withEmployeeStartedAfter1Feb2019()
.withClaimPeriodStart("2021-05-01")
.withLastYear(List())
.withFurloughInLastTaxYear(false)
.withPayPeriodsList(PayPeriodsList.Yes)
.withPartTimePeriods(List(FullPeriod(Period("2021-05-01".toLocalDate, "2021-05-07".toLocalDate)), FullPeriod(Period("2021-05-08".toLocalDate, "2021-05-14".toLocalDate))))
.withPayMethod(PayMethod.Variable)
.withPartTimeQuestion(PartTimeQuestion.PartTimeYes)
.withAnnualPayAmount(10000)
.withStatutoryLeaveData(days = 60, amount = 4000)
.withFurloughStartDate("2021-05-01")
.withClaimPeriodEnd("2021-05-31")
.withPayDate(List("2021-04-30", "2021-05-07", "2021-05-14"))
.withUsualHours(List(UsualHours("2021-05-07".toLocalDate, Hours(40.0)), UsualHours("2021-05-14".toLocalDate, Hours(50.0))))
.withPartTimeHours(List(PartTimeHours("2021-05-07".toLocalDate, Hours(14.0)), PartTimeHours("2021-05-14".toLocalDate, Hours(15.0))))
-> 667.09,
emptyUserAnswers
.withFurloughStatus(FurloughStatus.FurloughEnded)
.withEmployeeStartDate("2020-12-24")
.withFurloughEndDate("2021-05-14")
.withPreviousFurloughedPeriodsAnswer(false)
.withPaymentFrequency(Weekly)
.withEmployeeStartedAfter1Feb2019()
.withClaimPeriodStart("2021-05-01")
.withLastYear(List())
.withFurloughInLastTaxYear(false)
.withPayPeriodsList(PayPeriodsList.Yes)
.withPartTimePeriods(List(FullPeriod(Period("2021-05-01".toLocalDate, "2021-05-07".toLocalDate)), FullPeriod(Period("2021-05-08".toLocalDate, "2021-05-14".toLocalDate))))
.withPayMethod(PayMethod.Variable)
.withPartTimeQuestion(PartTimeQuestion.PartTimeYes)
.withAnnualPayAmount(10000)
.withFurloughStartDate("2021-05-01")
.withClaimPeriodEnd("2021-05-31")
.withPayDate(List("2021-04-30", "2021-05-07", "2021-05-14"))
.withUsualHours(List(UsualHours("2021-05-07".toLocalDate, Hours(40.0)), UsualHours("2021-05-14".toLocalDate, Hours(50.0))))
.withPartTimeHours(List(PartTimeHours("2021-05-07".toLocalDate, Hours(14.0)), PartTimeHours("2021-05-14".toLocalDate, Hours(15.0))))
-> 590.66
))
}
6 changes: 6 additions & 0 deletions it/utils/ITUserAnswersBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ trait ITUserAnswersBuilder extends ITCoreTestDataBuilder {
def withPartTimeQuestion(question: PartTimeQuestion): UserAnswers =
userAnswers.setValue(PartTimeQuestionPage, question)

def withStatutoryLeaveData(days: Int, amount: BigDecimal): UserAnswers =
userAnswers
.setValue(HasEmployeeBeenOnStatutoryLeavePage, true)
.setValue(NumberOfStatLeaveDaysPage, days)
.setValue(StatutoryLeavePayPage, Amount(amount))

def withAdditionalPaymentAmount(payment: AdditionalPayment, idx: Option[Int]): UserAnswers =
userAnswers.setValue(AdditionalPaymentAmountPage, payment, idx)

Expand Down
10 changes: 5 additions & 5 deletions test/forms/NumberOfStatLeaveDaysFormProviderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class NumberOfStatLeaveDaysFormProviderSpec extends IntFieldBehaviours with Guic

val fieldName = "value"
val minimum = 1
val maximum = Duration.between(boundaryStart.atStartOfDay(), boundaryEnd.atStartOfDay()).toDays.toInt + 1
val maximum = Duration.between(boundaryStart.atStartOfDay(), boundaryEnd.atStartOfDay()).toDays.toInt
val validDataGenerator = intsInRangeWithCommas(minimum, maximum)

behave like fieldThatBindsValidData(
Expand Down Expand Up @@ -88,27 +88,27 @@ class NumberOfStatLeaveDaysFormProviderSpec extends IntFieldBehaviours with Guic

"given 2 dates with more than 0 days between them" should {

"return the number of days between them as 11" in {
"return the number of days between them as 10" in {

val startDate = LocalDate.of(2020, 1, 10)
val endDate = LocalDate.of(2020, 1, 20)

val actual: Int = new NumberOfStatLeaveDaysFormProvider().daysBetween(startDate, endDate)
val expected = 11
val expected = 10

actual shouldEqual expected
}
}

"given 2 dates that are the same" should {

"return the number of days between them as 1" in {
"return the number of days between them as 0" in {

val startDate = LocalDate.of(2020, 1, 10)
val endDate = LocalDate.of(2020, 1, 10)

val actual: Int = new NumberOfStatLeaveDaysFormProvider().daysBetween(startDate, endDate)
val expected = 1
val expected = 0

actual shouldEqual expected
}
Expand Down
Loading

0 comments on commit 47617c7

Please sign in to comment.