Skip to content
Merged
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
13 changes: 13 additions & 0 deletions app/connectors/ConstructionIndustrySchemeConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package connectors

import models.amend.CreateAmendedMonthlyReturnRequest
import models.monthlyreturns.*
import models.requests.{GetMonthlyReturnForEditRequest, SendSuccessEmailRequest}
import models.submission.*
Expand Down Expand Up @@ -234,4 +235,16 @@ class ConstructionIndustrySchemeConnector @Inject() (config: ServicesConfig, htt
}
}

def createAmendedMonthlyReturn(request: CreateAmendedMonthlyReturnRequest)(implicit hc: HeaderCarrier): Future[Unit] =
http
.post(url"$cisBaseUrl/amend-monthly-return/create")
.withBody(Json.toJson(request))
.execute[HttpResponse]
.flatMap { response =>
response.status match {
case CREATED => Future.unit
case status => Future.failed(UpstreamErrorResponse(response.body, status, status))
}
}

}
78 changes: 68 additions & 10 deletions app/controllers/amend/ConfirmAmendmentController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,96 @@
package controllers.amend

import controllers.actions.*
import pages.amend.ConfirmAmendmentPage
import models.UserAnswers
import models.amend.{AmendmentDetails, CreateAmendedMonthlyReturnRequest}
import models.monthlyreturns.ContinueReturnJourneyQueryParams
import pages.amend.{AmendmentDetailsPage, ConfirmAmendmentPage}
import pages.monthlyreturns.CisIdPage
import play.api.Logging
import play.api.i18n.{I18nSupport, MessagesApi}
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents}
import repositories.SessionRepository
import services.AmendMonthlyReturnService
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController
import uk.gov.hmrc.play.http.HeaderCarrierConverter
import views.html.amend.ConfirmAmendmentView

import javax.inject.Inject
import scala.concurrent.ExecutionContext
import scala.concurrent.{ExecutionContext, Future}

class ConfirmAmendmentController @Inject() (
override val messagesApi: MessagesApi,
identify: IdentifierAction,
getData: DataRetrievalAction,
requireData: DataRequiredAction,
sessionRepository: SessionRepository,
amendMonthlyReturnService: AmendMonthlyReturnService,
val controllerComponents: MessagesControllerComponents,
view: ConfirmAmendmentView
)(implicit ec: ExecutionContext)
extends FrontendBaseController
with I18nSupport {
with I18nSupport
with Logging {

def onPageLoad: Action[AnyContent] = (identify andThen getData andThen requireData) { implicit request =>
Ok(view())
def onPageLoad(queryParams: ContinueReturnJourneyQueryParams): Action[AnyContent] = identify.async {
implicit request =>
val amendmentDetails = AmendmentDetails(
instanceId = queryParams.instanceId,
taxYear = queryParams.taxYear,
taxMonth = queryParams.taxMonth
)

val updatedUserAnswers =
UserAnswers(request.userId)
.set(CisIdPage, queryParams.instanceId)
.flatMap(_.set(AmendmentDetailsPage, amendmentDetails))
.get

sessionRepository.set(updatedUserAnswers).map { _ =>
Ok(view())
}
}

def onSubmit: Action[AnyContent] =
(identify andThen getData andThen requireData).async { implicit request =>
val updatedAnswers = request.userAnswers.set(ConfirmAmendmentPage, true).get
(identify andThen getData).async { implicit request =>
request.userAnswers.flatMap(_.get(AmendmentDetailsPage)) match {
case Some(amendmentDetails) =>
implicit val hc: HeaderCarrier = HeaderCarrierConverter.fromRequestAndSession(request, request.session)

val createRequest = CreateAmendedMonthlyReturnRequest(
instanceId = amendmentDetails.instanceId,
taxYear = amendmentDetails.taxYear,
taxMonth = amendmentDetails.taxMonth,
version = 0
)

(
for {
_ <- amendMonthlyReturnService.createAmendedMonthlyReturn(createRequest)
updatedAnswers = request.userAnswers.get.set(ConfirmAmendmentPage, true).get
_ <- sessionRepository.set(updatedAnswers)
} yield Redirect(
controllers.amend.routes.ConfirmAmendmentController.onPageLoad(
ContinueReturnJourneyQueryParams(
instanceId = amendmentDetails.instanceId,
taxYear = amendmentDetails.taxYear,
taxMonth = amendmentDetails.taxMonth
)
)
) // TODO: DTR-4657
).recover { case ex =>
Comment thread
jassalrichy marked this conversation as resolved.
logger.warn(
s"[ConfirmAmendmentController] Failed to create amended monthly return for instanceId ${amendmentDetails.instanceId}",
ex
)
Redirect(controllers.routes.JourneyRecoveryController.onPageLoad())
}

sessionRepository.set(updatedAnswers).map { _ =>
Redirect(controllers.amend.routes.ConfirmAmendmentController.onPageLoad())
case None =>
Comment thread
jassalrichy marked this conversation as resolved.
logger.warn(
s"[ConfirmAmendmentController] AmendmentDetails missing from userAnswers}"
)
Future.successful(Redirect(controllers.routes.JourneyRecoveryController.onPageLoad()))
}
}
}
29 changes: 29 additions & 0 deletions app/models/amend/AmendmentDetails.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2026 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 models.amend

import play.api.libs.json.{Json, OFormat}

case class AmendmentDetails(
instanceId: String,
taxYear: Int,
taxMonth: Int
)

object AmendmentDetails {
given format: OFormat[AmendmentDetails] = Json.format[AmendmentDetails]
}
30 changes: 30 additions & 0 deletions app/models/amend/CreateAmendedMonthlyReturnRequest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2026 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 models.amend

import play.api.libs.json.{Json, OFormat}

case class CreateAmendedMonthlyReturnRequest(
instanceId: String,
taxYear: Int,
taxMonth: Int,
version: Int
)

object CreateAmendedMonthlyReturnRequest {
given format: OFormat[CreateAmendedMonthlyReturnRequest] = Json.format[CreateAmendedMonthlyReturnRequest]
}
29 changes: 29 additions & 0 deletions app/pages/amend/AmendmentDetailsPage.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2026 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 pages.amend

import models.amend.AmendmentDetails
import pages.QuestionPage
import play.api.libs.json.JsPath

case object AmendmentDetailsPage extends QuestionPage[AmendmentDetails] {

override def path: JsPath = JsPath \ toString

override def toString: String = "amendmentDetails"

}
32 changes: 32 additions & 0 deletions app/services/AmendMonthlyReturnService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2026 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 services

import connectors.ConstructionIndustrySchemeConnector
import models.amend.CreateAmendedMonthlyReturnRequest
import uk.gov.hmrc.http.HeaderCarrier

import javax.inject.{Inject, Singleton}
import scala.concurrent.Future

@Singleton
class AmendMonthlyReturnService @Inject() (cisConnector: ConstructionIndustrySchemeConnector) {

def createAmendedMonthlyReturn(request: CreateAmendedMonthlyReturnRequest)(implicit hc: HeaderCarrier): Future[Unit] =
cisConnector.createAmendedMonthlyReturn(request)

}
4 changes: 2 additions & 2 deletions conf/app.routes
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ POST /monthly-return/cancel-amended-monthly-return
GET /amend-monthly-return/what-do-you-want-to-amend-nil controllers.amend.WhatDoYouWantToAmendNilController.onPageLoad()
POST /amend-monthly-return/what-do-you-want-to-amend-nil controllers.amend.WhatDoYouWantToAmendNilController.onSubmit()

GET /manage-cis-return/amend-monthly-return/confirm-amendment controllers.amend.ConfirmAmendmentController.onPageLoad()
POST /manage-cis-return/amend-monthly-return/confirm-amendment controllers.amend.ConfirmAmendmentController.onSubmit()
GET /manage-cis-return/amend-monthly-return/confirm-amendments controllers.amend.ConfirmAmendmentController.onPageLoad(queryParams: models.monthlyreturns.ContinueReturnJourneyQueryParams)
POST /manage-cis-return/amend-monthly-return/confirm-amendments controllers.amend.ConfirmAmendmentController.onSubmit()

GET /amend-monthly-return/what-do-you-want-to-amend-standard controllers.amend.WhatDoYouWantToAmendStandardController.onPageLoad()
POST /amend-monthly-return/what-do-you-want-to-amend-standard controllers.amend.WhatDoYouWantToAmendStandardController.onSubmit()
41 changes: 41 additions & 0 deletions it/test/connectors/ConstructionIndustrySchemeConnectorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package connectors
import com.github.tomakehurst.wiremock.client.WireMock.*
import itutil.ApplicationWithWiremock
import models.ReturnType.MonthlyNilReturn
import models.amend.CreateAmendedMonthlyReturnRequest
import models.requests.SendSuccessEmailRequest
import models.monthlyreturns.*
import models.submission.{ChrisSubmissionRequest, CreateSubmissionRequest, UpdateSubmissionRequest}
Expand Down Expand Up @@ -989,4 +990,44 @@ class ConstructionIndustrySchemeConnectorSpec extends AnyWordSpec
ex.asInstanceOf[UpstreamErrorResponse].statusCode mustBe INTERNAL_SERVER_ERROR
}
}

"createAmendedMonthlyReturn(payload)" should {

"POST /cis/amend-monthly-return/create and return Unit on 201" in {
val req = CreateAmendedMonthlyReturnRequest(
instanceId = cisId,
taxYear = 2025,
taxMonth = 1,
version = 0
)

stubFor(
post(urlPathEqualTo("/cis/amend-monthly-return/create"))
.withHeader("Content-Type", equalTo("application/json"))
.withRequestBody(equalToJson(Json.toJson(req).toString(), true, true))
.willReturn(aResponse().withStatus(CREATED))
)

connector.createAmendedMonthlyReturn(req).futureValue mustBe ((): Unit)
}

"fail the future when BE returns non-201 (e.g. 500)" in {
val req = CreateAmendedMonthlyReturnRequest(
instanceId = cisId,
taxYear = 2025,
taxMonth = 1,
version = 0
)

stubFor(
post(urlPathEqualTo("/cis/amend-monthly-return/create"))
.willReturn(aResponse().withStatus(INTERNAL_SERVER_ERROR).withBody("boom"))
)

val ex = connector.createAmendedMonthlyReturn(req).failed.futureValue

ex mustBe a[UpstreamErrorResponse]
ex.asInstanceOf[UpstreamErrorResponse].statusCode mustBe INTERNAL_SERVER_ERROR
}
}
}
Loading