Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,10 @@ class AppConfig @Inject() (config: Configuration, servicesConfig: ServicesConfig

lazy val nrsApiKey: String = servicesConfig.getConfString("nrs.api-key", "dummyNrsApiKey")

lazy val movementCollectionName = config.getOptional[String]("mongodb.movement.collectionName").getOrElse("movements")

lazy val movementTTL: Duration = config
.getOptional[String]("mongodb.movement.TTL")
.fold(Duration.create(30, DAYS))(Duration.create(_).asInstanceOf[FiniteDuration])

lazy val movementV2TTL: Duration = config
.getOptional[String]("mongodb.movementV2.TTL")
.fold(Duration.create(30, DAYS))(Duration.create(_).asInstanceOf[FiniteDuration])

lazy val transformLogTTL: Duration = config
.getOptional[String]("mongodb.transformLog.TTL")
.fold(Duration.create(30, DAYS))(Duration.create(_).asInstanceOf[FiniteDuration])

lazy val ernRetrievalTTL: Duration = config
.getOptional[String]("mongodb.ernRetrieval.TTL")
.fold(Duration.create(30, DAYS))(Duration.create(_).asInstanceOf[FiniteDuration])
Expand All @@ -61,10 +51,8 @@ class AppConfig @Inject() (config: Configuration, servicesConfig: ServicesConfig
.map(JavaDuration.ofMinutes)
.getOrElse(JavaDuration.ofMinutes(10))

lazy val runV1Validation: Boolean = config.getOptional[Boolean]("featureFlags.runV1Validation").getOrElse(false)
lazy val pushNotificationsEnabled: Boolean = servicesConfig.getBoolean("featureFlags.pushNotificationsEnabled")

lazy val pushNotificationsEnabled: Boolean = servicesConfig.getBoolean("featureFlags.pushNotificationsEnabled")
lazy val latestSpec: Boolean = servicesConfig.getBoolean("featureFlags.latestFunctionalSpecEnabled")
lazy val newAuditingEnabled: Boolean = config.getOptional[Boolean]("featureFlags.newAuditingEnabled").getOrElse(false)
lazy val oldAuditingEnabled: Boolean = config.getOptional[Boolean]("featureFlags.oldAuditingEnabled").getOrElse(false)
lazy val processingAuditingEnabled: Boolean =
Expand Down
41 changes: 3 additions & 38 deletions app/uk/gov/hmrc/excisemovementcontrolsystemapi/config/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,55 +18,20 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.config

import play.api.inject.Binding
import play.api.{Configuration, Environment}
import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.{NrsCircuitBreakerProvider, TraderMovementConnector, TraderMovementConnectorV1, TraderMovementConnectorV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.NrsCircuitBreakerProvider
import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.NrsConnector.NrsCircuitBreaker
import uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.{DraftExciseMovementController, DraftExciseMovementControllerV1, DraftExciseMovementControllerV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.factories.{IEMessageFactory, IEMessageFactoryV1, IEMessageFactoryV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.models.auditing.{AuditEventFactory, AuditEventFactoryV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.models.validation.{MessageValidation, MessageValidationV1, MessageValidationV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.scheduling.TransformJob
import uk.gov.hmrc.excisemovementcontrolsystemapi.services.{AuditService, AuditServiceV1, AuditServiceV2, MessageService, MessageServiceV1, MessageServiceV2}
import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.{NrsEventIdMapper, NrsEventIdMapperV1, NrsEventIdMapperV2}
import uk.gov.hmrc.mongo.metrix.MetricOrchestrator

import java.time.Clock

class Module extends play.api.inject.Module {

override def bindings(environment: Environment, configuration: Configuration): collection.Seq[Binding[_]] = {
val latestSpec = configuration.get[Boolean]("featureFlags.latestFunctionalSpecEnabled")
val transformJobEnabled = configuration.get[Boolean]("featureFlags.transformJobEnabled")
val transformJob = if (transformJobEnabled) Seq(bind[TransformJob].toSelf.eagerly()) else Seq()
val versionBindings =
if (latestSpec)
Seq(
bind[DraftExciseMovementController].to[DraftExciseMovementControllerV2],
bind[MessageService].to[MessageServiceV2],
bind[IEMessageFactory].to[IEMessageFactoryV2],
bind[TraderMovementConnector].to[TraderMovementConnectorV2],
bind[AuditEventFactory].to[AuditEventFactoryV2],
bind[NrsEventIdMapper].to[NrsEventIdMapperV2],
bind[AuditService].to[AuditServiceV2],
bind[MessageValidation].to[MessageValidationV2]
)
else
Seq(
bind[DraftExciseMovementController].to[DraftExciseMovementControllerV1],
bind[MessageService].to[MessageServiceV1],
bind[IEMessageFactory].to[IEMessageFactoryV1],
bind[TraderMovementConnector].to[TraderMovementConnectorV1],
bind[AuditEventFactory].to[AuditEventFactoryV2],
bind[NrsEventIdMapper].to[NrsEventIdMapperV1],
bind[AuditService].to[AuditServiceV1],
bind[MessageValidation].to[MessageValidationV1]
)

override def bindings(environment: Environment, configuration: Configuration): collection.Seq[Binding[_]] =
Seq(
bind[AppConfig].toSelf.eagerly(),
bind[JobScheduler].toSelf.eagerly(),
bind[Clock].toInstance(Clock.systemUTC()),
bind[MetricOrchestrator].toProvider[MetricsProvider],
bind[NrsCircuitBreaker].toProvider[NrsCircuitBreakerProvider]
) ++ versionBindings ++ transformJob
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors

import generated.v1
import generated.v2

import generated.NewMessagesDataResponse
import play.api.http.Status.OK
import play.api.libs.json.{Json, Reads}
import play.api.{Configuration, Logging}
Expand Down Expand Up @@ -147,27 +145,12 @@ class MessageConnector @Inject() (
response: EISConsumptionResponse
)(implicit headerCarrier: HeaderCarrier): Try[Seq[IEMessage]] = Try {
val decodedMessage: String = base64Decode(response.message)

if (appConfig.latestSpec) {
val xmlResponse = scalaxb.fromXML[v2.NewMessagesDataResponse](scala.xml.XML.loadString(decodedMessage))

if (appConfig.oldAuditingEnabled) {

xmlResponse.Messages.messagesoption.map(m => messageFactory.createIEMessage(Right(m))).tapEach {
auditService.auditMessage(_)(headerCarrier)
}
} else xmlResponse.Messages.messagesoption.map(m => messageFactory.createIEMessage(Right(m)))
} else {
val xmlResponse = scalaxb.fromXML[v1.NewMessagesDataResponse](scala.xml.XML.loadString(decodedMessage))

if (appConfig.oldAuditingEnabled) {

xmlResponse.Messages.messagesoption.map(m => messageFactory.createIEMessage(Left(m))).tapEach {
auditService.auditMessage(_)(headerCarrier)
}
} else xmlResponse.Messages.messagesoption.map(m => messageFactory.createIEMessage(Left(m)))
}

val xmlResponse = scalaxb.fromXML[NewMessagesDataResponse](scala.xml.XML.loadString(decodedMessage))
if (appConfig.oldAuditingEnabled) {
xmlResponse.Messages.messagesoption.map(messageFactory.createIEMessage).tapEach {
auditService.auditMessage(_)(headerCarrier)
}
} else xmlResponse.Messages.messagesoption.map(messageFactory.createIEMessage)
}

private def countOfMessagesAvailable(encodedMessage: String): Try[Int] = Try {
Expand Down
Copy link
Copy Markdown
Contributor Author

@DavidGregory084 DavidGregory084 Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git diff "4d01b58efd0f7b5911be9fe78155b46b7c8f11ed:app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/TraderMovementConnectorV2.scala" "HEAD:app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/TraderMovementConnector.scala"

Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,96 @@

package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors

import generated.{MessageBodyType, MovementForTraderDataResponse}
import play.api.{Configuration, Logging}
import play.api.http.Status.{OK, UNPROCESSABLE_ENTITY}
import play.api.libs.json.{Json, Reads}
import uk.gov.hmrc.excisemovementcontrolsystemapi.config.Service
import uk.gov.hmrc.excisemovementcontrolsystemapi.factories.IEMessageFactory
import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISConsumptionResponse
import uk.gov.hmrc.excisemovementcontrolsystemapi.models.messages.IEMessage
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader
import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService
import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService._
import uk.gov.hmrc.http.HttpReads.Implicits._
import uk.gov.hmrc.http.client.HttpClientV2
import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, StringContextOps}

import scala.concurrent.Future
import java.nio.charset.StandardCharsets
import java.util.{Base64, UUID}
import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

trait TraderMovementConnector {
def getMovementMessages(ern: String, arc: String)(implicit hc: HeaderCarrier): Future[Seq[IEMessage]]
@Singleton
class TraderMovementConnector @Inject() (
configuration: Configuration,
httpClient: HttpClientV2,
messageFactory: IEMessageFactory,
dateTimeService: DateTimeService
)(implicit ec: ExecutionContext)
extends Logging {

private def enforceCorrelationId(hc: HeaderCarrier): HeaderCarrier =
hc.headers(Seq(HttpHeader.xCorrelationId)).headOption match {
case Some(_) => hc
case None =>
val correlationId = UUID.randomUUID().toString
logger.info(s"generated new correlation id: $correlationId")
hc.withExtraHeaders(HttpHeader.xCorrelationId -> correlationId)
}

private val service: Service = configuration.get[Service]("microservice.services.eis")
private val bearerToken: String = configuration.get[String]("microservice.services.eis.movement-bearer-token")

def getMovementMessages(ern: String, arc: String)(implicit hc: HeaderCarrier): Future[Seq[IEMessage]] = {
logger.info(s"[TraderMovementConnector]: Getting movement messages")
val timestamp = dateTimeService.timestamp().asStringInMilliseconds
val hc2 = enforceCorrelationId(hc)

httpClient
.get(url"${service.baseUrl}/emcs/movements/v1/trader-movement?exciseregistrationnumber=$ern&arc=$arc")(hc2)
.setHeader("X-Forwarded-Host" -> "MDTP")
.setHeader("Source" -> "APIP")
.setHeader("DateTime" -> timestamp)
.setHeader("Authorization" -> s"Bearer $bearerToken")
.execute[HttpResponse]
.flatMap { response =>
if (response.status == OK) Future.fromTry {
for {
response <- parseJson[EISConsumptionResponse](response.body)
messages <- getMessages(response)
} yield messages
}
else if (response.status == UNPROCESSABLE_ENTITY) {
Future.successful(Seq())
} else {
logger.warn(s"[TraderMovementConnector]: Invalid status returned: ${response.status}")
Future.failed(new RuntimeException("[TraderMovementConnector]: Invalid status returned"))
}
}
}

private def parseJson[A](string: String)(implicit reads: Reads[A]): Try[A] =
for {
json <- Try(Json.parse(string))
response <- Try(json.as[A])
} yield response

private def getMessages(
response: EISConsumptionResponse
): Try[Seq[IEMessage]] = Try {
val decodedMessage: String = base64Decode(response.message)
val xmlResponse = scalaxb.fromXML[MovementForTraderDataResponse](scala.xml.XML.loadString(decodedMessage))
xmlResponse.IE934.Body.MessagePackage.MessageBody.map(getIeMessage)
}

private def getIeMessage(messageBodyType: MessageBodyType): IEMessage = {
val messageType = messageBodyType.TechnicalMessageType.toString
val decodedXml = scala.xml.XML.loadString(base64Decode(messageBodyType.MessageData.toString))
messageFactory.createFromXml(messageType, decodedXml)
}

private def base64Decode(string: String): String =
new String(Base64.getDecoder.decode(string), StandardCharsets.UTF_8)
}

This file was deleted.

Loading