-
Notifications
You must be signed in to change notification settings - Fork 1
DTR-3713: CIS MRF Business Functions F18d,18f,18g #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bd2a8e9
8a6357a
ab91d18
5ce64b1
bc43471
4342af7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,14 +20,14 @@ import javax.inject.Inject | |
| import play.api.mvc.* | ||
| import play.api.libs.json.* | ||
| import play.api.mvc.Results.* | ||
|
|
||
| import scala.concurrent.{ExecutionContext, Future} | ||
| import play.api.Logging | ||
| import play.api.http.Status.BAD_GATEWAY | ||
| import uk.gov.hmrc.constructionindustryscheme.actions.AuthAction | ||
| import uk.gov.hmrc.constructionindustryscheme.models.{ACCEPTED as AcceptedStatus, BuiltSubmissionPayload, DEPARTMENTAL_ERROR as DepartmentalErrorStatus, FATAL_ERROR as FatalErrorStatus, SUBMITTED as SubmittedStatus, SUBMITTED_NO_RECEIPT as SubmittedNoReceiptStatus, SubmissionResult} | ||
| import uk.gov.hmrc.constructionindustryscheme.models.{ACCEPTED as AcceptedStatus, BuiltSubmissionPayload, DEPARTMENTAL_ERROR as DepartmentalErrorStatus, EmployerReference, FATAL_ERROR as FatalErrorStatus, SUBMITTED as SubmittedStatus, SUBMITTED_NO_RECEIPT as SubmittedNoReceiptStatus, SubmissionResult} | ||
| import uk.gov.hmrc.constructionindustryscheme.config.AppConfig | ||
| import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController | ||
| import uk.gov.hmrc.constructionindustryscheme.models.requests.{ChrisSubmissionRequest, CreateSubmissionRequest, SendSuccessEmailRequest, UpdateSubmissionRequest} | ||
| import uk.gov.hmrc.constructionindustryscheme.models.requests.* | ||
| import uk.gov.hmrc.constructionindustryscheme.services.{AuditService, SubmissionService} | ||
| import uk.gov.hmrc.constructionindustryscheme.services.chris.ChrisSubmissionEnvelopeBuilder | ||
| import uk.gov.hmrc.http.HeaderCarrier | ||
|
|
@@ -40,6 +40,7 @@ import uk.gov.hmrc.play.bootstrap.binders.RedirectUrl.* | |
| import java.time.{Clock, Instant} | ||
| import java.util.UUID | ||
| import scala.util.{Failure, Success} | ||
| import scala.util.control.NonFatal | ||
|
|
||
| class SubmissionController @Inject() ( | ||
| authorise: AuthAction, | ||
|
|
@@ -78,40 +79,7 @@ class SubmissionController @Inject() ( | |
| .validate[ChrisSubmissionRequest] | ||
| .fold( | ||
| errs => Future.successful(BadRequest(Json.obj("message" -> JsError.toJson(errs)))), | ||
| csr => { | ||
| logger.info(s"Submitting Nil Monthly Return to ChRIS for UTR=${csr.utr}") | ||
|
|
||
| val correlationId = UUID.randomUUID().toString.replace("-", "").toUpperCase | ||
| val payload = ChrisSubmissionEnvelopeBuilder.buildPayload(csr, req, correlationId) | ||
|
|
||
| val monthlyNilReturnRequestJson: JsValue = createMonthlyNilReturnRequestJson(payload) | ||
| auditService.monthlyNilReturnRequestEvent(monthlyNilReturnRequestJson) | ||
|
|
||
| xmlValidator.validate(payload.irEnvelope) match { | ||
| case Success(_) => | ||
| logger.info( | ||
| s"ChRIS XML validation successful. Sending ChRIS submission for a correlationId = $correlationId." | ||
| ) | ||
| submissionService | ||
| .submitToChris(payload) | ||
| .map(renderSubmissionResponse(submissionId, payload)) | ||
| .recover { case ex => | ||
| logger.error("[submitToChris] upstream failure", ex) | ||
| val fatalErrorJson = Json.obj( | ||
| "submissionId" -> submissionId, | ||
| "status" -> "FATAL_ERROR", | ||
| "hmrcMarkGenerated" -> payload.irMark, | ||
| "error" -> "upstream-failure" | ||
| ) | ||
| val monthlyNilReturnResponse = AuditResponseReceivedModel(BAD_GATEWAY.toString, fatalErrorJson) | ||
| auditService.monthlyNilReturnResponseEvent(monthlyNilReturnResponse) | ||
| BadGateway(fatalErrorJson) | ||
| } | ||
| case Failure(e) => | ||
| logger.error(s"ChRIS XML validation failed: ${e.getMessage}", e) | ||
| Future.failed(new RuntimeException(s"XML validation failed: ${e.getMessage}", e)) | ||
| } | ||
| } | ||
| csr => handleSubmitToChris(submissionId, csr) | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -185,6 +153,19 @@ class SubmissionController @Inject() ( | |
| ) | ||
| } | ||
|
|
||
| def createMonthlyNilReturnRequestJson(payload: BuiltSubmissionPayload): JsValue = | ||
| XmlToJsonConvertor.convertXmlToJson(payload.envelope.toString) match { | ||
| case XmlConversionResult(true, Some(json), _) => json | ||
| case XmlConversionResult(false, _, Some(error)) => Json.obj("error" -> error) | ||
| case _ => Json.obj("error" -> "unexpected conversion failure") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unit test for case _
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @jassalrichy , I think it's already covered in unit test below, but kindly let me know if concern persists "return unexpected conversion failure when neither json nor error provided" |
||
| } | ||
|
|
||
| def createMonthlyNilReturnResponseJson(res: SubmissionResult): JsValue = | ||
| XmlToJsonConvertor.convertXmlToJson(res.rawXml) match { | ||
| case XmlConversionResult(true, Some(json), _) => json | ||
| case _ => Json.toJson(res.rawXml) | ||
| } | ||
|
|
||
| private def renderSubmissionResponse(submissionId: String, payload: BuiltSubmissionPayload)( | ||
| res: SubmissionResult | ||
| ): Result = { | ||
|
|
@@ -237,17 +218,121 @@ class SubmissionController @Inject() ( | |
| } | ||
| } | ||
|
|
||
| def createMonthlyNilReturnRequestJson(payload: BuiltSubmissionPayload): JsValue = | ||
| XmlToJsonConvertor.convertXmlToJson(payload.envelope.toString) match { | ||
| case XmlConversionResult(true, Some(json), _) => json | ||
| case XmlConversionResult(false, _, Some(error)) => Json.obj("error" -> error) | ||
| case _ => Json.obj("error" -> "unexpected conversion failure") | ||
| private def handleSubmitToChris(submissionId: String, csr: ChrisSubmissionRequest)(implicit | ||
| req: AuthenticatedRequest[JsValue] | ||
| ): Future[Result] = { | ||
| val correlationId = UUID.randomUUID().toString.replace("-", "").toUpperCase | ||
| val payload = ChrisSubmissionEnvelopeBuilder.buildPayload(csr, req, correlationId) | ||
|
|
||
| auditService.monthlyNilReturnRequestEvent(createMonthlyNilReturnRequestJson(payload)) | ||
|
|
||
| xmlValidator.validate(payload.irEnvelope) match { | ||
| case Failure(e) => | ||
| logger.error(s"ChRIS XML validation failed: ${e.getMessage}", e) | ||
| Future.failed(new RuntimeException(s"XML validation failed: ${e.getMessage}", e)) | ||
|
|
||
| case Success(_) => | ||
| logger.info(s"ChRIS XML validation successful. Sending ChRIS submission for a correlationId = $correlationId.") | ||
| submissionService | ||
| .submitToChris(payload) | ||
| .flatMap(res => handleChrisResponse(submissionId, csr, correlationId, payload, res)) | ||
| .recoverWith { case NonFatal(ex) => | ||
| handleChrisFailure(submissionId, csr, correlationId, payload, ex) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| def createMonthlyNilReturnResponseJson(res: SubmissionResult): JsValue = | ||
| XmlToJsonConvertor.convertXmlToJson(res.rawXml) match { | ||
| case XmlConversionResult(true, Some(json), _) => json | ||
| case _ => Json.toJson(res.rawXml) | ||
| private def handleChrisResponse( | ||
| submissionId: String, | ||
| csr: ChrisSubmissionRequest, | ||
| correlationId: String, | ||
| payload: BuiltSubmissionPayload, | ||
| res: SubmissionResult | ||
| )(implicit hc: HeaderCarrier): Future[Result] = | ||
| validateCorrelationId(correlationId, res.meta.correlationId) match { | ||
| case Left(reason) => | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unit test for case Left(reason)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added now |
||
| logger.error(s"Correlation ID validation failed: $reason") | ||
| Future.successful( | ||
| BadGateway(withError(baseSubmissionResponseJson(submissionId, payload, correlationId, "FATAL_ERROR"), reason)) | ||
| ) | ||
|
|
||
| case Right(_) => | ||
| submissionService | ||
| .initialiseGovTalkStatus( | ||
| EmployerReference(csr.clientTaxOfficeNumber, csr.clientTaxOfficeRef), | ||
| submissionId, | ||
| correlationId, | ||
| appConfig.chrisGatewayUrl | ||
| ) | ||
| .map(_ => renderSubmissionResponse(submissionId, payload)(res)) | ||
| } | ||
|
|
||
| private def handleChrisFailure( | ||
| submissionId: String, | ||
| csr: ChrisSubmissionRequest, | ||
| correlationId: String, | ||
| payload: BuiltSubmissionPayload, | ||
| ex: Throwable | ||
| )(implicit hc: HeaderCarrier): Future[Result] = { | ||
| logger.error(s"Received 5xx/Exception from ChRIS, treating as RESUBMIT for submissionId=$submissionId", ex) | ||
|
|
||
| submissionService | ||
| .initialiseGovTalkStatus( | ||
| EmployerReference(csr.clientTaxOfficeNumber, csr.clientTaxOfficeRef), | ||
| submissionId, | ||
| correlationId, | ||
| appConfig.chrisGatewayUrl | ||
| ) | ||
| .flatMap { instanceId => | ||
| submissionService | ||
| .updateGovTalkStatus( | ||
| UpdateGovTalkStatusRequest( | ||
| userIdentifier = instanceId, | ||
| formResultID = submissionId, | ||
| protocolStatus = "dataRequest" | ||
| ) | ||
| ) | ||
| .map { _ => | ||
| Ok( | ||
| withError(baseSubmissionResponseJson(submissionId, payload, correlationId, "STARTED"), "Chris failure") | ||
| ) | ||
| } | ||
| } | ||
| .recover { case ex => | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unit test for recover
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added now |
||
| logger.error(s"Failed to initialise/update GovTalk status after 5xx", ex) | ||
| InternalServerError( | ||
| withError( | ||
| baseSubmissionResponseJson(submissionId, payload, correlationId, "FATAL_ERROR"), | ||
| "GovTalk status already exists" | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private def baseSubmissionResponseJson( | ||
| submissionId: String, | ||
| payload: BuiltSubmissionPayload, | ||
| correlationId: String, | ||
| status: String, | ||
| gatewayTimestamp: String = Instant.now(clock).toString | ||
| ): JsObject = | ||
| Json.obj( | ||
| "submissionId" -> submissionId, | ||
| "hmrcMarkGenerated" -> payload.irMark, | ||
| "correlationId" -> correlationId, | ||
| "gatewayTimestamp" -> gatewayTimestamp, | ||
| "status" -> status | ||
| ) | ||
|
|
||
| private def withError(json: JsObject, text: String): JsObject = | ||
| json ++ Json.obj("error" -> Json.obj("text" -> text)) | ||
|
|
||
| private def validateCorrelationId(sentRaw: String, ackRaw: String): Either[String, Unit] = { | ||
| val sent = sentRaw.trim | ||
| val ack = ackRaw.trim | ||
|
|
||
| if (sent.isEmpty || ack.isEmpty) Left("empty correlationId") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unit test for if (sent.isEmpty || ack.isEmpty)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added now |
||
| else if (sent != ack) Left(s"correlationId mismatch: sent '$sent' but got '$ack'") | ||
| else Right(()) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unit test for case XmlConversionResult(false, _, Some(error))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @jassalrichy , I think it's already covered in unit test below, but kindly let me know if concern persists
"return error JSON when XML conversion fails"