diff --git a/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala b/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala
index 8cb4255bf..daa573ed3 100644
--- a/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala
+++ b/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala
@@ -61,8 +61,11 @@ object ValueClassBinder {
implicit val sectionTitle4GaBinder: PathBindable[SectionTitle4Ga] = valueClassBinder(_.value)
implicit val sectionNumberBinder: PathBindable[SectionNumber] = new PathBindable[SectionNumber] {
override def bind(key: String, value: String): Either[String, SectionNumber] = {
- val sectionNumber: Try[SectionNumber] = SectionNumber.parse(value)
- sectionNumber.map(_.asRight).getOrElse(s"No valid value in path $key: $value".asLeft)
+ val sectionNumber: Option[SectionNumber] = SectionNumber.parse(value)
+ sectionNumber
+ .map(_.asRight)
+ .getOrElse(SectionNumber.Legacy(value).asRight)
+ //.getOrElse(s"No valid value in path $key: $value".asLeft)
}
override def unbind(key: String, sectionNumber: SectionNumber): String = sectionNumber.value
}
@@ -173,7 +176,7 @@ object ValueClassBinder {
case "-" => Right(None)
case a =>
Try(value.toInt) match {
- case Success(a) => Right(Some(SectionNumber.Classic(a)))
+ case Success(a) => Right(Some(SectionNumber.Classic.NormalPage(TemplateSectionIndex(a))))
case Failure(error) => Left(("No valid value in url binding: " + value + ". Error: " + error))
}
}
@@ -191,7 +194,8 @@ object ValueClassBinder {
SectionNumber
.parse(value)
.map(_.asRight)
- .getOrElse(s"No valid value in path $key: $value".asLeft)
+ .getOrElse(SectionNumber.Legacy(value).asRight)
+ //.getOrElse(s"No valid value in path $key: $value".asLeft)
}
override def unbind(key: String, sectionNumber: SectionNumber): String =
@@ -232,21 +236,20 @@ object ValueClassBinder {
private def toSectionNumber(key: String, value: String): Either[String, SectionNumber] =
SectionNumber
.parse(value)
- .toOption
- .fold[Either[String, SectionNumber]](s"No valid value in path $key: $value".asLeft)(sn => sn.asRight)
+ .fold[Either[String, SectionNumber]](SectionNumber.Legacy(value).asRight)(sn => sn.asRight)
+ //.fold[Either[String, SectionNumber]](s"No valid value in path $key: $value".asLeft)(sn => sn.asRight)
- private val cyaPat = raw"cya([\d,]+)".r
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, FastForward]] =
params.get(key).flatMap(_.headOption).map {
case FastForward.ffYes => FastForward.Yes.asRight
case FastForward.ffCYAFormSummary => FastForward.CYA(SectionOrSummary.FormSummary).asRight
case FastForward.ffCYATaskSummary => FastForward.CYA(SectionOrSummary.TaskSummary).asRight
- case cyaPat(from) =>
+ case value if value.startsWith("cya:") =>
for {
- sn2 <- toSectionNumber(key, from)
+ sn2 <- toSectionNumber(key, value.drop(4))
} yield FastForward.CYA(SectionOrSummary.Section(sn2))
- case value if value.startsWith("back") =>
- toSectionNumber(key, value.replace("back", "")).map(FastForward.BackUntil)
+ case value if value.startsWith("back:") =>
+ toSectionNumber(key, value.replace("back:", "")).map(FastForward.BackUntil)
case value => toSectionNumber(key, value).map(FastForward.StopAt)
}
diff --git a/app/uk/gov/hmrc/gform/builder/BuilderController.scala b/app/uk/gov/hmrc/gform/builder/BuilderController.scala
index 531643198..4c82cc61b 100644
--- a/app/uk/gov/hmrc/gform/builder/BuilderController.scala
+++ b/app/uk/gov/hmrc/gform/builder/BuilderController.scala
@@ -67,9 +67,6 @@ class BuilderController(
final def apply(html: Html): Json = Json.fromString(html.toString)
}
- private val compatibilityVersion =
- 21 // This is managed manually. Increase it any time API used by builder extension is changed.
-
// Returns section from raw json which correspond to runtime sectionNumber parameter.
def originalSection(
formTemplateId: FormTemplateId,
@@ -89,7 +86,7 @@ class BuilderController(
val bracket = formModel.bracket(sectionNumber)
bracket.fold { nonRepeatingPage =>
- val pageModel = nonRepeatingPage.singleton
+ val pageModel = nonRepeatingPage.singleton.singleton
pageModel.allFormComponentIds match {
case Nil => None
@@ -97,8 +94,9 @@ class BuilderController(
val compName = head.baseComponentId
sectionNumber match {
- case SectionNumber.Classic(value) =>
+ case SectionNumber.Classic.NormalPage(value) =>
originalNonRepeatingPageJson(json.hcursor, compName, Nil)
+
case SectionNumber.TaskList(coordinates, sn) =>
val acursor: ACursor = json.hcursor
.downField("sections")
@@ -113,11 +111,18 @@ class BuilderController(
List(DownArray, DownField("sections"))
originalNonRepeatingPageJson(acursor, compName, historySuffix)
+ case _ => Option.empty[Json]
}
}
}(repeatingPage => Option.empty[Json]) { addToList =>
sectionNumber match {
- case SectionNumber.Classic(value) =>
+ case SectionNumber.Legacy(_) => Option.empty[Json]
+ case SectionNumber.Classic.NormalPage(_) | SectionNumber.Classic.RepeatedPage(_, _) =>
+ Option.empty[Json]
+ case SectionNumber.Classic.AddToListPage.DefaultPage(_) |
+ SectionNumber.Classic.AddToListPage.Page(_, _, _) |
+ SectionNumber.Classic.AddToListPage.CyaPage(_, _) |
+ SectionNumber.Classic.AddToListPage.RepeaterPage(_, _) =>
originalSectionInAddToList(json, addToList, formModel, sectionNumber, Nil)
case SectionNumber.TaskList(coordinates, sn) =>
json.hcursor
@@ -152,8 +157,7 @@ class BuilderController(
_.deepMerge(
Json.obj(
"hiddenComponentIds" := hiddenComponentIds,
- "hiddenChoicesLookup" := hiddenChoiceIndexes(formModelOptics, sectionNumber),
- "version" := compatibilityVersion
+ "hiddenChoicesLookup" := hiddenChoiceIndexes(formModelOptics, sectionNumber)
)
)
)
@@ -202,8 +206,7 @@ class BuilderController(
json
.map(json =>
Json.obj(
- "formTemplate" := json,
- "version" := compatibilityVersion
+ "formTemplate" := json
)
)
.fold(BadRequest(s"Form template not found for $formTemplateId"))(json => Ok(json))
@@ -1064,11 +1067,11 @@ class BuilderController(
val bracket: Bracket[DataExpanded] = formModel.bracket(sectionNumber)
bracket match {
- case Bracket.NonRepeatingPage(_, _, _) =>
+ case Bracket.NonRepeatingPage(_, _) =>
badRequest("Expected AddToList bracket, but got NonRepeatingPage").pure[Future]
case Bracket.RepeatingPage(_, _) =>
badRequest("Expected AddToList bracket, but got RepeatingPage").pure[Future]
- case bracket @ Bracket.AddToList(_, _) =>
+ case bracket @ Bracket.AddToList(_, _, _) =>
pageModel
.fold(singleton => badRequest("Invalid page model. Expected Repeater got Singleton"))(checkYourAnswers =>
badRequest("Invalid page model. Expected Repeater got CheckYourAnswers")
diff --git a/app/uk/gov/hmrc/gform/controllers/Navigation.scala b/app/uk/gov/hmrc/gform/controllers/Navigation.scala
index a82f1f7ba..b7b22faea 100644
--- a/app/uk/gov/hmrc/gform/controllers/Navigation.scala
+++ b/app/uk/gov/hmrc/gform/controllers/Navigation.scala
@@ -17,7 +17,7 @@
package uk.gov.hmrc.gform.controllers
import cats.syntax.eq._
-import uk.gov.hmrc.gform.models.{ Bracket, Visibility }
+import uk.gov.hmrc.gform.models.Visibility
import uk.gov.hmrc.gform.sharedmodel.formtemplate._
import uk.gov.hmrc.gform.models.FormModel
@@ -28,23 +28,24 @@ trait Navigation {
val availableSectionNumbers: List[SectionNumber] =
formModel.availableSectionNumbers
- val minSectionNumber: SectionNumber = availableSectionNumbers.min(Ordering.by((_: SectionNumber).numberValue))
+ val minSectionNumber: SectionNumber = availableSectionNumbers.min
- val addToListBrackets: List[Bracket.AddToList[Visibility]] =
- formModel.brackets.addToListBrackets
+ val addToListSectionNumbers: List[SectionNumber] = availableSectionNumbers.filter(_.isAddToList)
- val addToListSectionNumbers: List[SectionNumber] =
- addToListBrackets.flatMap(_.toPageModelWithNumber.toList).map(_._2)
-
- val addToListRepeaterSectionNumbers: List[SectionNumber] =
- addToListBrackets.flatMap(_.iterations.toList).map(_.repeater.sectionNumber)
+ val addToListRepeaterSectionNumbers: List[SectionNumber] = availableSectionNumbers.filter(_.isAddToListRepeaterPage)
val addToListNonRepeaterSectionNumbers: List[SectionNumber] =
addToListSectionNumbers.filterNot(addToListRepeaterSectionNumbers.toSet)
val samePageRepeatersSectionNumbers: List[List[SectionNumber]] =
- formModel.brackets.addToListBrackets
- .map(_.iterations.toList.map(_.repeater.sectionNumber))
+ addToListRepeaterSectionNumbers
+ .groupBy(
+ _.fold(a => (Coordinates(TaskSectionNumber(0), TaskNumber(0)), a.sectionIndex))(a =>
+ (a.coordinates, a.sectionNumber.sectionIndex)
+ )
+ )
+ .values
+ .toList
val filteredSectionNumbers: SectionNumber => List[SectionNumber] = sectionNumber => {
val availableSn = availableSectionNumbers.filter(
@@ -70,32 +71,21 @@ case class Navigator(
sectionNumber: SectionNumber,
formModel: FormModel[Visibility]
) extends Navigation {
- private val maxSectionNumber: SectionNumber =
- availableSectionNumbers.max(Ordering.by((_: SectionNumber).numberValue))
val previousSectionNumber: Option[SectionNumber] =
filteredSectionNumbers(sectionNumber).findLast(_ < sectionNumber)
val nextSectionNumber: SectionNumber = {
- val sn = sectionNumber.increment
+ val sn = sectionNumber.increment(formModel)
if (addToListSectionNumbers.contains(sectionNumber)) {
sn
} else {
- addToListRepeaterSectionNumbers
+ val repeaters = addToListRepeaterSectionNumbers
.filter(_.fold(_ => true)(taskList => taskList.coordinates === sn.toCoordinatesUnsafe))
.find(_ >= sn)
- .fold(sn) { nrsn =>
- filteredSectionNumbers(nrsn).filter(_ < nrsn).find(_ >= sn).getOrElse(nrsn)
- }
+ repeaters.fold(sn) { nrsn =>
+ filteredSectionNumbers(nrsn).filter(_ < nrsn).find(_ >= sn).getOrElse(nrsn)
+ }
}
}
-
- require(
- sectionNumber >= minSectionNumber,
- s"section number is too low: $sectionNumber is not >= $minSectionNumber"
- )
- require(
- sectionNumber <= maxSectionNumber,
- s"section number is too big: $sectionNumber is not <= $maxSectionNumber"
- )
}
diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala
index 071486d28..8c421ad23 100644
--- a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala
+++ b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala
@@ -16,7 +16,7 @@
package uk.gov.hmrc.gform.eval
-import uk.gov.hmrc.gform.models.{ BracketPlain, PageMode, Repeater, Singleton }
+import uk.gov.hmrc.gform.models.{ Bracket, PageMode, Repeater, SingletonWithNumber }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.Expr
/*
@@ -24,10 +24,10 @@ import uk.gov.hmrc.gform.sharedmodel.formtemplate.Expr
* This doesn't include expressions in fields
*/
object AllPageModelExpressions extends ExprExtractorHelpers {
- def unapply[A <: PageMode](bracket: BracketPlain[A]): Option[List[ExprMetadata]] = {
+ def unapply[A <: PageMode](bracket: Bracket[A]): Option[List[ExprMetadata]] = {
- def fromSingleton(singleton: Singleton[_]): List[Expr] = {
- val page = singleton.page
+ def fromSingleton(singleton: SingletonWithNumber[_]): List[Expr] = {
+ val page = singleton.singleton.page
val pageExprs = page.title.allInterpolations ++ fromOption(
page.description,
page.shortName,
@@ -51,13 +51,13 @@ object AllPageModelExpressions extends ExprExtractorHelpers {
repeater.expandedSummaryName
)
- def fromNonRepeatingBracket(bracket: BracketPlain.NonRepeatingPage[A]): List[Expr] =
+ def fromNonRepeatingBracket(bracket: Bracket.NonRepeatingPage[A]): List[Expr] =
fromSingleton(bracket.singleton)
- def fromRepeatedBracket(bracket: BracketPlain.RepeatingPage[A]): List[Expr] =
+ def fromRepeatedBracket(bracket: Bracket.RepeatingPage[A]): List[Expr] =
bracket.source.repeats :: bracket.singletons.toList.flatMap(fromSingleton)
- def fromAddToListBracket(bracket: BracketPlain.AddToList[A]): List[Expr] =
+ def fromAddToListBracket(bracket: Bracket.AddToList[A]): List[Expr] =
fromSmartStrings(
bracket.source.summaryName,
bracket.source.addAnotherQuestion.label,
@@ -66,7 +66,7 @@ object AllPageModelExpressions extends ExprExtractorHelpers {
bracket.source.infoMessage,
bracket.source.addAnotherQuestion.helpText
) ++ bracket.iterations.toList.flatMap { iteration =>
- iteration.singletons.toList.flatMap(fromSingleton) ::: fromRepeater(iteration.repeater)
+ iteration.singletons.toList.flatMap(fromSingleton) ::: fromRepeater(iteration.repeater.repeater)
}
val pageExprs: List[Expr] = bracket.fold(fromNonRepeatingBracket)(fromRepeatedBracket)(fromAddToListBracket)
diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala
index 16da073d8..d81e634ce 100644
--- a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala
+++ b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala
@@ -16,7 +16,7 @@
package uk.gov.hmrc.gform.eval
-import uk.gov.hmrc.gform.models.{ BracketPlain, DataExpanded, FormModel, PageMode, Singleton }
+import uk.gov.hmrc.gform.models.{ Bracket, DataExpanded, FormModel, PageMode, Singleton }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ BooleanExpr, Expr }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.CheckYourAnswersPage
@@ -24,13 +24,13 @@ object AllPageModelExpressionsGetter extends ExprExtractorHelpers {
/*
* Returns list of every single expression in a bracket
*/
- def allExprs[A <: PageMode](formModel: FormModel[DataExpanded])(bracketPlain: BracketPlain[A]): List[Expr] = {
+ def allExprs[A <: PageMode](formModel: FormModel[DataExpanded])(bracket: Bracket[A]): List[Expr] = {
val bracketExprs =
- bracketPlain match {
+ bracket match {
case AllPageModelExpressions(exprMetadatas) => exprMetadatas.map(_.expr)
case otherwise => List.empty[Expr]
}
- bracketExprs ++ formComponentsExprs(formModel)(bracketPlain)
+ bracketExprs ++ formComponentsExprs(formModel)(bracket)
}
@@ -78,16 +78,17 @@ object AllPageModelExpressionsGetter extends ExprExtractorHelpers {
private def formComponentsExprs[A <: PageMode](
formModel: FormModel[DataExpanded]
- )(bracketPlain: BracketPlain[A]): List[Expr] =
- bracketPlain.fold { nonRepeatingPage =>
- fromSingleton(formModel)(nonRepeatingPage.singleton)
+ )(bracket: Bracket[A]): List[Expr] =
+ bracket.fold { nonRepeatingPage =>
+ fromSingleton(formModel)(nonRepeatingPage.singleton.singleton)
} { repeatingPage =>
- repeatingPage.singletons.toList.flatMap(fromSingleton(formModel))
+ repeatingPage.singletons.map(_.singleton).toList.flatMap(fromSingleton(formModel))
} { addToList =>
- addToList.iterations.toList.flatMap { iteration =>
- iteration.singletons.toList.flatMap(fromSingleton(formModel)) ++
- addToList.source.cyaPage.map(fromCheckYourAnswerPage).getOrElse(Nil)
- }
+ addToList.defaultPage.map(_.singleton).toList.flatMap(fromSingleton(formModel)) ++
+ addToList.iterations.toList.flatMap { iteration =>
+ iteration.singletons.map(_.singleton).toList.flatMap(fromSingleton(formModel)) ++
+ addToList.source.cyaPage.map(fromCheckYourAnswerPage).getOrElse(Nil)
+ }
}
}
diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala
index 7a8bd54f4..4b79838af 100644
--- a/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala
+++ b/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala
@@ -16,11 +16,11 @@
package uk.gov.hmrc.gform.eval
-import uk.gov.hmrc.gform.models.{ BracketPlain, PageMode }
+import uk.gov.hmrc.gform.models.{ Bracket, PageMode }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.Sum
object AllPageModelSums {
- def unapply[A <: PageMode](bracket: BracketPlain[A]): Option[Set[Sum]] = bracket match {
+ def unapply[A <: PageMode](bracket: Bracket[A]): Option[Set[Sum]] = bracket match {
case AllPageModelExpressions(exprMetadatas) => Some(exprMetadatas.flatMap(_.expr.sums).toSet).filter(_.nonEmpty)
case _ => None
}
diff --git a/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala b/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala
index 098307a67..67ddd7755 100644
--- a/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala
+++ b/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala
@@ -17,7 +17,7 @@
package uk.gov.hmrc.gform.eval
import cats.syntax.eq._
-import uk.gov.hmrc.gform.models.{ BracketPlainCoordinated, PageMode, TaskModelCoordinated }
+import uk.gov.hmrc.gform.models.{ BracketPlainCoordinated, PageMode, TaskModel }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FormComponentId, FormCtx, Sum }
/** This represents \${abc.sum} expression, where this expression
@@ -41,13 +41,13 @@ object StandaloneSumInfo {
val allSums: List[Set[Sum]] =
brackets.fold { classic =>
- classic.bracketPlains.collect { case AllPageModelSums(sums) =>
+ classic.brackets.collect { case AllPageModelSums(sums) =>
sums
}
} { taskList =>
- taskList.bracketPlains
+ taskList.coordinatedBrackets
.map(_._2)
- .collect { case TaskModelCoordinated.Editable(brackets) => brackets.toList }
+ .collect { case TaskModel.Editable(brackets) => brackets.toList }
.flatten
.collect { case AllPageModelSums(sums) => sums }
}
diff --git a/app/uk/gov/hmrc/gform/gform/DownloadController.scala b/app/uk/gov/hmrc/gform/gform/DownloadController.scala
index f33e25d41..c58941fc0 100644
--- a/app/uk/gov/hmrc/gform/gform/DownloadController.scala
+++ b/app/uk/gov/hmrc/gform/gform/DownloadController.scala
@@ -52,7 +52,7 @@ class DownloadController(
OperationWithForm.DownloadFileByInternalLink
) { _ => _ => _ => _ => formModelOptics =>
val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val allExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel))
+ val allExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel))
val extension = fileName.substring(fileName.lastIndexOf('.') + 1)
if (!allExprs.contains(LinkCtx(InternalLink.Download(fileName)))) {
diff --git a/app/uk/gov/hmrc/gform/gform/FastForwardService.scala b/app/uk/gov/hmrc/gform/gform/FastForwardService.scala
index b184dba4b..ce5b9ab5c 100644
--- a/app/uk/gov/hmrc/gform/gform/FastForwardService.scala
+++ b/app/uk/gov/hmrc/gform/gform/FastForwardService.scala
@@ -160,7 +160,7 @@ class FastForwardService(
)
)
case _ =>
- val maybeCoordinates = maybeSectionNumber.flatMap(_.toCoordinates)
+ val maybeCoordinates = maybeSectionNumber.flatMap(_.maybeCoordinates)
Redirect(
routes.SummaryController
.summaryById(cache.formTemplate._id, maybeAccessCode, maybeCoordinates, None, ff = fastForward.headOption)
diff --git a/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala b/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala
index 660645702..68f89cf8f 100644
--- a/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala
+++ b/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala
@@ -87,7 +87,7 @@ class FormAddToListController(
.confirmRemoval(formTemplateId, maybeAccessCode, sectionNumber, index, addToListId)
val maybeBracket = formModel.bracket(sectionNumber)
maybeBracket match {
- case bracket @ Bracket.AddToList(iterations, source) =>
+ case bracket @ Bracket.AddToList(defaultPage, iterations, source) =>
val (pageError, fieldErrors) = {
val errorMessage =
source.errorMessage.fold(request.messages.messages("addToList.error.selectOption"))(error =>
diff --git a/app/uk/gov/hmrc/gform/gform/FormController.scala b/app/uk/gov/hmrc/gform/gform/FormController.scala
index 71d8c48c0..c44016669 100644
--- a/app/uk/gov/hmrc/gform/gform/FormController.scala
+++ b/app/uk/gov/hmrc/gform/gform/FormController.scala
@@ -147,7 +147,7 @@ class FormController(
val bracket = formModel.bracket(sectionNumber)
bracket match {
- case Bracket.NonRepeatingPage(singleton, sectionNumber, _) =>
+ case Bracket.NonRepeatingPage(SingletonWithNumber(singleton, sectionNumber), _) =>
val formModel = formModelOptics.formModelVisibilityOptics.formModel
// section numbers with form components that other components may refer to
val sns = formModel.pageModelLookup
@@ -196,7 +196,12 @@ class FormController(
validateSections(suppressErrors, sectionNumber)(
renderSingleton(bracket.singletonForSectionNumber(sectionNumber), sectionNumber, _)
)
- case bracket @ Bracket.AddToList(iterations, _) =>
+ case bracket @ Bracket.AddToList(Some(defaultPage), _, _)
+ if defaultPage.sectionNumber === sectionNumber =>
+ validateSections(suppressErrors, sectionNumber)(
+ renderSingleton(defaultPage.singleton, sectionNumber, _)
+ )
+ case bracket @ Bracket.AddToList(_, iterations, _) =>
val iteration: Bracket.AddToListIteration[DataExpanded] =
bracket.iterationForSectionNumber(sectionNumber)
val (repeater, repeaterSectionNumber) =
@@ -338,188 +343,195 @@ class FormController(
) =
auth.authAndRetrieveForm[SectionSelectorType.Normal](formTemplateId, maybeAccessCode, OperationWithForm.EditForm) {
implicit request => implicit lang => cache => implicit sse => formModelOptics =>
- val formVisibilityModel = formModelOptics.formModelVisibilityOptics.formModel
- val fastForward = filterTerminatedFastForward(sectionNumber, rawFastForward, formVisibilityModel)
- val navigator = Navigator(sectionNumber, formModelOptics.formModelVisibilityOptics.formModel)
-
- def callSelector(call1: => Call, call2: => Call, lastSectionNumber: Option[SectionNumber]): Future[Call] =
- for {
- invalidSectionNumber <-
- fastForwardService.maybeInvalidSectionNumber(lastSectionNumber, cache, formModelOptics)
- } yield if (invalidSectionNumber.isEmpty) call1 else call2
-
- def goBack(toSectionNumber: SectionNumber) = {
- val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val sectionTitle4Ga = sectionTitle4GaFactory(formModel(toSectionNumber), sectionNumber)
-
- def createBackUrl(sectionNumber: SectionNumber, fastForward: List[FastForward]) =
- routes.FormController
- .form(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber,
- sectionTitle4Ga,
- SuppressErrors.Yes,
- fastForward
- )
+ sectionNumber match {
+ case SectionNumber.Legacy(_) =>
+ // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost
+ redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode)
+ case _ =>
+ val formVisibilityModel = formModelOptics.formModelVisibilityOptics.formModel
+ val fastForward = filterTerminatedFastForward(sectionNumber, rawFastForward, formVisibilityModel)
+ val navigator = Navigator(sectionNumber, formModelOptics.formModelVisibilityOptics.formModel)
- def backCallF() = {
- val firstCYA = fastForward.find {
- case _: FastForward.CYA => true
- case _ => false
- }
- firstCYA match {
- case ff @ Some(FastForward.CYA(SectionOrSummary.FormSummary)) =>
- fastForward.last match {
- case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 =>
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber.toCoordinates,
- None,
- ff = ff
- )
- .pure[Future]
- case _ =>
- callSelector(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber.toCoordinates,
- None,
- ff = ff
- ),
- createBackUrl(toSectionNumber, fastForward),
- None
- )
- }
- case Some(FastForward.CYA(SectionOrSummary.Section(to))) =>
- fastForward.last match {
- case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 =>
- createBackUrl(to, fastForward).pure[Future]
- case _ =>
- callSelector(
- createBackUrl(to, fastForward),
- createBackUrl(toSectionNumber, fastForward),
- Some(to)
- )
- }
- case Some(FastForward.StopAt(sn)) =>
- sectionNumber.fold { classic =>
- createBackUrl(toSectionNumber, FastForward.StopAt(sectionNumber) :: fastForward).pure[Future]
- } { taskList =>
- val maybePreviousPage = navigator.previousSectionNumber
- if (maybePreviousPage.isEmpty) {
- uk.gov.hmrc.gform.tasklist.routes.TaskListController
- .landingPage(cache.formTemplateId, maybeAccessCode)
- .pure[Future]
- } else {
- createBackUrl(toSectionNumber, fastForward).pure[Future]
- }
+ def callSelector(call1: => Call, call2: => Call, lastSectionNumber: Option[SectionNumber]): Future[Call] =
+ for {
+ invalidSectionNumber <-
+ fastForwardService.maybeInvalidSectionNumber(lastSectionNumber, cache, formModelOptics)
+ } yield if (invalidSectionNumber.isEmpty) call1 else call2
+ def goBack(toSectionNumber: SectionNumber) = {
+ val formModel = formModelOptics.formModelRenderPageOptics.formModel
+ val sectionTitle4Ga = sectionTitle4GaFactory(formModel(toSectionNumber), sectionNumber)
+
+ def createBackUrl(sectionNumber: SectionNumber, fastForward: List[FastForward]) =
+ routes.FormController
+ .form(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber,
+ sectionTitle4Ga,
+ SuppressErrors.Yes,
+ fastForward
+ )
+
+ def backCallF() = {
+ val firstCYA = fastForward.find {
+ case _: FastForward.CYA => true
+ case _ => false
}
- case _ =>
- sectionNumber.fold { classic =>
- createBackUrl(toSectionNumber, fastForward).pure[Future]
- } { taskList =>
- fastForward match {
- case FastForward.CYA(SectionOrSummary.TaskSummary) :: xs =>
- xs.lastOption match {
- case Some(FastForward.BackUntil(sn)) if sectionNumber.compare(sn) <= 0 =>
- Future.successful(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber.toCoordinates,
- None,
- ff = fastForward.headOption
- )
+ firstCYA match {
+ case ff @ Some(FastForward.CYA(SectionOrSummary.FormSummary)) =>
+ fastForward.last match {
+ case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 =>
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None,
+ ff = ff
)
- case _ =>
- navigator.previousSectionNumber.fold(
- Future.successful(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber.toCoordinates,
- None,
- ff = fastForward.headOption
- )
- )
- )(previousSectionNumber => createBackUrl(previousSectionNumber, fastForward).pure[Future])
- }
- case _ =>
- val maybePreviousPage = navigator.previousSectionNumber
-
- if (maybePreviousPage.isEmpty) {
+ .pure[Future]
+ case _ =>
callSelector(
- uk.gov.hmrc.gform.tasklist.routes.TaskListController
- .landingPage(cache.formTemplateId, maybeAccessCode),
- uk.gov.hmrc.gform.tasklist.routes.TaskListController
- .landingPage(cache.formTemplateId, maybeAccessCode),
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None,
+ ff = ff
+ ),
+ createBackUrl(toSectionNumber, fastForward),
None
)
+ }
+ case Some(FastForward.CYA(SectionOrSummary.Section(to))) =>
+ fastForward.last match {
+ case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 =>
+ createBackUrl(to, fastForward).pure[Future]
+ case _ =>
+ callSelector(
+ createBackUrl(to, fastForward),
+ createBackUrl(toSectionNumber, fastForward),
+ Some(to)
+ )
+ }
+ case Some(FastForward.StopAt(sn)) =>
+ sectionNumber.fold { classic =>
+ createBackUrl(toSectionNumber, FastForward.StopAt(sectionNumber) :: fastForward).pure[Future]
+ } { taskList =>
+ val maybePreviousPage = navigator.previousSectionNumber
+ if (maybePreviousPage.isEmpty) {
+ uk.gov.hmrc.gform.tasklist.routes.TaskListController
+ .landingPage(cache.formTemplateId, maybeAccessCode)
+ .pure[Future]
} else {
createBackUrl(toSectionNumber, fastForward).pure[Future]
}
- }
+
+ }
+ case _ =>
+ sectionNumber.fold { classic =>
+ createBackUrl(toSectionNumber, fastForward).pure[Future]
+ } { taskList =>
+ fastForward match {
+ case FastForward.CYA(SectionOrSummary.TaskSummary) :: xs =>
+ xs.lastOption match {
+ case Some(FastForward.BackUntil(sn)) if sectionNumber.compare(sn) <= 0 =>
+ Future.successful(
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None,
+ ff = fastForward.headOption
+ )
+ )
+ case _ =>
+ navigator.previousSectionNumber.fold(
+ Future.successful(
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None,
+ ff = fastForward.headOption
+ )
+ )
+ )(previousSectionNumber => createBackUrl(previousSectionNumber, fastForward).pure[Future])
+ }
+ case _ =>
+ val maybePreviousPage = navigator.previousSectionNumber
+
+ if (maybePreviousPage.isEmpty) {
+ callSelector(
+ uk.gov.hmrc.gform.tasklist.routes.TaskListController
+ .landingPage(cache.formTemplateId, maybeAccessCode),
+ uk.gov.hmrc.gform.tasklist.routes.TaskListController
+ .landingPage(cache.formTemplateId, maybeAccessCode),
+ None
+ )
+ } else {
+ createBackUrl(toSectionNumber, fastForward).pure[Future]
+ }
+ }
+ }
}
+ }
+ backCallF().map(Redirect(_))
}
- }
- backCallF().map(Redirect(_))
- }
- val toSectionNumber = navigator.previousSectionNumber.getOrElse(sectionNumber)
- val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val bracket = formModel.bracket(toSectionNumber)
-
- bracket match {
- case Bracket.NonRepeatingPage(_, _, _) => goBack(toSectionNumber)
- case Bracket.RepeatingPage(_, _) => goBack(toSectionNumber)
- case bracket @ Bracket.AddToList(iterations, _) =>
- val iteration: Bracket.AddToListIteration[DataExpanded] =
- bracket.iterationForSectionNumber(toSectionNumber)
- val lastIteration: Bracket.AddToListIteration[DataExpanded] = iterations.last
- if (
- iteration.repeater.sectionNumber === toSectionNumber && iteration.repeater.sectionNumber < lastIteration.repeater.sectionNumber
- ) {
- val isCommited =
- formModelOptics.formModelVisibilityOptics.formModel.bracket(sectionNumber).withAddToListBracket {
- addToListBracket =>
- addToListBracket.iterationForSectionNumber(sectionNumber).isCommited(cache.form.visitsIndex)
+ val toSectionNumber = navigator.previousSectionNumber.getOrElse(sectionNumber)
+ val formModel = formModelOptics.formModelRenderPageOptics.formModel
+ val bracket = formModel.bracket(toSectionNumber)
+
+ bracket match {
+ case Bracket.NonRepeatingPage(_, _) => goBack(toSectionNumber)
+ case Bracket.RepeatingPage(_, _) => goBack(toSectionNumber)
+ case bracket @ Bracket.AddToList(defaultPage, iterations, _) =>
+ val iteration: Bracket.AddToListIteration[DataExpanded] =
+ bracket.iterationForSectionNumber(toSectionNumber)
+ val lastIteration: Bracket.AddToListIteration[DataExpanded] = iterations.last
+ if (
+ iteration.repeater.sectionNumber === toSectionNumber && iteration.repeater.sectionNumber < lastIteration.repeater.sectionNumber
+ ) {
+ val isCommited =
+ formModelOptics.formModelVisibilityOptics.formModel.bracket(sectionNumber).withAddToListBracket {
+ addToListBracket =>
+ addToListBracket.iterationForSectionNumber(sectionNumber).isCommited(cache.form.visitsIndex)
+ }
+ if (isCommited) {
+ goBack(toSectionNumber)
+ } else {
+ for {
+ processData <- processDataService
+ .getProcessData[SectionSelectorType.Normal](
+ formModelOptics.formModelRenderPageOptics.recData.variadicFormData
+ .asInstanceOf[VariadicFormData[OutOfDate]],
+ cache,
+ formModelOptics,
+ gformConnector.getAllTaxPeriods,
+ NoSpecificAction
+ )
+ redirect <- formProcessor.processRemoveAddToList(
+ cache,
+ maybeAccessCode,
+ List(FastForward.Yes),
+ formModelOptics,
+ processData,
+ bracket.iterations.size - 1,
+ bracket.source.id
+ )
+ } yield redirect
+ }
+ } else {
+ goBack(toSectionNumber)
}
- if (isCommited) {
- goBack(toSectionNumber)
- } else {
- for {
- processData <- processDataService
- .getProcessData[SectionSelectorType.Normal](
- formModelOptics.formModelRenderPageOptics.recData.variadicFormData
- .asInstanceOf[VariadicFormData[OutOfDate]],
- cache,
- formModelOptics,
- gformConnector.getAllTaxPeriods,
- NoSpecificAction
- )
- redirect <- formProcessor.processRemoveAddToList(
- cache,
- maybeAccessCode,
- List(FastForward.Yes),
- formModelOptics,
- processData,
- bracket.iterations.size - 1,
- bracket.source.id
- )
- } yield redirect
- }
- } else {
- goBack(toSectionNumber)
}
}
+
}
def addToListAction(
@@ -531,84 +543,102 @@ class FormController(
) =
auth.authAndRetrieveForm[SectionSelectorType.Normal](formTemplateId, maybeAccessCode, OperationWithForm.EditForm) {
implicit request => implicit l => cache => _ => formModelOptics =>
- val fastForward = removeDuplications(rawFastForward)
- def processEditAddToList(processData: ProcessData, idx: Int, addToListId: AddToListId): Future[Result] = {
-
- val addToListIteration = processData.formModel.brackets.addToListById(addToListId, idx)
-
- def defaultNavigation(): (SectionNumber, List[FastForward]) = {
- val firstAddToListPageSN: SectionNumber = processData.formModel.brackets
- .addToListBracket(addToListId)
- .source
- .defaultPage
- .fold(addToListIteration.firstSectionNumber) { _ =>
- addToListIteration.secondSectionNumber
+ sectionNumber match {
+ case SectionNumber.Legacy(_) =>
+ // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost
+ redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode)
+ case _ =>
+ val fastForward = removeDuplications(rawFastForward)
+ def processEditAddToList(processData: ProcessData, idx: Int, addToListId: AddToListId): Future[Result] = {
+
+ val addToListIteration =
+ processData.formModelOptics.formModelVisibilityOptics.formModel.brackets.addToListById(addToListId, idx)
+
+ def defaultNavigation(): (SectionNumber, List[FastForward]) = {
+ val sectionNumber = addToListIteration.firstSectionNumber
+ (sectionNumber, fastForward)
}
- val availableSectionNumbers =
- processData.formModelOptics.formModelVisibilityOptics.formModel.brackets
- .addToListById(addToListId, idx)
- .allSingletonSectionNumbers
+ def checkYourAnswersNavigation(
+ cya: CheckYourAnswersWithNumber[Visibility]
+ ): (SectionNumber, List[FastForward]) =
+ (cya.sectionNumber, FastForward.CYA(SectionOrSummary.Section(sectionNumber)) :: fastForward)
- val sectionNumber: SectionNumber = availableSectionNumbers.find(_ >= firstAddToListPageSN).head
+ val (gotoSectionNumber, ff) =
+ addToListIteration.checkYourAnswers.fold(defaultNavigation())(checkYourAnswersNavigation)
- (sectionNumber, fastForward)
- }
- def checkYourAnswersNavigation(
- cya: CheckYourAnswersWithNumber[DataExpanded]
- ): (SectionNumber, List[FastForward]) =
- (cya.sectionNumber, FastForward.CYA(SectionOrSummary.Section(sectionNumber)) :: fastForward)
- val (gotoSectionNumber, ff) =
- addToListIteration.checkYourAnswers.fold(defaultNavigation())(checkYourAnswersNavigation)
- val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
- Redirect(
- routes.FormController
- .form(
- cache.formTemplateId,
- maybeAccessCode,
- gotoSectionNumber,
- sectionTitle4Ga,
- SuppressErrors.Yes,
- ff
- )
- .url
- ).pure[Future]
+ val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
+
+ Redirect(
+ routes.FormController
+ .form(
+ cache.formTemplateId,
+ maybeAccessCode,
+ gotoSectionNumber,
+ sectionTitle4Ga,
+ SuppressErrors.Yes,
+ ff
+ )
+ .url
+ ).pure[Future]
+ }
+
+ for {
+ processData <- processDataService
+ .getProcessData[SectionSelectorType.Normal](
+ formModelOptics.formModelVisibilityOptics.recData.variadicFormData
+ .asInstanceOf[VariadicFormData[OutOfDate]],
+ cache,
+ formModelOptics,
+ gformConnector.getAllTaxPeriods,
+ NoSpecificAction
+ )
+ res <- direction match {
+ case EditAddToList(idx, addToListId) => processEditAddToList(processData, idx, addToListId)
+ case SaveAndContinue | SaveAndExit =>
+ // This request should have been a POST, with user data. However we have sporadically seen GET requests sent instead of POST to this endpoint
+ // the cause of which is not known yet. We redirect the user the page he/she is currently on, instead of throwing an error page
+ // e.g: GET /submissions/form/XXXX/-/0?ff=t&action=SaveAndContinue
+ logger.warn(s"Received GET request with direction $direction. Doing a redirect!")
+ val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
+ Redirect(
+ routes.FormController
+ .form(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber,
+ sectionTitle4Ga,
+ SuppressErrors.Yes,
+ fastForward
+ )
+ ).pure[Future]
+ case _ => throw new IllegalArgumentException(s"Direction $direction is not supported here")
+ }
+ } yield res
}
- for {
- processData <- processDataService
- .getProcessData[SectionSelectorType.Normal](
- formModelOptics.formModelVisibilityOptics.recData.variadicFormData
- .asInstanceOf[VariadicFormData[OutOfDate]],
- cache,
- formModelOptics,
- gformConnector.getAllTaxPeriods,
- NoSpecificAction
- )
- res <- direction match {
- case EditAddToList(idx, addToListId) => processEditAddToList(processData, idx, addToListId)
- case SaveAndContinue | SaveAndExit =>
- // This request should have been a POST, with user data. However we have sporadically seen GET requests sent instead of POST to this endpoint
- // the cause of which is not known yet. We redirect the user the page he/she is currently on, instead of throwing an error page
- // e.g: GET /submissions/form/XXXX/-/0?ff=t&action=SaveAndContinue
- logger.warn(s"Received GET request with direction $direction. Doing a redirect!")
- val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
- Redirect(
- routes.FormController
- .form(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber,
- sectionTitle4Ga,
- SuppressErrors.Yes,
- fastForward
- )
- ).pure[Future]
- case _ => throw new IllegalArgumentException(s"Direction $direction is not supported here")
- }
- } yield res
}
+ private def redirectOnLegacy(
+ formModelOptics: FormModelOptics[DataOrigin.Mongo],
+ formTemplateId: FormTemplateId,
+ maybeAccessCode: Option[AccessCode]
+ ) = {
+ val sn =
+ formModelOptics.formModelVisibilityOptics.formModel.availableSectionNumbers.head // In production this should always return some section
+ Redirect(
+ routes.FormController
+ .form(
+ formTemplateId,
+ maybeAccessCode,
+ sn,
+ SectionTitle4Ga("welcome"),
+ SuppressErrors.Yes,
+ List(FastForward.Yes)
+ )
+ ).pure[Future]
+ }
+
def updateFormData(
formTemplateId: FormTemplateId,
maybeAccessCode: Option[AccessCode],
@@ -620,295 +650,318 @@ class FormController(
implicit request => implicit l => cache => implicit sse => formModelOptics =>
val formModel = formModelOptics.formModelVisibilityOptics.formModel
val fastForward = filterFastForward(browserSectionNumber, rawFastForward, formModel)
- processResponseDataFromBody(request, formModelOptics.formModelRenderPageOptics, Some(browserSectionNumber)) {
- requestRelatedData => variadicFormData => enteredVariadicFormData =>
- val sectionNumber: SectionNumber =
- formModelOptics.formModelVisibilityOptics.formModel.visibleSectionNumber(browserSectionNumber)
+ browserSectionNumber match {
+ case SectionNumber.Legacy(_) =>
+ // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost
+ redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode)
+ case _ =>
+ processResponseDataFromBody(
+ request,
+ formModelOptics.formModelRenderPageOptics,
+ Some(browserSectionNumber)
+ ) { requestRelatedData => variadicFormData => enteredVariadicFormData =>
+ val sectionNumber: SectionNumber =
+ formModelOptics.formModelVisibilityOptics.formModel.visibleSectionNumber(browserSectionNumber)
- def processSaveAndContinue(
- processData: ProcessData
- ): Future[Result] =
- confirmationService.processConfirmation(
- sectionNumber,
- processData,
- cache.formTemplateId,
- maybeAccessCode,
- formModelOptics,
- fastForward
- ) match {
- case ConfirmationAction.NotConfirmed(redirect) => redirect.pure[Future]
- case ConfirmationAction.UpdateConfirmation(processDataUpdater, isConfirmationPage) =>
- val processDataUpd = processDataUpdater(processData)
- formProcessor.validateAndUpdateData(
- cache,
- processDataUpd,
- sectionNumber,
- sectionNumber,
- maybeAccessCode,
- fastForward,
- formModelOptics,
- enteredVariadicFormData,
- true
- ) { updatePostcodeLookup => maybeRedirectUrl => maybeSn =>
- def processRemoveItemIf() = {
- val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val bracket = formModel.bracket(sectionNumber)
-
- bracket match {
- case bracket @ Bracket.AddToList(_, _) =>
- val iteration = bracket.iterationForSectionNumber(sectionNumber)
- val sectionNumbers = iteration.allSingletonSectionNumbers
-
- sectionNumbers.flatMap { sectionNumber =>
- maybeAtlRemoveIteration(bracket, sectionNumber)
- }.headOption
- case _ => None
+ def processSaveAndContinue(
+ processData: ProcessData
+ ): Future[Result] =
+ confirmationService.processConfirmation(
+ sectionNumber,
+ processData,
+ cache.formTemplateId,
+ maybeAccessCode,
+ formModelOptics,
+ fastForward
+ ) match {
+ case ConfirmationAction.NotConfirmed(redirect) => redirect.pure[Future]
+ case ConfirmationAction.UpdateConfirmation(processDataUpdater, isConfirmationPage) =>
+ val processDataUpd = processDataUpdater(processData)
+ formProcessor.validateAndUpdateData(
+ cache,
+ processDataUpd,
+ sectionNumber,
+ sectionNumber,
+ maybeAccessCode,
+ fastForward,
+ formModelOptics,
+ enteredVariadicFormData,
+ true
+ ) { updatePostcodeLookup => maybeRedirectUrl => maybeSn =>
+ def processRemoveItemIf() = {
+ val formModel = formModelOptics.formModelRenderPageOptics.formModel
+ val bracket = formModel.bracket(sectionNumber)
+
+ bracket match {
+ case bracket @ Bracket.AddToList(Some(defaultPage), _, _)
+ if defaultPage.sectionNumber === sectionNumber =>
+ None
+ case bracket @ Bracket.AddToList(_, _, _) =>
+ val iteration = bracket.iterationForSectionNumber(sectionNumber)
+ val sectionNumbers = iteration.allSingletonSectionNumbers
+
+ sectionNumbers.flatMap { sectionNumber =>
+ maybeAtlRemoveIteration(bracket, sectionNumber)
+ }.headOption
+ case _ => None
+ }
}
- }
- def maybeAtlRemoveIteration(bracket: Bracket[DataExpanded], sectionNumber: SectionNumber) =
- bracket
- .atlIterationToRemove(
- sectionNumber,
- processDataUpd.formModelOptics.formModelVisibilityOptics
- )
- .map { case (atlId, index) =>
- Redirect(
- routes.FormAddToListController
- .removeItem(
- formTemplateId,
- maybeAccessCode,
+ def maybeAtlRemoveIteration(
+ bracket: Bracket[DataExpanded],
+ sectionNumber: SectionNumber
+ ): Option[Result] =
+ sectionNumber match {
+ case SectionNumber.Classic.AddToListPage.Page(_, _, _) =>
+ bracket
+ .atlIterationToRemove(
sectionNumber,
- index,
- atlId
+ processDataUpd.formModelOptics.formModelVisibilityOptics
)
- )
+ .map { case (atlId, index) =>
+ Redirect(
+ routes.FormAddToListController
+ .removeItem(
+ formTemplateId,
+ maybeAccessCode,
+ sectionNumber,
+ index,
+ atlId
+ )
+ )
+ }
+ case _ => None
}
- def continueJourney =
- maybeSn match {
- case SectionOrSummary.Section(sn) =>
- val endOfTask: Option[Coordinates] =
- sn.fold[Option[Coordinates]] { classic =>
- None
- } { taskList =>
- val cur = sectionNumber.unsafeToTaskList.coordinates
- if (taskList.coordinates === cur) {
+ def continueJourney: Result =
+ maybeSn match {
+ case SectionOrSummary.Section(sn) =>
+ val endOfTask: Option[Coordinates] =
+ sn.fold[Option[Coordinates]] { classic =>
None
- } else {
- Some(cur)
+ } { taskList =>
+ val cur = sectionNumber.unsafeToTaskList.coordinates
+ if (taskList.coordinates === cur) {
+ None
+ } else {
+ Some(cur)
+ }
}
- }
- if (endOfTask.isDefined) {
- Redirect(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- endOfTask,
- None,
- ff = fastForward.headOption
- )
- )
- } else {
- val isFirstLanding = sectionNumber < sn
- val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processDataUpd, sn)
+ if (endOfTask.isDefined) {
+ Redirect(
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ endOfTask,
+ None,
+ ff = fastForward.headOption
+ )
+ )
+ } else {
+ val isFirstLanding = sectionNumber < sn
+ val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processDataUpd, sn)
- val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val bracket = formModel.bracket(sectionNumber)
+ val formModel = formModelOptics.formModelRenderPageOptics.formModel
+ val bracket = formModel.bracket(sectionNumber)
- val isLastBracketIteration = bracket match {
- case bracket @ Bracket.AddToList(_, _) =>
- val iteration = bracket.iterationForSectionNumber(sectionNumber)
- sectionNumber === iteration.lastSectionNumber
- case _ => false
- }
+ val isLastBracketIteration = bracket match {
+ case bracket @ Bracket.AddToList(Some(defaultPage), _, _)
+ if defaultPage.sectionNumber === sectionNumber =>
+ false
+ case bracket @ Bracket.AddToList(_, _, _) =>
+ val iteration = bracket.iterationForSectionNumber(sectionNumber)
+ sectionNumber === iteration.lastSectionNumber
+ case _ => false
+ }
- maybeAtlRemoveIteration(bracket, sectionNumber)
- .getOrElse(
- Redirect(
- routes.FormController
- .form(
- cache.formTemplateId,
- maybeAccessCode,
- sn,
- sectionTitle4Ga,
- SuppressErrors(isFirstLanding),
- if (isLastBracketIteration) fastForward
- else if (isFirstLanding || sectionNumber.isTaskList) {
- fastForward match {
- case Nil => Nil
- case x :: FastForward.StopAt(s) :: xs
- if formModel.availableSectionNumbers.contains(s) =>
- FastForward.StopAt(s) :: xs
- case x :: xs =>
- x
- .next(
+ maybeAtlRemoveIteration(bracket, sectionNumber)
+ .getOrElse(
+ Redirect(
+ routes.FormController
+ .form(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sn,
+ sectionTitle4Ga,
+ SuppressErrors(isFirstLanding),
+ if (isLastBracketIteration) fastForward
+ else if (isFirstLanding || sectionNumber.isTaskList) {
+ fastForward match {
+ case Nil =>
+ Nil
+ case x :: FastForward.StopAt(s) :: xs
+ if formModel.availableSectionNumbers.contains(s) =>
+ FastForward.StopAt(s) :: xs
+ case x :: xs =>
+ x.next(
processDataUpd.formModelOptics.formModelVisibilityOptics.formModel,
sn
) :: xs
- }
- } else
- fastForward
- )
+ }
+ } else
+ fastForward
+ )
+ )
)
+ }
+ case SectionOrSummary.FormSummary =>
+ processRemoveItemIf().getOrElse(
+ Redirect(
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None,
+ true
+ )
)
- }
- case SectionOrSummary.FormSummary =>
- processRemoveItemIf().getOrElse(
- Redirect(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber.toCoordinates,
- None,
- true
- )
)
- )
- case SectionOrSummary.TaskSummary =>
- processRemoveItemIf().getOrElse(
+ case SectionOrSummary.TaskSummary =>
+ processRemoveItemIf().getOrElse(
+ Redirect(
+ routes.SummaryController
+ .summaryById(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber.maybeCoordinates,
+ None
+ )
+ )
+ )
+ }
+
+ maybeRedirectUrl match {
+ case Some(r) => Redirect(r)
+ case None =>
+ updatePostcodeLookup.fold(continueJourney) { case (formComponentId, _) =>
Redirect(
- routes.SummaryController
- .summaryById(
- cache.formTemplateId,
+ uk.gov.hmrc.gform.addresslookup.routes.AddressLookupController
+ .chooseAddress(
+ cache.formTemplate._id,
maybeAccessCode,
- sectionNumber.toCoordinates,
- None
+ formComponentId,
+ sectionNumber,
+ fastForward
)
)
- )
+ }
}
-
- maybeRedirectUrl match {
- case Some(r) => Redirect(r)
- case None =>
- updatePostcodeLookup.fold(continueJourney) { case (formComponentId, _) =>
- Redirect(
- uk.gov.hmrc.gform.addresslookup.routes.AddressLookupController
- .chooseAddress(
- cache.formTemplate._id,
- maybeAccessCode,
- formComponentId,
- sectionNumber,
- fastForward
- )
- )
- }
}
- }
- }
+ }
- def handleGroup(cacheUpd: AuthCacheWithForm, processData: ProcessData, anchor: String): Future[Result] =
- formProcessor.validateAndUpdateData(
- cacheUpd,
- processData,
- sectionNumber,
- sectionNumber,
- maybeAccessCode,
- fastForward,
- formModelOptics,
- enteredVariadicFormData,
- true
- ) { _ => _ => _ =>
- val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
- Redirect(
- routes.FormController
- .form(
- cache.formTemplateId,
- maybeAccessCode,
- sectionNumber,
- sectionTitle4Ga,
- SuppressErrors.Yes,
- List(FastForward.Yes)
- )
- .url + anchor
- )
- }
+ def handleGroup(cacheUpd: AuthCacheWithForm, processData: ProcessData, anchor: String): Future[Result] =
+ formProcessor.validateAndUpdateData(
+ cacheUpd,
+ processData,
+ sectionNumber,
+ sectionNumber,
+ maybeAccessCode,
+ fastForward,
+ formModelOptics,
+ enteredVariadicFormData,
+ true
+ ) { _ => _ => _ =>
+ val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber)
+ Redirect(
+ routes.FormController
+ .form(
+ cache.formTemplateId,
+ maybeAccessCode,
+ sectionNumber,
+ sectionTitle4Ga,
+ SuppressErrors.Yes,
+ List(FastForward.Yes)
+ )
+ .url + anchor
+ )
+ }
- def processAddGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = {
+ def processAddGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = {
- val incremented = modelComponentId.increment
+ val incremented = modelComponentId.increment
- def anchor(formModelOptics: FormModelOptics[DataOrigin.Browser]): Option[HtmlFieldId] = {
- val childs: List[FormComponent] =
- formModelOptics.formModelVisibilityOptics.fcLookup
- .get(incremented.toFormComponentId)
- .toList
- .flatMap(_.childrenFormComponents)
+ def anchor(formModelOptics: FormModelOptics[DataOrigin.Browser]): Option[HtmlFieldId] = {
+ val childs: List[FormComponent] =
+ formModelOptics.formModelVisibilityOptics.fcLookup
+ .get(incremented.toFormComponentId)
+ .toList
+ .flatMap(_.childrenFormComponents)
- childs
- .dropWhile {
- case IsInformationMessage(_) => true
- case _ => false
- }
- .headOption
- .map {
- case fc @ IsChoice(_) => HtmlFieldId.indexed(fc.id, "0")
- case fc @ IsRevealingChoice(_) => HtmlFieldId.indexed(fc.id, "0")
- case fc =>
- HtmlFieldId.pure(fc.multiValueId.fold[ModelComponentId](_.modelComponentId)(_.atoms.head))
- }
- }
+ childs
+ .dropWhile {
+ case IsInformationMessage(_) => true
+ case _ => false
+ }
+ .headOption
+ .map {
+ case fc @ IsChoice(_) => HtmlFieldId.indexed(fc.id, "0")
+ case fc @ IsRevealingChoice(_) => HtmlFieldId.indexed(fc.id, "0")
+ case fc =>
+ HtmlFieldId.pure(fc.multiValueId.fold[ModelComponentId](_.modelComponentId)(_.atoms.head))
+ }
+ }
- val variadicFormData = processData.formModelOptics.pageOpticsData
- val updatedVariadicFormData = variadicFormData.addOne(incremented -> "")
- for {
- updFormModelOptics <- FormModelOptics
- .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal](
- updatedVariadicFormData
- .asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]],
- cache,
- recalculation
- )
- res <- handleGroup(
- cache,
- processData.copy(formModelOptics = updFormModelOptics),
- anchor(updFormModelOptics).map("#" + _.toHtmlId).getOrElse("")
- )
- } yield res
+ val variadicFormData = processData.formModelOptics.pageOpticsData
+ val updatedVariadicFormData = variadicFormData.addOne(incremented -> "")
+ for {
+ updFormModelOptics <- FormModelOptics
+ .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal](
+ updatedVariadicFormData
+ .asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]],
+ cache,
+ recalculation
+ )
+ res <- handleGroup(
+ cache,
+ processData.copy(formModelOptics = updFormModelOptics),
+ anchor(updFormModelOptics).map("#" + _.toHtmlId).getOrElse("")
+ )
+ } yield res
- }
+ }
+
+ def processRemoveGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = {
+ val (updData, componentIdToFileId, filesToDelete) =
+ GroupUtils.removeRecord(processData, modelComponentId, sectionNumber, cache.form.componentIdToFileId)
+ val cacheUpd = cache.copy(form = cache.form.copy(componentIdToFileId = componentIdToFileId))
+ for {
+ updFormModelOptics <- FormModelOptics
+ .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal](
+ updData.asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]],
+ cache,
+ recalculation
+ )
+ res <- handleGroup(cacheUpd, processData.copy(formModelOptics = updFormModelOptics), "")
+ _ <- objectStoreAlgebra.deleteFiles(cache.form.envelopeId, filesToDelete)
+ } yield res
+ }
- def processRemoveGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = {
- val (updData, componentIdToFileId, filesToDelete) =
- GroupUtils.removeRecord(processData, modelComponentId, sectionNumber, cache.form.componentIdToFileId)
- val cacheUpd = cache.copy(form = cache.form.copy(componentIdToFileId = componentIdToFileId))
for {
- updFormModelOptics <- FormModelOptics
- .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal](
- updData.asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]],
- cache,
- recalculation
- )
- res <- handleGroup(cacheUpd, processData.copy(formModelOptics = updFormModelOptics), "")
- _ <- objectStoreAlgebra.deleteFiles(cache.form.envelopeId, filesToDelete)
+ processData <- processDataService
+ .getProcessData[SectionSelectorType.Normal](
+ variadicFormData,
+ cache,
+ formModelOptics,
+ gformConnector.getAllTaxPeriods,
+ NoSpecificAction
+ )
+ res <- save match {
+ case SaveAndContinue => processSaveAndContinue(processData)
+ case SaveAndExit =>
+ Redirect(
+ gform.routes.SaveAcknowledgementController
+ .saveAndExit(formTemplateId, maybeAccessCode, browserSectionNumber, fastForward)
+ ).pure[Future]
+ case AddGroup(modelComponentId) => processAddGroup(processData, modelComponentId)
+ case RemoveGroup(modelComponentId) => processRemoveGroup(processData, modelComponentId)
+ case _ => throw new IllegalArgumentException(s"Direction $save is not supported here")
+ }
} yield res
}
-
- for {
- processData <- processDataService
- .getProcessData[SectionSelectorType.Normal](
- variadicFormData,
- cache,
- formModelOptics,
- gformConnector.getAllTaxPeriods,
- NoSpecificAction
- )
- res <- save match {
- case SaveAndContinue => processSaveAndContinue(processData)
- case SaveAndExit =>
- Redirect(
- gform.routes.SaveAcknowledgementController
- .saveAndExit(formTemplateId, maybeAccessCode, browserSectionNumber, fastForward)
- ).pure[Future]
- case AddGroup(modelComponentId) => processAddGroup(processData, modelComponentId)
- case RemoveGroup(modelComponentId) => processRemoveGroup(processData, modelComponentId)
- case _ => throw new IllegalArgumentException(s"Direction $save is not supported here")
- }
- } yield res
}
+
}
private val formMaxAttachmentSizeMB = appConfig.formMaxAttachmentSizeMB
diff --git a/app/uk/gov/hmrc/gform/gform/ImageController.scala b/app/uk/gov/hmrc/gform/gform/ImageController.scala
index 97b98f86d..94c3bbd26 100644
--- a/app/uk/gov/hmrc/gform/gform/ImageController.scala
+++ b/app/uk/gov/hmrc/gform/gform/ImageController.scala
@@ -49,7 +49,7 @@ class ImageController(
OperationWithForm.ViewImageByInternalLink
) { _ => _ => _ => _ => formModelOptics =>
val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val allExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel))
+ val allExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel))
Future.successful {
if (!allExprs.contains(LinkCtx(InternalLink.Image(fileName)))) {
diff --git a/app/uk/gov/hmrc/gform/gform/NewFormController.scala b/app/uk/gov/hmrc/gform/gform/NewFormController.scala
index d59a4a2ea..bc94e0b2a 100644
--- a/app/uk/gov/hmrc/gform/gform/NewFormController.scala
+++ b/app/uk/gov/hmrc/gform/gform/NewFormController.scala
@@ -592,7 +592,7 @@ class NewFormController(
def formHasAuthItmpReferences(): Boolean = {
val formModel = formModelOptics.formModelRenderPageOptics.formModel
- val allBracketExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel))
+ val allBracketExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel))
val allCustomExprs = cache.formTemplateContext.formTemplate.formKind.allCustomExprs
val expressionsOutExprs =
cache.formTemplateContext.formTemplate.expressionsOutput.fold(List.empty[Expr])(_.lookup.values.toList)
@@ -611,7 +611,7 @@ class NewFormController(
val formModel = formModelOptics.formModelRenderPageOptics.formModel
val modelComponentIds = formModel.confirmationPageMap.flatMap { case (sectionNumber, confirmation) =>
val allBracketExprs =
- formModel.brackets.withSectionNumber(sectionNumber).toPlainBracket.allExprs(formModel)
+ formModel.brackets.withSectionNumber(sectionNumber).allExprs(formModel)
if (cacheItmpRetrievals.flatMap(_.itmpName) != itmpRetrievals.itmpName && hasItmpNameExpr(allBracketExprs)) {
Some(confirmation.question.id.modelComponentId)
diff --git a/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala b/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala
index 21cc5e6c5..a2f80a423 100644
--- a/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala
+++ b/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala
@@ -56,6 +56,7 @@ import uk.gov.hmrc.gform.ops.FormComponentOps
import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionTitle4Ga.sectionTitle4GaFactory
import uk.gov.hmrc.gform.sharedmodel.formtemplate.destinations.Destinations._
import uk.gov.hmrc.gform.summary.{ AddToListCYARender, AddressRecordLookup, FormComponentRenderDetails, FormComponentSummaryRenderer, SummaryRender }
+import uk.gov.hmrc.gform.tasklist.TaskListUtils
import uk.gov.hmrc.gform.upscan.{ FormMetaData, UpscanData, UpscanInitiate }
import uk.gov.hmrc.gform.validation.HtmlFieldId
import uk.gov.hmrc.gform.validation._
@@ -91,7 +92,6 @@ import uk.gov.hmrc.hmrcfrontend.views.html.components.HmrcCharacterCount
import uk.gov.hmrc.gform.views.summary.SummaryListRowHelper
import uk.gov.hmrc.govukfrontend.views.html.components.{ GovukCharacterCount, GovukInput, GovukSummaryList, GovukTable }
import MiniSummaryRow._
-import uk.gov.hmrc.gform.tasklist.TaskListUtils
import uk.gov.hmrc.auth.core.ConfidenceLevel
import uk.gov.hmrc.gform.sharedmodel.formtemplate.KeyDisplayWidth.KeyDisplayWidth
import uk.gov.hmrc.gform.models.helpers.MiniSummaryListHelper
@@ -219,10 +219,11 @@ class SectionRenderingService(
SectionRenderingService.atlCyaTitles(cache, sectionNumber, checkYourAnswers, formModelOptics)
val ff = fastForward match {
- case Nil => Nil
- case FastForward.CYA(to) :: xs => FastForward.CYA(to) :: xs
- case FastForward.StopAt(sectionNumber) :: xs => FastForward.StopAt(sectionNumber.increment) :: xs
- case otherwise => otherwise
+ case Nil => Nil
+ case FastForward.CYA(to) :: xs => FastForward.CYA(to) :: xs
+ case FastForward.StopAt(sectionNumber) :: xs =>
+ FastForward.StopAt(sectionNumber.increment(formModelOptics.formModelVisibilityOptics.formModel)) :: xs
+ case otherwise => otherwise
}
html.form.addToListCheckYourAnswers(
title,
@@ -751,33 +752,58 @@ class SectionRenderingService(
}
val classicPages: List[(PageModel[DataExpanded], SectionNumber.Classic)] =
- pages.toList.collect { case (pageModel, c @ SectionNumber.Classic(_)) =>
+ pages.toList.collect { case (pageModel, c: SectionNumber.Classic) =>
pageModel -> c
}
+
+ val specimenLinks: SpecimenLinks = SpecimenLinks.from(classicPages.map(_._2), classic)
+
specimen.navigation(
formTemplate,
classic,
classicPages,
+ specimenLinks,
maybeIncludeIf
)
} { taskList =>
+ val coordinates: Coordinates = taskList.coordinates
+ val classic: SectionNumber.Classic = taskList.sectionNumber
+
val pages: NonEmptyList[(PageModel[DataExpanded], SectionNumber)] =
formModelRenderPageOptics.formModel.pagesWithIndex
+ val allSectionNumbers: List[SectionNumber] =
+ pages.map(_._2).filter(_.maybeCoordinates.exists(_.taskSectionNumber === coordinates.taskSectionNumber))
+
+ val distinctCoordinates: List[Coordinates] = allSectionNumbers.flatMap(_.maybeCoordinates).distinct
+
+ val isFirstSectionNumber: Set[SectionNumber] = distinctCoordinates.flatMap { coordinates =>
+ allSectionNumbers.find(_.maybeCoordinates.contains(coordinates))
+ }.toSet
+
+ val firstSectionNumbers: List[SectionNumber] = allSectionNumbers.filter(isFirstSectionNumber)
+
val taskListPages: List[(PageModel[DataExpanded], SectionNumber.TaskList)] =
pages.toList.collect {
case (pageModel, c @ SectionNumber.TaskList(coordinates, _)) if coordinates === taskList.coordinates =>
pageModel -> c
}
- val tasks = TaskListUtils.withTaskSection(formTemplate, taskList.coordinates.taskSectionNumber)(section =>
- section.tasks.toList
- )
+ val tasks: List[uk.gov.hmrc.gform.sharedmodel.formtemplate.Task] =
+ TaskListUtils.withTaskSection(formTemplate, taskList.coordinates.taskSectionNumber)(section =>
+ section.tasks.toList
+ )
+
+ val tasksWithFirstSectionNumber = tasks.zip(firstSectionNumbers)
+
+ val specimenLinks: SpecimenLinks = SpecimenLinks.from(taskListPages.map(_._2.sectionNumber), classic)
+
specimen.navigation_tasklist(
formTemplate,
taskList,
taskListPages,
- tasks
+ tasksWithFirstSectionNumber,
+ specimenLinks
)
}
} else HtmlFormat.empty
diff --git a/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala b/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala
new file mode 100644
index 000000000..43e81075f
--- /dev/null
+++ b/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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 uk.gov.hmrc.gform.gform
+
+import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionNumber
+
+final case class SpecimenLinks(previous: Option[SectionNumber.Classic], next: Option[SectionNumber.Classic])
+
+object SpecimenLinks {
+ def from(sectionNumbers: List[SectionNumber.Classic], currentSectionNumber: SectionNumber.Classic): SpecimenLinks = {
+
+ val previous: Option[SectionNumber.Classic] = sectionNumbers.filter(_ < currentSectionNumber).maxOption
+ val next: Option[SectionNumber.Classic] = sectionNumbers.filter(_ > currentSectionNumber).minOption
+
+ SpecimenLinks(previous, next)
+ }
+}
diff --git a/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala b/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala
index a6428e787..09c658c3a 100644
--- a/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala
+++ b/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala
@@ -137,7 +137,7 @@ class FormValidator(implicit ec: ExecutionContext) {
currentSectionNumber: Option[SectionNumber],
formModelOptics: FormModelOptics[DataOrigin.Browser]
): List[SectionNumber] = {
- val maybeCoordinates = currentSectionNumber.flatMap(_.toCoordinates)
+ val maybeCoordinates = currentSectionNumber.flatMap(_.maybeCoordinates)
val availableSectionNumbers: List[SectionNumber] = Origin(
formModelOptics.formModelVisibilityOptics.formModel
@@ -157,7 +157,7 @@ class FormValidator(implicit ec: ExecutionContext) {
maybeSectionNumber: Option[SectionNumber]
): Future[SectionOrSummary] = {
- val maybeCoordinates = maybeSectionNumber.flatMap(_.toCoordinates)
+ val maybeCoordinates = maybeSectionNumber.flatMap(_.maybeCoordinates)
val formModelOptics: FormModelOptics[DataOrigin.Browser] = processData.formModelOptics
def atlHasSectionNumber(sectionNumber: SectionNumber): Boolean =
@@ -190,7 +190,7 @@ class FormValidator(implicit ec: ExecutionContext) {
maybeSectionNumber
)
- lazy val nextFrom = for {
+ val nextFrom = for {
sectionNumber <- maybeSectionNumber
next <- availableSectionNumbers.find(_ > sectionNumber)
} yield next
diff --git a/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala b/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala
index b3cc63448..e7456dd25 100644
--- a/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala
+++ b/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala
@@ -117,9 +117,6 @@ class FormProcessor(
val isLastIteration = addToListBracket.iterations.size === 1
- val visitsIndex: VisitIndex = VisitIndex
- .updateSectionVisits(updFormModel, processData.formModel, processData.visitsIndex)
-
val visitsIndexUpd =
if (isLastIteration) {
val iterationForSectionNumber: Bracket.AddToListIteration[DataExpanded] = addToListBracket
@@ -129,8 +126,8 @@ class FormProcessor(
.map(_.sectionNumber)
.toList ++ iterationForSectionNumber.checkYourAnswers.map(_.sectionNumber)
- visitsIndex.fold[VisitIndex] { classic =>
- val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToClassic.sectionNumber)
+ processData.visitsIndex.fold[VisitIndex] { classic =>
+ val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToClassic)
VisitIndex.Classic(
classic.visitsIndex -- toBeRemoved
)
@@ -138,7 +135,7 @@ class FormProcessor(
val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToTaskList.sectionNumber)
val coordinates = sn.toCoordinatesUnsafe
- val indexes =
+ val indexes: Set[SectionNumber.Classic] =
taskList.visitsIndex
.getOrElse(coordinates, throw new Exception(s"No VisitIndex found for coordinates $coordinates"))
@@ -149,7 +146,7 @@ class FormProcessor(
)
}
} else
- visitsIndex
+ processData.visitsIndex
val processDataUpd = processData.copy(
formModelOptics = updFormModelOptics,
@@ -391,15 +388,16 @@ class FormProcessor(
gformConnector.getAllTaxPeriods,
NoSpecificAction
)
- res <- fastForwardService
- .updateUserData(
- cacheUpd,
- newProcessData,
- maybeAccessCode,
- fastForward,
- envelopeWithMapping,
- Some(sectionNumber)
- )((a, b) => toResult(updatePostcodeLookup)(redirectUrl.map(_.value()))(a))
+ res <-
+ fastForwardService
+ .updateUserData(
+ cacheUpd,
+ newProcessData,
+ maybeAccessCode,
+ fastForward,
+ envelopeWithMapping,
+ Some(sectionNumber)
+ )((sectionOrSummary, _) => toResult(updatePostcodeLookup)(redirectUrl.map(_.value()))(sectionOrSummary))
} yield res
}
} yield res
diff --git a/app/uk/gov/hmrc/gform/models/AllSections.scala b/app/uk/gov/hmrc/gform/models/AllSections.scala
index 0532b9c94..ad7f972a3 100644
--- a/app/uk/gov/hmrc/gform/models/AllSections.scala
+++ b/app/uk/gov/hmrc/gform/models/AllSections.scala
@@ -17,11 +17,20 @@
package uk.gov.hmrc.gform.models
import cats.data.NonEmptyList
-import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, Section, TaskNumber, TaskSectionNumber }
+import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, Section, TaskNumber, TaskSectionNumber, TemplateSectionIndex }
+
+sealed trait IndexedSection {
+ def section: Section // Section which knows its position in json template
+}
+
+object IndexedSection {
+ case class SectionIndex(section: Section, index: TemplateSectionIndex) extends IndexedSection
+ case class SectionNoIndex(section: Section.NonRepeatingPage) extends IndexedSection
+}
sealed trait AllSections extends Product with Serializable {
- def sections: List[Section]
+ def sections: List[IndexedSection]
def fold[B](f: AllSections.Classic => B)(g: AllSections.TaskList => B): B =
this match {
@@ -29,9 +38,11 @@ sealed trait AllSections extends Product with Serializable {
case r: AllSections.TaskList => g(r)
}
- def mapSection[A <: PageMode](f: Section => Option[BracketPlain[A]]): BracketPlainCoordinated[A] =
+ def mapSection[A <: PageMode](
+ f: Option[Coordinates] => IndexedSection => Option[Bracket[A]]
+ ): BracketPlainCoordinated[A] =
fold[BracketPlainCoordinated[A]] { classic =>
- val xs: List[BracketPlain[A]] = classic.sections.map(f).collect { case Some(bracket) =>
+ val xs: List[Bracket[A]] = classic.sections.map(f(None)).collect { case Some(bracket) =>
bracket
}
NonEmptyList
@@ -40,37 +51,44 @@ sealed trait AllSections extends Product with Serializable {
BracketPlainCoordinated.Classic(_)
}
} { taskList =>
- val xs: NonEmptyList[(Coordinates, TaskModelCoordinated[A])] = taskList.coordSections.map {
- case (coordinates, sections) =>
- val xs: List[BracketPlain[A]] = sections.map(f).collect { case Some(bracket) =>
- bracket
- }
+ val xs: NonEmptyList[(Coordinates, TaskModel[A])] = taskList.coordSections.map { case (coordinates, sections) =>
+ val xs: List[Bracket[A]] = sections.map(f(Some(coordinates))).collect { case Some(bracket) =>
+ bracket
+ }
- val taskModelCoordinated: TaskModelCoordinated[A] = NonEmptyList
- .fromList(xs)
- .fold(TaskModelCoordinated.allHidden[A])(brackets => TaskModelCoordinated.editable[A](brackets))
+ val taskModelCoordinated: TaskModel[A] = NonEmptyList
+ .fromList(xs)
+ .fold(TaskModel.allHidden[A])(brackets => TaskModel.editable[A](brackets))
- coordinates -> taskModelCoordinated
+ coordinates -> taskModelCoordinated
}
BracketPlainCoordinated.TaskList(xs)
}
- def +(others: List[Section]) = fold[AllSections](_.copy(others = others))(_.copy(others = others))
+ def +(others: List[Section.NonRepeatingPage]) = fold[AllSections](_.copy(others = others))(_.copy(others = others))
}
object AllSections {
- case class Classic(sections0: List[Section], others: List[Section] = Nil) extends AllSections {
- val sections = sections0 ++ others
- }
- case class TaskList(sections0: NonEmptyList[(Coordinates, List[Section])], others: List[Section] = Nil)
+ case class Classic(sections0: List[IndexedSection], others: List[Section.NonRepeatingPage] = Nil)
extends AllSections {
- val coordSections: NonEmptyList[(Coordinates, List[Section])] = {
+ val sections = sections0 ++ others.map(section => IndexedSection.SectionNoIndex(section))
+ }
+ case class TaskList(
+ sections0: NonEmptyList[(Coordinates, List[IndexedSection])],
+ others: List[Section.NonRepeatingPage] = Nil
+ ) extends AllSections {
+ val coordSections: NonEmptyList[(Coordinates, List[IndexedSection])] = {
if (others.isEmpty) {
sections0
} else
- sections0.append((Coordinates(TaskSectionNumber(999999), TaskNumber(999999)), others))
+ sections0.append(
+ (
+ Coordinates(TaskSectionNumber(999999), TaskNumber(999999)),
+ others.map(section => IndexedSection.SectionNoIndex(section))
+ )
+ )
}
- val sections = sections0.toList.flatMap(_._2) ++ others
+ val sections = sections0.toList.flatMap(_._2) ++ others.map(section => IndexedSection.SectionNoIndex(section))
}
}
diff --git a/app/uk/gov/hmrc/gform/models/Bracket.scala b/app/uk/gov/hmrc/gform/models/Bracket.scala
index 07ce457e4..15cd8833d 100644
--- a/app/uk/gov/hmrc/gform/models/Bracket.scala
+++ b/app/uk/gov/hmrc/gform/models/Bracket.scala
@@ -20,11 +20,16 @@ import cats.data.NonEmptyList
import cats.syntax.eq._
import uk.gov.hmrc.gform.models.optics.{ DataOrigin, FormModelVisibilityOptics }
import uk.gov.hmrc.gform.sharedmodel.form.VisitIndex
-import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ AddToListId, Section, SectionNumber }
+import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ AddToListId, Expr, Section, SectionNumber }
+import uk.gov.hmrc.gform.eval.AllPageModelExpressionsGetter
sealed trait Bracket[A <: PageMode] extends Product with Serializable {
- def toPlainBracket: BracketPlain[A]
+
+ def allExprs(formModel: FormModel[DataExpanded]): List[Expr] =
+ AllPageModelExpressionsGetter.allExprs(formModel)(this)
+
def hasSectionNumber(sectionNumber: SectionNumber): Boolean
+
def map[B <: PageMode](
e: Singleton[A] => Singleton[B],
f: CheckYourAnswers[A] => CheckYourAnswers[B],
@@ -44,28 +49,33 @@ sealed trait Bracket[A <: PageMode] extends Product with Serializable {
}
def isToListById(addToListId: AddToListId): Boolean = this match {
- case Bracket.AddToList(_, source) => source.id === addToListId
- case _ => false
+ case Bracket.AddToList(_, _, source) => source.id === addToListId
+ case _ => false
}
def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] =
- fold[NonEmptyList[(PageModel[A], SectionNumber)]](a => NonEmptyList.one((a.singleton, a.sectionNumber)))(
- _.singletons.map(_.toPageModelWithNumber)
- )(_.iterations.flatMap(_.toPageModelWithNumber))
+ fold[NonEmptyList[(PageModel[A], SectionNumber)]](a =>
+ NonEmptyList.one((a.singleton.singleton, a.singleton.sectionNumber))
+ )(
+ _.singletons.map(_.toPageModelWithNumber4)
+ ) { aa =>
+ val ss = aa.iterations.flatMap(_.toPageModelWithNumber3)
+ aa.defaultPage.fold(ss)(dp => NonEmptyList(dp.toPageModelWithNumber4, ss.toList))
+ }
def toPageModel: NonEmptyList[PageModel[A]] =
- fold[NonEmptyList[PageModel[A]]](a => NonEmptyList.one(a.singleton))(_.singletons.map(_.singleton))(
+ fold[NonEmptyList[PageModel[A]]](a => NonEmptyList.one(a.singleton.singleton))(_.singletons.map(_.singleton))(
_.iterations.flatMap(_.toPageModel)
)
def filter(predicate: PageModel[A] => Boolean): Option[Bracket[A]] = this match {
- case Bracket.AddToList(iterations, source) =>
+ case Bracket.AddToList(defaultPage, iterations, source) =>
val filtered: Option[NonEmptyList[Bracket.AddToListIteration[A]]] = iterations.traverse(_.filter(predicate))
- filtered.map(Bracket.AddToList(_, source))
+ filtered.map(Bracket.AddToList(defaultPage, _, source))
case Bracket.RepeatingPage(singletons, source) =>
val filtered = singletons.filter(singletonWithNumber => predicate(singletonWithNumber.singleton))
NonEmptyList.fromList(filtered).map(Bracket.RepeatingPage(_, source))
- case b @ Bracket.NonRepeatingPage(singleton, _, _) => if (predicate(singleton)) Some(b) else None
+ case b @ Bracket.NonRepeatingPage(singleton, _) => if (predicate(singleton.singleton)) Some(b) else None
}
def withAddToListBracket[B](f: Bracket.AddToList[A] => B): B =
@@ -98,20 +108,14 @@ object Bracket {
def allSingletonSectionNumbers: List[SectionNumber] = singletons.map(_.sectionNumber).toList
- def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] =
- singletons.map(_.toPageModelWithNumber) ++ checkYourAnswers.toList.map(c =>
+ def toPageModelWithNumber3: NonEmptyList[(PageModel[A], SectionNumber)] =
+ singletons.map(_.toPageModelWithNumber4) ++ checkYourAnswers.toList.map(c =>
c.checkYourAnswers -> c.sectionNumber
) ::: NonEmptyList.one(repeater.repeater -> repeater.sectionNumber)
- def toPlainBracket: BracketPlain.AddToListIteration[A] =
- BracketPlain.AddToListIteration(
- singletons.map(_.singleton),
- checkYourAnswers.map(_.checkYourAnswers),
- repeater.repeater
- )
def hasSectionNumber(sectionNumber: SectionNumber): Boolean =
repeater.sectionNumber === sectionNumber || checkYourAnswers.exists(
- _.sectionNumber == sectionNumber
+ _.sectionNumber === sectionNumber
) || singletons.exists(_.sectionNumber === sectionNumber)
def singleton(sectionNumber: SectionNumber): Singleton[A] =
singletons.toList
@@ -139,33 +143,25 @@ object Bracket {
def lastSectionNumber: SectionNumber = singletons.last.sectionNumber
- def secondSectionNumber: SectionNumber = singletons.tail.head.sectionNumber
-
def isCommited(visitsIndex: VisitIndex): Boolean =
singletons.forall { singletonWithNumber =>
visitsIndex.contains(singletonWithNumber.sectionNumber)
}
}
- case class NonRepeatingPage[A <: PageMode](
- singleton: Singleton[A],
- sectionNumber: SectionNumber,
- source: Section.NonRepeatingPage
- ) extends Bracket[A] {
+ case class NonRepeatingPage[A <: PageMode](singleton: SingletonWithNumber[A], source: Section.NonRepeatingPage)
+ extends Bracket[A] {
def map[B <: PageMode](
e: Singleton[A] => Singleton[B],
f: CheckYourAnswers[A] => CheckYourAnswers[B],
g: Repeater[A] => Repeater[B]
): NonRepeatingPage[B] =
NonRepeatingPage(
- e(singleton),
- sectionNumber,
+ SingletonWithNumber(e(singleton.singleton), singleton.sectionNumber),
source
)
- def toPlainBracket: BracketPlain[A] = BracketPlain.NonRepeatingPage(singleton, source)
-
- def hasSectionNumber(sectionNumber: SectionNumber): Boolean = this.sectionNumber === sectionNumber
+ def hasSectionNumber(sectionNumber: SectionNumber): Boolean = this.singleton.sectionNumber === sectionNumber
}
case class RepeatingPage[A <: PageMode](
@@ -182,8 +178,6 @@ object Bracket {
source
)
- def toPlainBracket: BracketPlain[A] = BracketPlain.RepeatingPage(singletons.map(_.singleton), source)
-
def hasSectionNumber(sectionNumber: SectionNumber): Boolean = singletons.exists(_.sectionNumber === sectionNumber)
def singletonForSectionNumber(sectionNumber: SectionNumber): Singleton[A] =
@@ -199,6 +193,7 @@ object Bracket {
}
case class AddToList[A <: PageMode](
+ defaultPage: Option[SingletonWithNumber[A]],
iterations: NonEmptyList[
AddToListIteration[A]
], // There must be at least one iteration for Add-to-list to make sense
@@ -236,28 +231,35 @@ object Bracket {
f: CheckYourAnswers[A] => CheckYourAnswers[B],
g: Repeater[A] => Repeater[B]
): AddToList[B] = AddToList(
+ defaultPage.map(s => SingletonWithNumber(e(s.singleton), s.sectionNumber)),
iterations.map(_.map(e, f, g)),
source
)
- def toPlainBracket: BracketPlain[A] = BracketPlain.AddToList(iterations.map(_.toPlainBracket), source)
-
- def hasSectionNumber(sectionNumber: SectionNumber): Boolean = iterations.exists { iteration =>
- iteration.repeater.sectionNumber === sectionNumber || iteration.checkYourAnswers.exists(
- _.sectionNumber === sectionNumber
- ) || iteration.singletons.exists(
- _.sectionNumber === sectionNumber
- )
- }
+ def hasSectionNumber(sectionNumber: SectionNumber): Boolean =
+ iterations.exists { iteration =>
+ iteration.repeater.sectionNumber === sectionNumber || iteration.checkYourAnswers.exists(
+ _.sectionNumber === sectionNumber
+ ) || iteration.singletons.exists(
+ _.sectionNumber === sectionNumber
+ )
+ } || defaultPage.exists { defaultPage =>
+ defaultPage.sectionNumber === sectionNumber
+ }
def iterationForSectionNumber(sectionNumber: SectionNumber): Bracket.AddToListIteration[A] =
- iterations.toList
- .collectFirst {
- case iteration if iteration.hasSectionNumber(sectionNumber) => iteration
- }
- .getOrElse(
- throw new IllegalArgumentException(s"Invalid sectionNumber: $sectionNumber for Bracket.AddToListIteration")
- )
+ if (defaultPage.exists(_.sectionNumber === sectionNumber)) {
+ iterations.head
+ } else {
+ iterations.toList
+ .collectFirst {
+ case iteration if iteration.hasSectionNumber(sectionNumber) => iteration
+ }
+ .getOrElse(
+ throw new IllegalArgumentException(s"Invalid sectionNumber: $sectionNumber for Bracket.AddToListIteration")
+ )
+
+ }
def iterationForSectionNumberWithIndex(sectionNumber: SectionNumber): (Bracket.AddToListIteration[A], Int) =
iterations.zipWithIndex.toList
diff --git a/app/uk/gov/hmrc/gform/models/BracketPlain.scala b/app/uk/gov/hmrc/gform/models/BracketPlain.scala
deleted file mode 100644
index 55cc0f470..000000000
--- a/app/uk/gov/hmrc/gform/models/BracketPlain.scala
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 uk.gov.hmrc.gform.models
-
-import cats.data.NonEmptyList
-import uk.gov.hmrc.gform.eval.AllPageModelExpressionsGetter
-import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Expr, Section }
-
-sealed trait BracketPlain[A <: PageMode] extends Product with Serializable {
- def fold[B](
- f: BracketPlain.NonRepeatingPage[A] => B
- )(
- g: BracketPlain.RepeatingPage[A] => B
- )(
- h: BracketPlain.AddToList[A] => B
- ): B =
- this match {
- case b: BracketPlain.NonRepeatingPage[A] => f(b)
- case b: BracketPlain.RepeatingPage[A] => g(b)
- case b: BracketPlain.AddToList[A] => h(b)
- }
-
- def allExprs(formModel: FormModel[DataExpanded]): List[Expr] = AllPageModelExpressionsGetter.allExprs(formModel)(this)
-}
-
-object BracketPlain {
- case class AddToListIteration[A <: PageMode](
- singletons: NonEmptyList[Singleton[A]],
- checkYourAnswers: Option[CheckYourAnswers[A]],
- repeater: Repeater[A]
- )
-
- case class NonRepeatingPage[A <: PageMode](singleton: Singleton[A], source: Section.NonRepeatingPage)
- extends BracketPlain[A]
- case class RepeatingPage[A <: PageMode](singletons: NonEmptyList[Singleton[A]], source: Section.RepeatingPage)
- extends BracketPlain[A]
- case class AddToList[A <: PageMode](iterations: NonEmptyList[AddToListIteration[A]], source: Section.AddToList)
- extends BracketPlain[A]
-
-}
diff --git a/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala b/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala
index 924fac572..5ba605c65 100644
--- a/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala
+++ b/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala
@@ -28,7 +28,7 @@ sealed trait BracketPlainCoordinated[A <: PageMode] extends Product with Seriali
}
object BracketPlainCoordinated {
- case class Classic[A <: PageMode](bracketPlains: NonEmptyList[BracketPlain[A]]) extends BracketPlainCoordinated[A]
- case class TaskList[A <: PageMode](bracketPlains: NonEmptyList[(Coordinates, TaskModelCoordinated[A])])
+ case class Classic[A <: PageMode](brackets: NonEmptyList[Bracket[A]]) extends BracketPlainCoordinated[A]
+ case class TaskList[A <: PageMode](coordinatedBrackets: NonEmptyList[(Coordinates, TaskModel[A])])
extends BracketPlainCoordinated[A]
}
diff --git a/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala b/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala
index a9daab5a9..91086c264 100644
--- a/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala
+++ b/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala
@@ -33,14 +33,9 @@ sealed trait BracketsWithSectionNumber[A <: PageMode] extends Product with Seria
)(identity)
def toBracketPlainCoordinated: BracketPlainCoordinated[A] = fold[BracketPlainCoordinated[A]] { classic =>
- val bracketPlains: NonEmptyList[BracketPlain[A]] = classic.brackets.map(_.toPlainBracket)
- BracketPlainCoordinated.Classic(bracketPlains)
+ BracketPlainCoordinated.Classic(classic.brackets)
} { taskList =>
- val bracketPlains: NonEmptyList[(Coordinates, TaskModelCoordinated[A])] = taskList.brackets.map {
- case (coor, taskModel) =>
- (coor, taskModel.toTaskModelCoordinated)
- }
- BracketPlainCoordinated.TaskList(bracketPlains)
+ BracketPlainCoordinated.TaskList(taskList.brackets)
}
def map[B <: PageMode](
@@ -106,8 +101,8 @@ sealed trait BracketsWithSectionNumber[A <: PageMode] extends Product with Seria
}
}
- def toBracketsPlains: NonEmptyList[BracketPlain[A]] =
- fold(_.brackets)(_.allBrackets).map(_.toPlainBracket)
+ def toBrackets: NonEmptyList[Bracket[A]] =
+ fold(_.brackets)(_.allBrackets)
def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] =
fold { classic =>
@@ -136,44 +131,14 @@ object BracketsWithSectionNumber {
)
}
- def fromBracketsPlains[A <: PageMode](bracketPlains: BracketPlainCoordinated[A]): BracketsWithSectionNumber[A] =
+ def fromBracketCoordinated[A <: PageMode](bracketPlains: BracketPlainCoordinated[A]): BracketsWithSectionNumber[A] =
bracketPlains match {
- case BracketPlainCoordinated.Classic(bracketPlains) =>
- val iterator: Iterator[SectionNumber] = LazyList.from(0).map(SectionNumber.Classic(_)).iterator
- val res: NonEmptyList[Bracket[A]] = mkBrackets(iterator, bracketPlains)
- Classic(res)
- case BracketPlainCoordinated.TaskList(coordinatedBracketPlains) =>
+ case BracketPlainCoordinated.Classic(brackets) => Classic(brackets)
+ case BracketPlainCoordinated.TaskList(coordinatedBrackets) =>
val res: NonEmptyList[(Coordinates, TaskModel[A])] =
- coordinatedBracketPlains.map { case (coordinated, taskModelCoordinated) =>
- val mkSectionNumber =
- SectionNumber.TaskList(Coordinates(coordinated.taskSectionNumber, coordinated.taskNumber), _)
- val iterator: Iterator[SectionNumber] = LazyList.from(0).map(mkSectionNumber).iterator
- val taskModel: TaskModel[A] = taskModelCoordinated.toTaskModel(mkBrackets(iterator, _))
- (coordinated, taskModel)
+ coordinatedBrackets.map { case (coordinated, taskModelCoordinated) =>
+ (coordinated, taskModelCoordinated.toTaskModel())
}
TaskList(res)
}
-
- private def mkBrackets[A <: PageMode](
- iterator: Iterator[SectionNumber],
- brackets: NonEmptyList[BracketPlain[A]]
- ): NonEmptyList[Bracket[A]] =
- brackets.map {
- case BracketPlain.AddToList(iterations, source) =>
- Bracket.AddToList(
- iterations.map { it =>
- Bracket
- .AddToListIteration(
- it.singletons.map(singleton => SingletonWithNumber(singleton, iterator.next())),
- it.checkYourAnswers.map(CheckYourAnswersWithNumber(_, iterator.next())),
- RepeaterWithNumber(it.repeater, iterator.next())
- )
- },
- source
- )
- case BracketPlain.RepeatingPage(singletons, source) =>
- Bracket.RepeatingPage(singletons.map(singleton => SingletonWithNumber(singleton, iterator.next())), source)
- case BracketPlain.NonRepeatingPage(singleton, source) =>
- Bracket.NonRepeatingPage(singleton, iterator.next(), source)
- }
}
diff --git a/app/uk/gov/hmrc/gform/models/FastForward.scala b/app/uk/gov/hmrc/gform/models/FastForward.scala
index dff7ab706..7c38b6f64 100644
--- a/app/uk/gov/hmrc/gform/models/FastForward.scala
+++ b/app/uk/gov/hmrc/gform/models/FastForward.scala
@@ -39,8 +39,8 @@ sealed trait FastForward extends Product with Serializable {
case FastForward.CYA(SectionOrSummary.FormSummary) => FastForward.ffCYAFormSummary
case FastForward.CYA(SectionOrSummary.TaskSummary) => FastForward.ffCYATaskSummary
case FastForward.CYA(SectionOrSummary.Section(to)) =>
- FastForward.ffCYA + to.value
- }("back" + _.until.value)
+ FastForward.ffCYA + ":" + to.value
+ }("back:" + _.until.value)
def next(formModel: FormModel[Visibility], sn: SectionNumber): FastForward =
fold[FastForward](identity) { st =>
diff --git a/app/uk/gov/hmrc/gform/models/FormModel.scala b/app/uk/gov/hmrc/gform/models/FormModel.scala
index 08dd5ea97..cddb5b422 100644
--- a/app/uk/gov/hmrc/gform/models/FormModel.scala
+++ b/app/uk/gov/hmrc/gform/models/FormModel.scala
@@ -74,17 +74,10 @@ case class FormModel[A <: PageMode](
def allFormComponents(coordinates: Coordinates): List[FormComponent] =
availablePages(coordinates).flatMap(_.allFormComponents)
-
- def nextVisibleSectionNumber(
- tlSectionNumber: SectionNumber.TaskList
- ): SectionNumber.TaskList =
- availableSectionNumbers
- .collect { case t: SectionNumber.TaskList => t }
- .find(sn => tlSectionNumber.coordinates === sn.coordinates && sn.sectionNumber >= tlSectionNumber.sectionNumber)
- .getOrElse(throw new Exception("No more visible section numbers in the task"))
}
val nextVisibleSectionNumber: SectionNumber => Option[SectionNumber] = {
+ case r: SectionNumber.Legacy => availableSectionNumbers.headOption
case sectionNumber: SectionNumber.TaskList =>
availableSectionNumbers
.collect { case t: SectionNumber.TaskList => t }
@@ -170,7 +163,7 @@ case class FormModel[A <: PageMode](
fc.modelComponentId
}.toSet
- val exprsMetadata: List[ExprMetadata] = brackets.toBracketsPlains.toList.flatMap {
+ val exprsMetadata: List[ExprMetadata] = brackets.toBrackets.toList.flatMap {
case AllPageModelExpressions(exprMetadatas) => exprMetadatas
case _ => Nil
}
@@ -207,22 +200,23 @@ case class FormModel[A <: PageMode](
def flatMapRepeater(
f: (
- NonEmptyList[BracketPlain.AddToListIteration[A]],
+ NonEmptyList[Bracket.AddToListIteration[A]],
Section.AddToList
- ) => NonEmptyList[BracketPlain.AddToListIteration[A]]
+ ) => NonEmptyList[Bracket.AddToListIteration[A]]
): FormModel[A] = {
- def applyFToRepeater(bracketPlains: NonEmptyList[BracketPlain[A]]): NonEmptyList[BracketPlain[A]] =
- bracketPlains.map {
- case BracketPlain.AddToList(iterations, source) => BracketPlain.AddToList(f(iterations, source), source)
- case o => o
+ def applyFToRepeater(brackets: NonEmptyList[Bracket[A]]): NonEmptyList[Bracket[A]] =
+ brackets.map {
+ case Bracket.AddToList(defaultPage, iterations, source) =>
+ Bracket.AddToList(defaultPage, f(iterations, source), source)
+ case o => o
}
def bracketPlainCoordinated: BracketPlainCoordinated[A] =
brackets.toBracketPlainCoordinated.fold[BracketPlainCoordinated[A]] { classic =>
- BracketPlainCoordinated.Classic[A](applyFToRepeater(classic.bracketPlains))
+ BracketPlainCoordinated.Classic[A](applyFToRepeater(classic.brackets))
} { taskList =>
- BracketPlainCoordinated.TaskList[A](taskList.bracketPlains.map { case (coor, taskModelCoordinated) =>
+ BracketPlainCoordinated.TaskList[A](taskList.coordinatedBrackets.map { case (coor, taskModelCoordinated) =>
coor -> taskModelCoordinated.modifyBrackets(applyFToRepeater)
})
}
@@ -230,7 +224,8 @@ case class FormModel[A <: PageMode](
FormModel.fromPages(bracketPlainCoordinated, staticTypeInfo, revealingChoiceInfo, sumInfo, dataRetrieve)
}
- def apply(sectionNumber: SectionNumber): PageModel[A] = pageModelLookup(sectionNumber)
+ def apply(sectionNumber: SectionNumber): PageModel[A] =
+ pageModelLookup(sectionNumber)
def bracket(sectionNumber: SectionNumber): Bracket[A] =
brackets.withSectionNumber(sectionNumber)
@@ -372,7 +367,12 @@ object FormModel {
val singleton = Singleton(enrolmentSection.toPage).asInstanceOf[Singleton[A]]
FormModel.fromPages(
BracketPlainCoordinated.Classic(
- NonEmptyList.one(BracketPlain.NonRepeatingPage(singleton, enrolmentSection.toSection))
+ NonEmptyList.one(
+ Bracket.NonRepeatingPage(
+ SingletonWithNumber(singleton, SectionNumber.classicZero),
+ enrolmentSection.toSection
+ )
+ )
),
StaticTypeInfo.empty,
RevealingChoiceInfo.empty,
@@ -382,16 +382,16 @@ object FormModel {
}
def fromPages[A <: PageMode](
- bracketPlains: BracketPlainCoordinated[A],
+ brackets: BracketPlainCoordinated[A],
staticTypeInfo: StaticTypeInfo,
revealingChoiceInfo: RevealingChoiceInfo,
sumInfo: SumInfo,
dataRetrieve: Option[NonEmptyList[DataRetrieve]]
): FormModel[A] = {
- def standaloneSumInfo: StandaloneSumInfo = StandaloneSumInfo.from(bracketPlains, sumInfo)
+ def standaloneSumInfo: StandaloneSumInfo = StandaloneSumInfo.from(brackets, sumInfo)
- val bracketsWithSectionNumber = BracketsWithSectionNumber.fromBracketsPlains(bracketPlains)
+ val bracketsWithSectionNumber = BracketsWithSectionNumber.fromBracketCoordinated(brackets)
FormModel(
bracketsWithSectionNumber,
diff --git a/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala b/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala
index 3ba2cc687..102d56ef8 100644
--- a/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala
+++ b/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala
@@ -453,41 +453,81 @@ class FormModelBuilder[E, F[_]: Functor](
includeIf = Some(page.includeIf.fold(includeIf)(inIf => IncludeIf(And(inIf.booleanExpr, includeIf.booleanExpr))))
)
+ private def basicDefaultPage[T <: PageMode: FormModelExpander](
+ s: Section.AddToList,
+ templateSectionIndex: TemplateSectionIndex,
+ maybeCoordinates: Option[Coordinates],
+ data: VariadicFormData[SourceOrigin.OutOfDate]
+ ): Option[SingletonWithNumber[T]] = {
+ val addToListPages: Option[Page[Basic]] = s.defaultPage
+
+ addToListPages.map { page =>
+ val page2: Page[Basic] = mkSingleton(page, 1)(s)
+ val page3: Page[T] = implicitly[FormModelExpander[T]].lift(page2, data)
+ val sectionNumber = mkSectionNumber(
+ SectionNumber.Classic.AddToListPage.DefaultPage(templateSectionIndex),
+ maybeCoordinates
+ )
+ SingletonWithNumber[T](Singleton(page3), sectionNumber)
+ }
+ }
+
private def basicAddToList[T <: PageMode: FormModelExpander](
s: Section.AddToList,
- index: Int,
+ templateSectionIndex: TemplateSectionIndex,
+ maybeCoordinates: Option[Coordinates],
+ iterationIndex: Int,
data: VariadicFormData[SourceOrigin.OutOfDate]
- ): Option[BracketPlain.AddToListIteration[T]] = {
- val singletons: List[Singleton[T]] = {
- val addToListPages: NonEmptyList[Page[Basic]] =
- s.defaultPage.fold(s.pages) { dp =>
- val defaultIncludeIf = IncludeIf(Equals(Constant("1"), Count(s.addAnotherQuestion.id)))
- s.pages.prepend(
- dp.copy(includeIf =
- Some(
- dp.includeIf.fold(defaultIncludeIf)(inIf =>
- IncludeIf(And(inIf.booleanExpr, defaultIncludeIf.booleanExpr))
- )
- )
- )
- )
- }
+ ): Option[Bracket.AddToListIteration[T]] = {
+ val singletons: List[SingletonWithNumber[T]] = {
+ val addToListPages: NonEmptyList[Page[Basic]] = s.pages
- addToListPages.map { page =>
+ addToListPages.zipWithIndex.map { case (page, pageIndex) =>
val page1: Page[Basic] = s.includeIf.fold(page)(includeIf => mergeIncludeIfs(includeIf, page))
- val page2: Page[Basic] = mkSingleton(page1, index)(s)
+ val page2: Page[Basic] = mkSingleton(page1, iterationIndex)(s)
val page3: Page[T] = implicitly[FormModelExpander[T]].lift(page2, data)
- Singleton[T](page3)
+ val sectionNumber = mkSectionNumber(
+ SectionNumber.Classic.AddToListPage.Page(templateSectionIndex, iterationIndex, pageIndex),
+ maybeCoordinates
+ )
+ SingletonWithNumber[T](Singleton(page3), sectionNumber)
}.toList
}
- val repeater: Repeater[T] = mkRepeater(s, index)
+ val repeater: Repeater[T] = mkRepeater(s, iterationIndex)
- val checkYourAnswers: Option[CheckYourAnswers[T]] = s.cyaPage.map(c => mkCheckYourAnswers(c, s, index))
+ val checkYourAnswers: Option[CheckYourAnswersWithNumber[T]] = s.cyaPage.map(c =>
+ CheckYourAnswersWithNumber(
+ mkCheckYourAnswers(c, s, iterationIndex),
+ mkSectionNumber(
+ SectionNumber.Classic.AddToListPage.CyaPage(templateSectionIndex, iterationIndex),
+ maybeCoordinates
+ )
+ )
+ )
- NonEmptyList.fromList(singletons).map(BracketPlain.AddToListIteration(_, checkYourAnswers, repeater))
+ NonEmptyList
+ .fromList(singletons)
+ .map(
+ Bracket.AddToListIteration(
+ _,
+ checkYourAnswers,
+ RepeaterWithNumber(
+ repeater,
+ mkSectionNumber(
+ SectionNumber.Classic.AddToListPage.RepeaterPage(templateSectionIndex, iterationIndex),
+ maybeCoordinates
+ )
+ )
+ )
+ )
}
+ private def mkSectionNumber(
+ sn: SectionNumber.Classic,
+ coordinates: Option[Coordinates]
+ ): SectionNumber = coordinates.fold[SectionNumber](sn)(coordinates => SectionNumber.TaskList(coordinates, sn))
+
private def basic[T <: PageMode, U <: SectionSelectorType](
data: VariadicFormData[SourceOrigin.OutOfDate]
)(implicit formModelExpander: FormModelExpander[T], sectionIncluder: SectionSelector[U]): FormModel[T] = {
@@ -495,21 +535,33 @@ class FormModelBuilder[E, F[_]: Functor](
val allSections: AllSections = sectionIncluder.getSections(formTemplate)
val staticTypeInfo: StaticTypeInfo =
- allSections.sections.foldLeft(StaticTypeInfo.empty)(_ ++ _.staticTypeInfo)
+ allSections.sections.foldLeft(StaticTypeInfo.empty)(_ ++ _.section.staticTypeInfo)
val revealingChoiceInfo: RevealingChoiceInfo =
- allSections.sections.foldLeft(RevealingChoiceInfo.empty)(_ ++ _.revealingChoiceInfo)
-
- val brackets: BracketPlainCoordinated[T] = allSections.mapSection {
- case s: Section.NonRepeatingPage =>
- val page = formModelExpander.lift(s.page, data)
- Some(BracketPlain.NonRepeatingPage(Singleton[T](page), s))
- case s: Section.RepeatingPage => formModelExpander.liftRepeating(s, data)
- case s: Section.AddToList =>
- basicAddToList(s, 1, data).map(atl => BracketPlain.AddToList(NonEmptyList.one(atl), s))
+ allSections.sections.foldLeft(RevealingChoiceInfo.empty)(_ ++ _.section.revealingChoiceInfo)
+
+ val brackets: BracketPlainCoordinated[T] = allSections.mapSection { maybeCoordinates => indexedSection =>
+ indexedSection match {
+ case IndexedSection.SectionNoIndex(s) =>
+ val page = formModelExpander.lift(s.page, data)
+ val sectionNumber =
+ mkSectionNumber(SectionNumber.classicZero, maybeCoordinates)
+ Some(Bracket.NonRepeatingPage(SingletonWithNumber[T](Singleton(page), sectionNumber), s))
+ case IndexedSection.SectionIndex(s: Section.NonRepeatingPage, index) =>
+ val page = formModelExpander.lift(s.page, data)
+ val sectionNumber = mkSectionNumber(SectionNumber.Classic.NormalPage(index), maybeCoordinates)
+ Some(Bracket.NonRepeatingPage(SingletonWithNumber[T](Singleton(page), sectionNumber), s))
+ case IndexedSection.SectionIndex(s: Section.RepeatingPage, index) =>
+ formModelExpander.liftRepeating(s, index, data)
+ case IndexedSection.SectionIndex(s: Section.AddToList, index) =>
+ val defaultPage: Option[SingletonWithNumber[T]] = basicDefaultPage(s, index, maybeCoordinates, data)
+ basicAddToList(s, index, maybeCoordinates, 1, data).map(atl =>
+ Bracket.AddToList(defaultPage, NonEmptyList.one(atl), s)
+ )
+ }
}
- val sumInfo: SumInfo = allSections.sections.foldLeft(SumInfo.empty)(_ ++ _.sumInfo)
+ val sumInfo: SumInfo = allSections.sections.foldLeft(SumInfo.empty)(_ ++ _.section.sumInfo)
FormModel.fromPages(brackets, staticTypeInfo, revealingChoiceInfo, sumInfo, formTemplate.dataRetrieve)
}
@@ -524,16 +576,25 @@ class FormModelBuilder[E, F[_]: Functor](
}
private def answeredAddToListIterations[T <: PageMode: FormModelExpander](
- iteration: BracketPlain.AddToListIteration[T],
+ iteration: Bracket.AddToListIteration[T],
data: VariadicFormData[SourceOrigin.OutOfDate],
source: Section.AddToList
- ): NonEmptyList[BracketPlain.AddToListIteration[T]] = {
+ ): NonEmptyList[Bracket.AddToListIteration[T]] = {
def loop(
- repeater: Repeater[T],
- acc: NonEmptyList[BracketPlain.AddToListIteration[T]]
- ): NonEmptyList[BracketPlain.AddToListIteration[T]] =
- if (repeaterIsYes(repeater.addAnotherQuestion.modelComponentId, data)) {
- val maybeBracket = basicAddToList(source, repeater.index + 1, data)
+ repeater: RepeaterWithNumber[T],
+ acc: NonEmptyList[Bracket.AddToListIteration[T]]
+ ): NonEmptyList[Bracket.AddToListIteration[T]] =
+ if (repeaterIsYes(repeater.repeater.addAnotherQuestion.modelComponentId, data)) {
+ val templateSectionIndex: TemplateSectionIndex = repeater.sectionNumber.templateSectionIndex
+ val maybeCoordinates: Option[Coordinates] = repeater.sectionNumber.maybeCoordinates
+ val maybeBracket =
+ basicAddToList(
+ source,
+ templateSectionIndex,
+ maybeCoordinates,
+ repeater.repeater.index + 1,
+ data
+ ) // Add next iteration
maybeBracket
.map { bracket =>
loop(bracket.repeater, acc ::: NonEmptyList.one(bracket))
diff --git a/app/uk/gov/hmrc/gform/models/FormModelExpander.scala b/app/uk/gov/hmrc/gform/models/FormModelExpander.scala
index 2b11080cf..5a4de7c76 100644
--- a/app/uk/gov/hmrc/gform/models/FormModelExpander.scala
+++ b/app/uk/gov/hmrc/gform/models/FormModelExpander.scala
@@ -28,8 +28,9 @@ trait FormModelExpander[T <: PageMode] {
def lift(page: Page[Basic], data: VariadicFormData[SourceOrigin.OutOfDate]): Page[T]
def liftRepeating(
section: Section.RepeatingPage,
+ templateSectionIndex: TemplateSectionIndex,
data: VariadicFormData[SourceOrigin.OutOfDate]
- ): Option[BracketPlain.RepeatingPage[T]]
+ ): Option[Bracket.RepeatingPage[T]]
}
object FormModelExpander {
@@ -56,8 +57,9 @@ object FormModelExpander {
// Perfect we have access to FormModelVisibilityOptics, so we can evaluate 'section.repeats' expression
def liftRepeating(
section: Section.RepeatingPage,
+ templateSectionIndex: TemplateSectionIndex,
data: VariadicFormData[SourceOrigin.OutOfDate]
- ): Option[BracketPlain.RepeatingPage[DataExpanded]] = {
+ ): Option[Bracket.RepeatingPage[DataExpanded]] = {
val repeats = section.repeats
val bdRepeats: Option[BigDecimal] = fmvo.evalAndApplyTypeInfoFirst(repeats).numberRepresentation
val repeatCount = Math.min(bdRepeats.fold(0)(_.toInt), repeatsLimit)
@@ -65,7 +67,16 @@ object FormModelExpander {
val pageBasic: Page[Basic] = mkSingleton(section.page, index)(section)
Singleton(pageBasic.asInstanceOf[Page[DataExpanded]])
}
- NonEmptyList.fromList(singletons).map(BracketPlain.RepeatingPage(_, section))
+ NonEmptyList
+ .fromList(singletons)
+ .map(xs =>
+ Bracket.RepeatingPage(
+ xs.zipWithIndex.map { case (s, i) =>
+ SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i))
+ },
+ section
+ )
+ )
}
}
@@ -82,8 +93,9 @@ object FormModelExpander {
// Expand by data, we don't know value of 'section.repeats' expression here
def liftRepeating(
section: Section.RepeatingPage,
+ templateSectionIndex: TemplateSectionIndex,
data: VariadicFormData[SourceOrigin.OutOfDate]
- ): Option[BracketPlain.RepeatingPage[Interim]] = {
+ ): Option[Bracket.RepeatingPage[Interim]] = {
val baseIds: Set[BaseComponentId] =
section.allIds.map(_.baseComponentId).toSet
@@ -103,13 +115,25 @@ object FormModelExpander {
Singleton(pageBasic.asInstanceOf[Page[Interim]])
}
- NonEmptyList.fromList(singletons).map(BracketPlain.RepeatingPage(_, section))
+ NonEmptyList
+ .fromList(singletons)
+ .map(xs =>
+ Bracket.RepeatingPage(
+ xs.zipWithIndex.map { case (s, i) =>
+ SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i))
+ },
+ section
+ )
+ )
}
}
implicit val dependencyGraphVerification: FormModelExpander[DependencyGraphVerification] =
new FormModelExpander[DependencyGraphVerification] {
- def lift(page: Page[Basic], data: VariadicFormData[SourceOrigin.OutOfDate]): Page[DependencyGraphVerification] = {
+ def lift(
+ page: Page[Basic],
+ data: VariadicFormData[SourceOrigin.OutOfDate]
+ ): Page[DependencyGraphVerification] = {
val expanded = page.fields.flatMap {
case fc @ IsRevealingChoice(revealingChoice) =>
fc :: revealingChoice.options.flatMap(_.revealingFields)
@@ -120,11 +144,21 @@ object FormModelExpander {
}
def liftRepeating(
section: Section.RepeatingPage,
+ templateSectionIndex: TemplateSectionIndex,
data: VariadicFormData[SourceOrigin.OutOfDate]
- ): Option[BracketPlain.RepeatingPage[DependencyGraphVerification]] = {
+ ): Option[Bracket.RepeatingPage[DependencyGraphVerification]] = {
val pageBasic = mkSingleton(section.page, 1)(section)
- val singletons = NonEmptyList.one(Singleton(pageBasic.asInstanceOf[Page[DependencyGraphVerification]]))
- Some(BracketPlain.RepeatingPage(singletons, section))
+ val singletons = NonEmptyList.one(
+ Singleton(pageBasic.asInstanceOf[Page[DependencyGraphVerification]])
+ )
+ Some(
+ Bracket.RepeatingPage(
+ singletons.zipWithIndex.map { case (s, i) =>
+ SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i))
+ },
+ section
+ )
+ )
}
}
diff --git a/app/uk/gov/hmrc/gform/models/ProcessData.scala b/app/uk/gov/hmrc/gform/models/ProcessData.scala
index 2f1461287..1fb484afb 100644
--- a/app/uk/gov/hmrc/gform/models/ProcessData.scala
+++ b/app/uk/gov/hmrc/gform/models/ProcessData.scala
@@ -88,7 +88,6 @@ class ProcessDataService[F[_]: Monad](
val cachedObligations: Obligations = cache.form.thirdPartyData.obligations
- val mongoData = formModelOptics
for {
browserFormModelOptics <- FormModelOptics
@@ -106,16 +105,9 @@ class ProcessDataService[F[_]: Monad](
val dataUpd: FormModelOptics[DataOrigin.Browser] = new ObligationValidator {}
.validateWithDes(browserFormModelOptics, cachedObligations, obligations)
- val newVisitIndex =
- VisitIndex.updateSectionVisits(
- browserFormModelOptics.formModelRenderPageOptics.formModel,
- mongoData.formModelRenderPageOptics.formModel,
- cache.form.visitsIndex
- )
-
ProcessData(
dataUpd,
- newVisitIndex,
+ cache.form.visitsIndex,
obligations,
browserFormModelOptics.formModelVisibilityOptics.booleanExprCache
)
diff --git a/app/uk/gov/hmrc/gform/models/SectionIncluder.scala b/app/uk/gov/hmrc/gform/models/SectionIncluder.scala
index 26a784eb7..51f4292c2 100644
--- a/app/uk/gov/hmrc/gform/models/SectionIncluder.scala
+++ b/app/uk/gov/hmrc/gform/models/SectionIncluder.scala
@@ -40,7 +40,7 @@ object SectionSelector {
new SectionSelector[SectionSelectorType.WithDeclaration] {
def getSections(formTemplate: FormTemplate): AllSections = {
- val destinationSections: List[Section] = formTemplate.destinations.fold(destinationList =>
+ val destinationSections: List[Section.NonRepeatingPage] = formTemplate.destinations.fold(destinationList =>
destinationList.declarationSection.toList.map(_.toSection)
)(destinationPrint => Nil)
@@ -63,7 +63,7 @@ object SectionSelector {
new SectionSelector[SectionSelectorType.WithAcknowledgement] {
def getSections(formTemplate: FormTemplate): AllSections = {
- val destinationSections: List[Section] = formTemplate.destinations.fold(destinationList =>
+ val destinationSections: List[Section.NonRepeatingPage] = formTemplate.destinations.fold(destinationList =>
destinationList.declarationSection.toList.map(_.toSection) ++ List(
destinationList.acknowledgementSection.toSection
)
diff --git a/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala b/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala
index ca0acede1..4318291de 100644
--- a/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala
+++ b/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala
@@ -25,5 +25,5 @@ case class SingletonWithNumber[A <: PageMode](
def map[B <: PageMode](f: Singleton[A] => Singleton[B]): SingletonWithNumber[B] =
SingletonWithNumber(f(singleton), sectionNumber)
- def toPageModelWithNumber: (PageModel[A], SectionNumber) = (singleton, sectionNumber)
+ def toPageModelWithNumber4: (PageModel[A], SectionNumber) = (singleton, sectionNumber)
}
diff --git a/app/uk/gov/hmrc/gform/models/TaskModel.scala b/app/uk/gov/hmrc/gform/models/TaskModel.scala
index e610262fa..fe42b786c 100644
--- a/app/uk/gov/hmrc/gform/models/TaskModel.scala
+++ b/app/uk/gov/hmrc/gform/models/TaskModel.scala
@@ -34,11 +34,11 @@ sealed trait TaskModel[A <: PageMode] extends Product with Serializable {
def mapBracket[B <: PageMode](f: Bracket[A] => Bracket[B]): TaskModel[B] =
fold(_ => TaskModel.allHidden[B])(editable => TaskModel.editable(editable.brackets.map(f)))
- val toTaskModelCoordinated: TaskModelCoordinated[A] =
- fold[TaskModelCoordinated[A]](_ => TaskModelCoordinated.allHidden)(editable =>
- TaskModelCoordinated.editable(editable.brackets.map(_.toPlainBracket))
- )
+ def toTaskModel(): TaskModel[A] =
+ fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](editable.brackets))
+ def modifyBrackets(f: NonEmptyList[Bracket[A]] => NonEmptyList[Bracket[A]]): TaskModel[A] =
+ fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](f(editable.brackets)))
}
object TaskModel {
diff --git a/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala b/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala
deleted file mode 100644
index 0ca78d203..000000000
--- a/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 uk.gov.hmrc.gform.models
-
-import cats.data.NonEmptyList
-
-sealed trait TaskModelCoordinated[A <: PageMode] extends Product with Serializable {
-
- def fold[B](f: TaskModelCoordinated.AllHidden[A] => B)(g: TaskModelCoordinated.Editable[A] => B): B =
- this match {
- case n: TaskModelCoordinated.AllHidden[A] => f(n)
- case r: TaskModelCoordinated.Editable[A] => g(r)
- }
-
- def toTaskModel(f: NonEmptyList[BracketPlain[A]] => NonEmptyList[Bracket[A]]): TaskModel[A] =
- fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](f(editable.brackets)))
-
- def modifyBrackets(f: NonEmptyList[BracketPlain[A]] => NonEmptyList[BracketPlain[A]]): TaskModelCoordinated[A] =
- fold(_ => TaskModelCoordinated.allHidden[A])(editable => TaskModelCoordinated.editable[A](f(editable.brackets)))
-}
-
-object TaskModelCoordinated {
-
- def editable[A <: PageMode](bs: NonEmptyList[BracketPlain[A]]): TaskModelCoordinated[A] =
- TaskModelCoordinated.Editable[A](bs)
- def allHidden[A <: PageMode]: TaskModelCoordinated[A] = AllHidden[A]()
-
- case class Editable[A <: PageMode](brackets: NonEmptyList[BracketPlain[A]]) extends TaskModelCoordinated[A]
- case class AllHidden[A <: PageMode]() extends TaskModelCoordinated[A]
-}
diff --git a/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala b/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala
index c8871b4cf..03244a23b 100644
--- a/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala
+++ b/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala
@@ -53,8 +53,8 @@ object PDFPageModelBuilder {
buildFromSingleton(
cache,
envelopeWithMapping,
- nonRepeatingPage.singleton,
- nonRepeatingPage.sectionNumber,
+ nonRepeatingPage.singleton.singleton,
+ nonRepeatingPage.singleton.sectionNumber,
nonRepeatingPage.source,
validationResult,
formModelOptics.formModelVisibilityOptics
diff --git a/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala b/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala
index cf6cbd6ea..c197ee962 100644
--- a/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala
+++ b/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala
@@ -19,7 +19,6 @@ package uk.gov.hmrc.gform.sharedmodel.form
import cats.implicits._
import play.api.libs.json.{ JsArray, JsError, JsObject, JsSuccess, JsValue, Json, OFormat }
import scala.util.Try
-import uk.gov.hmrc.gform.models.{ DataExpanded, FormModel, PageModel }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, SectionNumber, TaskNumber, TaskSectionNumber }
sealed trait VisitIndex extends Product with Serializable {
@@ -31,7 +30,10 @@ sealed trait VisitIndex extends Product with Serializable {
}
private def visitClassic(sectionNumber: SectionNumber.Classic): VisitIndex =
- fold[VisitIndex](classic => VisitIndex.Classic(classic.visitsIndex + sectionNumber.sectionNumber))(identity)
+ fold[VisitIndex] { classic =>
+ val updatedVisits = VisitIndex.Classic(classic.visitsIndex + sectionNumber)
+ updatedVisits
+ }(identity)
private def visitTaskList(sectionNumber: SectionNumber.TaskList): VisitIndex =
fold[VisitIndex](identity) { taskList =>
@@ -49,13 +51,16 @@ sealed trait VisitIndex extends Product with Serializable {
def unvisit(sectionNumber: SectionNumber): VisitIndex = sectionNumber.fold(unvisitClassic)(unvisitTaskList)
private def unvisitClassic(sectionNumber: SectionNumber.Classic): VisitIndex =
- fold[VisitIndex](classic => VisitIndex.Classic(classic.visitsIndex - sectionNumber.sectionNumber))(identity)
+ fold[VisitIndex] { classic =>
+ val updatedVisits = VisitIndex.Classic(classic.visitsIndex - sectionNumber)
+ updatedVisits
+ }(identity)
private def unvisitTaskList(sectionNumber: SectionNumber.TaskList): VisitIndex =
fold[VisitIndex](identity)(identity)
private def containsClassic(sectionNumber: SectionNumber.Classic): Boolean =
- fold[Boolean](classic => classic.visitsIndex.contains(sectionNumber.sectionNumber))(_ => false)
+ fold[Boolean](classic => classic.visitsIndex.contains(sectionNumber))(_ => false)
private def containsTaskList(sectionNumber: SectionNumber.TaskList): Boolean =
fold[Boolean](_ => false)(taskList =>
@@ -67,8 +72,8 @@ sealed trait VisitIndex extends Product with Serializable {
object VisitIndex {
- final case class Classic(visitsIndex: Set[Int]) extends VisitIndex
- final case class TaskList(visitsIndex: Map[Coordinates, Set[Int]]) extends VisitIndex
+ final case class Classic(visitsIndex: Set[SectionNumber.Classic]) extends VisitIndex
+ final case class TaskList(visitsIndex: Map[Coordinates, Set[SectionNumber.Classic]]) extends VisitIndex
val key: String = "visitsIndex"
@@ -77,68 +82,38 @@ object VisitIndex {
implicit val format: OFormat[VisitIndex] = OFormat(
(jsValue: JsValue) =>
(jsValue \ key).toOption match {
- case None => JsError(s"Missing '$key' field. Failed to decode VisitIndex from: $jsValue")
- case Some(a: JsArray) => JsSuccess(Classic(a.value.map(_.as[Int]).toSet))
+ case None => JsError(s"Missing '$key' field. Failed to decode VisitIndex from: $jsValue")
+ case Some(a: JsArray) =>
+ a.validate[Set[SectionNumber.Classic]] match {
+ case JsSuccess(visits, _) => JsSuccess(Classic(visits))
+ case JsError(errors) => JsSuccess(Classic(Set.empty[SectionNumber.Classic]))
+ }
case Some(o: JsObject) =>
- val res: Try[List[(Coordinates, Set[Int])]] =
- o.value.toList.traverse { case (k, v) =>
- Try(k.split(",").toList.map(_.toInt)).collect { case taskSectionNumber :: taskNumber :: Nil =>
- (Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber))) -> v.as[Set[Int]]
+ o.value.toList
+ .traverse { case (k, v) =>
+ Try(k.split(",").toList).collect { case taskSectionNumber :: taskNumber :: Nil =>
+ val key = Coordinates(TaskSectionNumber(taskSectionNumber.toInt), TaskNumber(taskNumber.toInt))
+ val visits = v.validate[Set[SectionNumber.Classic]].getOrElse(Set.empty[SectionNumber.Classic])
+ key -> visits
}
}
- res.fold(
- error => JsError("Failed to decode VisitIndex for TaskList from json: " + jsValue),
- xs => JsSuccess(TaskList(xs.toMap))
- )
+ .fold(
+ error => JsError("Failed to decode VisitIndex for TaskList from json: " + jsValue),
+ xs => JsSuccess(TaskList(xs.toMap))
+ )
case Some(unexpected) => JsError("Unknown type. Failed to decode VisitIndex from json: " + unexpected)
},
(visitIndex: VisitIndex) =>
visitIndex match {
case Classic(visitsIndex) => Json.obj(key -> Json.toJson(visitsIndex))
case TaskList(visitsIndex) =>
- val s: Map[String, JsValue] = visitsIndex.toList.map {
- case (Coordinates(TaskSectionNumber(tsc), TaskNumber(tn)), indexes) =>
- List(tsc, tn).mkString(",") -> Json.toJson(indexes)
- }.toMap
+ val s: Map[String, JsValue] =
+ visitsIndex.toList.map { case (Coordinates(TaskSectionNumber(tsc), TaskNumber(tn)), indexes) =>
+ List(tsc, tn).mkString(",") -> {
+ Json.toJson(indexes)
+ }
+ }.toMap
Json.obj(key -> Json.toJson(s))
}
)
-
- def updateSectionVisits(
- formModel: FormModel[DataExpanded],
- mongoFormModel: FormModel[DataExpanded],
- visitsIndex: VisitIndex
- ): VisitIndex = {
- def update(
- xs: Set[Int],
- f: Int => SectionNumber,
- pages: FormModel[DataExpanded] => List[PageModel[DataExpanded]]
- ): Set[Int] =
- xs.map { index =>
- Try(mongoFormModel(f(index))).toOption.fold(-1) { page =>
- page.allFormComponents.headOption.fold(-1) { mongoHead =>
- val firstComponentId = mongoHead.id
- pages(formModel).indexWhere { pageModel =>
- pageModel.allFormComponents.headOption.fold(false)(_.id === firstComponentId)
- }
-
- }
- }
- }.filterNot(_ === -1)
-
- visitsIndex.fold[VisitIndex] { classic =>
- VisitIndex.Classic(update(classic.visitsIndex, SectionNumber.Classic(_), _.pages.toList))
- } { taskList =>
- VisitIndex.TaskList {
- taskList.visitsIndex.map { case (coordinates, indexes) =>
- coordinates ->
- update(
- indexes,
- SectionNumber.TaskList(coordinates, _),
- _.taskList.availablePages(coordinates).toList
- )
- }
- }
- }
- }
}
diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala
index 73a139037..e7e4c8cfd 100644
--- a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala
+++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala
@@ -19,7 +19,7 @@ package uk.gov.hmrc.gform.sharedmodel.formtemplate
import cats.data.NonEmptyList
import julienrf.json.derived
import play.api.libs.json.OFormat
-import uk.gov.hmrc.gform.models.AllSections
+import uk.gov.hmrc.gform.models.{ AllSections, IndexedSection }
import uk.gov.hmrc.gform.sharedmodel.formtemplate.JsonUtils._
import uk.gov.hmrc.gform.sharedmodel.SmartString
import com.softwaremill.quicklens._
@@ -44,7 +44,9 @@ sealed trait FormKind extends Product with Serializable {
}
val allSections: AllSections = fold[AllSections] { classic =>
- AllSections.Classic(classic.sections)
+ AllSections.Classic(classic.sections.zipWithIndex.map { case (s, i) =>
+ IndexedSection.SectionIndex(s, TemplateSectionIndex(i))
+ })
} { taskList =>
AllSections.TaskList {
taskList.sections.zipWithIndex.flatMap { case (taskSection, taskSectionIndex) =>
@@ -52,7 +54,7 @@ sealed trait FormKind extends Product with Serializable {
Coordinates(TaskSectionNumber(taskSectionIndex), TaskNumber(taskIndex)) -> updateIncludeIf(
task.sections.toList,
task.includeIf
- )
+ ).zipWithIndex.map { case (s, i) => IndexedSection.SectionIndex(s, TemplateSectionIndex(i)) }
}
}
}
@@ -62,9 +64,12 @@ sealed trait FormKind extends Product with Serializable {
case _ => Map.empty
}.toMap
private val repeatingMap =
- allSections.sections.collect { case Section.RepeatingPage(page, _) => page.allIds.map(i => (i, i)) }.flatten.toMap
+ allSections.sections
+ .collect { case IndexedSection.SectionIndex(Section.RepeatingPage(page, _), _) => page.allIds.map(i => (i, i)) }
+ .flatten
+ .toMap
private val groupMap = allSections.sections
- .collect { case Section.NonRepeatingPage(page) => page.allFieldsNested }
+ .collect { case IndexedSection.SectionIndex(Section.NonRepeatingPage(page), _) => page.allFieldsNested }
.flatten
.collect { case fc @ IsGroup(_) => fc.childrenFormComponents.map(c => (c.id, fc.id)) }
.flatten
diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala
index 9c47b6271..6018c3fd1 100644
--- a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala
+++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala
@@ -19,51 +19,68 @@ package uk.gov.hmrc.gform.sharedmodel.formtemplate
import cats.Eq
import cats.implicits._
import play.api.libs.json._
-import scala.util.{ Failure, Success, Try }
+import scala.util.Try
+import uk.gov.hmrc.gform.models.{ FormModel, Visibility }
sealed trait SectionNumber extends Ordered[SectionNumber] with Product with Serializable {
+ def isAddToList: Boolean = this match {
+ case _: SectionNumber.Legacy => false
+ case _: SectionNumber.Classic.NormalPage => false
+ case _: SectionNumber.Classic.RepeatedPage => false
+ case _: SectionNumber.Classic.AddToListPage.DefaultPage => true
+ case _: SectionNumber.Classic.AddToListPage.Page => true
+ case _: SectionNumber.Classic.AddToListPage.CyaPage => true
+ case _: SectionNumber.Classic.AddToListPage.RepeaterPage => true
+ case SectionNumber.TaskList(_, sectionNumber) => sectionNumber.isAddToList
+ }
+
+ def isAddToListRepeaterPage: Boolean = this match {
+ case _: SectionNumber.Classic.AddToListPage.RepeaterPage => true
+ case SectionNumber.TaskList(_, sectionNumber) => sectionNumber.isAddToListRepeaterPage
+ case _ => false
+ }
+
+ def templateSectionIndex: TemplateSectionIndex =
+ fold(_.sectionIndex)(_.sectionNumber.sectionIndex)
+
def fold[B](f: SectionNumber.Classic => B)(g: SectionNumber.TaskList => B): B =
this match {
case n: SectionNumber.Classic => f(n)
case r: SectionNumber.TaskList => g(r)
+ case r: SectionNumber.Legacy => throw new Exception("Trying to fold over Legacy")
}
def isTaskList: Boolean = fold(_ => false)(_ => true)
override def compare(that: SectionNumber): Int = (this, that) match {
- case (SectionNumber.Classic(sn0), SectionNumber.Classic(sn1)) => sn0.compare(sn1)
+ case (a: SectionNumber.Classic, b: SectionNumber.Classic) => a.compareClassic(b)
case (SectionNumber.TaskList(Coordinates(tsn0, tn0), sn0), SectionNumber.TaskList(Coordinates(tsn1, tn1), sn1)) =>
if (tsn0 === tsn1 && tn0 === tn1) sn0.compare(sn1)
else if (tsn0 === tsn1) tn0.compare(tn1)
else tsn0.compare(tsn1)
-
- case (l, r) => throw new Exception(s"Cannot compare SectionNumber: $l with $r")
+ case (a: SectionNumber.Legacy, _) => -1
+ case (_, a: SectionNumber.Legacy) => 1
+ case (l, r) => throw new Exception(s"Cannot compare SectionNumber: $l with $r")
}
- def toCoordinates: Option[Coordinates] =
- fold[Option[Coordinates]](_ => None)(taskList => Some(taskList.coordinates))
+ def maybeCoordinates: Option[Coordinates] =
+ fold(_ => Option.empty[Coordinates])(tl => Some(tl.coordinates))
def toCoordinatesUnsafe: Coordinates =
fold[Coordinates](_ => throw new Exception(s"Cannot convert $this to coordinates"))(_.coordinates)
- def contains(coordinates: Coordinates): Boolean = toCoordinates.fold(false)(_ === coordinates)
+ def contains(coordinates: Coordinates): Boolean = maybeCoordinates.fold(false)(_ === coordinates)
- def increment: SectionNumber = fold[SectionNumber] { classic =>
- SectionNumber.Classic(classic.sectionNumber + 1)
- } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) =>
- SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber + 1)
- }
- def decrement: SectionNumber = fold[SectionNumber] { classic =>
- SectionNumber.Classic(classic.sectionNumber - 1)
- } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) =>
- SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber - 1)
- }
-
- def +(i: Int): SectionNumber = fold[SectionNumber] { classic =>
- SectionNumber.Classic(classic.sectionNumber + i)
- } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) =>
- SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber + i)
+ def increment(formModel: FormModel[Visibility]): SectionNumber = {
+ val as = formModel.availableSectionNumbers
+ val indexOfThis: Int = as.indexOf(this)
+ val nextIndex = indexOfThis + 1
+ if (as.size === nextIndex) {
+ this
+ } else {
+ formModel.availableSectionNumbers(nextIndex)
+ }
}
def unsafeToClassic: SectionNumber.Classic =
@@ -72,44 +89,203 @@ sealed trait SectionNumber extends Ordered[SectionNumber] with Product with Seri
fold(classic => throw new Exception("unsafeToTaskList invoked on Classic type: " + classic))(identity)
def value: String = this match {
- case SectionNumber.Classic(value) => value.toString
+ case SectionNumber.Classic.NormalPage(TemplateSectionIndex(sectionIndex)) => "n" + sectionIndex.toString
+ case SectionNumber.Classic.AddToListPage.DefaultPage(TemplateSectionIndex(sectionIndex)) =>
+ "ad" + sectionIndex.toString
+ case SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(sectionIndex), iterationNumber, pageNumber) =>
+ "ap" + sectionIndex.toString + "." + iterationNumber.toString + "." + pageNumber.toString
+ case SectionNumber.Classic.AddToListPage.CyaPage(TemplateSectionIndex(sectionIndex), iterationNumber) =>
+ "ac" + sectionIndex.toString + "." + iterationNumber.toString
+ case SectionNumber.Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(sectionIndex), iterationNumber) =>
+ "ar" + sectionIndex.toString + "." + iterationNumber.toString
+ case SectionNumber.Classic.RepeatedPage(TemplateSectionIndex(sectionIndex), pageNumber) =>
+ "r" + sectionIndex.toString + "." + pageNumber.toString
case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) =>
- List(taskSectionNumber.value, taskNumber.value, sectionNumber).mkString(",")
- }
+ List(taskSectionNumber.value.toString, taskNumber.value.toString, sectionNumber.value).mkString(",")
- def numberValue: Int = this match {
- case SectionNumber.Classic(value) => value
- case SectionNumber.TaskList(coordinates, sectionNumber) => coordinates.numberValue + sectionNumber
+ case r: SectionNumber.Legacy => throw new Exception("Trying to render Legacy")
}
}
object SectionNumber {
- final case class Classic(sectionNumber: Int) extends SectionNumber
+
+ sealed trait Classic extends SectionNumber with Product with Serializable {
+ def sectionIndex: TemplateSectionIndex
+
+ def compareClassic(that: Classic): Int = (this, that) match {
+ case (Classic.NormalPage(sn0), Classic.NormalPage(sn1)) => sn0.index.compare(sn1.index)
+ case (Classic.NormalPage(sn0), Classic.AddToListPage.DefaultPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.NormalPage(sn0), Classic.AddToListPage.Page(sn1, _, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.NormalPage(sn0), Classic.AddToListPage.CyaPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.NormalPage(sn0), Classic.AddToListPage.RepeaterPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.NormalPage(sn0), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.DefaultPage(sn0), Classic.NormalPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.Page(sn0, _, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.CyaPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.RepeaterPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.RepeatedPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 => sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.DefaultPage(sn0), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.Page(sn0, _, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.CyaPage(sn0, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.AddToListPage.RepeaterPage(sn0, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.DefaultPage(sn1)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.Page(sn1, _, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.CyaPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.RepeaterPage(sn1, _)) if sn0 =!= sn1 =>
+ sn0.index.compare(sn1.index)
+ case (a: Classic.AddToListPage, b: Classic.AddToListPage) => a.compareAddToListPage(b)
+ case (Classic.RepeatedPage(sn0, pn0), Classic.RepeatedPage(sn1, pn1)) =>
+ if (sn0 === sn1) pn0.compare(pn1)
+ else sn0.index.compare(sn1.index)
+ case (l, r) => throw new Exception(s"SectionNumber $l cannot have same sectionIndex as $r")
+ }
+ }
+ object Classic {
+ case class NormalPage(sectionIndex: TemplateSectionIndex) extends Classic
+ case class RepeatedPage(sectionIndex: TemplateSectionIndex, pageNumber: Int) extends Classic
+ sealed trait AddToListPage extends Classic {
+ def compareAddToListPage(that: AddToListPage): Int = (this, that) match {
+ case (AddToListPage.DefaultPage(sn0), AddToListPage.DefaultPage(sn1)) => compDef(0, sn0, sn1)
+ case (AddToListPage.DefaultPage(sn0), AddToListPage.Page(sn1, _, _)) => compDef(-1, sn0, sn1)
+ case (AddToListPage.Page(sn0, _, _), AddToListPage.DefaultPage(sn1)) => compDef(1, sn0, sn1)
+ case (AddToListPage.DefaultPage(sn0), AddToListPage.CyaPage(sn1, _)) => compDef(-1, sn0, sn1)
+ case (AddToListPage.CyaPage(sn1, _), AddToListPage.DefaultPage(sn0)) => compDef(1, sn0, sn1)
+ case (AddToListPage.DefaultPage(sn0), AddToListPage.RepeaterPage(sn1, _)) => compDef(-1, sn0, sn1)
+ case (AddToListPage.RepeaterPage(sn0, _), AddToListPage.DefaultPage(sn1)) => compDef(1, sn0, sn1)
+ case (AddToListPage.CyaPage(sn0, in0), AddToListPage.Page(sn1, in1, _)) => comp(1, sn0, in0, sn1, in1)
+ case (AddToListPage.Page(sn0, in0, _), AddToListPage.CyaPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1)
+ case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.Page(sn1, in1, _)) => comp(1, sn0, in0, sn1, in1)
+ case (AddToListPage.Page(sn0, in0, _), AddToListPage.RepeaterPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1)
+ case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.CyaPage(sn1, in1)) => comp(1, sn0, in0, sn1, in1)
+ case (AddToListPage.CyaPage(sn0, in0), AddToListPage.RepeaterPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1)
+ case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.RepeaterPage(sn1, in1)) => comp(0, sn0, in0, sn1, in1)
+ case (AddToListPage.CyaPage(sn0, in0), AddToListPage.CyaPage(sn1, in1)) => comp(0, sn0, in0, sn1, in1)
+ case (AddToListPage.Page(sn0, in0, pn0), AddToListPage.Page(sn1, in1, pn1)) =>
+ comp(pn0.compare(pn1), sn0, in0, sn1, in1)
+ }
+
+ private def compDef(default: Int, sn0: TemplateSectionIndex, sn1: TemplateSectionIndex): Int =
+ if (sn0 === sn1) default
+ else sn0.index.compare(sn1.index)
+
+ private def comp(default: Int, sn0: TemplateSectionIndex, in0: Int, sn1: TemplateSectionIndex, in1: Int): Int =
+ if (sn0 === sn1 && in0 === in1) default
+ else if (sn0 === sn1) in0.compare(in1)
+ else sn0.index.compare(sn1.index)
+ }
+
+ object AddToListPage {
+ case class DefaultPage(sectionIndex: TemplateSectionIndex) extends AddToListPage
+ case class Page(sectionIndex: TemplateSectionIndex, iterationNumber: Int, pageNumber: Int) extends AddToListPage
+ case class CyaPage(sectionIndex: TemplateSectionIndex, iterationNumber: Int) extends AddToListPage
+ case class RepeaterPage(sectionIndex: TemplateSectionIndex, iterationNumber: Int) extends AddToListPage
+ }
+ implicit val equal: Eq[SectionNumber.Classic] = Eq.fromUniversalEquals
+ }
+
+ final case class Legacy(
+ sectionNumber: String
+ ) extends SectionNumber
+
final case class TaskList(
coordinates: Coordinates,
- sectionNumber: Int
+ sectionNumber: Classic
) extends SectionNumber
- val classicZero = Classic(0)
- val taskListZero = TaskList(Coordinates(TaskSectionNumber(0), TaskNumber(0)), 0)
+ object TaskList {
+ implicit val equal: Eq[SectionNumber.TaskList] = Eq.fromUniversalEquals
+ }
+
+ val classicZero = Classic.NormalPage(TemplateSectionIndex(0))
+ val taskListZero = TaskList(Coordinates(TaskSectionNumber(0), TaskNumber(0)), classicZero)
implicit val equal: Eq[SectionNumber] = Eq.fromUniversalEquals
- implicit val format: Format[SectionNumber] = Format[SectionNumber](
- Reads[SectionNumber] {
+ implicit val format: Format[SectionNumber.Classic] = Format[SectionNumber.Classic](
+ Reads[SectionNumber.Classic] {
case JsString(value) =>
- parse(value) match {
- case Success(sectionNumber) => JsSuccess(sectionNumber)
- case Failure(error) => JsError(s"Invalid section number: $value. Error: $error")
+ parseClassic(value) match {
+ case Some(sectionNumber) => JsSuccess(sectionNumber)
+ case None => JsError(s"Invalid section number: $value")
}
case unknown => JsError(s"JsString value expected, got: $unknown")
},
- Writes[SectionNumber](a => JsString(a.value))
+ Writes[SectionNumber.Classic](a => JsString(a.value))
)
- def parse(value: String): Try[SectionNumber] =
- value.split(",").toList.traverse(n => Try(n.toInt)).collect {
- case sectionNumber :: Nil => SectionNumber.Classic(sectionNumber)
- case taskSectionNumber :: taskNumber :: sectionNumber :: Nil =>
- SectionNumber.TaskList(Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber)), sectionNumber)
+ implicit val writer: Writes[SectionNumber] = Writes[SectionNumber](a => JsString(a.value))
+
+ // format: off
+ private val NormalPageRegex = "^n(\\d+)$".r
+ private val AddToListDefaultPageRegex = "^ad(\\d+)$".r
+ private val AddToListPageRegex = "^ap(\\d+)\\.(\\d+)\\.(\\d+)$".r
+ private val AddToListCyaPageRegex = "^ac(\\d+)\\.(\\d+)$".r
+ private val AddToListRepeaterPageRegex = "^ar(\\d+)\\.(\\d+)$".r
+ private val RepeatedPageRegex = "^r(\\d+)\\.(\\d+)$".r
+ // format: on
+
+ def parseClassic(string: String): Option[SectionNumber.Classic] =
+ string match {
+ case NormalPageRegex(sectionIndex) =>
+ Some(SectionNumber.Classic.NormalPage(TemplateSectionIndex(sectionIndex.toInt)))
+ case AddToListDefaultPageRegex(sectionIndex) =>
+ Some(SectionNumber.Classic.AddToListPage.DefaultPage(TemplateSectionIndex(sectionIndex.toInt)))
+ case AddToListPageRegex(sectionIndex, iterationNumber, pageNumber) =>
+ Some(
+ SectionNumber.Classic.AddToListPage
+ .Page(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt, pageNumber.toInt)
+ )
+ case AddToListCyaPageRegex(sectionIndex, iterationNumber) =>
+ Some(
+ SectionNumber.Classic.AddToListPage
+ .CyaPage(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt)
+ )
+ case AddToListRepeaterPageRegex(sectionIndex, iterationNumber) =>
+ Some(
+ SectionNumber.Classic.AddToListPage
+ .RepeaterPage(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt)
+ )
+ case RepeatedPageRegex(sectionIndex, pageNumber) =>
+ Some(SectionNumber.Classic.RepeatedPage(TemplateSectionIndex(sectionIndex.toInt), pageNumber.toInt))
+ case _ => None
+ }
+
+ def parse(string: String): Option[SectionNumber] =
+ if (string.contains(",")) {
+ string.split(",").toList match {
+ case maybeTaskSectionNumber :: maybeTaskNumber :: sectionNumber :: Nil =>
+ (
+ Try(maybeTaskSectionNumber.toInt).toOption,
+ Try(maybeTaskNumber.toInt).toOption,
+ parseClassic(sectionNumber)
+ ) match {
+ case (Some(taskSectionNumber), Some(taskNumber), Some(sn: SectionNumber.Classic)) =>
+ Some(
+ SectionNumber
+ .TaskList(
+ Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber)),
+ sn
+ )
+ )
+ case _ => None
+ }
+ case _ => None
+ }
+ } else {
+ parseClassic(string)
}
}
diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala
new file mode 100644
index 000000000..935d66f74
--- /dev/null
+++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 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 uk.gov.hmrc.gform.sharedmodel.formtemplate
+
+import cats.Eq
+
+final case class TemplateSectionIndex(index: Int) extends AnyVal
+
+object TemplateSectionIndex {
+ implicit val equal: Eq[TemplateSectionIndex] = Eq.fromUniversalEquals
+}
diff --git a/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala b/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala
index 49442c06f..0cb25b1d7 100644
--- a/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala
+++ b/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala
@@ -639,7 +639,7 @@ object SummaryRenderingService {
val (accumulatedRows, summaryLists) =
brackets.foldLeft((Map.empty[HtmlFormat.Appendable, List[SummaryListRow]], List.empty[HtmlFormat.Appendable])) {
- case ((accumulatedRows, accList), bracket @ Bracket.AddToList(_, _)) =>
+ case ((accumulatedRows, accList), bracket @ Bracket.AddToList(_, _, _)) =>
accumulatedRows.headOption match {
case Some((heading, rows)) =>
val updatedList =
@@ -668,7 +668,10 @@ object SummaryRenderingService {
}
}
- case ((accumulatedRows, accList), Bracket.NonRepeatingPage(singleton, sectionNumber, source)) =>
+ case (
+ (accumulatedRows, accList),
+ Bracket.NonRepeatingPage(SingletonWithNumber(singleton, sectionNumber), source)
+ ) =>
val middleRows = middleSummaryListRows(singleton, sectionNumber)
val pageTitle = getPageTitle(source, singleton.page)
diff --git a/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala b/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala
index 4b2078e53..3c9625df0 100644
--- a/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala
+++ b/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala
@@ -92,7 +92,7 @@ object CannotStartYetResolver {
val dependingFcIdLookup: Map[Coordinates, Set[BaseComponentId]] = taskList.brackets.toList.map {
case (coordinates, brackets) =>
val allExprs: List[Expr] =
- brackets.toBracketsList.flatMap(_.toPlainBracket.allExprs(formModel))
+ brackets.toBracketsList.flatMap(_.allExprs(formModel))
val baseComponentIds: List[BaseComponentId] =
allExprs.flatMap(_.leafs(formModel)).collect {
diff --git a/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala b/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala
index 4689e1773..e1ef12fdc 100644
--- a/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala
+++ b/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala
@@ -87,7 +87,7 @@ class TaskListController(
sectionNumber,
sectionTitle4Ga,
SuppressErrors.Yes,
- List(FastForward.StopAt(sectionNumber.increment))
+ List(FastForward.StopAt(sectionNumber.increment(formModelOptics.formModelVisibilityOptics.formModel)))
)
if (isCompleted) {
@@ -106,10 +106,18 @@ class TaskListController(
)
).pure[Future]
} else {
+ val coordinates = Coordinates(taskSectionNumber, taskNumber)
val formModel = formModelOptics.formModelVisibilityOptics.formModel
- val nextVisibleSectionNumber = formModel.taskList.nextVisibleSectionNumber(
- SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), 0)
- )
+
+ val maybeSn: Option[SectionNumber] = formModel.availableSectionNumbers.collectFirst {
+ case sectionNumber if sectionNumber.maybeCoordinates.contains(coordinates) => sectionNumber
+ }
+
+ val nextVisibleSectionNumber =
+ maybeSn.getOrElse(
+ throw new Exception(s"Cannot determine first sectionNumber for task with $coordinates")
+ )
+
val isAddToListSectionNumber = formModel.addToListSectionNumbers.contains(nextVisibleSectionNumber)
val sn =
@@ -122,7 +130,17 @@ class TaskListController(
Redirect(sectionUrl(sn)).pure[Future]
}
} else {
- val sn = SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), 0)
+
+ val coordinates = Coordinates(taskSectionNumber, taskNumber)
+
+ val formModel = formModelOptics.formModelVisibilityOptics.formModel
+
+ val maybeSn: Option[SectionNumber] = formModel.availableSectionNumbers.collectFirst {
+ case sectionNumber if sectionNumber.maybeCoordinates.contains(coordinates) => sectionNumber
+ }
+ val sn =
+ maybeSn.getOrElse(throw new Exception(s"Cannot determine first sectionNumber for task with $coordinates"))
+
if (cache.formTemplate.isSpecimen) {
Redirect(sectionUrl(sn)).pure[Future]
diff --git a/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html b/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html
index d5a473daa..b2efa7c4b 100644
--- a/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html
+++ b/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html
@@ -21,37 +21,77 @@
-
-
+ @modelName
-
+
@modelName
+