diff --git a/.terraformignore b/.terraformignore
index ea97880c..2cee3633 100644
--- a/.terraformignore
+++ b/.terraformignore
@@ -1,12 +1,14 @@
**/.terraform/
-*.tfstate
-*.tfstate.*
-*.tfvars
-.terraform.lock.hcl
**/.terraform.lock.hcl
-crash.log
-crash.*.log
-override.tf
-override.tf.json
-*_override.tf
-*_override.tf.json
+**/*.tfstate
+**/*.tfstate.*
+**/*.tfvars
+**/*.tfvars.json
+**/crash.log
+**/crash.*.log
+**/override.tf
+**/override.tf.json
+**/*_override.tf
+**/*_override.tf.json
+**/.terraformrc
+**/terraform.rc
\ No newline at end of file
diff --git a/docs/request-journey.md b/docs/request-journey.md
index 01ceff4e..f33c0e14 100644
--- a/docs/request-journey.md
+++ b/docs/request-journey.md
@@ -293,7 +293,7 @@ The summary page renders a GOV.UK summary list of all answers. It can only be re
- **Remove / Include** — a single "Pupil name" row shows `SelectedPupil` with a Change link to the primary `PupilSearch` page.
- **Merge** — two rows replace the single "Pupil name" row:
- **"First record to merge"** — `"{Firstname} {Surname}, {d MMMM yyyy}"` (e.g. `"Jane Smith, 27 July 2010"`) with a Change link to the primary `PupilSearch` page.
- - **"Second record to merge"** — `"{Cypmd_Id}, {Firstname} {Surname}"` (e.g. `"CYPMD456, John Doe"`) with a Change link to the match `PupilSearch` page.
+ - **"Second record to merge"** — `"{Firstname} {Surname} {d MMMM yyyy} ({Cypmd_Id})"` (e.g. `"John Doe 2 February 2010 (CYPMD456)"`) with a Change link to the match `PupilSearch` page. If the DOB cannot be parsed the raw stored value is shown; if it is missing entirely the DOB segment is omitted (`"{name} ({id})"`).
Change links for `PupilSearch` pages use the `PupilSearchPage` action rather than the `Page` action. The back link on the summary page also uses `PupilSearchPage` when the last page in `QuestionHistory` is a `PupilSearch` page.
diff --git a/src/DfE.CheckPerformanceData.Application/AmendmentRequests/SubmittedRequestService.cs b/src/DfE.CheckPerformanceData.Application/AmendmentRequests/SubmittedRequestService.cs
index 4fb2e4f2..fed37a9d 100644
--- a/src/DfE.CheckPerformanceData.Application/AmendmentRequests/SubmittedRequestService.cs
+++ b/src/DfE.CheckPerformanceData.Application/AmendmentRequests/SubmittedRequestService.cs
@@ -1,4 +1,3 @@
-using System.Globalization;
using DfE.CheckPerformanceData.Application.CurrentUser;
using DfE.CheckPerformanceData.Application.Journey;
using DfE.CheckPerformanceData.Application.RequestSubmission;
@@ -120,12 +119,6 @@ private static (string? first, string? second) BuildMergeDisplays(RequestState j
if (journey.MatchedPupil is not { } mp || journey.SelectedPupil is not { } sp)
return (null, null);
- var dob = DateTime.TryParseExact(sp.DateOfBirth, "dd/MM/yyyy",
- CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed)
- ? parsed.ToString("d MMMM yyyy")
- : sp.DateOfBirth;
- var first = $"{sp.Firstname} {sp.Surname}, {dob}".Trim();
- var second = $"{mp.Cypmd_Id}, {mp.Firstname} {mp.Surname}".Trim();
- return (first, second);
+ return (MergeRecordDisplays.First(sp), MergeRecordDisplays.Second(mp));
}
}
diff --git a/src/DfE.CheckPerformanceData.Application/Journey/MergeRecordDisplays.cs b/src/DfE.CheckPerformanceData.Application/Journey/MergeRecordDisplays.cs
new file mode 100644
index 00000000..ef73ab5d
--- /dev/null
+++ b/src/DfE.CheckPerformanceData.Application/Journey/MergeRecordDisplays.cs
@@ -0,0 +1,37 @@
+using System.Globalization;
+using DfE.CheckPerformanceData.Application.CheckYourPupilData;
+
+namespace DfE.CheckPerformanceData.Application.Journey;
+
+///
+/// Single source of truth for the two record displays shown when merging duplicate
+/// pupils. Both the in-journey summary and the submitted-request view delegate here so
+/// the format can never drift between surfaces.
+///
+public static class MergeRecordDisplays
+{
+ public static string First(PupilDto pupil)
+ {
+ var dob = FormatDob(pupil.DateOfBirth);
+ return $"{pupil.Firstname} {pupil.Surname}, {dob}".Trim();
+ }
+
+ public static string Second(PupilDto pupil)
+ {
+ var dob = FormatDob(pupil.DateOfBirth);
+ var name = $"{pupil.Firstname} {pupil.Surname}".Trim();
+ var display = dob.Length > 0 ? $"{name} {dob} ({pupil.Cypmd_Id})" : $"{name} ({pupil.Cypmd_Id})";
+ return display.Trim();
+ }
+
+ private static string FormatDob(string raw)
+ {
+ if (string.IsNullOrEmpty(raw))
+ return string.Empty;
+
+ return DateTime.TryParseExact(raw, "dd/MM/yyyy",
+ CultureInfo.InvariantCulture, DateTimeStyles.None, out var d)
+ ? d.ToString("d MMMM yyyy", CultureInfo.InvariantCulture)
+ : raw;
+ }
+}
\ No newline at end of file
diff --git a/src/DfE.CheckPerformanceData.Web/Controllers/Journey/JourneyViewModelBuilder.cs b/src/DfE.CheckPerformanceData.Web/Controllers/Journey/JourneyViewModelBuilder.cs
index e71a5dbc..9ba34b42 100644
--- a/src/DfE.CheckPerformanceData.Web/Controllers/Journey/JourneyViewModelBuilder.cs
+++ b/src/DfE.CheckPerformanceData.Web/Controllers/Journey/JourneyViewModelBuilder.cs
@@ -50,13 +50,8 @@ public SummaryViewModel BuildSummaryVm(
string? secondRecordDisplay = null;
if (journey.MatchedPupil is { } mp && journey.SelectedPupil is { } sp)
{
- var dob = DateTime.TryParseExact(sp.DateOfBirth, "dd/MM/yyyy",
- System.Globalization.CultureInfo.InvariantCulture,
- System.Globalization.DateTimeStyles.None, out var d)
- ? d.ToString("d MMMM yyyy")
- : sp.DateOfBirth;
- firstRecordDisplay = $"{sp.Firstname} {sp.Surname}, {dob}".Trim();
- secondRecordDisplay = $"{mp.Cypmd_Id}, {mp.Firstname} {mp.Surname}".Trim();
+ firstRecordDisplay = MergeRecordDisplays.First(sp);
+ secondRecordDisplay = MergeRecordDisplays.Second(mp);
}
return new SummaryViewModel
diff --git a/tests/DfE.CheckPerformanceData.UnitTests/AmendmentRequests/SubmittedRequestServiceTests.cs b/tests/DfE.CheckPerformanceData.UnitTests/AmendmentRequests/SubmittedRequestServiceTests.cs
index 671dad72..9dbe81c1 100644
--- a/tests/DfE.CheckPerformanceData.UnitTests/AmendmentRequests/SubmittedRequestServiceTests.cs
+++ b/tests/DfE.CheckPerformanceData.UnitTests/AmendmentRequests/SubmittedRequestServiceTests.cs
@@ -254,6 +254,35 @@ public async Task GetConfirmDataCorrectAsync_WhenConfirmCorrect_MapsView()
Assert.Equal(Reference, result.ReferenceNumber);
}
+ [Fact]
+ public async Task GetAsync_MergeJourney_PopulatesRecordDisplays()
+ {
+ var journey = Journey();
+ journey.MatchedPupil = new PupilDto
+ {
+ Id = Guid.NewGuid(),
+ Firstname = "John",
+ Surname = "Doe",
+ Sex = "M",
+ DateOfBirth = "02/02/2010",
+ Age = 16,
+ Cypmd_Id = "CYPMD456",
+ Identifier = "456456"
+ };
+ var page = new JourneyPage
+ {
+ Id = "reason",
+ Questions = [new Question { Id = "q1", Type = QuestionType.Radio, Title = "Why?" }]
+ };
+ Setup(journey, page);
+
+ var result = await _sut.GetAsync(WindowId, Reference);
+
+ Assert.NotNull(result);
+ Assert.Equal("Jane Smith, 1 January 2010", result!.FirstRecordDisplay);
+ Assert.Equal("John Doe 2 February 2010 (CYPMD456)", result.SecondRecordDisplay);
+ }
+
private void Setup(RequestState journey, JourneyPage page)
{
_blob.GetAsync(WindowId, Reference).Returns(journey);
diff --git a/tests/DfE.CheckPerformanceData.UnitTests/Journey/JourneyViewModelBuilderTests.cs b/tests/DfE.CheckPerformanceData.UnitTests/Journey/JourneyViewModelBuilderTests.cs
index a7e7f136..7ca8b2e7 100644
--- a/tests/DfE.CheckPerformanceData.UnitTests/Journey/JourneyViewModelBuilderTests.cs
+++ b/tests/DfE.CheckPerformanceData.UnitTests/Journey/JourneyViewModelBuilderTests.cs
@@ -165,7 +165,7 @@ public void BuildSummaryVm_WhenMatchedPupil_PopulatesRecordDisplays()
var vm = _sut.BuildSummaryVm(WindowId, journey, mergeConfig);
Assert.Equal("Jane Smith, 27 July 2010", vm.FirstRecordDisplay);
- Assert.Equal("CYPMD456, John Doe", vm.SecondRecordDisplay);
+ Assert.Equal("John Doe 2 February 2010 (CYPMD456)", vm.SecondRecordDisplay);
}
[Fact]
diff --git a/tests/DfE.CheckPerformanceData.UnitTests/Journey/MergeRecordDisplaysTests.cs b/tests/DfE.CheckPerformanceData.UnitTests/Journey/MergeRecordDisplaysTests.cs
new file mode 100644
index 00000000..0d031c5d
--- /dev/null
+++ b/tests/DfE.CheckPerformanceData.UnitTests/Journey/MergeRecordDisplaysTests.cs
@@ -0,0 +1,75 @@
+using DfE.CheckPerformanceData.Application.CheckYourPupilData;
+using DfE.CheckPerformanceData.Application.Journey;
+
+namespace DfE.CheckPerformanceData.Application.UnitTests.Journey;
+
+public class MergeRecordDisplaysTests
+{
+ private static PupilDto Pupil(string? dob = "02/02/2010", string cypmdId = "CYPMD456") => new()
+ {
+ Id = Guid.NewGuid(),
+ Firstname = "John",
+ Surname = "Doe",
+ Sex = "M",
+ DateOfBirth = dob!,
+ Age = 16,
+ Cypmd_Id = cypmdId,
+ Identifier = "UPN002"
+ };
+
+ [Fact]
+ public void First_ParseableDob_RendersNameCommaDate()
+ {
+ var result = MergeRecordDisplays.First(Pupil());
+
+ Assert.Equal("John Doe, 2 February 2010", result);
+ }
+
+ [Fact]
+ public void Second_ParseableDob_RendersNameDateInParenthesisedId()
+ {
+ var result = MergeRecordDisplays.Second(Pupil());
+
+ Assert.Equal("John Doe 2 February 2010 (CYPMD456)", result);
+ }
+
+ [Fact]
+ public void First_UnparseableDob_EchoesRawValue()
+ {
+ var result = MergeRecordDisplays.First(Pupil(dob: "unknown"));
+
+ Assert.Equal("John Doe, unknown", result);
+ }
+
+ [Fact]
+ public void Second_UnparseableDob_EchoesRawValue()
+ {
+ var result = MergeRecordDisplays.Second(Pupil(dob: "unknown"));
+
+ Assert.Equal("John Doe unknown (CYPMD456)", result);
+ }
+
+ [Fact]
+ public void Second_EmptyDob_OmitsDobSegmentWithoutArtifacts()
+ {
+ var result = MergeRecordDisplays.Second(Pupil(dob: ""));
+
+ Assert.Equal("John Doe (CYPMD456)", result);
+ }
+
+ [Fact]
+ public void Second_EmptyCypmdId_StillRendersNameAndDob()
+ {
+ var result = MergeRecordDisplays.Second(Pupil(cypmdId: ""));
+
+ Assert.Equal("John Doe 2 February 2010 ()", result);
+ }
+
+ [Fact]
+ public void Second_SingleDigitDayAndMonth_FormatsWithoutLeadingZeros()
+ {
+ var result = MergeRecordDisplays.Second(Pupil(dob: "05/06/2010"));
+
+ Assert.Equal("John Doe 5 June 2010 (CYPMD456)", result);
+ }
+}
\ No newline at end of file
diff --git a/tests/DfE.CheckPerformanceData.UnitTests/Journey/PupilSearchJourneyTests.cs b/tests/DfE.CheckPerformanceData.UnitTests/Journey/PupilSearchJourneyTests.cs
index 9f57fe01..da8b44ba 100644
--- a/tests/DfE.CheckPerformanceData.UnitTests/Journey/PupilSearchJourneyTests.cs
+++ b/tests/DfE.CheckPerformanceData.UnitTests/Journey/PupilSearchJourneyTests.cs
@@ -578,7 +578,7 @@ public async Task Summary_ForMerge_PopulatesSecondRecordDisplay()
var view = Assert.IsType(result);
var vm = Assert.IsType(view.Model);
- Assert.Equal("CYPMD456, John Doe", vm.SecondRecordDisplay);
+ Assert.Equal("John Doe 2 February 2010 (CYPMD456)", vm.SecondRecordDisplay);
}
[Fact]