Skip to content

Commit

Permalink
MMCA-4898 | Display MRN details on click of MRN hyperlink from the Ca…
Browse files Browse the repository at this point in the history
…sh Account Transactions List (#134)

* MMCA-4898 | Added declaration details, route, unit tests

* MMCA-4898 | Cleanup

* MMCA-4898 | Added H2Inner component, more unit tests

* MMCA-4898 | Fix unit tests

* MMCA-4898 | Added TaxType to fix backend, fixed amount issue

* MMCA-4898 | Removed 'Action' from SummaryLists

* MMCA-4898 | Removed extra spacing
  • Loading branch information
Neozxc authored Sep 9, 2024
1 parent 133ca0d commit d2ee940
Show file tree
Hide file tree
Showing 28 changed files with 1,166 additions and 91 deletions.
24 changes: 20 additions & 4 deletions app/connectors/CustomsFinancialsApiConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import uk.gov.hmrc.http.HttpReads.Implicits.*
import uk.gov.hmrc.http.client.HttpClientV2
import uk.gov.hmrc.http.{HeaderCarrier, StringContextOps, UpstreamErrorResponse}

import java.net.URL
import java.time.LocalDate
import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.ws.JsonBodyWritables.writeableOf_JsValue

import java.util.UUID

class CustomsFinancialsApiConnector @Inject()(httpClient: HttpClientV2,
appConfig: AppConfig,
metricsReporter: MetricsReporterService,
Expand Down Expand Up @@ -91,6 +92,18 @@ class CustomsFinancialsApiConnector @Inject()(httpClient: HttpClientV2,
(implicit hc: HeaderCarrier): Future[Either[ErrorResponse, CashTransactions]] = {
val cashDailyStatementRequest = CashDailyStatementRequest(can, from, to)

def addUUIDToCashTransaction(response: CashTransactions): CashTransactions = {
response.copy(
cashDailyStatements = response.cashDailyStatements.map { statement =>
statement.copy(
declarations = statement.declarations.map { declaration =>
declaration.copy(secureMovementReferenceNumber = Some(UUID.randomUUID().toString))
}
)
}
)
}

cacheRepository.get(can).flatMap {
case Some(value) => Future.successful(Right(value))

Expand All @@ -99,11 +112,14 @@ class CustomsFinancialsApiConnector @Inject()(httpClient: HttpClientV2,
.withBody[CashDailyStatementRequest](cashDailyStatementRequest)
.execute[CashTransactions]
.flatMap { response =>
cacheRepository.set(can, response).map { successfulWrite =>
val transactionsWithUUID = addUUIDToCashTransaction(response)

cacheRepository.set(can, transactionsWithUUID).map { successfulWrite =>
if (!successfulWrite) {
logger.error("Failed to store data in the session cache defaulting to the api response")
}
Right(response)

Right(transactionsWithUUID)
}
}
}.recover {
Expand All @@ -116,7 +132,7 @@ class CustomsFinancialsApiConnector @Inject()(httpClient: HttpClientV2,
Left(NoTransactionsAvailable)

case e =>
logger.error(s"Unable to retrieve cash transactions :${e.getMessage}")
logger.error(s"Unable to retrieve cash transactions: ${e.getMessage}")
Left(UnknownException)
}
}
Expand Down
81 changes: 81 additions & 0 deletions app/controllers/DeclarationDetailController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2023 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 controllers

import config.{AppConfig, ErrorHandler}
import connectors.CustomsFinancialsApiConnector
import controllers.actions.{EmailAction, IdentifierAction}
import helpers.CashAccountUtils
import models.CashAccount
import models.request.IdentifierRequest
import play.api.i18n.I18nSupport
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents, Result}
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController
import views.html.{cash_account_declaration_details, cash_transactions_no_result}

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import play.api.Logging
import viewmodels.{DeclarationDetailViewModel, ResultsPageSummary}

import java.time.LocalDate

class DeclarationDetailController @Inject()(authenticate: IdentifierAction,
verifyEmail: EmailAction,
apiConnector: CustomsFinancialsApiConnector,
errorHandler: ErrorHandler,
mcc: MessagesControllerComponents,
view: cash_account_declaration_details,
cashAccountUtils: CashAccountUtils,
noTransactions: cash_transactions_no_result
)(implicit executionContext: ExecutionContext,
val appConfig: AppConfig
) extends FrontendController(mcc) with I18nSupport with Logging {

def displayDetails(ref: String, page: Option[Int]): Action[AnyContent] =
(authenticate andThen verifyEmail).async { implicit request =>

apiConnector.getCashAccount(request.eori).flatMap {

case Some(account) =>
val (from, to) = cashAccountUtils.transactionDateRange()
retrieveCashAccountTransactionAndDisplay(account, from, to, ref, page)

case None => Future.successful(NotFound(errorHandler.notFoundTemplate))
}
}

private def retrieveCashAccountTransactionAndDisplay(account: CashAccount,
from: LocalDate,
to: LocalDate,
ref: String,
page: Option[Int])
(implicit request: IdentifierRequest[_]): Future[Result] = {

apiConnector.retrieveCashTransactions(account.number, from, to).map {

case Right(transactions) =>
transactions.cashDailyStatements
.flatMap(_.declarations)
.find(_.secureMovementReferenceNumber.contains(ref))
.map(declaration => Ok(view(DeclarationDetailViewModel(request.eori, account), declaration, page)))
.getOrElse(NotFound(errorHandler.notFoundTemplate))

case Left(_) => Ok(noTransactions(new ResultsPageSummary(from, to)))
}
}
}
9 changes: 6 additions & 3 deletions app/crypto/CashTransactionsEncrypter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

package crypto

import models._
import models.*
import utils.Utils.emptyString

import java.util.UUID
import javax.inject.Inject

class CashTransactionsEncrypter @Inject()(crypto: AesGCMCrypto) {
Expand Down Expand Up @@ -69,7 +70,8 @@ class CashTransactionsEncrypter @Inject()(crypto: AesGCMCrypto) {
declaration.declarantReference.map(encrypt),
declaration.date,
declaration.amount,
declaration.taxGroups
declaration.taxGroups,
declaration.secureMovementReferenceNumber.getOrElse(UUID.randomUUID().toString)
)
}

Expand All @@ -86,7 +88,8 @@ class CashTransactionsEncrypter @Inject()(crypto: AesGCMCrypto) {
encryptedDeclaration.declarantReference.map(decrypt),
encryptedDeclaration.date,
encryptedDeclaration.amount,
encryptedDeclaration.taxGroups
encryptedDeclaration.taxGroups,
Some(encryptedDeclaration.secureMovementReferenceNumber)
)
}
}
2 changes: 1 addition & 1 deletion app/helpers/Formatters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ object Formatters {

numberFormat.setMaximumFractionDigits(maxDecimalPlaces)
numberFormat.setMinimumFractionDigits(maxDecimalPlaces)
numberFormat.format(amount.abs)
numberFormat.format(amount)
}
}
10 changes: 6 additions & 4 deletions app/models/Declaration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
package models

import crypto.EncryptedValue
import models.domain.{EORI, MRN}
import models.domain.{EORI, MRN, UCR}
import play.api.libs.json.{Json, OFormat}

import java.time.LocalDate

case class Declaration(movementReferenceNumber: MRN,
importerEori: Option[String],
declarantEori: EORI,
declarantReference: Option[String],
declarantReference: Option[UCR],
date: LocalDate,
amount: BigDecimal,
taxGroups: Seq[TaxGroup])
taxGroups: Seq[TaxGroup],
secureMovementReferenceNumber: Option[String])
extends Ordered[Declaration] {
override def compare(that: Declaration): Int = movementReferenceNumber.compareTo(that.movementReferenceNumber)
}
Expand All @@ -43,7 +44,8 @@ case class EncryptedDeclaration(movementReferenceNumber: EncryptedValue,
declarantReference: Option[EncryptedValue],
date: LocalDate,
amount: BigDecimal,
taxGroups: Seq[TaxGroup])
taxGroups: Seq[TaxGroup],
secureMovementReferenceNumber: String)

object EncryptedDeclaration {
implicit val format: OFormat[EncryptedDeclaration] = Json.format[EncryptedDeclaration]
Expand Down
8 changes: 7 additions & 1 deletion app/models/TaxGroup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ package models

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

case class TaxGroup(taxTypeGroup: TaxGroupType, amount: BigDecimal)
case class TaxGroup(taxGroupDescription: TaxGroupType, amount: BigDecimal, taxTypes: Seq[TaxType])

case class TaxType(reasonForSecurity: String, taxTypeID: String, amount: BigDecimal)

object TaxType {
implicit val format: OFormat[TaxType] = Json.format[TaxType]
}

object TaxGroup {
implicit val format: OFormat[TaxGroup] = Json.format[TaxGroup]
Expand Down
1 change: 1 addition & 0 deletions app/models/domain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package object domain {
type LinkId = String
type CAN = String
type MRN = String
type UCR = String

implicit def optionBindable: PathBindable[Option[LinkId]] = new PathBindable[Option[LinkId]] {

Expand Down
2 changes: 1 addition & 1 deletion app/viewmodels/CashTransactionCsvRow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ object CashTransactionCsvRow {
}

private def findTaxGroups(taxGroupType: TaxGroupType, groups: Seq[TaxGroup]): Option[BigDecimal] = {
groups.find(_.taxTypeGroup == taxGroupType).map(_.amount.abs).orElse(Some(BigDecimal(0)))
groups.find(_.taxGroupDescription == taxGroupType).map(_.amount.abs).orElse(Some(BigDecimal(0)))
}

val withdrawals: Seq[CashTransactionCsvRow] = cashDailyStatement.withdrawals.map { withdrawal =>
Expand Down
96 changes: 96 additions & 0 deletions app/viewmodels/DeclarationDetailViewModel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2023 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 viewmodels

import helpers.Formatters
import models.{CashAccount, CustomsDuty, Declaration, ExciseDuty, ImportVat}
import models.domain.EORI
import play.api.i18n.Messages
import uk.gov.hmrc.govukfrontend.views.Aliases.{HtmlContent, SummaryList, SummaryListRow, Text}
import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.{Key, Value}
import utils.Utils.emptyString

case class DeclarationDetailViewModel(eori: EORI, account: CashAccount)

object DeclarationDetailViewModel {

def declarationSummaryList(declaration: Declaration)(implicit messages: Messages): SummaryList = {
SummaryList(
attributes = Map("id" -> "mrn"),
rows = Seq(
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.date"))),
value = Value(content = HtmlContent(Formatters.dateAsDayMonthAndYear(declaration.date)))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.movementReferenceNumber"))),
value = Value(content = HtmlContent(declaration.movementReferenceNumber))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.uniqueConsignmentReference"))),
value = Value(content = HtmlContent(declaration.declarantReference.getOrElse(emptyString)))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.declarantEori"))),
value = Value(content = HtmlContent(declaration.declarantEori))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.importerEori"))),
value = Value(content = HtmlContent(declaration.importerEori.getOrElse(emptyString)))
)
)
)
}

def taxSummaryList(declaration: Declaration)(implicit messages: Messages): SummaryList = {
SummaryList(
attributes = Map("id" -> "tax-details"),
rows = Seq(
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.duty"))),
value = Value(content = HtmlContent(
Formatters.formatCurrencyAmount(declaration.taxGroups.find(_.taxGroupDescription == CustomsDuty)
.map(_.amount)
.getOrElse(BigDecimal(0)))
))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.vat"))),
value = Value(content = HtmlContent(
Formatters.formatCurrencyAmount(declaration.taxGroups.find(_.taxGroupDescription == ImportVat)
.map(_.amount)
.getOrElse(BigDecimal(0)))
))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.csv.excise"))),
value = Value(content = HtmlContent(
declaration.taxGroups.find(_.taxGroupDescription == ExciseDuty)
.map(_.amount)
.fold(emptyString)(amount => Formatters.formatCurrencyAmount(amount))
))
),
SummaryListRow(
key = Key(content = Text(messages("cf.cash-account.detail.total.paid"))),
value = Value(content = HtmlContent(
Formatters.formatCurrencyAmount(declaration.amount)
))
)
)
)
}
}
45 changes: 45 additions & 0 deletions app/views/cash_account_declaration_details.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@*
* Copyright 2023 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.
*@

@import helpers.Formatters
@import viewmodels.DeclarationDetailViewModel

@this(main_template: Layout, govukSummaryList: GovukSummaryList, h1: components.h1, h2: components.h2Inner)

@(viewModel: DeclarationDetailViewModel,
declarationDetails: Declaration,
pageNumber: Option[Int]
)(implicit request: Request[_], messages: Messages)

@main_template(
pageTitle = Some(messages("cf.cash-account.detail.title")),
backLink = Some(routes.CashAccountController.showAccountDetails(page = pageNumber).url),
fullWidth = false
) {

@h2(msg = "cf.cash-account.detail.account",
innerMsg = viewModel.account.number,
id = Some("account-number"),
classes = "govuk-caption-xl"
)

@h1(msg="cf.cash-account.detail.declaration.title")

@govukSummaryList(DeclarationDetailViewModel.declarationSummaryList(declarationDetails))

@govukSummaryList(DeclarationDetailViewModel.taxSummaryList(declarationDetails))

}
Loading

0 comments on commit d2ee940

Please sign in to comment.