Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions app/uk/gov/hmrc/tai/model/nps/NpsDate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import play.api.libs.json._
import uk.gov.hmrc.tai.model.nps.NpsDate._

import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.{DateTimeFormatter, DateTimeParseException}
import scala.language.implicitConversions
import scala.util.Try

Expand All @@ -38,7 +38,7 @@ object NpsDate {
}
implicit val writes: Writes[NpsDate] = (date: NpsDate) => JsString(date.toNpsString)

private val taxPlatformDateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
private[nps] val taxPlatformDateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
private val hipDateRegex = """^(\d\d\d\d)-(\d\d)-(\d\d)$""".r
private val npsDateRegex = """^(\d\d)/(\d\d)/(\d\d\d\d)$""".r
private val npsDateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy")
Expand All @@ -59,19 +59,15 @@ object NpsDateImplicitConversions {
implicit def localToNps(date: LocalDate): NpsDate = NpsDate(date)
}

// TODO: Replace this with our existing date serialization code
object localDateSerializer {

private val localDateRegex = """^(\d\d\d\d)-(\d\d)-(\d\d)$""".r
def deserialize(str: String): LocalDate =
try LocalDate.parse(str, NpsDate.taxPlatformDateFormat)
catch {
case _: DateTimeParseException => throw new IllegalArgumentException(parseError(str))
}

def deserialize(str: String): LocalDate = str match {
case localDateRegex(y, m, d) =>
LocalDate.of(y.toInt, m.toInt, d.toInt)
case _ => throw new Exception(parseError(str))
}

def serialize(value: LocalDate): String =
"%04d-%02d-%02d".format(value.getYear, value.getMonthValue, value.getDayOfMonth)
def serialize(value: LocalDate): String = value.format(NpsDate.taxPlatformDateFormat)

private def parseError(str: String) =
s"Unable to parse '$str' to type 'LocalDate', expected a valid value with format: yyyy-MM-dd"
Expand Down
8 changes: 7 additions & 1 deletion app/uk/gov/hmrc/tai/model/templates/PdfSubmission.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ case class PdfSubmission(
formId: String,
numberOfPages: Int,
attachmentCount: Int = 0,
hmrcReceivedAt: LocalDateTime = LocalDateTime.now(ZoneId.of(LondonEuropeTimezone)), // TODO extract from config
hmrcReceivedAt: LocalDateTime = LocalDateTime.now(ZoneId.of(LondonEuropeTimezone)),
submissionMark: String = "",
casKey: String = "",
businessArea: String = "PSA",
Expand All @@ -48,3 +48,9 @@ case class PdfSubmission(
val fileFormat = "pdf"
val mimeType: String = "application/pdf"
}

object PdfSubmission {

def apply(customerId: String, formId: String, numberOfPages: Int, timezone: String): PdfSubmission =
new PdfSubmission(customerId, formId, numberOfPages, hmrcReceivedAt = LocalDateTime.now(ZoneId.of(timezone)))
}
8 changes: 5 additions & 3 deletions app/uk/gov/hmrc/tai/service/CodingComponentService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package uk.gov.hmrc.tai.service

import com.google.inject.{Inject, Singleton}
import play.api.libs.json.JsValue
import uk.gov.hmrc.domain.Nino
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.tai.connectors.TaxAccountConnector
Expand All @@ -32,9 +33,10 @@ class CodingComponentService @Inject() (
)(implicit ec: ExecutionContext) {

def codingComponents(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[Seq[CodingComponent]] =
taxAccountConnector
.taxAccount(nino, year)
.map(_.as[Seq[CodingComponent]](CodingComponentHipReads.codingComponentReads))
codingComponents(taxAccountConnector.taxAccount(nino, year))

def codingComponents(taxAccountDetails: Future[JsValue]): Future[Seq[CodingComponent]] =
taxAccountDetails.map(_.as[Seq[CodingComponent]](CodingComponentHipReads.codingComponentReads))

def codingComponentsForTaxCodeId(nino: Nino, taxCodeId: Int)(implicit
hc: HeaderCarrier
Expand Down
9 changes: 8 additions & 1 deletion app/uk/gov/hmrc/tai/service/IncomeService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import cats.data.EitherT
import com.google.inject.{Inject, Singleton}
import play.api.Logging
import play.api.http.Status.NOT_FOUND
import play.api.libs.json.JsValue
import play.api.mvc.Request
import uk.gov.hmrc.domain.Nino
import uk.gov.hmrc.http.{HeaderCarrier, UpstreamErrorResponse}
Expand Down Expand Up @@ -86,8 +87,14 @@ class IncomeService @Inject() (
def taxCodeIncomes(nino: Nino, year: TaxYear)(implicit
hc: HeaderCarrier,
request: Request[_]
): Future[Seq[TaxCodeIncome]] =
taxCodeIncomes(taxAccountConnector.taxAccount(nino, year), nino, year)

def taxCodeIncomes(taxAccountDetails: Future[JsValue], nino: Nino, year: TaxYear)(implicit
hc: HeaderCarrier,
request: Request[_]
): Future[Seq[TaxCodeIncome]] = {
lazy val eventualIncomes = taxCodeIncomeHelper.fetchTaxCodeIncomes(nino, year)
lazy val eventualIncomes = taxCodeIncomeHelper.fetchTaxCodeIncomes(taxAccountDetails)
lazy val eventualEmployments = employmentService
.employmentsAsEitherT(nino, year)
.leftMap { error =>
Expand Down
28 changes: 15 additions & 13 deletions app/uk/gov/hmrc/tai/service/TaxAccountSummaryService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package uk.gov.hmrc.tai.service

import com.google.inject.{Inject, Singleton}
import play.api.libs.json.JsValue
import play.api.mvc.Request
import uk.gov.hmrc.domain.Nino
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.tai.connectors.TaxAccountConnector
import uk.gov.hmrc.tai.model.domain.*
import uk.gov.hmrc.tai.model.domain.calculation.CodingComponent
import uk.gov.hmrc.tai.model.domain._
import uk.gov.hmrc.tai.model.tai.TaxYear
import uk.gov.hmrc.tai.service.helper.TaxAccountHelper

Expand All @@ -33,26 +35,25 @@ class TaxAccountSummaryService @Inject() (
codingComponentService: CodingComponentService,
incomeService: IncomeService,
totalTaxService: TotalTaxService,
taxAccountHelper: TaxAccountHelper
taxAccountHelper: TaxAccountHelper,
taxAccountConnector: TaxAccountConnector
)(implicit
ec: ExecutionContext
) {

def taxAccountSummary(nino: Nino, year: TaxYear)(implicit
hc: HeaderCarrier,
request: Request[_]
): Future[TaxAccountSummary] =
// This is really bad if for some reason the cache does not work properly.
// It generates a large number of http calls to the same API.
// The calls are sequential so there is no race condition to the cache.
// todo: refactor all different calls to taxAccount connector into a single call and the proper reads.
): Future[TaxAccountSummary] = {
val taxAccountDetails: Future[JsValue] = taxAccountConnector.taxAccount(nino, year)

for {
totalEstimatedTax <- taxAccountHelper.totalEstimatedTax(nino, year)
taxFreeAmountComponents <- codingComponentService.codingComponents(nino, year)
taxCodeIncomes <- incomeService.taxCodeIncomes(nino, year)
totalTax <- totalTaxService.totalTax(nino, year)
taxFreeAllowance <- totalTaxService.taxFreeAllowance(nino, year)
date <- taxAccountHelper.dateOfTaxAccount(nino, year)
totalEstimatedTax <- taxAccountHelper.totalEstimatedTax(taxAccountDetails)
taxFreeAmountComponents <- codingComponentService.codingComponents(taxAccountDetails)
taxCodeIncomes <- incomeService.taxCodeIncomes(taxAccountDetails, nino, year)
totalTax <- totalTaxService.totalTax(taxAccountDetails)
taxFreeAllowance <- totalTaxService.taxFreeAllowance(taxAccountDetails)
date <- taxAccountHelper.dateOfTaxAccount(taxAccountDetails)
} yield {

val taxFreeAmount = taxFreeAmountCalculation(taxFreeAmountComponents)
Expand All @@ -75,6 +76,7 @@ class TaxAccountSummaryService @Inject() (
date
)
}
}

private[service] def taxFreeAmountCalculation(codingComponents: Seq[CodingComponent]): BigDecimal =
codingComponents.foldLeft(BigDecimal(0))((total: BigDecimal, component: CodingComponent) =>
Expand Down
16 changes: 8 additions & 8 deletions app/uk/gov/hmrc/tai/service/TotalTaxService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package uk.gov.hmrc.tai.service

import com.google.inject.{Inject, Singleton}
import play.api.libs.json.JsValue
import uk.gov.hmrc.domain.Nino
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.tai.connectors.TaxAccountConnector
Expand All @@ -32,11 +33,13 @@ class TotalTaxService @Inject() (
taxAccountConnector: TaxAccountConnector,
taxAccountHelper: TaxAccountHelper
)(implicit ec: ExecutionContext) {
def totalTax(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[TotalTax] = {
val taxAccountDetails = taxAccountConnector.taxAccount(nino, year)
def totalTax(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[TotalTax] =
totalTax(taxAccountConnector.taxAccount(nino, year))

def totalTax(taxAccountDetails: Future[JsValue]): Future[TotalTax] =
for {
incomeCategories <- taxAccountDetails.map(_.as[Seq[IncomeCategory]](TotalTaxHipReads.incomeCategorySeqReads))
totalTaxAmount <- taxAccountHelper.totalEstimatedTax(nino, year)
totalTaxAmount <- taxAccountHelper.totalEstimatedTax(taxAccountDetails)
reliefsGivingBackTax <- taxAccountHelper.reliefsGivingBackTaxComponents(taxAccountDetails)
otherTaxDue <- taxAccountHelper.otherTaxDueComponents(taxAccountDetails)
alreadyTaxedAtSource <- taxAccountHelper.alreadyTaxedAtSourceComponents(taxAccountDetails)
Expand All @@ -51,10 +54,7 @@ class TotalTaxService @Inject() (
taxOnOtherIncome,
taxReliefComponents
)
}

def taxFreeAllowance(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[BigDecimal] =
taxAccountConnector
.taxAccount(nino, year)
.map(_.as[BigDecimal](TotalTaxHipReads.taxFreeAllowanceReads))
def taxFreeAllowance(taxAccountDetails: Future[JsValue]): Future[BigDecimal] =
taxAccountDetails.map(_.as[BigDecimal](TotalTaxHipReads.taxFreeAllowanceReads))
}
18 changes: 9 additions & 9 deletions app/uk/gov/hmrc/tai/service/helper/TaxAccountHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class TaxAccountHelper @Inject() (taxAccountConnector: TaxAccountConnector)(impl
ec: ExecutionContext
) extends Logging {

def dateOfTaxAccount(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[Option[LocalDate]] = {
def dateOfTaxAccount(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[Option[LocalDate]] =
dateOfTaxAccount(taxAccountConnector.taxAccount(nino, year))

def dateOfTaxAccount(taxAccountDetails: Future[JsValue]): Future[Option[LocalDate]] = {
val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

val localDateOptionReads: Reads[Option[LocalDate]] = Reads[Option[LocalDate]] { jsValue =>
Expand All @@ -58,19 +61,16 @@ class TaxAccountHelper @Inject() (taxAccountConnector: TaxAccountConnector)(impl
}
}

taxAccountConnector
.taxAccount(nino, year)
.map { taxAccount =>
(taxAccount \ "date").asOpt[Option[LocalDate]](localDateOptionReads).flatten
}
taxAccountDetails.map { taxAccount =>
(taxAccount \ "date").asOpt[Option[LocalDate]](localDateOptionReads).flatten
}
}

def totalEstimatedTax(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[BigDecimal] = {
def totalEstimatedTax(taxAccountDetails: Future[JsValue]): Future[BigDecimal] = {
val componentTypesCanAffectTotalEst: Seq[TaxComponentType] =
Seq(UnderPaymentFromPreviousYear, OutstandingDebt, EstimatedTaxYouOweThisYear)

taxAccountConnector
.taxAccount(nino, year)
taxAccountDetails
.flatMap { taxAccount =>
val totalTax = taxAccount.as[BigDecimal](TaxOnOtherIncomeHipReads.taxAccountSummaryReads)

Expand Down
8 changes: 5 additions & 3 deletions app/uk/gov/hmrc/tai/service/helper/TaxCodeIncomeHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package uk.gov.hmrc.tai.service.helper

import com.google.inject.{Inject, Singleton}
import play.api.libs.json.JsValue
import uk.gov.hmrc.domain.Nino
import uk.gov.hmrc.http.{HeaderCarrier, NotFoundException}
import uk.gov.hmrc.tai.connectors.TaxAccountConnector
Expand All @@ -31,9 +32,10 @@ class TaxCodeIncomeHelper @Inject() (
taxAccountConnector: TaxAccountConnector
)(implicit ec: ExecutionContext) {
def fetchTaxCodeIncomes(nino: Nino, year: TaxYear)(implicit hc: HeaderCarrier): Future[Seq[TaxCodeIncome]] =
taxAccountConnector
.taxAccount(nino, year)
.map(_.as[Seq[TaxCodeIncome]](TaxCodeIncomeHipReads.taxCodeIncomeSourcesReads))
fetchTaxCodeIncomes(taxAccountConnector.taxAccount(nino, year))

def fetchTaxCodeIncomes(taxAccountDetails: Future[JsValue]): Future[Seq[TaxCodeIncome]] =
taxAccountDetails.map(_.as[Seq[TaxCodeIncome]](TaxCodeIncomeHipReads.taxCodeIncomeSourcesReads))

def incomeAmountForEmploymentId(nino: Nino, year: TaxYear, employmentId: Int)(implicit
hc: HeaderCarrier
Expand Down
30 changes: 24 additions & 6 deletions test/uk/gov/hmrc/tai/service/IncomeServiceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,19 @@ class IncomeServiceSpec @Inject (config: IncomeDetailsConfig) extends BaseSpec {

val mockEmploymentService = mock[EmploymentService]
val mockTaxCodeIncomeHelper = mock[TaxCodeIncomeHelper]
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any(), any())(any()))
val mockTaxAccountConnector = mock[TaxAccountConnector]
when(mockTaxAccountConnector.taxAccount(any(), any())(any())).thenReturn(Future.successful(Json.obj()))
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any[Future[JsValue]]()))
.thenReturn(Future.successful(taxCodeIncomes))

when(mockEmploymentService.employmentsAsEitherT(any[Nino], any[TaxYear])(any[HeaderCarrier], any()))
.thenReturn(EitherT.rightT(Employments(Seq(employment, employment2), None)))

val sut = createSUT(employmentService = mockEmploymentService, taxCodeIncomeHelper = mockTaxCodeIncomeHelper)
val sut = createSUT(
employmentService = mockEmploymentService,
taxCodeIncomeHelper = mockTaxCodeIncomeHelper,
taxAccountConnector = mockTaxAccountConnector
)
val result = sut.taxCodeIncomes(nino, TaxYear())(HeaderCarrier(), FakeRequest()).futureValue

result mustBe taxCodeIncomes.map(_.copy(status = Ceased))
Expand Down Expand Up @@ -213,13 +219,19 @@ class IncomeServiceSpec @Inject (config: IncomeDetailsConfig) extends BaseSpec {

val mockEmploymentService = mock[EmploymentService]
val mockTaxCodeIncomeHelper = mock[TaxCodeIncomeHelper]
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any(), any())(any()))
val mockTaxAccountConnector = mock[TaxAccountConnector]
when(mockTaxAccountConnector.taxAccount(any(), any())(any())).thenReturn(Future.successful(Json.obj()))
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any[Future[JsValue]]()))
.thenReturn(Future.successful(taxCodeIncomes))

when(mockEmploymentService.employmentsAsEitherT(any[Nino], any[TaxYear])(any[HeaderCarrier], any()))
.thenReturn(EitherT.rightT(Employments(Seq.empty, None)))

val sut = createSUT(employmentService = mockEmploymentService, taxCodeIncomeHelper = mockTaxCodeIncomeHelper)
val sut = createSUT(
employmentService = mockEmploymentService,
taxCodeIncomeHelper = mockTaxCodeIncomeHelper,
taxAccountConnector = mockTaxAccountConnector
)
val result = sut.taxCodeIncomes(nino, TaxYear())(HeaderCarrier(), FakeRequest()).futureValue

result mustBe taxCodeIncomes
Expand Down Expand Up @@ -257,13 +269,19 @@ class IncomeServiceSpec @Inject (config: IncomeDetailsConfig) extends BaseSpec {

val mockEmploymentService = mock[EmploymentService]
val mockTaxCodeIncomeHelper = mock[TaxCodeIncomeHelper]
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any(), any())(any()))
val mockTaxAccountConnector = mock[TaxAccountConnector]
when(mockTaxAccountConnector.taxAccount(any(), any())(any())).thenReturn(Future.successful(Json.obj()))
when(mockTaxCodeIncomeHelper.fetchTaxCodeIncomes(any[Future[JsValue]]()))
.thenReturn(Future.successful(taxCodeIncomes))

when(mockEmploymentService.employmentsAsEitherT(any[Nino], any[TaxYear])(any[HeaderCarrier], any()))
.thenReturn(EitherT.leftT(UpstreamErrorResponse("not found", NOT_FOUND)))

val sut = createSUT(employmentService = mockEmploymentService, taxCodeIncomeHelper = mockTaxCodeIncomeHelper)
val sut = createSUT(
employmentService = mockEmploymentService,
taxCodeIncomeHelper = mockTaxCodeIncomeHelper,
taxAccountConnector = mockTaxAccountConnector
)
val result = sut.taxCodeIncomes(nino, TaxYear())(HeaderCarrier(), FakeRequest()).futureValue

result mustBe taxCodeIncomes
Expand Down
Loading