Skip to content

Commit

Permalink
[AC][OPS-13077] income+expenditure+paymentPlan in test PEGA journey (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AndyHWChung authored Jan 6, 2025
1 parent b21bd92 commit 6bace99
Show file tree
Hide file tree
Showing 20 changed files with 1,084 additions and 214 deletions.
4 changes: 2 additions & 2 deletions app/config/AppConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ class AppConfig @Inject() (config: Configuration, servicesConfig: ServicesConfig
Some(config.get[String]("pega.start-redirect-url"))
.filter(_.nonEmpty)
.map(_ + toPegaQueryParamString(taxRegime, lang))
.getOrElse(testOnly.controllers.routes.PegaController.dummyPegaPage(taxRegime).url)
.getOrElse(testOnly.controllers.routes.PegaController.start(taxRegime).url)

def pegaChangeLinkReturnUrl(taxRegime: TaxRegime, lang: Language): String =
Some(config.get[String]("pega.change-link-return-url"))
.filter(_.nonEmpty)
.map(_ + toPegaQueryParamString(taxRegime, lang))
.getOrElse(testOnly.controllers.routes.PegaController.dummyPegaPage(taxRegime).url)
.getOrElse(testOnly.controllers.routes.PegaController.start(taxRegime).url)

private def toPegaQueryParamString(taxRegime: TaxRegime, lang: Language): String = {
val regimeValue = taxRegime match {
Expand Down
6 changes: 3 additions & 3 deletions app/services/TtpService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ object TtpService {
)
}

private def toDebtItemCharge(chargeTypeAssessment: ChargeTypeAssessment): List[DebtItemCharge] = {
def toDebtItemCharge(chargeTypeAssessment: ChargeTypeAssessment): List[DebtItemCharge] = {
chargeTypeAssessment.charges.map { charge: Charges =>
DebtItemCharge(
outstandingDebtAmount = OutstandingDebtAmount(charge.charges1.outstandingAmount.value),
Expand All @@ -319,7 +319,7 @@ object TtpService {
}
}

private def calculateCumulativeInterest(eligibilityCheckResult: EligibilityCheckResult): AmountInPence = AmountInPence(
def calculateCumulativeInterest(eligibilityCheckResult: EligibilityCheckResult): AmountInPence = AmountInPence(
eligibilityCheckResult.chargeTypeAssessment
.flatMap(_.charges)
.map(_.charges1.accruedInterest.value.value)
Expand Down Expand Up @@ -368,7 +368,7 @@ object TtpService {
.padTo(8, '0')
.reverse

private def maxPlanLength(
def maxPlanLength(
eligibilityCheckResult: EligibilityCheckResult,
journey: Journey
): PaymentPlanMaxLength =
Expand Down
188 changes: 188 additions & 0 deletions app/testOnly/PegaPlanService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2025 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 testOnly

import javax.inject.{Inject, Singleton}
import _root_.connectors.{EssttpBackendConnector, TtpConnector}
import essttp.journey.model.{Journey, PaymentPlanAnswers, UpfrontPaymentAnswers}
import essttp.rootmodel.AmountInPence
import essttp.rootmodel.dates.InitialPayment
import essttp.rootmodel.dates.startdates.{PreferredDayOfMonth, StartDatesRequest, StartDatesResponse}
import essttp.rootmodel.ttp.affordablequotes._
import essttp.rootmodel.ttp.{PaymentPlanFrequencies, RegimeType}
import play.api.libs.json.{JsBoolean, JsObject, JsValue, Json}
import play.api.mvc.RequestHeader
import services.TtpService
import services.TtpService.{maxPlanLength, toDebtItemCharge}
import testOnly.connectors.EssttpStubConnector
import testOnly.models.TestOnlyJourney
import testOnly.models.formsmodel.IncomeAndExpenditure
import uk.gov.hmrc.http.HeaderCarrier

import scala.concurrent.{ExecutionContext, Future}

@Singleton
class PegaPlanService @Inject() (
datesApiConnector: EssttpBackendConnector,
ttpConnector: TtpConnector,
essttpStubConnector: EssttpStubConnector
)(implicit ex: ExecutionContext) {

def getPlans(journey: Journey, incomeAndExpenditure: IncomeAndExpenditure)(implicit rh: RequestHeader): Future[List[PaymentPlan]] =
for {
startDatesResponse <- callStartDatesApi(journey)
plan <- callPlansApi(journey, incomeAndExpenditure, startDatesResponse)
} yield plan.paymentPlans

def storePegaGetCaseResponse(
journey: Journey,
testOnlyJourney: TestOnlyJourney
)(implicit hc: HeaderCarrier): Future[Unit] = {
val caseId = journey match {
case j: Journey.AfterStartedPegaCase => j.startCaseResponse.caseId.value
case j: Journey.AfterCheckedPaymentPlan =>
j.paymentPlanAnswers match {
case p: PaymentPlanAnswers.PaymentPlanAfterAffordability => p.startCaseResponse.caseId.value
case _: PaymentPlanAnswers.PaymentPlanNoAffordability => sys.error("Trying to find case ID on non-affordability journey")
}
case other => sys.error(s"Could not find PEGA case id for journey in stage ${other.stage.toString}")
}

val incomeAndExpenditure = testOnlyJourney.incomeAndExpenditure.getOrElse(sys.error("Could not find income and expenditure answers"))
val paymentPlan = testOnlyJourney.paymentPlan.getOrElse(sys.error("Could not find paymentPlan"))
val getCaseResponse = constructPegaGetCaseResponse(incomeAndExpenditure, paymentPlan)

essttpStubConnector.storePegaGetCaseResponse(caseId, getCaseResponse)
}

private def upfrontPaymentAnswersFromJourney(journey: Journey): UpfrontPaymentAnswers = journey match {
case j: Journey.AfterUpfrontPaymentAnswers => j.upfrontPaymentAnswers
case other => sys.error(s"Could not find upfront payment answers for journey in stage ${other.stage.toString}")
}

private def callStartDatesApi(journey: Journey)(implicit r: RequestHeader): Future[StartDatesResponse] = {
val dayOfMonth: PreferredDayOfMonth = PreferredDayOfMonth(28)
val upfrontPaymentAnswers = upfrontPaymentAnswersFromJourney(journey)
val initialPayment = upfrontPaymentAnswers match {
case _: UpfrontPaymentAnswers.DeclaredUpfrontPayment => InitialPayment(value = true)
case UpfrontPaymentAnswers.NoUpfrontPayment => InitialPayment(value = false)
}
val startDatesRequest: StartDatesRequest = StartDatesRequest(initialPayment, dayOfMonth)
datesApiConnector.startDates(startDatesRequest)
}

private def callPlansApi(
journey: Journey,
incomeAndExpenditure: IncomeAndExpenditure,
startDatesResponse: StartDatesResponse
)(implicit rh: RequestHeader): Future[AffordableQuotesResponse] = {
val initialPaymentAmount = upfrontPaymentAnswersFromJourney(journey) match {
case UpfrontPaymentAnswers.NoUpfrontPayment => None
case UpfrontPaymentAnswers.DeclaredUpfrontPayment(amount) => Some(amount)
}

val monthlyPaymentAmount = {
val totalIncome = incomeAndExpenditure.income.mainIncome + incomeAndExpenditure.income.otherIncome

val totalExpenditure =
incomeAndExpenditure.expenditure.wagesAndSalaries +
incomeAndExpenditure.expenditure.mortgageAndRent +
incomeAndExpenditure.expenditure.bills +
incomeAndExpenditure.expenditure.materialAndStockCosts +
incomeAndExpenditure.expenditure.businessTravel +
incomeAndExpenditure.expenditure.employeeBenefits +
incomeAndExpenditure.expenditure.other

AmountInPence((totalIncome - totalExpenditure).value / 2)
}

val eligibilityCheckResult = journey match {
case j: Journey.AfterEligibilityChecked => j.eligibilityCheckResult
case other => sys.error(s"Could not find eligibility check result in journey with stage ${other.stage.toString}")
}

val debtItemCharges = eligibilityCheckResult.chargeTypeAssessment.flatMap(toDebtItemCharge)

val affordableQuotesRequest: AffordableQuotesRequest = AffordableQuotesRequest(
channelIdentifier = ChannelIdentifiers.eSSTTP,
regimeType = RegimeType.fromTaxRegime(journey.taxRegime),
paymentPlanAffordableAmount = PaymentPlanAffordableAmount(monthlyPaymentAmount),
paymentPlanFrequency = PaymentPlanFrequencies.Monthly,
paymentPlanMaxLength = maxPlanLength(eligibilityCheckResult, journey),
paymentPlanMinLength = eligibilityCheckResult.paymentPlanMinLength,
accruedDebtInterest = AccruedDebtInterest(TtpService.calculateCumulativeInterest(eligibilityCheckResult)),
paymentPlanStartDate = startDatesResponse.instalmentStartDate,
initialPaymentDate = startDatesResponse.initialPaymentDate,
initialPaymentAmount = initialPaymentAmount,
debtItemCharges = debtItemCharges,
customerPostcodes = eligibilityCheckResult.customerPostcodes
)

ttpConnector.callAffordableQuotesApi(affordableQuotesRequest, journey.correlationId)
}

private def constructPegaGetCaseResponse(incomeAndExpenditure: IncomeAndExpenditure, paymentPlan: PaymentPlan): JsValue = {
val paymentPlanJsonString = {
val json = Json.toJson(paymentPlan).as[JsObject] + ("planSelected", JsBoolean(true))
json.toString
}
def toAmountOfMoneyItem(value: AmountInPence, label: String) =
s"""{
| "amountValue": "${value.formatInPounds}",
| "pyLabel": "$label"
|}
|""".stripMargin

val incomeArray = {
val items: List[String] = List(
incomeAndExpenditure.income.mainIncome -> "Main income",
incomeAndExpenditure.income.otherIncome -> "Other income"
).map{ (toAmountOfMoneyItem _).tupled }

s"""[ ${items.mkString(",")} ]"""
}

val expenditureArray = {
val items: List[String] =
List(
incomeAndExpenditure.expenditure.wagesAndSalaries -> "Wages and salaries",
incomeAndExpenditure.expenditure.mortgageAndRent -> "Mortgage and rental payments on business premises",
incomeAndExpenditure.expenditure.bills -> "Bills for business premises",
incomeAndExpenditure.expenditure.materialAndStockCosts -> "Material and stock costs",
incomeAndExpenditure.expenditure.businessTravel -> "Business travel",
incomeAndExpenditure.expenditure.employeeBenefits -> "Employee benefits",
incomeAndExpenditure.expenditure.other -> "Other",
AmountInPence.zero -> "My company or partnership does not have any expenditure"
).map{ (toAmountOfMoneyItem _).tupled }

s"""[ ${items.mkString(",")} ]"""
}

Json.parse(
s"""{
| "AA": {
| "paymentDay": "28",
| "paymentPlan": [ $paymentPlanJsonString ],
| "expenditure": $expenditureArray,
| "income": $incomeArray
| }
|}
|""".stripMargin
)
}

}
13 changes: 10 additions & 3 deletions app/testOnly/connectors/EssttpStubConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package testOnly.connectors
import essttp.crypto.CryptoFormat
import essttp.rootmodel.ttp.eligibility.EligibilityCheckResult
import play.api.Configuration
import play.api.libs.json.Json
import play.api.libs.json.{JsValue, Json}
import uk.gov.hmrc.http.HttpReads.Implicits._
import uk.gov.hmrc.http.client.HttpClientV2
import uk.gov.hmrc.http.{HeaderCarrier, StringContextOps}
Expand All @@ -29,17 +29,24 @@ import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}

@Singleton
class EssttpStubConnector @Inject() (httpClient: HttpClientV2, config: Configuration) extends ServicesConfig(config) {
class EssttpStubConnector @Inject() (httpClient: HttpClientV2, config: Configuration)(implicit ex: ExecutionContext) extends ServicesConfig(config) {

implicit val cryptoFormat: CryptoFormat = CryptoFormat.NoOpCryptoFormat

val stubsBaseUrl: String = baseUrl("essttp-stubs")

val insertEligibilityDataUrl: String = s"$stubsBaseUrl/debts/time-to-pay/eligibility/insert"

def primeStubs(response: EligibilityCheckResult)(implicit hc: HeaderCarrier, ec: ExecutionContext): Future[Unit] =
def storePegaGetCaseResponseUrl(caseId: String): String = s"$stubsBaseUrl/pega/case/$caseId"

def primeStubs(response: EligibilityCheckResult)(implicit hc: HeaderCarrier): Future[Unit] =
httpClient.post(url"$insertEligibilityDataUrl")
.withBody(Json.toJson(response))
.execute[Unit]

def storePegaGetCaseResponse(caseId: String, getCaseResponse: JsValue)(implicit rh: HeaderCarrier): Future[Unit] =
httpClient.post(url"${storePegaGetCaseResponseUrl(caseId)}")
.withBody(getCaseResponse)
.execute[Unit]

}
Loading

0 comments on commit 6bace99

Please sign in to comment.