Skip to content

Commit

Permalink
SASS-11093- Foreign property - Invalid payload when submitting adjust…
Browse files Browse the repository at this point in the history
…ments (#142)
  • Loading branch information
tapiwa-tiyemba authored Feb 14, 2025
1 parent 7373165 commit 4c22fed
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 155 deletions.
43 changes: 42 additions & 1 deletion app/connectors/IntegrationFrameworkConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import connectors.response._
import models.common.TaxYear.{asTyBefore24, asTys}
import models.common.{IncomeSourceId, Nino, TaxYear}
import models.errors.ApiError
import models.request.foreign.{AnnualForeignPropertySubmission, CreateForeignPropertyPeriodicSubmissionRequest, UpdateForeignPropertyPeriodicSubmissionRequest}
import models.request.foreign.{AnnualForeignPropertySubmission, AnnualForeignPropertySubmissionAdjustments, CreateForeignPropertyPeriodicSubmissionRequest, UpdateForeignPropertyPeriodicSubmissionRequest}
import models.request.{CreateUKPropertyPeriodicSubmissionRequest, UpdateUKPropertyPeriodicSubmissionRequest}
import models.responses._
import org.slf4j.{Logger, LoggerFactory}
Expand Down Expand Up @@ -455,5 +455,46 @@ class IntegrationFrameworkConnector @Inject() (http: HttpClientV2, appConfig: Ap
response.result
}
}
def createOrUpdateAnnualForeignPropertySubmissionAdjustments(
taxYear: TaxYear,
incomeSourceId: IncomeSourceId,
nino: Nino,
foreignProperty: AnnualForeignPropertySubmissionAdjustments
)(implicit hc: HeaderCarrier): Future[Either[ApiError, Unit]] = {
val (url, apiVersion) = if (taxYear.isAfter24) {
(
s"""${appConfig.ifBaseUrl}/income-tax/business/property/annual/${asTys(taxYear)}/$nino/$incomeSourceId""",
"1804"
)
} else {
(
s"""${appConfig.ifBaseUrl}/income-tax/business/property/annual?taxableEntityId=$nino&taxYear=${asTyBefore24(
taxYear
)}&incomeSourceId=$incomeSourceId""",
"1597"
)
}
logger.debug(
s"Calling createOrUpdateAnnualForeignPropertySubmissionAdjustments with url: $url, body: ${Json.toJson(foreignProperty)}"
)

http
.put(url"$url")(hcWithCorrelationId(hc))
.setHeader("Environment" -> appConfig.ifEnvironment)
.setHeader(HeaderNames.authorisation -> s"Bearer ${appConfig.authorisationTokenFor(apiVersion)}")
.withBody(Json.toJson(foreignProperty))
.execute[PutAnnualSubmissionResponse]
.map { response: PutAnnualSubmissionResponse =>
if (response.result.isLeft) {
val correlationId =
response.httpResponse.header(key = "CorrelationId").map(id => s" CorrelationId: $id").getOrElse("")
logger.error(
s"Error creating a foreign property annual submission from the Integration Framework: URL: $url" +
s" correlationId: $correlationId; status: ${response.httpResponse.status}; Body:${response.httpResponse.body}"
)
}
response.result
}
}

}
136 changes: 76 additions & 60 deletions app/models/request/foreign/AnnualForeignPropertySubmission.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,27 @@ case class AnnualForeignProperty(
allowances: Option[ForeignPropertyAllowances]
)

case class AnnualForeignPropertyAdjustments(countryCode: String, adjustments: Option[ForeignPropertyAdjustments])

object AnnualForeignPropertyAdjustments {
implicit val format: OFormat[AnnualForeignPropertyAdjustments] = Json.format[AnnualForeignPropertyAdjustments]
}

object AnnualForeignProperty {
implicit val format: OFormat[AnnualForeignProperty] = Json.format[AnnualForeignProperty]
}

case class AnnualForeignPropertySubmission(
foreignProperty: Option[Seq[AnnualForeignProperty]]
)
case class AnnualForeignPropertySubmissionAdjustments(
foreignProperty: Option[Seq[AnnualForeignPropertyAdjustments]]
)

object AnnualForeignPropertySubmissionAdjustments {
implicit val format: OFormat[AnnualForeignPropertySubmissionAdjustments] =
Json.format[AnnualForeignPropertySubmissionAdjustments]
}

object AnnualForeignPropertySubmission {
implicit val format: OFormat[AnnualForeignPropertySubmission] = Json.format[AnnualForeignPropertySubmission]
Expand Down Expand Up @@ -195,83 +209,85 @@ object AnnualForeignPropertySubmission {
Right(annualForeignPropertySubmissionWithNewAllowances)
}

def fromForeignPropertyAdjustments(
mayBeAnnualForeignPropertySubmissionFromDownstream: Option[AnnualForeignPropertySubmission],
def fromForeignPropertyAdjustmentsPIA(
foreignPropertyAdjustmentsWithCountryCode: ForeignPropertyAdjustmentsWithCountryCode
): Either[ServiceError, AnnualForeignPropertySubmission] = {
): AnnualForeignPropertySubmission = {
val foreignPropertyAllowancesLens = GenLens[AnnualForeignProperty](_.allowances)
val targetCountryCode = foreignPropertyAdjustmentsWithCountryCode.countryCode
val foreignPropertyLens = GenLens[AnnualForeignPropertySubmission](_.foreignProperty)
val foreignPropertyAdjustmentsLens = GenLens[AnnualForeignProperty](_.adjustments)
val firstForeignPropertyAdjustmentsLens: Optional[AnnualForeignPropertySubmission, ForeignPropertyAdjustments] =
foreignPropertyLens.some.index(0).andThen(foreignPropertyAdjustmentsLens.some)
val firstForeignPropertyAllowancesLens: Optional[AnnualForeignPropertySubmission, ForeignPropertyAllowances] =
foreignPropertyLens.some.index(0).andThen(foreignPropertyAllowancesLens.some)

val maybeForeignPropertyAllowances: Option[ForeignPropertyAllowances] =
mayBeAnnualForeignPropertySubmissionFromDownstream match {
case Some(annualForeignPropertySubmission) =>
annualForeignPropertySubmission.foreignProperty.flatMap(_.find(_.countryCode == targetCountryCode)) match {
case Some(foreignPropertyForTheCountryCode) =>
foreignPropertyForTheCountryCode.allowances
case _ => None
}
case _ => None
}
val newForeignPropertyAdjustments = ForeignPropertyAdjustments(
privateUseAdjustment = Some(foreignPropertyAdjustmentsWithCountryCode.privateUseAdjustment),
balancingCharge = foreignPropertyAdjustmentsWithCountryCode.balancingCharge.balancingChargeAmount
)

if (foreignPropertyAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim.isDefined) {
val foreignPropertyAllowancesLens = GenLens[AnnualForeignProperty](_.allowances)
val firstForeignPropertyAllowancesLens: Optional[AnnualForeignPropertySubmission, ForeignPropertyAllowances] =
foreignPropertyLens.some.index(0).andThen(foreignPropertyAllowancesLens.some)
val newForeignPropertyAllowances = ForeignPropertyAllowances(
propertyAllowance = foreignPropertyAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim,
zeroEmissionsCarAllowance = None,
zeroEmissionsGoodsVehicleAllowance = None,
costOfReplacingDomesticItems = None,
otherCapitalAllowance = None,
annualInvestmentAllowance = None,
electricChargePointAllowance = None,
structuredBuildingAllowance = None
)
val emptyAnnualForeignPropertySubmission = AnnualForeignPropertySubmission(
foreignProperty = Some(
Seq(
AnnualForeignProperty(
countryCode = targetCountryCode,
adjustments = Some(ForeignPropertyAdjustments(None, None)),
allowances = Some(ForeignPropertyAllowances(None, None, None, None, None, None, None, None))
)
val newForeignPropertyAllowances = ForeignPropertyAllowances(
propertyAllowance = foreignPropertyAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim,
zeroEmissionsCarAllowance = None,
zeroEmissionsGoodsVehicleAllowance = None,
costOfReplacingDomesticItems = None,
otherCapitalAllowance = None,
annualInvestmentAllowance = None,
electricChargePointAllowance = None,
structuredBuildingAllowance = None
)
val emptyAnnualForeignPropertySubmission = AnnualForeignPropertySubmission(
foreignProperty = Some(
Seq(
AnnualForeignProperty(
countryCode = targetCountryCode,
adjustments = Some(ForeignPropertyAdjustments(None, None)),
allowances = Some(ForeignPropertyAllowances(None, None, None, None, None, None, None, None))
)
)
)
val annualForeignPropertySubmissionWithNewAdjustments =
firstForeignPropertyAdjustmentsLens.replace(newForeignPropertyAdjustments)(
emptyAnnualForeignPropertySubmission
)
val annualForeignPropertySubmissionWithBoth =
firstForeignPropertyAllowancesLens.replace(newForeignPropertyAllowances)(
annualForeignPropertySubmissionWithNewAdjustments
)
Right(annualForeignPropertySubmissionWithBoth)
} else {
val allowances = if(maybeForeignPropertyAllowances.isEmpty) None else maybeForeignPropertyAllowances
val annualForeignPropertySubmissionRetainingAllowances = AnnualForeignPropertySubmission(
foreignProperty = Some(
Seq(
AnnualForeignProperty(
countryCode = targetCountryCode,
adjustments = Some(ForeignPropertyAdjustments(None, None)),
allowances = allowances
)
)
val annualForeignPropertySubmissionWithNewAdjustments =
firstForeignPropertyAdjustmentsLens.replace(newForeignPropertyAdjustments)(
emptyAnnualForeignPropertySubmission
)
val annualForeignPropertySubmissionWithBoth =
firstForeignPropertyAllowancesLens.replace(newForeignPropertyAllowances)(
annualForeignPropertySubmissionWithNewAdjustments
)
annualForeignPropertySubmissionWithBoth

}

def fromForeignPropertyAdjustments(
foreignPropertyAdjustmentsWithCountryCode: ForeignPropertyAdjustmentsWithCountryCode
): AnnualForeignPropertySubmissionAdjustments = {
val targetCountryCode = foreignPropertyAdjustmentsWithCountryCode.countryCode
val foreignPropertyLens = GenLens[AnnualForeignPropertySubmissionAdjustments](_.foreignProperty)
val foreignPropertyAdjustmentsLens = GenLens[AnnualForeignPropertyAdjustments](_.adjustments)
val firstForeignPropertyAdjustmentsLens
: Optional[AnnualForeignPropertySubmissionAdjustments, ForeignPropertyAdjustments] =
foreignPropertyLens.some.index(0).andThen(foreignPropertyAdjustmentsLens.some)

val newForeignPropertyAdjustments = ForeignPropertyAdjustments(
privateUseAdjustment = Some(foreignPropertyAdjustmentsWithCountryCode.privateUseAdjustment),
balancingCharge = foreignPropertyAdjustmentsWithCountryCode.balancingCharge.balancingChargeAmount
)
val emptyAdjustments = AnnualForeignPropertySubmissionAdjustments(
foreignProperty = Some(
Seq(
AnnualForeignPropertyAdjustments(
countryCode = targetCountryCode,
adjustments = Some(ForeignPropertyAdjustments(None, None))
)
)
)
val annualForeignPropertySubmissionWithNewAdjustments =
firstForeignPropertyAdjustmentsLens.replace(newForeignPropertyAdjustments)(
annualForeignPropertySubmissionRetainingAllowances
)
Right(annualForeignPropertySubmissionWithNewAdjustments)
}
)
val annualForeignPropertySubmissionAdjustments =
firstForeignPropertyAdjustmentsLens.replace(newForeignPropertyAdjustments)(
emptyAdjustments
)
annualForeignPropertySubmissionAdjustments
}

}
108 changes: 64 additions & 44 deletions app/services/ForeignPropertyService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,22 @@ class ForeignPropertyService @Inject() (
.leftMap(e => ApiServiceError(e.status))
}

def createOrUpdateAnnualForeignPropertySubmissionAdjustments(
taxYear: TaxYear,
incomeSourceId: IncomeSourceId,
nino: Nino,
body: AnnualForeignPropertySubmissionAdjustments
)(implicit hc: HeaderCarrier): ITPEnvelope[Boolean] =
body match {
case AnnualForeignPropertySubmissionAdjustments(None) =>
ITPEnvelope.liftPure(false)
case _ =>
EitherT(
connector.createOrUpdateAnnualForeignPropertySubmissionAdjustments(taxYear, incomeSourceId, nino, body)
).map(_ => true)
.leftMap(e => ApiServiceError(e.status))
}

def saveForeignPropertyAllowances(
journeyContext: JourneyContext,
nino: Nino,
Expand Down Expand Up @@ -420,66 +436,70 @@ class ForeignPropertyService @Inject() (

}


def saveForeignPropertyAdjustments(
journeyContext: JourneyContext,
nino: Nino,
foreignAdjustmentsWithCountryCode: ForeignPropertyAdjustmentsWithCountryCode
)(implicit hc: HeaderCarrier): EitherT[Future, ServiceError, Boolean] = {
val emptyAnnualForeignPropertySubmission = AnnualForeignPropertySubmission(None)
journeyContext: JourneyContext,
nino: Nino,
foreignAdjustmentsWithCountryCode: ForeignPropertyAdjustmentsWithCountryCode
)(implicit hc: HeaderCarrier): EitherT[Future, ServiceError, Boolean] =
for {
annualForeignPropertySubmissionFromDownstream <- this.getAnnualForeignPropertySubmissionFromDownStream(
_ <- {
if (foreignAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim.isDefined) {
val annualForeignPropertySubmission =
AnnualForeignPropertySubmission.fromForeignPropertyAdjustmentsPIA(foreignAdjustmentsWithCountryCode)
createOrUpdateAnnualForeignPropertySubmission(
journeyContext.taxYear,
journeyContext.incomeSourceId,
nino,
journeyContext.incomeSourceId
annualForeignPropertySubmission
)
.leftFlatMap {
case DataNotFoundError => ITPEnvelope.liftPure(emptyAnnualForeignPropertySubmission)
case e => ITPEnvelope.liftEither(e.asLeft[AnnualForeignPropertySubmission])
}
annualSubmissionResponse <- {
val annualForeignPropertySubmissionWithNewAdjustments = AnnualForeignPropertySubmission
.fromForeignPropertyAdjustments(
Some(annualForeignPropertySubmissionFromDownstream),
foreignAdjustmentsWithCountryCode
).fold(_ => emptyAnnualForeignPropertySubmission, identity)
createOrUpdateAnnualForeignPropertySubmission(
journeyContext.taxYear,
journeyContext.incomeSourceId,
nino,
annualForeignPropertySubmissionWithNewAdjustments
)
} else {
val annualForeignPropertySubmissionWithNewAdjustments = AnnualForeignPropertySubmission
.fromForeignPropertyAdjustments(
foreignAdjustmentsWithCountryCode
)
createOrUpdateAnnualForeignPropertySubmissionAdjustments(
journeyContext.taxYear,
journeyContext.incomeSourceId,
nino,
annualForeignPropertySubmissionWithNewAdjustments
)
}

}
periodicSubmissionResponse <- {
if(foreignAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim.isDefined){
_ <- {
if (foreignAdjustmentsWithCountryCode.propertyIncomeAllowanceClaim.isDefined) {
ITPEnvelope.liftPure(Option.empty[PeriodicSubmissionId])
} else {
for {
currentPeriodicSubmission <- getCurrentPeriodicSubmission(
journeyContext.taxYear,
nino,
journeyContext.incomeSourceId
)
journeyContext.taxYear,
nino,
journeyContext.incomeSourceId
)
submissionResponse <- createOrUpdatePeriodicSubmission(
journeyContext.toJourneyContextWithNino(nino),
currentPeriodicSubmission,
foreignAdjustmentsWithCountryCode
)
journeyContext.toJourneyContextWithNino(nino),
currentPeriodicSubmission,
foreignAdjustmentsWithCountryCode
)
} yield submissionResponse
}

}
res <- persistForeignAnswers(
journeyContext,
ForeignAdjustmentsStoreAnswers(
balancingChargeYesNo = foreignAdjustmentsWithCountryCode.balancingCharge.balancingChargeYesNo,
foreignUnusedResidentialFinanceCostYesNo = foreignAdjustmentsWithCountryCode.unusedResidentialFinanceCost.map(_.foreignUnusedResidentialFinanceCostYesNo),
unusedLossesPreviousYearsYesNo = foreignAdjustmentsWithCountryCode.unusedLossesPreviousYears.unusedLossesPreviousYearsYesNo,
whenYouReportedTheLoss = foreignAdjustmentsWithCountryCode.whenYouReportedTheLoss
),
foreignAdjustmentsWithCountryCode.countryCode
)
journeyContext,
ForeignAdjustmentsStoreAnswers(
balancingChargeYesNo = foreignAdjustmentsWithCountryCode.balancingCharge.balancingChargeYesNo,
foreignUnusedResidentialFinanceCostYesNo =
foreignAdjustmentsWithCountryCode.unusedResidentialFinanceCost.map(
_.foreignUnusedResidentialFinanceCostYesNo
),
unusedLossesPreviousYearsYesNo =
foreignAdjustmentsWithCountryCode.unusedLossesPreviousYears.unusedLossesPreviousYearsYesNo,
whenYouReportedTheLoss = foreignAdjustmentsWithCountryCode.whenYouReportedTheLoss
),
foreignAdjustmentsWithCountryCode.countryCode
)
} yield res
}

def saveForeignPropertySba(
journeyContext: JourneyContext,
Expand Down
Loading

0 comments on commit 4c22fed

Please sign in to comment.