diff --git a/app/models/submission/ChrisPollResponse.scala b/app/models/submission/ChrisPollResponse.scala index 86edfb0a..d3b4c50c 100644 --- a/app/models/submission/ChrisPollResponse.scala +++ b/app/models/submission/ChrisPollResponse.scala @@ -24,7 +24,8 @@ case class ChrisPollResponse( intervalSeconds: Option[Int], error: Option[JsValue], irMarkReceived: Option[String], - lastMessageDate: Option[String] + lastMessageDate: Option[String], + acceptedTime: Option[String] ) object ChrisPollResponse { diff --git a/app/models/submission/ChrisSubmissionResponse.scala b/app/models/submission/ChrisSubmissionResponse.scala index 7cb4bc5a..ced4a5c1 100644 --- a/app/models/submission/ChrisSubmissionResponse.scala +++ b/app/models/submission/ChrisSubmissionResponse.scala @@ -34,6 +34,7 @@ final case class ChrisSubmissionResponse( correlationId: Option[String] = None, responseEndPoint: Option[ResponseEndPointDto] = None, gatewayTimestamp: Option[String] = None, + acceptedTime: Option[String] = None, error: Option[JsValue] = None ) diff --git a/app/services/submission/SubmissionService.scala b/app/services/submission/SubmissionService.scala index d6336a78..697c2c94 100644 --- a/app/services/submission/SubmissionService.scala +++ b/app/services/submission/SubmissionService.scala @@ -35,9 +35,8 @@ import uk.gov.hmrc.http.HeaderCarrier import utils.DateTimeFormats import utils.TypeUtils.* -import java.time.format.DateTimeFormatter import java.time.{Instant, LocalDateTime, YearMonth} -import java.util.{Locale, TimeZone} +import java.util.Locale import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Success, Try} @@ -51,13 +50,6 @@ class SubmissionService @Inject() ( )(implicit ec: ExecutionContext) extends Logging { - private val dateFormatter = - DateTimeFormatter - .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS") - .withZone(TimeZone.getTimeZone("GMT").toZoneId) - - // Orchestration - def create(ua: UserAnswers)(implicit hc: HeaderCarrier): Future[(CreateSubmissionResponse, UserAnswers)] = for { req <- buildCreateRequest(ua) @@ -102,7 +94,7 @@ class SubmissionService @Inject() ( ua, chrisResp.hmrcMarkGenerated, chrisResp.status, - chrisResp.gatewayTimestamp, + chrisResp.acceptedTime, None, chrisResp.error ) @@ -112,10 +104,15 @@ class SubmissionService @Inject() ( ua: UserAnswers, hmrcMarkGenerated: String, status: String, - gatewayTimestamp: Option[String], + acceptedTime: Option[String], irMarkReceived: Option[String] = None, error: Option[JsValue] = None )(implicit req: DataRequest[AnyContent], hc: HeaderCarrier): Future[Unit] = { + val acceptedTimestamp = Option.when(status == "SUBMITTED" || status == "SUBMITTED_NO_RECEIPT") { + acceptedTime + .flatMap(t => Try(LocalDateTime.parse(t)).toOption) + .getOrElse(LocalDateTime.now()) + } val instanceId = ua.get(CisIdPage).getOrElse(throw new RuntimeException("CIS ID missing")) val ym = selectedYearMonth(ua) @@ -130,7 +127,7 @@ class SubmissionService @Inject() ( taxYear = ym.getYear, taxMonth = ym.getMonthValue, submittableStatus = status, - acceptedTime = gatewayTimestamp, + acceptedTime = acceptedTimestamp.map(_.toString), submissionRequestDate = Some(LocalDateTime.now()), govtalkErrorCode = error.flatMap(js => (js \ "number").asOpt[String]), govtalkErrorType = error.flatMap(js => (js \ "type").asOpt[String]), @@ -214,7 +211,7 @@ class SubmissionService @Inject() ( userAnswers, submissionDetails.irMark, result.status, - Some(dateFormatter.format(submissionDetails.submittedAt)), + result.acceptedTime, result.irMarkReceived, result.error ) diff --git a/test/models/submission/ChrisPollResponseSpec.scala b/test/models/submission/ChrisPollResponseSpec.scala index 340d5ead..6adf0ee3 100644 --- a/test/models/submission/ChrisPollResponseSpec.scala +++ b/test/models/submission/ChrisPollResponseSpec.scala @@ -33,7 +33,8 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { | "intervalSeconds": 1, | "error": { "number": "5005", "type": "fatal", "text": "Boom" }, | "irMarkReceived": "2342345asdfasdgf", - | "lastMessageDate": "2026-03-20T10:15:30Z" + | "lastMessageDate": "2026-03-20T10:15:30Z", + | "acceptedTime": "2026-03-20T10:15:30" |} """.stripMargin ) @@ -45,7 +46,8 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { Some(1), Some(JsObject(Seq("number" -> JsString("5005"), "type" -> JsString("fatal"), "text" -> JsString("Boom")))), Some("2342345asdfasdgf"), - Some("2026-03-20T10:15:30Z") + Some("2026-03-20T10:15:30Z"), + Some("2026-03-20T10:15:30") ) ) } @@ -75,6 +77,7 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { Some(1), Some(JsObject(Seq("number" -> JsString("5005"), "type" -> JsString("fatal"), "text" -> JsString("Boom")))), Some("2342345asdfasdgf"), + Some("2026-03-20T10:15:30Z"), Some("2026-03-20T10:15:30Z") ) val js = Json.toJson(model) @@ -90,7 +93,7 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { } "write to expected JSON when pollUrl is None" in { - val model = ChrisPollResponse("SUBMITTED", None, None, None, None, None) + val model = ChrisPollResponse("SUBMITTED", None, None, None, None, None, None) val js = Json.toJson(model) (js \ "status").as[String] mustBe "SUBMITTED" @@ -108,6 +111,7 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { Some(1), Some(JsObject(Seq("number" -> JsString("5005"), "type" -> JsString("fatal"), "text" -> JsString("Boom")))), Some("2342345asdfasdgf"), + Some("2026-03-20T10:15:30Z"), Some("2026-03-20T10:15:30Z") ) @@ -118,7 +122,7 @@ class ChrisPollResponseSpec extends AnyWordSpec with Matchers { } "round-trip (write then read) preserves values without pollUrl" in { - val model = ChrisPollResponse("ACCEPTED", None, None, None, None, None) + val model = ChrisPollResponse("ACCEPTED", None, None, None, None, None, None) val js = Json.toJson(model) val out = js.as[ChrisPollResponse] diff --git a/test/services/submission/SubmissionServiceSpec.scala b/test/services/submission/SubmissionServiceSpec.scala index 0baa728b..d1826543 100644 --- a/test/services/submission/SubmissionServiceSpec.scala +++ b/test/services/submission/SubmissionServiceSpec.scala @@ -466,7 +466,8 @@ class SubmissionServiceSpec extends SpecBase with TryValues { val chrisResp = mkChrisResp( status = "DEPARTMENTAL_ERROR", ts = "2025-02-02T10:20:30", - err = Some(Json.obj("number" -> "123", "type" -> "business", "text" -> "oops")) + err = Some(Json.obj("number" -> "123", "type" -> "business", "text" -> "oops")), + accepted = Some("2025-02-02T10:20:30") ) service.updateSubmissionFromChrisResponse("sub-123", ua, chrisResp).futureValue @@ -481,7 +482,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { upd.taxMonth mustBe 10 upd.hmrcMarkGenerated mustBe Some("Dj5TVJDyRYCn9zta5EdySeY4fyA=") upd.submittableStatus mustBe "DEPARTMENTAL_ERROR" - upd.acceptedTime mustBe Some("2025-02-02T10:20:30") + upd.acceptedTime mustBe None upd.emailRecipient mustBe Some("test@test.com") upd.govtalkErrorCode mustBe Some("123") upd.govtalkErrorType mustBe Some("business") @@ -611,7 +612,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("someUrl"), None, None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("someUrl"), None, None, None, None, None))) when(sessionRepository.set(any[UserAnswers])) .thenReturn(Future.successful(true)) @@ -686,7 +687,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("ACCEPTED", Some("someUrl"), None, None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("ACCEPTED", Some("someUrl"), None, None, None, None, None))) when(sessionRepository.set(any[UserAnswers])) .thenReturn(Future.successful(true)) when(connector.updateSubmission(any[String], any[UpdateSubmissionRequest])(any[HeaderCarrier])) @@ -715,7 +716,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { val ua = uaBase when(connector.getSubmissionStatus(any, any)(any)) - .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("someUrl"), None, None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("someUrl"), None, None, None, None, None))) val result = service.checkAndUpdateSubmissionStatus(ua).failed.futureValue @@ -803,6 +804,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { JsObject(Seq("number" -> JsString("5005"), "type" -> JsString("fatal"), "text" -> JsString("Boom"))) ), Some("2342345asdfasdgf"), + Some("2025-01-01T00:00:30Z"), Some("2025-01-01T00:00:30Z") ) ) @@ -863,7 +865,9 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("PENDING", Some("newPollUrl"), Some(30), None, None, None))) + .thenReturn( + Future.successful(ChrisPollResponse("PENDING", Some("newPollUrl"), Some(30), None, None, None, None)) + ) when(connector.updateSubmission(any[String], any[UpdateSubmissionRequest])(any[HeaderCarrier])) .thenReturn(Future.unit) when(sessionRepository.set(any[UserAnswers])) @@ -921,7 +925,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("newUrl"), Some(10), None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("SUBMITTED", Some("newUrl"), Some(10), None, None, None, None))) when(connector.updateSubmission(any[String], any[UpdateSubmissionRequest])(any[HeaderCarrier])) .thenReturn(Future.unit) when(sessionRepository.set(any[UserAnswers])) @@ -972,7 +976,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("PENDING", Some("someurl"), None, None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("PENDING", Some("someurl"), None, None, None, None, None))) when(connector.updateSubmission(any[String], any[UpdateSubmissionRequest])(any[HeaderCarrier])) .thenReturn(Future.unit) @@ -1025,7 +1029,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { .value when(connector.getSubmissionStatus(any, any[String])(any[HeaderCarrier])) - .thenReturn(Future.successful(ChrisPollResponse("ACCEPTED", Some("someurl"), None, None, None, None))) + .thenReturn(Future.successful(ChrisPollResponse("ACCEPTED", Some("someurl"), None, None, None, None, None))) when(connector.updateSubmission(any[String], any[UpdateSubmissionRequest])(any[HeaderCarrier])) .thenReturn(Future.unit) @@ -1564,7 +1568,8 @@ class SubmissionServiceSpec extends SpecBase with TryValues { irmark: String = "Dj5TVJDyRYCn9zta5EdySeY4fyA=", corr: String = "CID123", ts: String = "2025-01-01T00:00:00", - err: Option[JsObject] = None + err: Option[JsObject] = None, + accepted: Option[String] = None ): ChrisSubmissionResponse = ChrisSubmissionResponse( submissionId = "sub-123", @@ -1573,6 +1578,7 @@ class SubmissionServiceSpec extends SpecBase with TryValues { correlationId = Some(corr), responseEndPoint = None, gatewayTimestamp = Some(ts), + acceptedTime = accepted, error = err )