From 3c5f0795b3bbbb9ae2178133ac103d4f967136ae Mon Sep 17 00:00:00 2001 From: Scott Goodwin <33629936+scott-goodwin@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:10:59 +0100 Subject: [PATCH] DL-8900 - added new data for 1811 call --- .../financialData/FinancialDataResponse.scala | 94 ++++++++----- .../examples/penalties/FinancialDetails.json | 16 ++- .../schemas/penalties/FinancialDetails.json | 56 +++++++- .../FinancialDataHttpParserSpec.scala | 26 +++- .../v1/constants/FinancialDataConstants.scala | 126 +++++++++++++----- 5 files changed, 247 insertions(+), 71 deletions(-) diff --git a/app/v1/models/response/financialData/FinancialDataResponse.scala b/app/v1/models/response/financialData/FinancialDataResponse.scala index 29200e8f..d5c192b7 100644 --- a/app/v1/models/response/financialData/FinancialDataResponse.scala +++ b/app/v1/models/response/financialData/FinancialDataResponse.scala @@ -20,66 +20,92 @@ import play.api.libs.json._ import v1.models.errors.MtdError import play.api.libs.functional.syntax._ -case class LineItemInterestDetails(interestKey: String, - interestStartDate: String) +case class LineItemInterestDetails(interestKey: Option[String], + currentInterestRate: Option[BigDecimal], + interestPostedAmount: Option[BigDecimal], + interestAccruingAmount: Option[BigDecimal], + interestStartDate: Option[String]) object LineItemInterestDetails { implicit val format: OFormat[LineItemInterestDetails] = Json.format[LineItemInterestDetails] } -case class LineItemDetail(periodFromDate: String, - periodToDate: String, - periodKey: String, - netDueDate: String, - amount: BigDecimal, - lineItemInterestDetails: LineItemInterestDetails) +case class LineItemDetail( + chargeDescription: Option[String], + periodFromDate: Option[String], + periodToDate: Option[String], + periodKey: Option[String], + netDueDate: Option[String], + amount: Option[BigDecimal], + lineItemInterestDetails: Option[LineItemInterestDetails]) object LineItemDetail { implicit val format: OFormat[LineItemDetail] = Json.format[LineItemDetail] } -case class DocumentDetail(postingDate: String, - issueDate: String, - documentTotalAmount: BigDecimal, - documentClearedAmount: BigDecimal, - documentOutstandingAmount: BigDecimal, - documentInterestTotal: BigDecimal, - lineItemDetails: Seq[LineItemDetail]) +case class DocumentInterestTotals(interestPostedAmount: Option[BigDecimal], + interestAccruingAmount: Option[BigDecimal]) + +object DocumentInterestTotals { + implicit val format: OFormat[DocumentInterestTotals] = Json.format[DocumentInterestTotals] +} + +case class DocumentDetail(postingDate: Option[String], + issueDate: Option[String], + documentTotalAmount: Option[BigDecimal], + documentClearedAmount: Option[BigDecimal], + documentInterestTotals: Option[DocumentInterestTotals], + documentOutstandingAmount: Option[BigDecimal], + documentInterestTotal: Option[BigDecimal], + lineItemDetails: Option[Seq[LineItemDetail]]) object DocumentDetail { implicit val reads: Reads[DocumentDetail] = ( - (JsPath \ "postingDate").read[String] and - (JsPath \ "issueDate").read[String] and - (JsPath \ "documentTotalAmount").read[BigDecimal] and - (JsPath \ "documentClearedAmount").read[BigDecimal] and - (JsPath \ "documentOutstandingAmount").read[BigDecimal] and - (JsPath \ "documentInterestTotals" \ "interestTotalAmount").read[BigDecimal] and - (JsPath \ "lineItemDetails").read[Seq[LineItemDetail]] + (JsPath \ "postingDate").readNullable[String] and + (JsPath \ "issueDate").readNullable[String] and + (JsPath \ "documentTotalAmount").readNullable[BigDecimal] and + (JsPath \ "documentClearedAmount").readNullable[BigDecimal] and + (JsPath \ "documentInterestTotals").readNullable[DocumentInterestTotals] and + (JsPath \ "documentOutstandingAmount").readNullable[BigDecimal] and + (JsPath \ "documentInterestTotals" \ "interestTotalAmount").readNullable[BigDecimal] and + (JsPath \ "lineItemDetails").readNullable[Seq[LineItemDetail]] )(DocumentDetail.apply _) implicit val writes: OWrites[DocumentDetail] = Json.writes[DocumentDetail] } -case class Totalisation(totalOverdue: BigDecimal, - totalNotYetDue: BigDecimal, - totalBalance: BigDecimal, - totalCredit: BigDecimal, - totalCleared: BigDecimal) + +case class AdditionalReceivableTotalisations(totalAccountPostedInterest: Option[BigDecimal], + totalAccountAccruingInterest: Option[BigDecimal]) + +object AdditionalReceivableTotalisations { + implicit val format: OFormat[AdditionalReceivableTotalisations] = Json.format[AdditionalReceivableTotalisations] + +} + +case class Totalisation(totalOverdue: Option[BigDecimal], + totalNotYetDue: Option[BigDecimal], + totalBalance: Option[BigDecimal], + totalCredit: Option[BigDecimal], + totalCleared: Option[BigDecimal], + additionalReceivableTotalisations: Option[AdditionalReceivableTotalisations]) object Totalisation { implicit val reads: Reads[Totalisation] = ( - (JsPath \ "targetedSearch" \ "totalOverdue").read[BigDecimal] and - (JsPath \ "targetedSearch" \ "totalNotYetDue").read[BigDecimal] and - (JsPath \ "targetedSearch" \ "totalBalance").read[BigDecimal] and - (JsPath \ "targetedSearch" \ "totalCredit").read[BigDecimal] and - (JsPath \ "targetedSearch" \ "totalCleared").read[BigDecimal] + (JsPath \ "targetedSearch_SelectionCriteriaTotalisation" \ "totalOverdue").readNullable[BigDecimal] and + (JsPath \ "targetedSearch_SelectionCriteriaTotalisation" \ "totalNotYetDue").readNullable[BigDecimal] and + (JsPath \ "targetedSearch_SelectionCriteriaTotalisation" \ "totalBalance").readNullable[BigDecimal] and + (JsPath \ "targetedSearch_SelectionCriteriaTotalisation" \ "totalCredit").readNullable[BigDecimal] and + (JsPath \ "targetedSearch_SelectionCriteriaTotalisation" \ "totalCleared").readNullable[BigDecimal] and + (JsPath \ "additionalReceivableTotalisations").readNullable[AdditionalReceivableTotalisations] )(Totalisation.apply _) implicit val writes: OWrites[Totalisation] = Json.writes[Totalisation] } -case class FinancialDataResponse(totalisation: Totalisation, - documentDetails: Seq[DocumentDetail]) + +case class FinancialDataResponse(totalisation: Option[Totalisation], + documentDetails: Option[Seq[DocumentDetail]]) object FinancialDataResponse { implicit val format: OFormat[FinancialDataResponse] = Json.format[FinancialDataResponse] diff --git a/resources/public/api/conf/1.0/examples/penalties/FinancialDetails.json b/resources/public/api/conf/1.0/examples/penalties/FinancialDetails.json index 8db8742c..b2c2b105 100644 --- a/resources/public/api/conf/1.0/examples/penalties/FinancialDetails.json +++ b/resources/public/api/conf/1.0/examples/penalties/FinancialDetails.json @@ -4,7 +4,11 @@ "totalNotYetDue": 12.34, "totalBalance": 12.45, "totalCredit": 13.46, - "totalCleared": 12.35 + "totalCleared": 12.35, + "additionalReceivableTotalisations" :{ + "totalAccountPostedInterest": 10.1, + "totalAccountAccruingInterest": 11.2 + } }, "documentDetails": [ { @@ -13,9 +17,14 @@ "documentTotalAmount": 123.45, "documentClearedAmount": 111.11, "documentOutstandingAmount": 12.34, + "documentInterestTotals": { + "interestPostedAmount": 13.12, + "interestAccruingAmount": 12.10 + }, "documentInterestTotal": 1.23, "lineItemDetails": [ { + "chargeDescription": "VAT Return", "periodFromDate": "2022-03-01", "periodToDate": "2022-03-01", "periodKey": "13RL", @@ -23,7 +32,10 @@ "amount": 123.45, "lineItemInterestDetails": { "interestKey": "01", - "interestStartDate": "2022-03-01" + "interestStartDate": "2022-03-01", + "currentInterestRate": 2, + "interestPostedAmount": 12, + "interestAccruingAmount": 13.1 } } ] diff --git a/resources/public/api/conf/1.0/schemas/penalties/FinancialDetails.json b/resources/public/api/conf/1.0/schemas/penalties/FinancialDetails.json index 5042c6ec..5f9aa373 100644 --- a/resources/public/api/conf/1.0/schemas/penalties/FinancialDetails.json +++ b/resources/public/api/conf/1.0/schemas/penalties/FinancialDetails.json @@ -31,6 +31,23 @@ "description": "The amount that has been paid, reversed, removed or cleared. The value must be between -99999999999.99 and 99999999999.99 to 2 decimal places.", "type": "number", "example": "5009.99" + }, + "additionalReceivableTotalisations" : { + "description": "Interest totals that relate to the VAT account.", + "type": "object", + "properties": { + "totalAccountPostedInterest": { + "description": "The total amount of interest that has been posted to the account.", + "type": "number", + "example": "5009.99" + }, + "totalAccountAccruingInterest": { + "description": "The total amount of interest that the account has accrued since interest was last posted to it. This amount is yet to be posted to the account.", + "type": "number", + "example": "5009.99" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -51,6 +68,23 @@ "type": "string", "example": "2022-08-16" }, + "documentInterestTotals": { + "description": "Interest totals that relate to the charge.", + "type": "object", + "properties": { + "interestPostedAmount": { + "description": "The amount of interest, relating to the charge, that has been posted to the account.", + "type": "number", + "example": "5009.99" + }, + "interestAccruingAmount": { + "description": "The amount of interest that has accrued since interest on the charge was last posted to the account.", + "type": "number", + "example": "5009.99" + } + }, + "additionalProperties": false + }, "documentTotalAmount": { "description": "The total sum of the line items within the document. The value must be between -99999999999.99 and 99999999999.99 to 2 decimal places.", "type": "number", @@ -77,6 +111,11 @@ "items": { "type": "object", "properties": { + "chargeDescription": { + "description": "Describes what the charge relates to.", + "type": "string", + "example": "VAT Return" + }, "periodFromDate": { "description": "The start date of this period.", "type": "string", @@ -103,7 +142,7 @@ "example": "5009.99" }, "lineItemInterestDetails": { - "description": "", + "description": "Details of the interest that relates to the line item.", "type": "object", "properties": { "interestKey": { @@ -115,6 +154,21 @@ "description": "The date that the interest charges started.", "type": "string", "example": "2022-08-16" + }, + "currentInterestRate": { + "description": "The rate of interest that currently applies to the line item.", + "type": "number", + "example": "2" + }, + "interestPostedAmount": { + "description": "The amount of interest, relating to the line item, that has been posted to the account.", + "type": "number", + "example": "10" + }, + "interestAccruingAmount": { + "description": "The amount of interest that has accrued since interest on the line item was last posted to the account.", + "type": "number", + "example": "10" } }, "additionalProperties": false diff --git a/test/v1/connectors/httpparsers/FinancialDataHttpParserSpec.scala b/test/v1/connectors/httpparsers/FinancialDataHttpParserSpec.scala index 40442517..5fecb6bc 100644 --- a/test/v1/connectors/httpparsers/FinancialDataHttpParserSpec.scala +++ b/test/v1/connectors/httpparsers/FinancialDataHttpParserSpec.scala @@ -64,18 +64,38 @@ class FinancialDataHttpParserSpec extends UnitSpec { result shouldBe Right(FinancialDataConstants.wrappedFinancialDataResponse(FinancialDataConstants.testFinancialDataResponse)) } + + "return Right(FinancialDataResponse) No Document Details" in { + + val result = FinancialDataHttpReads.read("", "", + HttpResponse( + status = Status.OK, + json = FinancialDataConstants.testDownstreamFinancialDetailsNoDocumentDetails, + headers = Map( + "CorrelationId" -> Seq(FinancialDataConstants.correlationId) + ) + ) + ) + + result shouldBe Right(FinancialDataConstants.wrappedFinancialDataResponse(FinancialDataConstants.testFinancialNoDocumentDetailsDataResponse)) + } } "json is invalid" must { "return Left(InvalidJson)" in { + val jsonObject = + Json.parse(""" + | "totalisations" { + | "test": "test" + | } + |""".stripMargin) + val result = FinancialDataHttpReads.read("", "", HttpResponse( status = Status.OK, - json = Json.obj( - "totalisations" -> "test" - ), + json = jsonObject, headers = Map( "CorrelationId" -> Seq(FinancialDataConstants.correlationId) ) diff --git a/test/v1/constants/FinancialDataConstants.scala b/test/v1/constants/FinancialDataConstants.scala index 91b86a16..61521606 100644 --- a/test/v1/constants/FinancialDataConstants.scala +++ b/test/v1/constants/FinancialDataConstants.scala @@ -17,18 +17,15 @@ package v1.constants import config.AppConfig -import play.api.libs.json.{JsObject, JsValue, Json} +import play.api.libs.json.{JsValue, Json} import play.api.mvc.AnyContentAsEmpty import play.api.test.FakeRequest -import v1.constants.FinancialDataConstants.vrn -import v1.constants.PenaltiesConstants.correlationId import v1.controllers.UserRequest import v1.models.auth.UserDetails import v1.models.domain.Vrn import v1.models.errors.{ErrorWrapper, MtdError} import v1.models.outcomes.ResponseWrapper import v1.models.request.penalties.{FinancialRawData, FinancialRequest} -import v1.models.response.financialData import v1.models.response.financialData._ object FinancialDataConstants { @@ -44,9 +41,37 @@ object FinancialDataConstants { val invalidVrn = "fakeVRN" val invalidRawData: FinancialRawData = FinancialRawData(invalidVrn) - def financialDataUrl(vrn: String = vrn)(implicit appConfig: AppConfig) = s"/penalties/penalty/financial-data/VRN/${vrn}/VATC" + def financialDataUrl(vrn: String = vrn)(implicit appConfig: AppConfig): String = s"/penalties/penalty/financial-data/VRN/$vrn/VATC" - def financialDataUrlWithConfig(vrn: String = vrn)(implicit appConfig: AppConfig) = appConfig.penaltiesBaseUrl + s"/penalties/penalty/financial-data/VRN/${vrn}/VATC" + def financialDataUrlWithConfig(vrn: String = vrn)(implicit appConfig: AppConfig): String = appConfig.penaltiesBaseUrl + s"/penalties/penalty/financial-data/VRN/$vrn/VATC" + + + val testDownstreamFinancialDetailsNoDocumentDetails: JsValue = { + Json.parse( + """{ + | "totalisation": { + | "regimeTotalisation": { + | "totalAccountOverdue": 1000.00, + | "totalAccountNotYetDue": 250.00, + | "totalAccountCredit": 40.00, + | "totalAccountBalance": 1210.00 + | }, + | "targetedSearch_SelectionCriteriaTotalisation": { + | "totalOverdue": 123.45, + | "totalNotYetDue": 12.34, + | "totalBalance": 12.45, + | "totalCredit": 13.46, + | "totalCleared": 12.35 + | }, + | "additionalReceivableTotalisations" :{ + | "totalAccountPostedInterest": 100, + | "totalAccountAccruingInterest": 100 + | } + | } + |} + |""".stripMargin + ) + } val testDownstreamFinancialDetails: JsValue = { Json.parse( @@ -58,12 +83,16 @@ object FinancialDataConstants { | "totalAccountCredit": 40.00, | "totalAccountBalance": 1210.00 | }, - | "targetedSearch": { + | "targetedSearch_SelectionCriteriaTotalisation": { | "totalOverdue": 123.45, | "totalNotYetDue": 12.34, | "totalBalance": 12.45, | "totalCredit": 13.46, | "totalCleared": 12.35 + | }, + | "additionalReceivableTotalisations" :{ + | "totalAccountPostedInterest": 100, + | "totalAccountAccruingInterest": 100 | } | }, | "documentDetails": [ @@ -100,6 +129,7 @@ object FinancialDataConstants { | }, | "lineItemDetails": [ | { + | "chargeDescription": "IN1", | "itemNumber": "0001", | "subItemNumber": "003", | "mainTransaction": "4576", @@ -122,7 +152,10 @@ object FinancialDataConstants { | }, | "lineItemInterestDetails": { | "interestKey": "01", - | "interestStartDate": "2022-03-01" + | "interestStartDate": "2022-03-01", + | "currentInterestRate": 2, + | "interestPostedAmount": 123, + | "interestAccruingAmount": 123 | } | } | ] @@ -141,18 +174,27 @@ object FinancialDataConstants { | "totalNotYetDue": 12.34, | "totalBalance": 12.45, | "totalCredit": 13.46, - | "totalCleared": 12.35 + | "totalCleared": 12.35, + | "additionalReceivableTotalisations":{ + | "totalAccountPostedInterest": 100, + | "totalAccountAccruingInterest": 100 + | } | }, | "documentDetails": [ | { | "postingDate": "2022-03-01", | "issueDate": "2022-03-01", | "documentTotalAmount": 123.45, + | "documentInterestTotals": { + | "interestPostedAmount": 13.12, + | "interestAccruingAmount": 12.10 + | }, | "documentClearedAmount": 111.11, | "documentOutstandingAmount": 12.34, | "documentInterestTotal": 1.23, | "lineItemDetails": [ | { + | "chargeDescription": "IN1", | "periodFromDate": "2022-03-01", | "periodToDate": "2022-03-01", | "periodKey": "13RL", @@ -160,7 +202,10 @@ object FinancialDataConstants { | "amount": 123.45, | "lineItemInterestDetails": { | "interestKey": "01", - | "interestStartDate": "2022-03-01" + | "interestStartDate": "2022-03-01", + | "currentInterestRate": 2, + | "interestPostedAmount": 123, + | "interestAccruingAmount": 123 | } | } | ] @@ -172,37 +217,56 @@ object FinancialDataConstants { } val testLineItemInterestDetails: LineItemInterestDetails = LineItemInterestDetails( - interestKey = "01", - interestStartDate = "2022-03-01" + interestKey = Some("01"), + currentInterestRate = Some(2), + interestPostedAmount = Some(123), + interestAccruingAmount = Some(123), + interestStartDate = Some("2022-03-01") ) val testLineItemDetails: LineItemDetail = LineItemDetail( - periodFromDate = "2022-03-01", - periodToDate = "2022-03-01", - periodKey = "13RL", - netDueDate = "2022-03-01", - amount = 123.45, - lineItemInterestDetails = testLineItemInterestDetails + chargeDescription = Some("IN1"), + periodFromDate = Some("2022-03-01"), + periodToDate = Some("2022-03-01"), + periodKey = Some("13RL"), + netDueDate = Some("2022-03-01"), + amount = Some(123.45), + lineItemInterestDetails = Some(testLineItemInterestDetails) + ) + + val testDocumentInterestTotals: DocumentInterestTotals = DocumentInterestTotals( + interestPostedAmount = Some(13.12), + interestAccruingAmount = Some(12.10) ) + val testDocumentDetail: DocumentDetail = DocumentDetail( - postingDate = "2022-03-01", - issueDate = "2022-03-01", - documentTotalAmount = 123.45, - documentClearedAmount = 111.11, - documentOutstandingAmount = 12.34, - documentInterestTotal = 1.23, - lineItemDetails = Seq(testLineItemDetails) + postingDate = Some("2022-03-01"), + issueDate = Some("2022-03-01"), + documentTotalAmount = Some(123.45), + documentClearedAmount = Some(111.11), + documentInterestTotals = Some(testDocumentInterestTotals), + documentOutstandingAmount = Some(12.34), + documentInterestTotal = Some(1.23), + lineItemDetails = Some(Seq(testLineItemDetails)) + ) + + val testAdditionalReceivableTotalisations: AdditionalReceivableTotalisations = AdditionalReceivableTotalisations( + totalAccountPostedInterest = Some(100), + totalAccountAccruingInterest = Some(100) ) val testTotalisation: Totalisation = Totalisation( - totalOverdue = 123.45, - totalNotYetDue = 12.34, - totalBalance = 12.45, - totalCredit = 13.46, - totalCleared = 12.35) + totalOverdue = Some(123.45), + totalNotYetDue = Some(12.34), + totalBalance = Some(12.45), + totalCredit = Some(13.46), + totalCleared = Some(12.35), + additionalReceivableTotalisations = Some(testAdditionalReceivableTotalisations) + ) - val testFinancialDataResponse: FinancialDataResponse = FinancialDataResponse(testTotalisation, Seq(testDocumentDetail)) + val testFinancialNoDocumentDetailsDataResponse: FinancialDataResponse = FinancialDataResponse(Some(testTotalisation), None) + val testFinancialDataResponse: FinancialDataResponse = FinancialDataResponse(Some(testTotalisation), Some(Seq(testDocumentDetail))) def wrappedFinancialDataResponse(financialResponse: FinancialDataResponse = testFinancialDataResponse): ResponseWrapper[FinancialDataResponse] = { ResponseWrapper(correlationId, financialResponse)