diff --git a/app/controllers/returns/ViewReturnController.scala b/app/controllers/returns/ViewReturnController.scala
index 6ccc7a0b..52261d17 100644
--- a/app/controllers/returns/ViewReturnController.scala
+++ b/app/controllers/returns/ViewReturnController.scala
@@ -28,7 +28,7 @@ import viewmodels.returns.ViewReturnViewModel
import views.html.returns.ViewReturnView
import javax.inject.Inject
-import scala.concurrent.ExecutionContext
+import scala.concurrent.{ExecutionContext, Future}
class ViewReturnController @Inject() (
override val messagesApi: MessagesApi,
@@ -47,50 +47,68 @@ class ViewReturnController @Inject() (
def onPageLoad(periodKey: String): Action[AnyContent] = identify.async { implicit request =>
val appaId = request.appaId
- (for {
- returnDetails <- alcoholDutyReturnsConnector.getReturn(appaId, periodKey)
- periodKey = returnDetails.identification.periodKey
- returnPeriodsAndTaxCodes =
- returnDetails.alcoholDeclared.taxCodes.map((periodKey, _)) ++ returnDetails.adjustments.returnPeriodsAndTaxCodes
- ratePeriodsAndTaxCodes = returnPeriodsAndTaxCodes.flatMap { case (periodKey, taxCode) =>
- ReturnPeriod
- .fromPeriodKey(periodKey)
- .map(returnPeriod => (returnPeriod.period, taxCode))
- }
- ratePeriodsAndTaxCodesToRateBands <- calculatorConnector.rateBands(ratePeriodsAndTaxCodes)
- } yield ReturnPeriod
+ ReturnPeriod
.fromPeriodKey(periodKey)
.fold {
logger.warn(s"Cannot parse period key $periodKey for $appaId on return")
- Redirect(controllers.routes.JourneyRecoveryController.onPageLoad())
+ Future.successful(Redirect(controllers.routes.JourneyRecoveryController.onPageLoad()))
} { returnPeriod =>
- val dutyToDeclareViewModel =
- viewModel.createAlcoholDeclaredViewModel(returnDetails, ratePeriodsAndTaxCodesToRateBands)
- val adjustmentsViewModel =
- viewModel.createAdjustmentsViewModel(returnDetails, ratePeriodsAndTaxCodesToRateBands)
- val totalDueViewModel = viewModel.createTotalDueViewModel(returnDetails)
- val netDutySuspension = viewModel.createNetDutySuspensionViewModel(returnDetails)
- val returnPeriodStr = dateTimeHelper.formatMonthYear(returnPeriod.period)
- val submittedDate = dateTimeHelper.instantToLocalDate(returnDetails.identification.submittedTime)
- val submittedDateStr = dateTimeHelper.formatDateMonthYear(submittedDate)
- val submittedTime = dateTimeHelper.instantToLocalTime(returnDetails.identification.submittedTime)
- val submittedTimeStr = dateTimeHelper.formatHourMinuteMeridiem(submittedTime)
+ (for {
+ returnDetails <- alcoholDutyReturnsConnector.getReturn(appaId, periodKey)
+ _ <- if (returnDetails.identification.periodKey != periodKey) {
+ val error =
+ s"Period key on the return ${returnDetails.identification.periodKey} does not match the return $periodKey requested for $appaId"
+ logger.warn(error)
+ Future.failed(new RuntimeException(error))
+ } else {
+ Future.unit
+ }
+ periodKey = returnDetails.identification.periodKey
+ returnPeriodsAndTaxCodes =
+ returnDetails.alcoholDeclared.taxCodes.map(
+ (periodKey, _)
+ ) ++ returnDetails.adjustments.returnPeriodsAndTaxCodes
+ ratePeriodsAndTaxCodes = returnPeriodsAndTaxCodes.flatMap { case (periodKey, taxCode) =>
+ ReturnPeriod
+ .fromPeriodKey(periodKey)
+ .map(returnPeriod => (returnPeriod.period, taxCode))
+ }
+ ratePeriodsAndTaxCodesToRateBands <- calculatorConnector.rateBands(ratePeriodsAndTaxCodes)
+ } yield {
+ val dutyToDeclareViewModel =
+ viewModel.createAlcoholDeclaredViewModel(returnDetails, ratePeriodsAndTaxCodesToRateBands)
+ val adjustmentsViewModel =
+ viewModel.createAdjustmentsViewModel(returnDetails, ratePeriodsAndTaxCodesToRateBands)
+ val totalDueViewModel = viewModel.createTotalDueViewModel(returnDetails)
+ val netDutySuspension = viewModel.createNetDutySuspensionViewModel(returnDetails)
+ val spirits = if (returnPeriod.hasQuarterlySpirits) {
+ viewModel.createSpiritsViewModels(returnDetails)
+ } else {
+ Seq.empty
+ }
+ val returnPeriodStr = dateTimeHelper.formatMonthYear(returnPeriod.period)
+ val submittedDate = dateTimeHelper.instantToLocalDate(returnDetails.identification.submittedTime)
+ val submittedDateStr = dateTimeHelper.formatDateMonthYear(submittedDate)
+ val submittedTime = dateTimeHelper.instantToLocalTime(returnDetails.identification.submittedTime)
+ val submittedTimeStr = dateTimeHelper.formatHourMinuteMeridiem(submittedTime)
- Ok(
- view(
- returnPeriodStr,
- submittedDateStr,
- submittedTimeStr,
- dutyToDeclareViewModel,
- adjustmentsViewModel,
- totalDueViewModel,
- netDutySuspension
+ Ok(
+ view(
+ returnPeriodStr,
+ submittedDateStr,
+ submittedTimeStr,
+ dutyToDeclareViewModel,
+ adjustmentsViewModel,
+ totalDueViewModel,
+ netDutySuspension,
+ spirits
+ )
)
- )
- })
- .recover { case _ =>
- logger.warn(s"Unable to fetch return $appaId $periodKey")
- Redirect(controllers.routes.JourneyRecoveryController.onPageLoad())
+ })
+ .recover { case e =>
+ logger.warn(s"Unable to fetch return $appaId $periodKey: ${e.getMessage}")
+ Redirect(controllers.routes.JourneyRecoveryController.onPageLoad())
+ }
}
}
}
diff --git a/app/models/checkAndSubmit/AdrReturnSubmission.scala b/app/models/checkAndSubmit/AdrReturnSubmission.scala
index cd3ce27c..20beee26 100644
--- a/app/models/checkAndSubmit/AdrReturnSubmission.scala
+++ b/app/models/checkAndSubmit/AdrReturnSubmission.scala
@@ -136,8 +136,8 @@ object AdrDutySuspended {
case class AdrSpiritsVolumes(
totalSpirits: BigDecimal,
- scotchWhiskey: BigDecimal,
- irishWhisky: BigDecimal
+ scotchWhisky: BigDecimal,
+ irishWhiskey: BigDecimal
)
object AdrSpiritsVolumes {
diff --git a/app/models/returns/ReturnDetails.scala b/app/models/returns/ReturnDetails.scala
index 481745dd..abdb251e 100644
--- a/app/models/returns/ReturnDetails.scala
+++ b/app/models/returns/ReturnDetails.scala
@@ -16,6 +16,7 @@
package models.returns
+import models.checkAndSubmit.AdrTypeOfSpirit
import play.api.libs.json.{Json, OFormat}
import java.time.Instant
@@ -25,7 +26,8 @@ case class ReturnDetails(
alcoholDeclared: ReturnAlcoholDeclared,
adjustments: ReturnAdjustments,
totalDutyDue: ReturnTotalDutyDue,
- netDutySuspension: Option[ReturnNetDutySuspension]
+ netDutySuspension: Option[ReturnNetDutySuspension],
+ spirits: Option[ReturnSpirits]
)
object ReturnDetails {
@@ -134,3 +136,23 @@ case class ReturnNetDutySuspension(
object ReturnNetDutySuspension {
implicit val returnTotalDutyDueFormat: OFormat[ReturnNetDutySuspension] = Json.format[ReturnNetDutySuspension]
}
+
+case class ReturnSpiritsVolumes(
+ totalSpirits: BigDecimal,
+ scotchWhisky: BigDecimal,
+ irishWhiskey: BigDecimal
+)
+
+case object ReturnSpiritsVolumes {
+ implicit val returnSpiritsVolumesFormat: OFormat[ReturnSpiritsVolumes] = Json.format[ReturnSpiritsVolumes]
+}
+
+case class ReturnSpirits(
+ spiritsVolumes: ReturnSpiritsVolumes,
+ typesOfSpirit: Set[AdrTypeOfSpirit],
+ otherSpiritTypeName: Option[String]
+)
+
+case object ReturnSpirits {
+ implicit val returnSpiritsFormat: OFormat[ReturnSpirits] = Json.format[ReturnSpirits]
+}
diff --git a/app/services/checkAndSubmit/AdrReturnSubmissionService.scala b/app/services/checkAndSubmit/AdrReturnSubmissionService.scala
index 4f50d0b3..e283060b 100644
--- a/app/services/checkAndSubmit/AdrReturnSubmissionService.scala
+++ b/app/services/checkAndSubmit/AdrReturnSubmissionService.scala
@@ -378,11 +378,11 @@ class AdrReturnSubmissionServiceImpl @Inject() (
def declareSpiritsTotal(userAnswers: UserAnswers): EitherT[Future, String, AdrSpiritsVolumes] =
for {
totalSpirits <- getValue(userAnswers, DeclareSpiritsTotalPage)
- whiskey <- getValue(userAnswers, WhiskyPage)
+ whisky <- getValue(userAnswers, WhiskyPage)
} yield AdrSpiritsVolumes(
totalSpirits = totalSpirits,
- scotchWhiskey = whiskey.scotchWhisky,
- irishWhisky = whiskey.irishWhiskey
+ scotchWhisky = whisky.scotchWhisky,
+ irishWhiskey = whisky.irishWhiskey
)
def getTypeOfSpirits(userAnswers: UserAnswers): EitherT[Future, String, Set[AdrTypeOfSpirit]] =
diff --git a/app/viewmodels/TableViewModel.scala b/app/viewmodels/TableViewModel.scala
index f17591a3..a65e2c71 100644
--- a/app/viewmodels/TableViewModel.scala
+++ b/app/viewmodels/TableViewModel.scala
@@ -22,12 +22,14 @@ import uk.gov.hmrc.govukfrontend.views.viewmodels.table.{HeadCell, TableRow}
case class TableViewModel(
head: Seq[HeadCell],
rows: Seq[TableRowViewModel],
- total: Option[TableTotalViewModel] = None
+ total: Option[TableTotalViewModel] = None,
+ caption: Option[String] = None
)
object TableViewModel {
- def empty(): TableViewModel = TableViewModel(Seq.empty, Seq.empty, None)
+ def empty(): TableViewModel = TableViewModel(Seq.empty, Seq.empty, None, None)
}
+
case class TableRowViewModel(cells: Seq[TableRow], actions: Seq[TableRowActionViewModel] = Seq.empty)
case class TableTotalViewModel(legend: HeadCell, total: HeadCell) {
diff --git a/app/viewmodels/returns/ViewReturnViewModel.scala b/app/viewmodels/returns/ViewReturnViewModel.scala
index 69484f17..25121c85 100644
--- a/app/viewmodels/returns/ViewReturnViewModel.scala
+++ b/app/viewmodels/returns/ViewReturnViewModel.scala
@@ -18,6 +18,8 @@ package viewmodels.returns
import config.Constants.Css
import config.FrontendAppConfig
+import models.checkAndSubmit.AdrTypeOfSpirit
+import models.checkAndSubmit.AdrTypeOfSpirit._
import models.returns._
import models.{RateBand, ReturnPeriod}
import play.api.i18n.Messages
@@ -361,4 +363,111 @@ class ViewReturnViewModel @Inject() (appConfig: FrontendAppConfig) {
content = Text(messages("viewReturn.table.description.legend"))
)
)
+
+ def createSpiritsViewModels(
+ returnDetails: ReturnDetails
+ )(implicit messages: Messages): Seq[TableViewModel] =
+ returnDetails.spirits match {
+ case Some(spirits) =>
+ Seq(
+ TableViewModel(
+ head = spiritsDeclaredTableHeader(),
+ rows = spiritsDeclaredRows(spirits),
+ caption = Some(messages("viewReturn.spirits.caption"))
+ ),
+ TableViewModel(
+ head = spiritsTypesDeclaredTableHeader(),
+ rows = spiritsTypesDeclaredRows(spirits)
+ )
+ )
+ case None =>
+ Seq(
+ TableViewModel(
+ head = spiritsNotDeclaredTableHeader(),
+ rows = spiritsNotDeclaredRow(),
+ caption = Some(messages("viewReturn.spirits.caption"))
+ )
+ )
+ }
+
+ private def spiritsDeclaredTableHeader()(implicit messages: Messages): Seq[HeadCell] =
+ Seq(
+ HeadCell(
+ content = Text(messages("viewReturn.table.description.legend"))
+ ),
+ HeadCell(
+ content = Text(messages("viewReturn.table.totalVolume.lpa.legend")),
+ classes = Css.textAlignRightWrapCssClass
+ )
+ )
+
+ private def spiritsDeclaredRows(spirits: ReturnSpirits)(implicit messages: Messages): Seq[TableRowViewModel] =
+ Seq(
+ ("viewReturn.spirits.totalVolume", spirits.spiritsVolumes.totalSpirits),
+ ("viewReturn.spirits.scotchWhisky", spirits.spiritsVolumes.scotchWhisky),
+ ("viewReturn.spirits.irishWhiskey", spirits.spiritsVolumes.irishWhiskey)
+ ).map { case (legendKey, value) =>
+ TableRowViewModel(
+ cells = Seq(
+ TableRow(content = Text(messages(legendKey))),
+ TableRow(
+ content = Text(messages("site.2DP", value)),
+ classes = s"${Css.textAlignRightCssClass} ${Css.numericCellClass}"
+ )
+ )
+ )
+ }
+
+ private def spiritsTypesDeclaredTableHeader()(implicit messages: Messages): Seq[HeadCell] =
+ Seq(
+ HeadCell(
+ content = Text(messages("viewReturn.table.typesOfSpirits.legend"))
+ )
+ )
+
+ private val spiritsTypeToMessageKey: Map[AdrTypeOfSpirit, String] =
+ Map(
+ Malt -> "viewReturn.spirits.type.malt",
+ Grain -> "viewReturn.spirits.type.grain",
+ NeutralAgricultural -> "viewReturn.spirits.type.neutralAgricultural",
+ NeutralIndustrial -> "viewReturn.spirits.type.neutralIndustrial",
+ Beer -> "viewReturn.spirits.type.beer",
+ CiderOrPerry -> "viewReturn.spirits.type.cider",
+ WineOrMadeWine -> "viewReturn.spirits.type.wine"
+ )
+
+ private def spiritsTypesDeclaredRows(spirits: ReturnSpirits)(implicit messages: Messages): Seq[TableRowViewModel] = {
+ val typesOfSpirit = spirits.typesOfSpirit
+
+ val spiritsTypesDetails = AdrTypeOfSpirit.values
+ .flatMap {
+ case Other if typesOfSpirit.contains(Other) => spirits.otherSpiritTypeName
+ case typeOfSpirit if typesOfSpirit.contains(typeOfSpirit) =>
+ spiritsTypeToMessageKey.get(typeOfSpirit).map(messages(_))
+ case _ => None
+ }
+ .mkString(", ")
+
+ Seq(
+ TableRowViewModel(
+ cells = Seq(
+ TableRow(content = Text(spiritsTypesDetails))
+ )
+ )
+ )
+ }
+
+ private def spiritsNotDeclaredTableHeader()(implicit messages: Messages): Seq[HeadCell] = Seq(
+ HeadCell(
+ content = Text(messages("viewReturn.table.description.legend"))
+ )
+ )
+
+ private def spiritsNotDeclaredRow()(implicit messages: Messages) = Seq(
+ TableRowViewModel(
+ cells = Seq(
+ TableRow(content = Text(messages("viewReturn.spirits.noneDeclared")))
+ )
+ )
+ )
}
diff --git a/app/views/returns/ViewReturnView.scala.html b/app/views/returns/ViewReturnView.scala.html
index 97a0020f..91b41505 100644
--- a/app/views/returns/ViewReturnView.scala.html
+++ b/app/views/returns/ViewReturnView.scala.html
@@ -28,7 +28,16 @@
printPage: PrintPage
)
-@(period: String, submittedAtDate: String, submittedAtTime: String, dutyToDeclare: TableViewModel, adjustments: TableViewModel, totalDue: TableTotalViewModel, netDutySuspension: TableViewModel)(implicit request: Request[_], messages: Messages)
+@(
+ period: String,
+ submittedAtDate: String,
+ submittedAtTime: String,
+ dutyToDeclare: TableViewModel,
+ adjustments: TableViewModel,
+ totalDue: TableTotalViewModel,
+ netDutySuspension: TableViewModel,
+ spirits: Seq[TableViewModel]
+)(implicit request: Request[_], messages: Messages)
@layout(pageTitle = titleNoForm(messages("viewReturn.title", period)), fullWidth = true, withPrintCss = true) {
@@ -85,5 +94,16 @@
+ @spirits.map { spiritsTable =>
+ @govukTable(Table(
+ caption = spiritsTable.caption,
+ captionClasses = Css.tableCaptionMCssClass,
+ head = Some(spiritsTable.head),
+ rows = spiritsTable.rows.map(_.cells)
+ ))
+ }
+
+
+
@printPage("print-past-payments-link", messages("viewReturn.printYourReturn"))
}
\ No newline at end of file
diff --git a/conf/messages.en b/conf/messages.en
index f338655b..5e193bbf 100644
--- a/conf/messages.en
+++ b/conf/messages.en
@@ -140,16 +140,30 @@ viewReturn.adjustments.type.spoilt = Spoilt
viewReturn.adjustments.type.drawback = Drawback
viewReturn.dutyDue.caption = Total
viewReturn.dutyDue.total.legend = Total duty value
+viewReturn.netDutySuspension.caption = Duty suspended deliveries
+viewReturn.netDutySuspension.noneDeclared = Nothing declared
+viewReturn.spirits.caption = Spirits production in the last 3 months
+viewReturn.spirits.totalVolume = Total volume of spirits
+viewReturn.spirits.scotchWhisky = Scotch whisky
+viewReturn.spirits.irishWhiskey = Irish whiskey
+viewReturn.spirits.type.malt = Malt spirit
+viewReturn.spirits.type.grain = Grain spirit
+viewReturn.spirits.type.neutralAgricultural = Neutral spirit (agricultural origin)
+viewReturn.spirits.type.neutralIndustrial = Neutral spirit (industrial origin)
+viewReturn.spirits.type.beer = Beer-based spirit
+viewReturn.spirits.type.wine = Wine or made-wine-based spirit
+viewReturn.spirits.type.cider = Cider or perry-based spirit
+viewReturn.spirits.noneDeclared = Nothing declared
viewReturn.table.adjustmentType.legend = Adjustment
viewReturn.table.description.legend = Description
viewReturn.table.totalVolume.legend = Total volume (litres)
viewReturn.table.lpa.legend = Litres of pure alcohol (LPA)
+viewReturn.table.totalVolume.lpa.legend = Total volume (LPA)
viewReturn.table.dutyRate.legend = Duty rate (per litre)
viewReturn.table.dutyValue.legend = Duty value
viewReturn.table.dutyDue.legend = Duty value
+viewReturn.table.typesOfSpirits.legend = Types of spirits produced
viewReturn.printYourReturn = Print your return
-viewReturn.netDutySuspension.caption = Duty suspended deliveries
-viewReturn.netDutySuspension.noneDeclared = Nothing declared
viewPastPayments.heading = Alcohol Duty payments
viewPastPayments.title = Alcohol Duty payments
diff --git a/test-utils/common/TestData.scala b/test-utils/common/TestData.scala
index 9df58bad..5b45551c 100644
--- a/test-utils/common/TestData.scala
+++ b/test-utils/common/TestData.scala
@@ -275,6 +275,17 @@ trait TestData extends ModelGenerators {
totalLtsPureAlcoholWine = Some(BigDecimal("0.5965")),
totalLtsPureAlcoholOtherFermented = Some(BigDecimal("0.1894"))
)
+ ),
+ spirits = Some(
+ ReturnSpirits(
+ ReturnSpiritsVolumes(
+ totalSpirits = BigDecimal("0.05"),
+ scotchWhisky = BigDecimal("0.26"),
+ irishWhiskey = BigDecimal("0.16")
+ ),
+ typesOfSpirit = Set(AdrTypeOfSpirit.NeutralAgricultural),
+ otherSpiritTypeName = Some("Coco Pops Vodka")
+ )
)
)
}
@@ -291,7 +302,8 @@ trait TestData extends ModelGenerators {
total = BigDecimal("0")
),
totalDutyDue = ReturnTotalDutyDue(totalDue = BigDecimal("0")),
- netDutySuspension = None
+ netDutySuspension = None,
+ spirits = None
)
def nilReturnDetailsWithEmptySections(periodKey: String, now: Instant): ReturnDetails =
@@ -306,7 +318,8 @@ trait TestData extends ModelGenerators {
total = BigDecimal("0")
),
totalDutyDue = ReturnTotalDutyDue(totalDue = BigDecimal("0")),
- netDutySuspension = None
+ netDutySuspension = None,
+ spirits = None
)
def returnWithSpoiltAdjustment(periodKey: String, now: Instant): ReturnDetails = {
@@ -341,7 +354,8 @@ trait TestData extends ModelGenerators {
total = BigDecimal("-3151.50")
),
totalDutyDue = ReturnTotalDutyDue(totalDue = BigDecimal("-6303.00")),
- netDutySuspension = None
+ netDutySuspension = None,
+ spirits = None
)
}
diff --git a/test/controllers/returns/ViewReturnControllerSpec.scala b/test/controllers/returns/ViewReturnControllerSpec.scala
index eb35fd57..d30dad7a 100644
--- a/test/controllers/returns/ViewReturnControllerSpec.scala
+++ b/test/controllers/returns/ViewReturnControllerSpec.scala
@@ -18,6 +18,7 @@ package controllers.returns
import base.SpecBase
import connectors.{AlcoholDutyCalculatorConnector, AlcoholDutyReturnsConnector}
+import models.ReturnPeriod
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchersSugar.eqTo
import play.api.inject.bind
@@ -32,8 +33,63 @@ import scala.concurrent.Future
class ViewReturnControllerSpec extends SpecBase {
"ViewReturnController" - {
- "should return a view if able to fetch the return" in new SetUp {
- when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKey))(any))
+ "should return a view if able to fetch the return and a spirits month" in new SetUp {
+ override def periodKeyUnderTest: String = periodKeyForSpirits
+
+ when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKeyUnderTest))(any))
+ .thenReturn(Future.successful(returnDetails))
+ when(mockCalculatorConnector.rateBands(any())(any))
+ .thenReturn(Future.successful(rateBands))
+
+ val application = applicationBuilder(userAnswers = Some(emptyUserAnswers))
+ .overrides(bind[AlcoholDutyReturnsConnector].toInstance(mockReturnsConnector))
+ .overrides(bind[AlcoholDutyCalculatorConnector].toInstance(mockCalculatorConnector))
+ .overrides(bind[ViewReturnViewModel].toInstance(mockViewModel))
+ .build()
+ running(application) {
+ implicit val messages = getMessages(application)
+
+ when(mockViewModel.createTotalDueViewModel(returnDetails)).thenReturn(totalTableModel)
+ when(mockViewModel.createAlcoholDeclaredViewModel(eqTo(returnDetails), any())(any()))
+ .thenReturn(tableModel)
+ when(mockViewModel.createAdjustmentsViewModel(eqTo(returnDetails), any())(any()))
+ .thenReturn(tableModel)
+ when(mockViewModel.createNetDutySuspensionViewModel(eqTo(returnDetails))(any())).thenReturn(tableModel)
+ when(mockViewModel.createSpiritsViewModels(eqTo(returnDetails))(any())).thenReturn(Seq(tableModel))
+ when(mockViewModel.createAlcoholDeclaredViewModel(eqTo(returnDetails), any())(any()))
+ .thenReturn(tableModel)
+ when(mockViewModel.createAdjustmentsViewModel(eqTo(returnDetails), any())(any()))
+ .thenReturn(tableModel)
+
+ val request =
+ FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKeyUnderTest).url)
+ val result = route(application, request).value
+
+ val view = application.injector.instanceOf[ViewReturnView]
+
+ status(result) mustEqual OK
+ contentAsString(result) mustEqual view(
+ returnPeriodStr,
+ submittedAtDateStr,
+ submittedAtTimeStr,
+ tableModel,
+ tableModel,
+ totalTableModel,
+ tableModel,
+ Seq(tableModel)
+ )(
+ request,
+ messages
+ ).toString
+ }
+
+ verify(mockViewModel, times(1)).createSpiritsViewModels(any)(any)
+ }
+
+ "should return a view if able to fetch the return and not a spirits month" in new SetUp {
+ override def periodKeyUnderTest: String = periodKeyNotForSpirits
+
+ when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKeyUnderTest))(any))
.thenReturn(Future.successful(returnDetails))
when(mockCalculatorConnector.rateBands(any())(any))
.thenReturn(Future.successful(rateBands))
@@ -43,6 +99,7 @@ class ViewReturnControllerSpec extends SpecBase {
.overrides(bind[AlcoholDutyCalculatorConnector].toInstance(mockCalculatorConnector))
.overrides(bind[ViewReturnViewModel].toInstance(mockViewModel))
.build()
+
running(application) {
implicit val messages = getMessages(application)
@@ -52,12 +109,14 @@ class ViewReturnControllerSpec extends SpecBase {
when(mockViewModel.createAdjustmentsViewModel(eqTo(returnDetails), any())(any()))
.thenReturn(tableModel)
when(mockViewModel.createNetDutySuspensionViewModel(returnDetails)).thenReturn(tableModel)
+ when(mockViewModel.createSpiritsViewModels(returnDetails)).thenReturn(Seq(tableModel))
when(mockViewModel.createAlcoholDeclaredViewModel(eqTo(returnDetails), any())(any()))
.thenReturn(tableModel)
when(mockViewModel.createAdjustmentsViewModel(eqTo(returnDetails), any())(any()))
.thenReturn(tableModel)
- val request = FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKey).url)
+ val request =
+ FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKeyUnderTest).url)
val result = route(application, request).value
val view = application.injector.instanceOf[ViewReturnView]
@@ -70,63 +129,101 @@ class ViewReturnControllerSpec extends SpecBase {
tableModel,
tableModel,
totalTableModel,
- tableModel
+ tableModel,
+ Seq.empty
)(
request,
messages
).toString
}
+
+ verify(mockViewModel, never).createSpiritsViewModels(any)(any)
+ }
+
+ "should redirect to the journey recovery page if unable to parse the period key" in new SetUp {
+ override def periodKeyUnderTest: String = periodKeyForSpirits
+
+ val application = applicationBuilder(userAnswers = Some(emptyUserAnswers))
+ .overrides(bind[AlcoholDutyReturnsConnector].toInstance(mockReturnsConnector))
+ .overrides(bind[AlcoholDutyCalculatorConnector].toInstance(mockCalculatorConnector))
+ .build()
+
+ running(application) {
+ val request = FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(badPeriodKey).url)
+ val result = route(application, request).value
+
+ status(result) mustEqual SEE_OTHER
+ redirectLocation(result).value mustEqual controllers.routes.JourneyRecoveryController.onPageLoad().url
+ }
+
+ verify(mockReturnsConnector, never).getReturn(any, any)(any)
}
"should redirect to the journey recovery page if unable to fetch the return" in new SetUp {
- when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKey))(any))
+ override def periodKeyUnderTest: String = periodKeyForSpirits
+
+ when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKeyUnderTest))(any))
.thenReturn(Future.failed(new IllegalArgumentException("error")))
val application = applicationBuilder(userAnswers = Some(emptyUserAnswers))
.overrides(bind[AlcoholDutyReturnsConnector].toInstance(mockReturnsConnector))
.build()
running(application) {
- val request = FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKey).url)
+ val request =
+ FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKeyUnderTest).url)
val result = route(application, request).value
status(result) mustEqual SEE_OTHER
redirectLocation(result).value mustEqual controllers.routes.JourneyRecoveryController.onPageLoad().url
}
+
+ verify(mockReturnsConnector, times(1)).getReturn(any, any)(any)
+ verify(mockCalculatorConnector, never).rateBands(any())(any)
}
- "should redirect to the journey recovery page if unable to parse the returned period key" in new SetUp {
- when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKey))(any))
- .thenReturn(Future.successful(returnDetailsWithBadPeriodKey))
+ "should redirect to the journey recovery page if the period key on the return doesn't match that of the request" in new SetUp {
+ override def periodKeyUnderTest: String = periodKeyForSpirits
+
+ when(mockReturnsConnector.getReturn(eqTo(appaId), eqTo(periodKeyUnderTest))(any))
+ .thenReturn(
+ Future.successful(
+ returnDetails.copy(identification = returnDetails.identification.copy(periodKey = periodKeyNotForSpirits))
+ )
+ )
when(mockCalculatorConnector.rateBands(any())(any))
.thenReturn(Future.successful(rateBands))
val application = applicationBuilder(userAnswers = Some(emptyUserAnswers))
.overrides(bind[AlcoholDutyReturnsConnector].toInstance(mockReturnsConnector))
- .overrides(bind[AlcoholDutyCalculatorConnector].toInstance(mockCalculatorConnector))
.build()
running(application) {
- val request = FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKey).url)
+ val request =
+ FakeRequest(GET, controllers.returns.routes.ViewReturnController.onPageLoad(periodKeyUnderTest).url)
val result = route(application, request).value
status(result) mustEqual SEE_OTHER
redirectLocation(result).value mustEqual controllers.routes.JourneyRecoveryController.onPageLoad().url
}
+
+ verify(mockReturnsConnector, times(1)).getReturn(any, any)(any)
+ verify(mockCalculatorConnector, never).rateBands(any())(any)
}
}
- class SetUp {
- val periodKey = periodKeyJan
- val returnDetails = exampleReturnDetails(periodKey, Instant.now(clock))
- val returnDetailsWithBadPeriodKey =
- returnDetails.copy(identification = returnDetails.identification.copy(periodKey = badPeriodKey))
- val returnPeriodStr = dateTimeHelper.formatMonthYear(returnPeriodJan.period)
- val submittedAtDateStr = dateTimeHelper.formatDateMonthYear(
+ abstract class SetUp {
+ val periodKeyForSpirits = periodKeyJan
+ val periodKeyNotForSpirits = periodKeyFeb
+ def periodKeyUnderTest: String
+ val returnPeriodUnderTest = ReturnPeriod.fromPeriodKeyOrThrow(periodKeyUnderTest)
+ val returnDetails = exampleReturnDetails(periodKeyUnderTest, Instant.now(clock))
+ val returnPeriodStr = dateTimeHelper.formatMonthYear(returnPeriodUnderTest.period)
+ val submittedAtDateStr = dateTimeHelper.formatDateMonthYear(
dateTimeHelper.instantToLocalDate(returnDetails.identification.submittedTime)
)
- val submittedAtTimeStr = dateTimeHelper.formatHourMinuteMeridiem(
+ val submittedAtTimeStr = dateTimeHelper.formatHourMinuteMeridiem(
dateTimeHelper.instantToLocalTime(returnDetails.identification.submittedTime)
)
- val rateBands = exampleRateBands(periodKey)
+ val rateBands = exampleRateBands(periodKeyUnderTest)
val tableModel = TableViewModel.empty()
val totalTableModel = TableTotalViewModel(HeadCell(), HeadCell())
diff --git a/test/viewmodels/returns/ViewReturnViewModelSpec.scala b/test/viewmodels/returns/ViewReturnViewModelSpec.scala
index 17ecfa1f..3512aa93 100644
--- a/test/viewmodels/returns/ViewReturnViewModelSpec.scala
+++ b/test/viewmodels/returns/ViewReturnViewModelSpec.scala
@@ -18,8 +18,8 @@ package viewmodels.returns
import base.SpecBase
import config.FrontendAppConfig
+import models.checkAndSubmit.AdrTypeOfSpirit
import models.returns.{ReturnAdjustments, ReturnAlcoholDeclared, ReturnDetails, ReturnTotalDutyDue}
-import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
import play.api.Application
import play.api.i18n.Messages
import uk.gov.hmrc.govukfrontend.views.Aliases.Text
@@ -34,12 +34,12 @@ class ViewReturnViewModelSpec extends SpecBase {
val alcoholDeclaredViewModel =
viewModel.createAlcoholDeclaredViewModel(returnDetails, exampleRateBands(periodKey))
- alcoholDeclaredViewModel.rows.size shouldBe returnDetails.alcoholDeclared.alcoholDeclaredDetails.get.size
- alcoholDeclaredViewModel.total.get.total.content shouldBe Text(
+ alcoholDeclaredViewModel.rows.size mustBe returnDetails.alcoholDeclared.alcoholDeclaredDetails.get.size
+ alcoholDeclaredViewModel.total.get.total.content mustBe Text(
messages("site.currency.2DP", returnDetails.alcoholDeclared.total)
)
- alcoholDeclaredViewModel.rows.head.cells.head.content shouldBe Text("311")
- alcoholDeclaredViewModel.rows(3).cells.head.content shouldBe Text(
+ alcoholDeclaredViewModel.rows.head.cells.head.content mustBe Text("311")
+ alcoholDeclaredViewModel.rows(3).cells.head.content mustBe Text(
"Non-draught beer between 1% and 2% ABV (123)"
)
}
@@ -47,15 +47,15 @@ class ViewReturnViewModelSpec extends SpecBase {
"should return a model with no entries when a nil return" in new SetUp {
val alcoholDeclaredViewModel = viewModel.createAlcoholDeclaredViewModel(nilReturn, emptyRateBands)
- alcoholDeclaredViewModel.rows.size shouldBe 1
- alcoholDeclaredViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ alcoholDeclaredViewModel.rows.size mustBe 1
+ alcoholDeclaredViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
"should return a model with no entries when a nil return with empty sections" in new SetUp {
val alcoholDeclaredViewModel = viewModel.createAlcoholDeclaredViewModel(emptyReturnDetails, emptyRateBands)
- alcoholDeclaredViewModel.rows.size shouldBe 1
- alcoholDeclaredViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ alcoholDeclaredViewModel.rows.size mustBe 1
+ alcoholDeclaredViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
}
@@ -63,13 +63,13 @@ class ViewReturnViewModelSpec extends SpecBase {
"should return a model with data when adjustments declared" in new SetUp {
val adjustmentsViewModel = viewModel.createAdjustmentsViewModel(returnDetails, exampleRateBands(periodKey2))
- adjustmentsViewModel.rows.size shouldBe 4
- adjustmentsViewModel.total.get.total.content shouldBe Text(
+ adjustmentsViewModel.rows.size mustBe 4
+ adjustmentsViewModel.total.get.total.content mustBe Text(
s"$minus${messages("site.currency.2DP", returnDetails.adjustments.total.abs)}"
)
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text("321")
- adjustmentsViewModel.rows(3).cells(1).content shouldBe Text("Non-draught beer between 1% and 2% ABV (125)")
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text("321")
+ adjustmentsViewModel.rows(3).cells(1).content mustBe Text("Non-draught beer between 1% and 2% ABV (125)")
}
"should return a model with data when a spoilt adjustment declared" in new SetUp {
@@ -80,26 +80,26 @@ class ViewReturnViewModelSpec extends SpecBase {
exampleRateBands(periodKey2)
)
- adjustmentsViewModel.rows.size shouldBe 2
- adjustmentsViewModel.total.get.total.content shouldBe Text(
+ adjustmentsViewModel.rows.size mustBe 2
+ adjustmentsViewModel.total.get.total.content mustBe Text(
s"$minus${messages("site.currency.2DP", returnDetailWithSpoilt.adjustments.total.abs)}"
)
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text("123")
- adjustmentsViewModel.rows(1).cells(1).content shouldBe Text("Wine")
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text("123")
+ adjustmentsViewModel.rows(1).cells(1).content mustBe Text("Wine")
}
"should return a model with no entries when a nil return" in new SetUp {
val adjustmentsViewModel = viewModel.createAdjustmentsViewModel(nilReturn, emptyRateBands)
- adjustmentsViewModel.rows.size shouldBe 1
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ adjustmentsViewModel.rows.size mustBe 1
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
"should return a model with no entries when a nil return with empty sections" in new SetUp {
val adjustmentsViewModel = viewModel.createAdjustmentsViewModel(emptyReturnDetails, emptyRateBands)
- adjustmentsViewModel.rows.size shouldBe 1
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ adjustmentsViewModel.rows.size mustBe 1
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
}
@@ -107,13 +107,13 @@ class ViewReturnViewModelSpec extends SpecBase {
"should return a model with a total when a total exists" in new SetUp {
val totalViewModel = viewModel.createTotalDueViewModel(returnDetails)
- totalViewModel.total.content shouldBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
+ totalViewModel.total.content mustBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
}
"should return a model with no entries when a nil return" in new SetUp {
val totalViewModel = viewModel.createTotalDueViewModel(nilReturn)
- totalViewModel.total.content shouldBe Text(messages("site.nil"))
+ totalViewModel.total.content mustBe Text(messages("site.nil"))
}
"should return a model with a total when a total exists even if no declarations" in new SetUp {
@@ -121,7 +121,7 @@ class ViewReturnViewModelSpec extends SpecBase {
emptyReturnDetails.copy(totalDutyDue = ReturnTotalDutyDue(totalDue = nonZeroAmount))
)
- totalViewModel.total.content shouldBe Text(messages("site.currency.2DP", nonZeroAmount))
+ totalViewModel.total.content mustBe Text(messages("site.currency.2DP", nonZeroAmount))
}
"should return a model with a total when a total exists even if no alcohol is declared" in new SetUp {
@@ -131,7 +131,7 @@ class ViewReturnViewModelSpec extends SpecBase {
)
)
- totalViewModel.total.content shouldBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
+ totalViewModel.total.content mustBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
}
"should return a model with a total when a total exists when no adjustments exist" in new SetUp {
@@ -141,21 +141,21 @@ class ViewReturnViewModelSpec extends SpecBase {
)
)
- totalViewModel.total.content shouldBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
+ totalViewModel.total.content mustBe Text(messages("site.currency.2DP", returnDetails.totalDutyDue.totalDue))
}
"should return a model with no entries when a nil return (nothing declared, no total)" in new SetUp {
val adjustmentsViewModel = viewModel.createAdjustmentsViewModel(nilReturn, emptyRateBands)
- adjustmentsViewModel.rows.size shouldBe 1
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ adjustmentsViewModel.rows.size mustBe 1
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
"should return a model with no entries when a nil return with empty sections (nothing declared, no total)" in new SetUp {
val adjustmentsViewModel = viewModel.createAdjustmentsViewModel(emptyReturnDetails, emptyRateBands)
- adjustmentsViewModel.rows.size shouldBe 1
- adjustmentsViewModel.rows.head.cells(1).content shouldBe Text(messages("site.nil"))
+ adjustmentsViewModel.rows.size mustBe 1
+ adjustmentsViewModel.rows.head.cells(1).content mustBe Text(messages("site.nil"))
}
}
@@ -163,10 +163,10 @@ class ViewReturnViewModelSpec extends SpecBase {
"should return a model with data when duty suspension is declared" in new SetUp {
val netDutySuspensionViewModel = viewModel.createNetDutySuspensionViewModel(returnDetails)
- netDutySuspensionViewModel.head.size shouldBe 3
- netDutySuspensionViewModel.rows.size shouldBe 5
+ netDutySuspensionViewModel.head.size mustBe 3
+ netDutySuspensionViewModel.rows.size mustBe 5
netDutySuspensionViewModel.rows.foreach { row =>
- row.cells.size shouldBe 3
+ row.cells.size mustBe 3
}
}
@@ -183,20 +183,97 @@ class ViewReturnViewModelSpec extends SpecBase {
val netDutySuspensionViewModel = viewModel.createNetDutySuspensionViewModel(returnDetailsWithoutCider)
- netDutySuspensionViewModel.head.size shouldBe 3
- netDutySuspensionViewModel.rows.size shouldBe 4
+ netDutySuspensionViewModel.head.size mustBe 3
+ netDutySuspensionViewModel.rows.size mustBe 4
}
"should return a model with the right label when nothing declared" in new SetUp {
val netDutySuspensionViewModel = viewModel.createNetDutySuspensionViewModel(nilReturn)
- netDutySuspensionViewModel.rows.size shouldBe 1
- netDutySuspensionViewModel.rows.head.cells.head.content shouldBe Text(
+ netDutySuspensionViewModel.rows.size mustBe 1
+ netDutySuspensionViewModel.rows.head.cells.head.content mustBe Text(
messages("viewReturn.netDutySuspension.noneDeclared")
)
}
}
+
+ "createSpiritsViewModels" - {
+ "should return a model with data when quarterly spirits are declared" in new SetUp {
+ val spiritsViewModels = viewModel.createSpiritsViewModels(
+ returnDetails.copy(spirits =
+ Some(returnDetails.spirits.get.copy(typesOfSpirit = AdrTypeOfSpirit.values.toSet))
+ )
+ )
+
+ spiritsViewModels.size mustBe 2
+ spiritsViewModels.head.head.size mustBe 2
+ spiritsViewModels.head.head.map(_.content) mustBe Seq(
+ Text(messages("viewReturn.table.description.legend")),
+ Text(messages("viewReturn.table.totalVolume.lpa.legend"))
+ )
+ spiritsViewModels.head.rows.size mustBe 3
+ spiritsViewModels.head.rows.foreach { row =>
+ row.cells.size mustBe 2
+ }
+ spiritsViewModels.head.rows.map(_.cells.head.content) mustBe Seq(
+ Text(messages("viewReturn.spirits.totalVolume")),
+ Text(messages("viewReturn.spirits.scotchWhisky")),
+ Text(messages("viewReturn.spirits.irishWhiskey"))
+ )
+ spiritsViewModels.head.caption mustBe Some(messages("viewReturn.spirits.caption"))
+ spiritsViewModels.last.head.size mustBe 1
+ spiritsViewModels.last.head.map(_.content) mustBe Seq(Text(messages("viewReturn.table.typesOfSpirits.legend")))
+ spiritsViewModels.last.rows.size mustBe 1
+ spiritsViewModels.last.rows.head.cells.size mustBe 1
+ spiritsViewModels.last.rows.head.cells.head.content mustBe Text(
+ Seq(
+ messages("viewReturn.spirits.type.malt"),
+ messages("viewReturn.spirits.type.grain"),
+ messages("viewReturn.spirits.type.neutralAgricultural"),
+ messages("viewReturn.spirits.type.neutralIndustrial"),
+ messages("viewReturn.spirits.type.beer"),
+ messages("viewReturn.spirits.type.cider"),
+ messages("viewReturn.spirits.type.wine"),
+ "Coco Pops Vodka"
+ ).mkString(", ")
+ )
+ spiritsViewModels.last.caption mustBe None
+ }
+
+ "should return a model with data when quarterly spirits is declared and handling missing other spirits type name gracefully" in new SetUp {
+ val spiritsViewModels = viewModel.createSpiritsViewModels(
+ returnDetails.copy(spirits =
+ Some(
+ returnDetails.spirits.get.copy(typesOfSpirit = AdrTypeOfSpirit.values.toSet, otherSpiritTypeName = None)
+ )
+ )
+ )
+
+ spiritsViewModels.last.rows.head.cells.head.content mustBe Text(
+ Seq(
+ messages("viewReturn.spirits.type.malt"),
+ messages("viewReturn.spirits.type.grain"),
+ messages("viewReturn.spirits.type.neutralAgricultural"),
+ messages("viewReturn.spirits.type.neutralIndustrial"),
+ messages("viewReturn.spirits.type.beer"),
+ messages("viewReturn.spirits.type.cider"),
+ messages("viewReturn.spirits.type.wine")
+ ).mkString(", ")
+ )
+ }
+
+ "should return a model with the right label when nothing declared" in new SetUp {
+ val spiritsViewModels = viewModel.createSpiritsViewModels(nilReturn)
+
+ spiritsViewModels.size mustBe 1
+ spiritsViewModels.head.rows.size mustBe 1
+ spiritsViewModels.head.rows.head.cells.head.content mustBe Text(
+ messages("viewReturn.spirits.noneDeclared")
+ )
+ spiritsViewModels.head.caption mustBe Some(messages("viewReturn.spirits.caption"))
+ }
+ }
}
class SetUp {