Skip to content

Commit 47a5886

Browse files
authored
- Added postcode to the autoComplete confirmation page for Academies (#138)
1 parent 8c5b7e2 commit 47a5886

8 files changed

Lines changed: 207 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,32 @@
22

33
All notable changes to this service will be documented in this file.
44

5-
## [1.0.0] – Public Beta
5+
## [1.0.0]
66
### Notes
77
- First formally versioned public beta release.
88

9-
## [1.0.1] – Public Beta
9+
## [1.0.1]
1010
### Notes
1111
- Added Client Side AppInsights SDK.
1212

13-
## [1.1.0] – Public Beta
13+
## [1.1.0]
1414
### Notes
1515
- Added Support for Multi-Tenancy. This service is now a Tenant of EAT API.
1616

17-
## [1.2.0] – Public Beta
17+
## [1.2.0]
1818
### Notes
1919
- As part of the Multi-Tenancy, we have now converted EAT Web to a single repository deployed to multiple services
2020
- Each service will have it's own set of appsettings.json files, and it is decided at the time of the deployment which one is deployed to the container.
2121

22-
## [1.2.1] – Public Beta
22+
## [1.2.1]
2323
### Notes
2424
- Updated LSRP Test env appsettings with an update DSI auth details and Front-Door URL.
2525

26-
## [1.2.2] – Public Beta
26+
## [1.2.2]
2727
### Notes
28-
- Improved Event-Mapping to support multi handlers when an application is submitted.
28+
- Improved Event-Mapping to support multi handlers when an application is submitted.
29+
30+
## [1.2.3]
31+
### Notes
32+
- Added postcode to the Academies Auto-Complete confirmation page.
33+
- Fixed a bug in Auto-Complete where duplicate items couldn't be selected.

src/DfE.ExternalApplications.Infrastructure/Services/ButtonConfirmationService.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ private static object ConvertJsonElement(JsonElement je)
222222
}
223223

224224
/// <summary>
225-
/// If the provided string contains a JSON object with well-known fields (name, ukprn, companiesHouseNumber),
225+
/// If the provided string contains a JSON object with well-known fields (name, postcode, ukprn, companiesHouseNumber),
226226
/// augment the flattened form data with those keys for easier confirmation display.
227-
/// Adds common key variants (e.g., trustname/trustName, companiesHousenumber/companiesHouseNumber).
227+
/// Adds common key variants (e.g., trustname/trustName, companiesHousenumber/companiesHouseNumber, postCode/postcode).
228228
/// </summary>
229229
private static void TryAugmentFromJsonString(string value, Dictionary<string, object> sink)
230230
{
@@ -241,13 +241,28 @@ private static void TryAugmentFromJsonString(string value, Dictionary<string, ob
241241
string? name = null;
242242
string? ukprn = null;
243243
string? chNo = null;
244+
string? postcode = null;
244245

245246
if (root.TryGetProperty("name", out var n) && n.ValueKind == JsonValueKind.String)
246247
name = n.GetString();
247248
if (root.TryGetProperty("ukprn", out var u) && (u.ValueKind == JsonValueKind.String || u.ValueKind == JsonValueKind.Number))
248249
ukprn = u.ToString();
249250
if (root.TryGetProperty("companiesHouseNumber", out var c) && c.ValueKind == JsonValueKind.String)
250251
chNo = c.GetString();
252+
if (root.TryGetProperty("postcode", out var pc) && pc.ValueKind == JsonValueKind.String)
253+
postcode = pc.GetString();
254+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("postCode", out var pc2) && pc2.ValueKind == JsonValueKind.String)
255+
postcode = pc2.GetString();
256+
// Nested address object (e.g. DfE establishments API: address.postcode)
257+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("address", out var addr) && addr.ValueKind == JsonValueKind.Object)
258+
{
259+
if (addr.TryGetProperty("postcode", out var apc) && apc.ValueKind == JsonValueKind.String)
260+
postcode = apc.GetString();
261+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postCode", out var apc2) && apc2.ValueKind == JsonValueKind.String)
262+
postcode = apc2.GetString();
263+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postalCode", out var apc3) && apc3.ValueKind == JsonValueKind.String)
264+
postcode = apc3.GetString();
265+
}
251266

252267
// Only add if not already present
253268
void AddIfMissing(string key, string? val)
@@ -258,6 +273,7 @@ void AddIfMissing(string key, string? val)
258273

259274
AddIfMissing("trustName", name);
260275
AddIfMissing("trustname", name);
276+
AddIfMissing("postcode", postcode);
261277
AddIfMissing("ukprn", ukprn);
262278
AddIfMissing("companiesHouseNumber", chNo);
263279
AddIfMissing("companiesHousenumber", chNo); // tolerate common misspelling

src/DfE.ExternalApplications.Infrastructure/Services/ConfirmationDataService.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using DfE.ExternalApplications.Application.Interfaces;
22
using Microsoft.Extensions.Logging;
33
using System.Globalization;
4+
using System.Text.Json;
45

56
namespace DfE.ExternalApplications.Infrastructure.Services
67
{
@@ -32,6 +33,10 @@ public Dictionary<string, string> FormatDisplayData(Dictionary<string, object> f
3233
return result;
3334
}
3435

36+
// Enrich form data from any JSON object values (e.g. complex field "Data[EstablishmentComplexField]")
37+
// so that trustname, ukprn, postcode etc. are available for display, same as UKPRN.
38+
AugmentFormDataFromJsonValues(formData);
39+
3540
// If no specific display fields are specified, show all non-system fields
3641
var fieldsToShow = displayFields?.Any() == true
3742
? displayFields
@@ -59,6 +64,107 @@ public Dictionary<string, string> FormatDisplayData(Dictionary<string, object> f
5964
return result;
6065
}
6166

67+
/// <summary>
68+
/// Enriches form data by parsing any JSON object values (e.g. complex autocomplete field values)
69+
/// and adding trustname, ukprn, postcode so they can be displayed on the confirmation page,
70+
/// matching how UKPRN is sourced from the same JSON.
71+
/// </summary>
72+
private void AugmentFormDataFromJsonValues(Dictionary<string, object> formData)
73+
{
74+
if (formData == null) return;
75+
76+
foreach (var kvp in formData.ToList())
77+
{
78+
var value = kvp.Value;
79+
if (value == null) continue;
80+
81+
if (value is string s && s.TrimStart().StartsWith("{"))
82+
{
83+
TryExtractDisplayFieldsFromJson(s, formData);
84+
continue;
85+
}
86+
87+
if (value is JsonElement je && je.ValueKind == JsonValueKind.Object)
88+
{
89+
TryExtractDisplayFieldsFromJsonElement(je, formData);
90+
}
91+
}
92+
}
93+
94+
private static void TryExtractDisplayFieldsFromJson(string value, Dictionary<string, object> sink)
95+
{
96+
if (string.IsNullOrWhiteSpace(value) || !value.Trim().StartsWith("{")) return;
97+
try
98+
{
99+
using var doc = JsonDocument.Parse(value);
100+
if (doc.RootElement.ValueKind != JsonValueKind.Object) return;
101+
ExtractDisplayFieldsFromRoot(doc.RootElement, sink);
102+
}
103+
catch
104+
{
105+
// ignore
106+
}
107+
}
108+
109+
private static void TryExtractDisplayFieldsFromJsonElement(JsonElement root, Dictionary<string, object> sink)
110+
{
111+
if (root.ValueKind != JsonValueKind.Object) return;
112+
try
113+
{
114+
ExtractDisplayFieldsFromRoot(root, sink);
115+
}
116+
catch
117+
{
118+
// ignore
119+
}
120+
}
121+
122+
private static void ExtractDisplayFieldsFromRoot(JsonElement root, Dictionary<string, object> sink)
123+
{
124+
string? name = null;
125+
string? ukprn = null;
126+
string? postcode = null;
127+
string? chNo = null;
128+
129+
if (root.TryGetProperty("name", out var n) && n.ValueKind == JsonValueKind.String)
130+
name = n.GetString();
131+
if (root.TryGetProperty("ukprn", out var u) && (u.ValueKind == JsonValueKind.String || u.ValueKind == JsonValueKind.Number))
132+
ukprn = u.ToString();
133+
if (root.TryGetProperty("postcode", out var pc) && pc.ValueKind == JsonValueKind.String)
134+
postcode = pc.GetString();
135+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("postCode", out var pc2) && pc2.ValueKind == JsonValueKind.String)
136+
postcode = pc2.GetString();
137+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("postalCode", out var pc3) && pc3.ValueKind == JsonValueKind.String)
138+
postcode = pc3.GetString();
139+
if (root.TryGetProperty("companiesHouseNumber", out var c) && c.ValueKind == JsonValueKind.String)
140+
chNo = c.GetString();
141+
if (string.IsNullOrWhiteSpace(chNo) && root.TryGetProperty("companiesHousenumber", out var c2) && c2.ValueKind == JsonValueKind.String)
142+
chNo = c2.GetString();
143+
144+
if (root.TryGetProperty("address", out var addr) && addr.ValueKind == JsonValueKind.Object)
145+
{
146+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postcode", out var apc) && apc.ValueKind == JsonValueKind.String)
147+
postcode = apc.GetString();
148+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postCode", out var apc2) && apc2.ValueKind == JsonValueKind.String)
149+
postcode = apc2.GetString();
150+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postalCode", out var apc3) && apc3.ValueKind == JsonValueKind.String)
151+
postcode = apc3.GetString();
152+
}
153+
154+
void AddIfMissing(string key, string? val)
155+
{
156+
if (string.IsNullOrWhiteSpace(val)) return;
157+
if (!sink.ContainsKey(key)) sink[key] = val;
158+
}
159+
160+
AddIfMissing("trustName", name);
161+
AddIfMissing("trustname", name);
162+
AddIfMissing("ukprn", ukprn);
163+
AddIfMissing("postcode", postcode);
164+
AddIfMissing("companiesHouseNumber", chNo);
165+
AddIfMissing("companiesHousenumber", chNo);
166+
}
167+
62168
/// <summary>
63169
/// Gets a user-friendly display name for a field
64170
/// </summary>

src/DfE.ExternalApplications.Web/DfE.ExternalApplications.Web.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<UserSecretsId>8051c984-585b-4a5e-b6d7-833e5dd4afe7</UserSecretsId>
8-
<Version>1.2.2</Version>
9-
<InformationalVersion>1.2.2</InformationalVersion>
8+
<Version>1.2.3</Version>
9+
<InformationalVersion>1.2.3</InformationalVersion>
1010
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
1111
</PropertyGroup>
1212

src/DfE.ExternalApplications.Web/Services/AutocompleteService.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ private object ExtractDisplayValue(JsonElement item, string complexFieldId)
221221
}
222222
}
223223

224-
// Try to get UKPRN or other identifier fields (support common casing variants)
225-
var identifierProperties = new[] { "ukprn", "id", "urn", "companiesHouseNumber", "companieshousenumber", "companies_house_number", "code", "localAuthorityName", "gor" };
224+
// Try to get UKPRN or other identifier/display fields (support common casing variants)
225+
var identifierProperties = new[] { "ukprn", "id", "urn", "companiesHouseNumber", "companieshousenumber", "companies_house_number", "code", "localAuthorityName", "gor", "postcode", "postCode" };
226226
foreach (var propertyName in identifierProperties)
227227
{
228228
if (item.TryGetProperty(propertyName, out var property))
@@ -256,7 +256,33 @@ private object ExtractDisplayValue(JsonElement item, string complexFieldId)
256256
}
257257
}
258258
}
259-
259+
260+
// For establishments, also extract postcode from nested address (e.g. DfE API: address.postcode)
261+
if (string.Equals(complexFieldId, "EstablishmentComplexField", StringComparison.OrdinalIgnoreCase)
262+
&& !result.ContainsKey("postcode")
263+
&& item.TryGetProperty("address", out var addressEl)
264+
&& addressEl.ValueKind == JsonValueKind.Object)
265+
{
266+
if (addressEl.TryGetProperty("postcode", out var postcodeEl) && postcodeEl.ValueKind == JsonValueKind.String)
267+
{
268+
var postcodeVal = postcodeEl.GetString();
269+
if (!string.IsNullOrEmpty(postcodeVal))
270+
result["postcode"] = postcodeVal;
271+
}
272+
if (!result.ContainsKey("postcode") && addressEl.TryGetProperty("postCode", out var postCodeEl) && postCodeEl.ValueKind == JsonValueKind.String)
273+
{
274+
var postcodeVal = postCodeEl.GetString();
275+
if (!string.IsNullOrEmpty(postcodeVal))
276+
result["postcode"] = postcodeVal;
277+
}
278+
if (!result.ContainsKey("postcode") && addressEl.TryGetProperty("postalCode", out var postalCodeEl) && postalCodeEl.ValueKind == JsonValueKind.String)
279+
{
280+
var postcodeVal = postalCodeEl.GetString();
281+
if (!string.IsNullOrEmpty(postcodeVal))
282+
result["postcode"] = postcodeVal;
283+
}
284+
}
285+
260286
// If we found a display name and at least one other field, return the object
261287
if (!string.IsNullOrEmpty(displayName) && result.Count > 1)
262288
{

src/DfE.ExternalApplications.Web/Views/Shared/Fields/_AutocompleteComplexField.cshtml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@
138138
currentValues.Add(Model.CurrentValue);
139139
}
140140
}
141+
142+
// For establishment/academy fields, show postcode on confirmation; for others use default display fields
143+
var confirmationDisplayFields = string.Equals(complexFieldId, "EstablishmentComplexField", StringComparison.OrdinalIgnoreCase)
144+
? "trustname,postcode,ukprn,companiesHousenumber"
145+
: "trustname,ukprn,companiesHousenumber";
141146
}
142147

143148
@if (string.IsNullOrEmpty(complexFieldId))
@@ -224,7 +229,7 @@ else
224229
@Html.RenderPrimaryConfirmationButton(
225230
buttonText: "Search",
226231
handler: "Page",
227-
displayFields: "trustname,ukprn,companiesHousenumber",
232+
displayFields: confirmationDisplayFields,
228233
buttonId: "autocomplete-confirm-button",
229234
title: $"Is this the right {@configuration.Label.ToLower()}?",
230235
requiredMessage: $"Select yes if this is the right { @configuration.Label.ToLower()}")
@@ -680,11 +685,25 @@ document.addEventListener('DOMContentLoaded', function() {
680685
return;
681686
}
682687
683-
// Look up the full object from cache to ensure we have ALL properties
688+
// Look up the full object from cache to ensure we have ALL properties.
689+
// Match by unique identifier when present (ukprn, companiesHouseNumber, urn) so that
690+
// items with the same name select the correct one; try each identifier if the others are not available.
684691
var fullOption = option;
685692
if (typeof option === 'object' && option.name && cachedResults.length > 0) {
686693
var foundInCache = cachedResults.find(function(item) {
687-
return item.name === option.name;
694+
if (typeof item !== 'object' || item === null || item.name !== option.name) return false;
695+
var itemUkprn = item.ukprn != null ? String(item.ukprn).trim() : '';
696+
var optUkprn = option.ukprn != null ? String(option.ukprn).trim() : '';
697+
if (itemUkprn && optUkprn) return itemUkprn === optUkprn;
698+
var itemCh = (item.companiesHouseNumber ?? item.companiesHousenumber ?? item.companies_house_number);
699+
var optCh = (option.companiesHouseNumber ?? option.companiesHousenumber ?? option.companies_house_number);
700+
itemCh = itemCh != null ? String(itemCh).trim() : '';
701+
optCh = optCh != null ? String(optCh).trim() : '';
702+
if (itemCh && optCh) return itemCh === optCh;
703+
var itemUrn = item.urn != null ? String(item.urn).trim() : '';
704+
var optUrn = option.urn != null ? String(option.urn).trim() : '';
705+
if (itemUrn && optUrn) return itemUrn === optUrn;
706+
return true;
688707
});
689708
if (foundInCache) {
690709
console.log('Found full object in cache with', Object.keys(foundInCache).length, 'properties');

src/DfE.ExternalApplications.Web/Views/Shared/FormEngine/_SingleCollectionFlow.cshtml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,12 +508,21 @@
508508

509509
var root = doc.RootElement;
510510
string name = root.TryGetProperty("name", out var n) && n.ValueKind == JsonValueKind.String ? n.GetString() ?? string.Empty : string.Empty;
511+
string postcode = root.TryGetProperty("postcode", out var pc) && pc.ValueKind == JsonValueKind.String ? pc.GetString() ?? string.Empty : string.Empty;
512+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("postCode", out var pc2) && pc2.ValueKind == JsonValueKind.String) postcode = pc2.GetString() ?? string.Empty;
513+
if (string.IsNullOrWhiteSpace(postcode) && root.TryGetProperty("address", out var addr) && addr.ValueKind == JsonValueKind.Object)
514+
{
515+
if (addr.TryGetProperty("postcode", out var apc) && apc.ValueKind == JsonValueKind.String) postcode = apc.GetString() ?? string.Empty;
516+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postCode", out var apc2) && apc2.ValueKind == JsonValueKind.String) postcode = apc2.GetString() ?? string.Empty;
517+
if (string.IsNullOrWhiteSpace(postcode) && addr.TryGetProperty("postalCode", out var apc3) && apc3.ValueKind == JsonValueKind.String) postcode = apc3.GetString() ?? string.Empty;
518+
}
511519
string ukprn = root.TryGetProperty("ukprn", out var u) ? u.ToString() : string.Empty;
512520
string ch = root.TryGetProperty("companiesHouseNumber", out var c) && c.ValueKind == JsonValueKind.String ? c.GetString() ?? string.Empty : string.Empty;
513521
if (string.IsNullOrWhiteSpace(ch) && root.TryGetProperty("companiesHousenumber", out var c2)) ch = c2.ToString();
514522

515523
var sb = new System.Text.StringBuilder();
516524
if (!string.IsNullOrWhiteSpace(name)) sb.Append($"<strong class=\"govuk-!-font-weight-bold\">{System.Net.WebUtility.HtmlEncode(name)}</strong>");
525+
if (!string.IsNullOrWhiteSpace(postcode)) sb.Append($"<br/>Postcode: {System.Net.WebUtility.HtmlEncode(postcode)}");
517526
if (!string.IsNullOrWhiteSpace(ukprn)) sb.Append($"<br/>UKPRN: {System.Net.WebUtility.HtmlEncode(ukprn)}");
518527
if (!string.IsNullOrWhiteSpace(ch)) sb.Append($"<br/>Companies house number: {System.Net.WebUtility.HtmlEncode(ch)}");
519528
return sb.ToString();

0 commit comments

Comments
 (0)