@defra/fcp-sfd-frontend-engine 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  mutations: () => mutations,
35
35
  presenters: () => presenters,
36
36
  schemas: () => schemas,
37
+ services: () => services,
37
38
  utils: () => utils
38
39
  });
39
40
  module.exports = __toCommonJS(index_exports);
@@ -224,338 +225,6 @@ var mapBusinessDetails = (value) => {
224
225
  };
225
226
  };
226
227
 
227
- // src/mappers/mappers.js
228
- var mappers = {
229
- personalBusinessDetails: mapPersonalBusinessDetails,
230
- address: mapAddress,
231
- customerName: mapCustomerName,
232
- businessDetails: mapBusinessDetails
233
- };
234
-
235
- // src/mutations/business/update-business-email.js
236
- var updateBusinessEmailMutation = `
237
- mutation Mutation($input: UpdateBusinessEmailInput!) {
238
- updateBusinessEmail(input: $input) {
239
- business {
240
- info {
241
- email {
242
- address
243
- }
244
- }
245
- }
246
- success
247
- }
248
- }
249
- `;
250
-
251
- // src/mutations/business/update-business-name.js
252
- var updateBusinessNameMutation = `
253
- mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
254
- updateBusinessName(input: $input) {
255
- business {
256
- info {
257
- name
258
- }
259
- }
260
- success
261
- }
262
- }
263
- `;
264
-
265
- // src/mutations/personal/update-customer-name.js
266
- var updateCustomerNameMutation = `
267
- mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
268
- updateCustomerName(input: $input) {
269
- customer {
270
- info {
271
- name {
272
- first
273
- last
274
- middle
275
- }
276
- }
277
- }
278
- }
279
- }
280
- `;
281
-
282
- // src/mutations/personal/update-customer-dob.js
283
- var updateCustomerDobMutation = `
284
- mutation UpdateCustomerDateOfBirth($input: UpdateCustomerDateOfBirthInput!) {
285
- updateCustomerDateOfBirth(input: $input) {
286
- customer {
287
- info {
288
- dateOfBirth
289
- }
290
- }
291
- }
292
- }
293
- `;
294
-
295
- // src/mutations/personal/update-customer-phone.js
296
- var updateCustomerPhoneMutation = `
297
- mutation UpdateCustomerPhone($input: UpdateCustomerPhoneInput!) {
298
- updateCustomerPhone(input: $input) {
299
- customer {
300
- info {
301
- phone {
302
- landline
303
- mobile
304
- }
305
- }
306
- }
307
- }
308
- }
309
- `;
310
-
311
- // src/mutations/personal/update-customer-email.js
312
- var updateCustomerEmailMutation = `
313
- mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
314
- updateCustomerEmail(input: $input) {
315
- customer {
316
- info {
317
- email {
318
- address
319
- }
320
- }
321
- }
322
- }
323
- }
324
- `;
325
-
326
- // src/mutations/mutations.js
327
- var mutations = {
328
- updateBusinessEmail: updateBusinessEmailMutation,
329
- updateBusinessName: updateBusinessNameMutation,
330
- updateCustomerName: updateCustomerNameMutation,
331
- updateCustomerDob: updateCustomerDobMutation,
332
- updateCustomerPhone: updateCustomerPhoneMutation,
333
- updateCustomerEmail: updateCustomerEmailMutation
334
- };
335
-
336
- // src/presenters/base-presenter.js
337
- var BACK_LINK_DISPLAY_MAX = 50;
338
- var formatBackLink = (businessName) => {
339
- if (businessName.length > BACK_LINK_DISPLAY_MAX) {
340
- return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
341
- }
342
- return `Back to ${businessName}`;
343
- };
344
- var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
345
- if (payloadNumber !== void 0) {
346
- return payloadNumber;
347
- }
348
- if (changedNumber !== void 0) {
349
- return changedNumber;
350
- }
351
- return originalNumber;
352
- };
353
- var formatDatePart = (changed, original) => {
354
- return changed ?? (original == null ? void 0 : original.toString()) ?? "";
355
- };
356
- var formatDateInputValues = (payloadDob, changedDob, originalDob) => {
357
- if (payloadDob) {
358
- return {
359
- day: payloadDob.day ?? "",
360
- month: payloadDob.month ?? "",
361
- year: payloadDob.year ?? ""
362
- };
363
- }
364
- return {
365
- day: formatDatePart(changedDob == null ? void 0 : changedDob.day, originalDob == null ? void 0 : originalDob.day),
366
- month: formatDatePart(changedDob == null ? void 0 : changedDob.month, originalDob == null ? void 0 : originalDob.month),
367
- year: formatDatePart(changedDob == null ? void 0 : changedDob.year, originalDob == null ? void 0 : originalDob.year)
368
- };
369
- };
370
- var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
371
- const sortedErrors = [];
372
- for (const section of orderedSectionsToFix) {
373
- const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
374
- for (const field of fieldsInSection) {
375
- if (errors[field]) {
376
- sortedErrors.push({
377
- field,
378
- ...errors[field]
379
- });
380
- }
381
- }
382
- }
383
- return sortedErrors;
384
- };
385
-
386
- // src/presenters/address-presenter.js
387
- var addressChangeLink = (postcodeLookup, context) => {
388
- if (context === "business") {
389
- if (postcodeLookup) {
390
- return "/business-address-change";
391
- }
392
- return "/business-address-enter";
393
- }
394
- if (context === "personal") {
395
- if (postcodeLookup) {
396
- return "/account-address-change";
397
- }
398
- return "/account-address-enter";
399
- }
400
- return null;
401
- };
402
- var addressBackLink = (postcodeLookup, context) => {
403
- if (context === "business") {
404
- if (postcodeLookup) {
405
- return { href: "/business-address-select" };
406
- }
407
- return { href: "/business-address-enter" };
408
- }
409
- if (context === "personal") {
410
- if (postcodeLookup) {
411
- return { href: "/account-address-select" };
412
- }
413
- return { href: "/account-address-enter" };
414
- }
415
- return null;
416
- };
417
- var buildAddressLine = (parts) => {
418
- return parts.filter(Boolean).join(", ") || null;
419
- };
420
- var buildStreetLine = (buildingRange, street) => {
421
- return [buildingRange, street].filter(Boolean).join(" ") || null;
422
- };
423
- var formatLookupAddress = (lookup, city, country, postcode) => ({
424
- address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
425
- address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
426
- address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
427
- county: lookup.county ?? null,
428
- city: city ?? null,
429
- country: country ?? null,
430
- postcode: postcode ?? null
431
- });
432
- var formatManualAddress = (manual, city, country, postcode) => ({
433
- address1: manual.line1 ?? null,
434
- address2: manual.line2 ?? null,
435
- address3: manual.line3 ?? null,
436
- city: city ?? null,
437
- county: manual.line4 ?? null,
438
- country: country ?? null,
439
- postcode: postcode ?? null
440
- });
441
- var formatDisplayAddress = (address) => {
442
- const { lookup, manual, postcode, country, city } = address;
443
- let addressLines = [];
444
- if (lookup.uprn) {
445
- const buildingAndStreet = [
446
- lookup.buildingNumberRange,
447
- lookup.street
448
- ].filter(Boolean).join(" ");
449
- addressLines = [
450
- lookup.pafOrganisationName,
451
- lookup.flatName,
452
- lookup.buildingName,
453
- buildingAndStreet,
454
- lookup.doubleDependentLocality,
455
- lookup.dependentLocality,
456
- city,
457
- lookup.county
458
- ];
459
- } else {
460
- addressLines = [
461
- manual.line1,
462
- manual.line2,
463
- manual.line3,
464
- city,
465
- manual.line4,
466
- // County
467
- manual.line5
468
- ];
469
- }
470
- return [
471
- ...addressLines.filter(Boolean),
472
- postcode,
473
- country
474
- ];
475
- };
476
- var formatOriginalAddress = (originalAddress) => {
477
- const { lookup, manual, city, country, postcode } = originalAddress;
478
- return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
479
- };
480
- var formatChangedAddress = (changeBusinessAddress) => {
481
- if (!changeBusinessAddress.uprn) {
482
- return changeBusinessAddress;
483
- }
484
- const {
485
- pafOrganisationName,
486
- flatName,
487
- buildingName,
488
- buildingNumberRange,
489
- street,
490
- doubleDependentLocality,
491
- dependentLocality,
492
- city,
493
- county,
494
- country,
495
- postcode
496
- } = changeBusinessAddress;
497
- return {
498
- address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
499
- address2: buildStreetLine(buildingNumberRange, street),
500
- address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
501
- city: city ?? null,
502
- county: county ?? null,
503
- country: country ?? null,
504
- postcode: postcode ?? null
505
- };
506
- };
507
- var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
508
- const displayAddresses = addresses.map((address) => ({
509
- value: `${address.uprn}${address.displayAddress}`,
510
- text: address.displayAddress,
511
- selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
512
- }));
513
- const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
514
- const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
515
- displayAddresses.unshift({
516
- value: "display",
517
- text,
518
- selected: !hasSelectedAddress
519
- });
520
- return displayAddresses;
521
- };
522
-
523
- // src/presenters/business-details-presenter.js
524
- var getActionText = (value) => {
525
- return value ? "Change" : "Add";
526
- };
527
- var formatCph = (countyParishHoldings) => {
528
- return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
529
- };
530
- var formatCphText = (count) => {
531
- return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
532
- };
533
- var formatBusinessAddress = (businessAddress) => {
534
- var _a, _b;
535
- if (((_a = businessAddress == null ? void 0 : businessAddress.lookup) == null ? void 0 : _a.uprn) || ((_b = businessAddress == null ? void 0 : businessAddress.manual) == null ? void 0 : _b.line1)) {
536
- return formatDisplayAddress(businessAddress);
537
- }
538
- return [];
539
- };
540
-
541
- // src/presenters/presenters.js
542
- var presenters = {
543
- formatBackLink,
544
- formatNumber,
545
- formatDateInputValues,
546
- formatDisplayAddress,
547
- formatOriginalAddress,
548
- formatChangedAddress,
549
- formatDisplayAddresses,
550
- sortErrorsBySectionOrder,
551
- getActionText,
552
- formatCph,
553
- formatCphText,
554
- formatBusinessAddress,
555
- addressBackLink,
556
- addressChangeLink
557
- };
558
-
559
228
  // src/utils/joi.js
560
229
  var import_joi = __toESM(require("joi"), 1);
561
230
  var Joi = import_joi.default.extend((joi) => ({
@@ -630,332 +299,789 @@ var businessNameSchema = Joi.object({
630
299
  })
631
300
  });
632
301
 
633
- // src/schemas/business/business-email-schema.js
634
- var businessEmailSchema = Joi.object({
635
- businessEmail: Joi.string().required().max(EMAIL_MAX).email({
302
+ // src/schemas/business/business-email-schema.js
303
+ var businessEmailSchema = Joi.object({
304
+ businessEmail: Joi.string().required().max(EMAIL_MAX).email({
305
+ minDomainSegments: 2,
306
+ tlds: {
307
+ allow: true
308
+ }
309
+ }).messages({
310
+ "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
311
+ "string.empty": "Enter business email address",
312
+ "string.email": "Enter an email address, like name@example.com",
313
+ "string.noControlChars": "Business email address must not contain invalid characters"
314
+ })
315
+ });
316
+
317
+ // src/schemas/business/business-phone-schema.js
318
+ var businessPhoneSchema = Joi.object({
319
+ businessTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
320
+ "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
321
+ "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
322
+ "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
323
+ "string.noControlChars": "Business telephone number must not contain invalid characters"
324
+ }),
325
+ businessMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
326
+ "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
327
+ "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
328
+ "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
329
+ "string.noControlChars": "Business mobile phone number must not contain invalid characters"
330
+ })
331
+ }).or("businessTelephone", "businessMobile").messages({
332
+ "object.missing": "Enter at least one phone number"
333
+ });
334
+
335
+ // src/schemas/business/business-vat-schema.js
336
+ var businessVatSchema = Joi.object({
337
+ vatNumber: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
338
+ "string.pattern.base": "Enter a VAT registration number, like 123456789",
339
+ "string.noControlChars": "VAT registration number must not contain invalid characters"
340
+ })
341
+ });
342
+
343
+ // src/schemas/business/business-vat-change-schema.js
344
+ var businessVatChangeSchema = Joi.object({
345
+ vatNumber: Joi.string().pattern(/^\d{9}$/).required().messages({
346
+ "string.pattern.base": "Enter a VAT registration number, like 123456789",
347
+ "string.empty": "Enter a VAT registration number",
348
+ "any.required": "Enter a VAT registration number",
349
+ "string.noControlChars": "VAT registration number must not contain invalid characters"
350
+ })
351
+ });
352
+
353
+ // src/schemas/business/business-vat-remove-schema.js
354
+ var businessVatRemoveSchema = Joi.object({
355
+ confirmRemove: Joi.string().valid("yes", "no").required().messages({
356
+ "any.required": "Select yes if you want to remove your VAT registration number",
357
+ "any.only": "Select yes if you want to remove your VAT registration number"
358
+ })
359
+ });
360
+
361
+ // src/schemas/business/business-schemas.js
362
+ var businessSchemas = {
363
+ sbi: businessSbiSchema,
364
+ details: {
365
+ name: businessNameSchema,
366
+ address: addressSchema,
367
+ phone: businessPhoneSchema,
368
+ email: businessEmailSchema,
369
+ vat: businessVatSchema
370
+ },
371
+ vat: {
372
+ change: businessVatChangeSchema,
373
+ remove: businessVatRemoveSchema
374
+ }
375
+ };
376
+
377
+ // src/schemas/customer/customer-crn-schema.js
378
+ var customerCrnSchema = Joi.object({
379
+ crn: Joi.string().pattern(/^\d{10}$/).allow("").optional().messages({
380
+ "string.pattern.base": "Enter the full CRN",
381
+ "string.noControlChars": "CRN must not contain invalid characters"
382
+ })
383
+ });
384
+
385
+ // src/schemas/customer/customer-schemas.js
386
+ var customerSchemas = {
387
+ crn: customerCrnSchema
388
+ };
389
+
390
+ // src/schemas/personal/personal-name-schema.js
391
+ var personalNameSchema = Joi.object({
392
+ first: Joi.string().trim().required().max(FIRST_NAME_MAX).messages({
393
+ "string.empty": "Enter first name",
394
+ "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
395
+ "any.required": "Enter first name",
396
+ "string.noControlChars": "First name must not contain invalid characters"
397
+ }),
398
+ last: Joi.string().trim().required().max(LAST_NAME_MAX).messages({
399
+ "string.empty": "Enter last name",
400
+ "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
401
+ "any.required": "Enter last name",
402
+ "string.noControlChars": "Last name must not contain invalid characters"
403
+ }),
404
+ middle: Joi.string().trim().allow("").max(MIDDLE_NAMES_MAX).messages({
405
+ "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`,
406
+ "string.noControlChars": "Middle names must not contain invalid characters"
407
+ })
408
+ });
409
+
410
+ // src/schemas/personal/personal-dob-schema.js
411
+ var personalDobSchema = Joi.object({
412
+ day: Joi.string().allow(""),
413
+ month: Joi.string().allow(""),
414
+ year: Joi.string().allow("")
415
+ }).custom((value, helpers) => {
416
+ const { day, month, year } = value;
417
+ const missingFieldsError = checkMissingFields(day, month, year, helpers);
418
+ if (missingFieldsError) {
419
+ return missingFieldsError;
420
+ }
421
+ if (year && year.length !== 4) {
422
+ return makeError(helpers, "dob.yearLength", ["year"]);
423
+ }
424
+ const monthValue = getMonthNumber(month, helpers);
425
+ if (monthValue.isJoiError) {
426
+ return monthValue;
427
+ }
428
+ const fullDate = getFullDate(day, monthValue, year, helpers);
429
+ if (fullDate.isJoiError) {
430
+ return fullDate;
431
+ }
432
+ const today = /* @__PURE__ */ new Date();
433
+ today.setUTCHours(0, 0, 0, 0);
434
+ if (fullDate >= today) {
435
+ return makeError(helpers, "dob.future", ["day", "month", "year"]);
436
+ }
437
+ const tooOldError = checkNotTooOld(fullDate, helpers);
438
+ if (tooOldError) {
439
+ return tooOldError;
440
+ }
441
+ return value;
442
+ }).messages({
443
+ "dob.missingAll": "Enter your date of birth",
444
+ "dob.missingDay": "Date of birth must include a day",
445
+ "dob.missingMonth": "Date of birth must include a month",
446
+ "dob.missingYear": "Date of birth must include a year",
447
+ "dob.missingDayMonth": "Date of birth must include a day and month",
448
+ "dob.missingDayYear": "Date of birth must include a day and year",
449
+ "dob.missingMonthYear": "Date of birth must include a month and year",
450
+ "dob.yearLength": "Enter a year with 4 numbers, like 1975",
451
+ "dob.invalid": "Date of birth must be a real date",
452
+ "dob.future": "Date of birth must be in the past",
453
+ "dob.tooOld": "Date of birth must be on or after {{#oldest}}",
454
+ "string.noControlChars": "Date of birth must not contain invalid characters"
455
+ });
456
+ var checkNotTooOld = (fullDate, helpers) => {
457
+ const oldestDateAllowed = getOldestAllowedDate();
458
+ if (fullDate < oldestDateAllowed) {
459
+ const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
460
+ day: "numeric",
461
+ month: "long",
462
+ year: "numeric"
463
+ });
464
+ const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
465
+ error.path = ["day", "month", "year"];
466
+ return error;
467
+ }
468
+ return null;
469
+ };
470
+ var getOldestAllowedDate = () => {
471
+ const date = /* @__PURE__ */ new Date();
472
+ date.setUTCHours(0, 0, 0, 0);
473
+ date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
474
+ return date;
475
+ };
476
+ var getFullDate = (day, monthValue, year, helpers) => {
477
+ const dayValue = Number.parseInt(day, 10);
478
+ const yearValue = Number.parseInt(year, 10);
479
+ const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
480
+ if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
481
+ return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
482
+ }
483
+ return date;
484
+ };
485
+ var getMonthNumber = (month, helpers) => {
486
+ if (Number.isNaN(Number(month))) {
487
+ const lower = month.toLowerCase();
488
+ const mapped = MONTH_MAP[lower];
489
+ if (!mapped) {
490
+ return makeError(helpers, "dob.invalid", ["month"]);
491
+ }
492
+ return mapped;
493
+ }
494
+ const parsedMonth = Number.parseInt(month, 10);
495
+ if (parsedMonth < 1 || parsedMonth > 12) {
496
+ return makeError(helpers, "dob.invalid", ["month"]);
497
+ }
498
+ return parsedMonth;
499
+ };
500
+ var checkMissingFields = (day, month, year, helpers) => {
501
+ if (!day && !month && !year) {
502
+ return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
503
+ }
504
+ if (!day && month && year) {
505
+ return makeError(helpers, "dob.missingDay", ["day"]);
506
+ }
507
+ if (day && !month && year) {
508
+ return makeError(helpers, "dob.missingMonth", ["month"]);
509
+ }
510
+ if (day && month && !year) {
511
+ return makeError(helpers, "dob.missingYear", ["year"]);
512
+ }
513
+ if (!day && !month && year) {
514
+ return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
515
+ }
516
+ if (!day && month && !year) {
517
+ return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
518
+ }
519
+ if (day && !month && !year) {
520
+ return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
521
+ }
522
+ return null;
523
+ };
524
+ var makeError = (helpers, code, fields) => {
525
+ const error = helpers.error(code);
526
+ error.path = fields;
527
+ error.isJoiError = true;
528
+ return error;
529
+ };
530
+
531
+ // src/schemas/personal/personal-email-schema.js
532
+ var personalEmailSchema = Joi.object({
533
+ personalEmail: Joi.string().required().max(EMAIL_MAX).email({
636
534
  minDomainSegments: 2,
637
535
  tlds: {
638
- allow: true
536
+ allow: true,
537
+ min: 2
639
538
  }
640
539
  }).messages({
641
- "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
642
- "string.empty": "Enter business email address",
540
+ "string.max": `Email address must be ${EMAIL_MAX} characters or less`,
541
+ "string.empty": "Enter a personal email address",
643
542
  "string.email": "Enter an email address, like name@example.com",
644
- "string.noControlChars": "Business email address must not contain invalid characters"
543
+ "string.noControlChars": "Personal email address must not contain invalid characters"
645
544
  })
646
545
  });
647
546
 
648
- // src/schemas/business/business-phone-schema.js
649
- var businessPhoneSchema = Joi.object({
650
- businessTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
651
- "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
652
- "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
653
- "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
654
- "string.noControlChars": "Business telephone number must not contain invalid characters"
547
+ // src/schemas/personal/personal-phone-schema.js
548
+ var personalPhoneSchema = Joi.object({
549
+ personalTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
550
+ "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
551
+ "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
552
+ "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
553
+ "string.noControlChars": "Personal telephone number must not contain invalid characters"
655
554
  }),
656
- businessMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
657
- "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
658
- "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
659
- "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
660
- "string.noControlChars": "Business mobile phone number must not contain invalid characters"
555
+ personalMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
556
+ "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
557
+ "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
558
+ "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
559
+ "string.noControlChars": "Personal mobile phone number must not contain invalid characters"
661
560
  })
662
- }).or("businessTelephone", "businessMobile").messages({
561
+ }).or("personalTelephone", "personalMobile").messages({
663
562
  "object.missing": "Enter at least one phone number"
664
563
  });
665
564
 
666
- // src/schemas/business/business-vat-schema.js
667
- var businessVatSchema = Joi.object({
668
- vatNumber: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
669
- "string.pattern.base": "Enter a VAT registration number, like 123456789",
670
- "string.noControlChars": "VAT registration number must not contain invalid characters"
671
- })
672
- });
565
+ // src/schemas/personal/personal-schemas.js
566
+ var personalSchemas = {
567
+ name: personalNameSchema,
568
+ dob: personalDobSchema,
569
+ address: addressSchema,
570
+ phone: personalPhoneSchema,
571
+ email: personalEmailSchema
572
+ };
673
573
 
674
- // src/schemas/business/business-vat-change-schema.js
675
- var businessVatChangeSchema = Joi.object({
676
- vatNumber: Joi.string().pattern(/^\d{9}$/).required().messages({
677
- "string.pattern.base": "Enter a VAT registration number, like 123456789",
678
- "string.empty": "Enter a VAT registration number",
679
- "any.required": "Enter a VAT registration number",
680
- "string.noControlChars": "VAT registration number must not contain invalid characters"
574
+ // src/schemas/os-places/address-lookup-schema.js
575
+ var addressLookupSchema = Joi.object({
576
+ properties: Joi.object({
577
+ UPRN: Joi.string().required(),
578
+ ADDRESS: Joi.string().required(),
579
+ ORGANISATION_NAME: Joi.string().allow(null),
580
+ DEPARTMENT_NAME: Joi.string().allow(null),
581
+ SUB_BUILDING_NAME: Joi.string().allow(null),
582
+ BUILDING_NAME: Joi.string().allow(null),
583
+ BUILDING_NUMBER: Joi.string().allow(null),
584
+ DEPENDENT_THOROUGHFARE_NAME: Joi.string().allow(null),
585
+ THOROUGHFARE_NAME: Joi.string().allow(null),
586
+ DOUBLE_DEPENDENT_LOCALITY: Joi.string().allow(null),
587
+ DEPENDENT_LOCALITY: Joi.string().allow(null),
588
+ POST_TOWN: Joi.string().required(),
589
+ POSTCODE: Joi.string().required(),
590
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: Joi.string().allow(null),
591
+ COUNTRY_CODE: Joi.string().required()
592
+ }).unknown(true).required()
593
+ }).unknown(true);
594
+
595
+ // src/schemas/os-places/addresses-schema.js
596
+ var CHOOSE_ADDRESS_ERROR = "Choose an address";
597
+ var addressesSchema = Joi.object({
598
+ addresses: Joi.string().invalid("display").required().messages({
599
+ "any.required": CHOOSE_ADDRESS_ERROR,
600
+ "string.empty": CHOOSE_ADDRESS_ERROR,
601
+ "any.invalid": CHOOSE_ADDRESS_ERROR
681
602
  })
682
603
  });
683
604
 
684
- // src/schemas/business/business-vat-remove-schema.js
685
- var businessVatRemoveSchema = Joi.object({
686
- confirmRemove: Joi.string().valid("yes", "no").required().messages({
687
- "any.required": "Select yes if you want to remove your VAT registration number",
688
- "any.only": "Select yes if you want to remove your VAT registration number"
605
+ // src/schemas/os-places/uk-postcode-schema.js
606
+ var ukPostcodeSchema = Joi.object({
607
+ postcode: Joi.string().trim().required().uppercase().max(POSTCODE_MAX).pattern(/^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$/).messages({
608
+ "any.required": "Enter a postcode",
609
+ "string.empty": "Enter a postcode",
610
+ "string.max": `Postal code must be ${POSTCODE_MAX} characters or less`,
611
+ "string.pattern.base": "Enter a full UK postcode, like AA3 1AB"
689
612
  })
690
613
  });
691
614
 
692
- // src/schemas/business/business-schemas.js
693
- var businessSchemas = {
694
- sbi: businessSbiSchema,
695
- details: {
696
- name: businessNameSchema,
697
- address: addressSchema,
698
- phone: businessPhoneSchema,
699
- email: businessEmailSchema,
700
- vat: businessVatSchema
701
- },
702
- vat: {
703
- change: businessVatChangeSchema,
704
- remove: businessVatRemoveSchema
705
- }
615
+ // src/schemas/os-places/os-places-schemas.js
616
+ var osPlacesSchemas = {
617
+ addressLookup: addressLookupSchema,
618
+ addresses: addressesSchema,
619
+ ukPostcode: ukPostcodeSchema
706
620
  };
707
621
 
708
- // src/schemas/customer/customer-crn-schema.js
709
- var customerCrnSchema = Joi.object({
710
- crn: Joi.string().pattern(/^\d{10}$/).allow("").optional().messages({
711
- "string.pattern.base": "Enter the full CRN",
712
- "string.noControlChars": "CRN must not contain invalid characters"
713
- })
714
- });
622
+ // src/schemas/schemas.js
623
+ var schemas = {
624
+ business: businessSchemas,
625
+ customer: customerSchemas,
626
+ personal: personalSchemas,
627
+ osPlaces: osPlacesSchemas
628
+ };
715
629
 
716
- // src/schemas/customer/customer-schemas.js
717
- var customerSchemas = {
718
- crn: customerCrnSchema
630
+ // src/constants/country-names.js
631
+ var COUNTRY_NAMES = {
632
+ E: "ENGLAND",
633
+ W: "WALES",
634
+ S: "SCOTLAND",
635
+ N: "NORTHERN IRELAND",
636
+ L: "CHANNEL ISLANDS",
637
+ M: "ISLE OF MAN"
719
638
  };
720
639
 
721
- // src/schemas/personal/personal-name-schema.js
722
- var personalNameSchema = Joi.object({
723
- first: Joi.string().trim().required().max(FIRST_NAME_MAX).messages({
724
- "string.empty": "Enter first name",
725
- "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
726
- "any.required": "Enter first name",
727
- "string.noControlChars": "First name must not contain invalid characters"
728
- }),
729
- last: Joi.string().trim().required().max(LAST_NAME_MAX).messages({
730
- "string.empty": "Enter last name",
731
- "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
732
- "any.required": "Enter last name",
733
- "string.noControlChars": "Last name must not contain invalid characters"
734
- }),
735
- middle: Joi.string().trim().allow("").max(MIDDLE_NAMES_MAX).messages({
736
- "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`,
737
- "string.noControlChars": "Middle names must not contain invalid characters"
738
- })
739
- });
640
+ // src/mappers/address-lookup-mapper.js
641
+ var addressLookupMapper = (addresses) => {
642
+ if (!Array.isArray(addresses)) {
643
+ return [];
644
+ }
645
+ return addresses.map((address) => {
646
+ const { error } = schemas.osPlaces.addressLookup.validate(address);
647
+ if (error) {
648
+ return null;
649
+ }
650
+ const {
651
+ UPRN,
652
+ ADDRESS,
653
+ PO_BOX_NUMBER,
654
+ ORGANISATION_NAME,
655
+ DEPARTMENT_NAME,
656
+ SUB_BUILDING_NAME,
657
+ BUILDING_NAME,
658
+ BUILDING_NUMBER,
659
+ DEPENDENT_THOROUGHFARE_NAME,
660
+ THOROUGHFARE_NAME,
661
+ DOUBLE_DEPENDENT_LOCALITY,
662
+ DEPENDENT_LOCALITY,
663
+ POST_TOWN,
664
+ POSTCODE,
665
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION,
666
+ COUNTRY_CODE
667
+ } = address.properties;
668
+ const buildingName = PO_BOX_NUMBER ? `PO BOX ${PO_BOX_NUMBER}` : BUILDING_NAME || null;
669
+ return {
670
+ displayAddress: ADDRESS,
671
+ pafOrganisationName: filterAndJoin([ORGANISATION_NAME, DEPARTMENT_NAME]),
672
+ flatName: SUB_BUILDING_NAME ?? null,
673
+ buildingName,
674
+ buildingNumberRange: BUILDING_NUMBER ?? null,
675
+ street: filterAndJoin([DEPENDENT_THOROUGHFARE_NAME, THOROUGHFARE_NAME]),
676
+ dependentLocality: DEPENDENT_LOCALITY ?? null,
677
+ doubleDependentLocality: DOUBLE_DEPENDENT_LOCALITY ?? null,
678
+ city: POST_TOWN,
679
+ county: formatCounty(LOCAL_CUSTODIAN_CODE_DESCRIPTION, POST_TOWN),
680
+ postcode: POSTCODE,
681
+ country: COUNTRY_NAMES[COUNTRY_CODE] ?? null,
682
+ uprn: UPRN
683
+ };
684
+ }).filter(Boolean);
685
+ };
686
+ var formatCounty = (localCustodianCodeDescription, postTown) => {
687
+ if (localCustodianCodeDescription === "ORDNANCE SURVEY" || localCustodianCodeDescription === postTown) {
688
+ return null;
689
+ }
690
+ return localCustodianCodeDescription;
691
+ };
692
+ var filterAndJoin = (addressesProperties) => {
693
+ return addressesProperties.filter(Boolean).join(", ") || null;
694
+ };
695
+
696
+ // src/mappers/mappers.js
697
+ var mappers = {
698
+ personalBusinessDetails: mapPersonalBusinessDetails,
699
+ address: mapAddress,
700
+ customerName: mapCustomerName,
701
+ businessDetails: mapBusinessDetails,
702
+ addressLookup: addressLookupMapper
703
+ };
704
+
705
+ // src/mutations/business/update-business-email.js
706
+ var updateBusinessEmailMutation = `
707
+ mutation Mutation($input: UpdateBusinessEmailInput!) {
708
+ updateBusinessEmail(input: $input) {
709
+ business {
710
+ info {
711
+ email {
712
+ address
713
+ }
714
+ }
715
+ }
716
+ success
717
+ }
718
+ }
719
+ `;
720
+
721
+ // src/mutations/business/update-business-name.js
722
+ var updateBusinessNameMutation = `
723
+ mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
724
+ updateBusinessName(input: $input) {
725
+ business {
726
+ info {
727
+ name
728
+ }
729
+ }
730
+ success
731
+ }
732
+ }
733
+ `;
734
+
735
+ // src/mutations/personal/update-customer-name.js
736
+ var updateCustomerNameMutation = `
737
+ mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
738
+ updateCustomerName(input: $input) {
739
+ customer {
740
+ info {
741
+ name {
742
+ first
743
+ last
744
+ middle
745
+ }
746
+ }
747
+ }
748
+ }
749
+ }
750
+ `;
751
+
752
+ // src/mutations/personal/update-customer-dob.js
753
+ var updateCustomerDobMutation = `
754
+ mutation UpdateCustomerDateOfBirth($input: UpdateCustomerDateOfBirthInput!) {
755
+ updateCustomerDateOfBirth(input: $input) {
756
+ customer {
757
+ info {
758
+ dateOfBirth
759
+ }
760
+ }
761
+ }
762
+ }
763
+ `;
740
764
 
741
- // src/schemas/personal/personal-dob-schema.js
742
- var personalDobSchema = Joi.object({
743
- day: Joi.string().allow(""),
744
- month: Joi.string().allow(""),
745
- year: Joi.string().allow("")
746
- }).custom((value, helpers) => {
747
- const { day, month, year } = value;
748
- const missingFieldsError = checkMissingFields(day, month, year, helpers);
749
- if (missingFieldsError) {
750
- return missingFieldsError;
765
+ // src/mutations/personal/update-customer-phone.js
766
+ var updateCustomerPhoneMutation = `
767
+ mutation UpdateCustomerPhone($input: UpdateCustomerPhoneInput!) {
768
+ updateCustomerPhone(input: $input) {
769
+ customer {
770
+ info {
771
+ phone {
772
+ landline
773
+ mobile
774
+ }
775
+ }
776
+ }
777
+ }
751
778
  }
752
- if (year && year.length !== 4) {
753
- return makeError(helpers, "dob.yearLength", ["year"]);
779
+ `;
780
+
781
+ // src/mutations/personal/update-customer-email.js
782
+ var updateCustomerEmailMutation = `
783
+ mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
784
+ updateCustomerEmail(input: $input) {
785
+ customer {
786
+ info {
787
+ email {
788
+ address
789
+ }
790
+ }
791
+ }
792
+ }
754
793
  }
755
- const monthValue = getMonthNumber(month, helpers);
756
- if (monthValue.isJoiError) {
757
- return monthValue;
794
+ `;
795
+
796
+ // src/mutations/personal/update-customer-address.js
797
+ var updateCustomerAddressMutation = `
798
+ mutation UpdateCustomerAddress($input: UpdateCustomerAddressInput!) {
799
+ updateCustomerAddress(input: $input) {
800
+ customer {
801
+ info {
802
+ address {
803
+ line1
804
+ line2
805
+ line3
806
+ line4
807
+ line5
808
+ postalCode
809
+ country
810
+ city
811
+ buildingNumberRange
812
+ buildingName
813
+ flatName
814
+ street
815
+ county
816
+ uprn
817
+ }
818
+ }
819
+ }
820
+ }
758
821
  }
759
- const fullDate = getFullDate(day, monthValue, year, helpers);
760
- if (fullDate.isJoiError) {
761
- return fullDate;
822
+ `;
823
+
824
+ // src/mutations/business/update-business-address.js
825
+ var updateBusinessAddressMutation = `
826
+ mutation UpdateBusinessAddress($input: UpdateBusinessAddressInput!) {
827
+ updateBusinessAddress(input: $input) {
828
+ business {
829
+ info {
830
+ address {
831
+ line1
832
+ line2
833
+ line3
834
+ line4
835
+ line5
836
+ postalCode
837
+ country
838
+ city
839
+ buildingNumberRange
840
+ buildingName
841
+ flatName
842
+ street
843
+ county
844
+ uprn
845
+ }
846
+ }
847
+ }
848
+ }
762
849
  }
763
- const today = /* @__PURE__ */ new Date();
764
- today.setUTCHours(0, 0, 0, 0);
765
- if (fullDate >= today) {
766
- return makeError(helpers, "dob.future", ["day", "month", "year"]);
850
+ `;
851
+
852
+ // src/mutations/mutations.js
853
+ var mutations = {
854
+ updateBusinessEmail: updateBusinessEmailMutation,
855
+ updateBusinessName: updateBusinessNameMutation,
856
+ updateCustomerName: updateCustomerNameMutation,
857
+ updateCustomerDob: updateCustomerDobMutation,
858
+ updateCustomerPhone: updateCustomerPhoneMutation,
859
+ updateCustomerEmail: updateCustomerEmailMutation,
860
+ updateCustomerAddress: updateCustomerAddressMutation,
861
+ updateBusinessAddress: updateBusinessAddressMutation
862
+ };
863
+
864
+ // src/presenters/base-presenter.js
865
+ var BACK_LINK_DISPLAY_MAX = 50;
866
+ var formatBackLink = (businessName) => {
867
+ if (businessName.length > BACK_LINK_DISPLAY_MAX) {
868
+ return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
767
869
  }
768
- const tooOldError = checkNotTooOld(fullDate, helpers);
769
- if (tooOldError) {
770
- return tooOldError;
870
+ return `Back to ${businessName}`;
871
+ };
872
+ var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
873
+ if (payloadNumber !== void 0) {
874
+ return payloadNumber;
771
875
  }
772
- return value;
773
- }).messages({
774
- "dob.missingAll": "Enter your date of birth",
775
- "dob.missingDay": "Date of birth must include a day",
776
- "dob.missingMonth": "Date of birth must include a month",
777
- "dob.missingYear": "Date of birth must include a year",
778
- "dob.missingDayMonth": "Date of birth must include a day and month",
779
- "dob.missingDayYear": "Date of birth must include a day and year",
780
- "dob.missingMonthYear": "Date of birth must include a month and year",
781
- "dob.yearLength": "Enter a year with 4 numbers, like 1975",
782
- "dob.invalid": "Date of birth must be a real date",
783
- "dob.future": "Date of birth must be in the past",
784
- "dob.tooOld": "Date of birth must be on or after {{#oldest}}",
785
- "string.noControlChars": "Date of birth must not contain invalid characters"
786
- });
787
- var checkNotTooOld = (fullDate, helpers) => {
788
- const oldestDateAllowed = getOldestAllowedDate();
789
- if (fullDate < oldestDateAllowed) {
790
- const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
791
- day: "numeric",
792
- month: "long",
793
- year: "numeric"
794
- });
795
- const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
796
- error.path = ["day", "month", "year"];
797
- return error;
876
+ if (changedNumber !== void 0) {
877
+ return changedNumber;
798
878
  }
799
- return null;
879
+ return originalNumber;
800
880
  };
801
- var getOldestAllowedDate = () => {
802
- const date = /* @__PURE__ */ new Date();
803
- date.setUTCHours(0, 0, 0, 0);
804
- date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
805
- return date;
881
+ var formatDatePart = (changed, original) => {
882
+ return changed ?? (original == null ? void 0 : original.toString()) ?? "";
806
883
  };
807
- var getFullDate = (day, monthValue, year, helpers) => {
808
- const dayValue = Number.parseInt(day, 10);
809
- const yearValue = Number.parseInt(year, 10);
810
- const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
811
- if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
812
- return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
884
+ var formatDateInputValues = (payloadDob, changedDob, originalDob) => {
885
+ if (payloadDob) {
886
+ return {
887
+ day: payloadDob.day ?? "",
888
+ month: payloadDob.month ?? "",
889
+ year: payloadDob.year ?? ""
890
+ };
813
891
  }
814
- return date;
892
+ return {
893
+ day: formatDatePart(changedDob == null ? void 0 : changedDob.day, originalDob == null ? void 0 : originalDob.day),
894
+ month: formatDatePart(changedDob == null ? void 0 : changedDob.month, originalDob == null ? void 0 : originalDob.month),
895
+ year: formatDatePart(changedDob == null ? void 0 : changedDob.year, originalDob == null ? void 0 : originalDob.year)
896
+ };
815
897
  };
816
- var getMonthNumber = (month, helpers) => {
817
- if (Number.isNaN(Number(month))) {
818
- const lower = month.toLowerCase();
819
- const mapped = MONTH_MAP[lower];
820
- if (!mapped) {
821
- return makeError(helpers, "dob.invalid", ["month"]);
898
+ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
899
+ const sortedErrors = [];
900
+ for (const section of orderedSectionsToFix) {
901
+ const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
902
+ for (const field of fieldsInSection) {
903
+ if (errors[field]) {
904
+ sortedErrors.push({
905
+ field,
906
+ ...errors[field]
907
+ });
908
+ }
822
909
  }
823
- return mapped;
824
- }
825
- const parsedMonth = Number.parseInt(month, 10);
826
- if (parsedMonth < 1 || parsedMonth > 12) {
827
- return makeError(helpers, "dob.invalid", ["month"]);
828
910
  }
829
- return parsedMonth;
911
+ return sortedErrors;
830
912
  };
831
- var checkMissingFields = (day, month, year, helpers) => {
832
- if (!day && !month && !year) {
833
- return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
834
- }
835
- if (!day && month && year) {
836
- return makeError(helpers, "dob.missingDay", ["day"]);
913
+
914
+ // src/presenters/address-presenter.js
915
+ var addressChangeLink = (postcodeLookup, context) => {
916
+ if (context === "business") {
917
+ if (postcodeLookup) {
918
+ return "/business-address-change";
919
+ }
920
+ return "/business-address-enter";
837
921
  }
838
- if (day && !month && year) {
839
- return makeError(helpers, "dob.missingMonth", ["month"]);
922
+ if (context === "personal") {
923
+ if (postcodeLookup) {
924
+ return "/account-address-change";
925
+ }
926
+ return "/account-address-enter";
840
927
  }
841
- if (day && month && !year) {
842
- return makeError(helpers, "dob.missingYear", ["year"]);
928
+ return null;
929
+ };
930
+ var addressBackLink = (postcodeLookup, context) => {
931
+ if (context === "business") {
932
+ if (postcodeLookup) {
933
+ return { href: "/business-address-select" };
934
+ }
935
+ return { href: "/business-address-enter" };
843
936
  }
844
- if (!day && !month && year) {
845
- return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
937
+ if (context === "personal") {
938
+ if (postcodeLookup) {
939
+ return { href: "/account-address-select" };
940
+ }
941
+ return { href: "/account-address-enter" };
846
942
  }
847
- if (!day && month && !year) {
848
- return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
943
+ return null;
944
+ };
945
+ var buildAddressLine = (parts) => {
946
+ return parts.filter(Boolean).join(", ") || null;
947
+ };
948
+ var buildStreetLine = (buildingRange, street) => {
949
+ return [buildingRange, street].filter(Boolean).join(" ") || null;
950
+ };
951
+ var formatLookupAddress = (lookup, city, country, postcode) => ({
952
+ address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
953
+ address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
954
+ address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
955
+ county: lookup.county ?? null,
956
+ city: city ?? null,
957
+ country: country ?? null,
958
+ postcode: postcode ?? null
959
+ });
960
+ var formatManualAddress = (manual, city, country, postcode) => ({
961
+ address1: manual.line1 ?? null,
962
+ address2: manual.line2 ?? null,
963
+ address3: manual.line3 ?? null,
964
+ city: city ?? null,
965
+ county: manual.line4 ?? null,
966
+ country: country ?? null,
967
+ postcode: postcode ?? null
968
+ });
969
+ var formatDisplayAddress = (address) => {
970
+ const { lookup, manual, postcode, country, city } = address;
971
+ let addressLines = [];
972
+ if (lookup.uprn) {
973
+ const buildingAndStreet = [
974
+ lookup.buildingNumberRange,
975
+ lookup.street
976
+ ].filter(Boolean).join(" ");
977
+ addressLines = [
978
+ lookup.pafOrganisationName,
979
+ lookup.flatName,
980
+ lookup.buildingName,
981
+ buildingAndStreet,
982
+ lookup.doubleDependentLocality,
983
+ lookup.dependentLocality,
984
+ city,
985
+ lookup.county
986
+ ];
987
+ } else {
988
+ addressLines = [
989
+ manual.line1,
990
+ manual.line2,
991
+ manual.line3,
992
+ city,
993
+ manual.line4,
994
+ // County
995
+ manual.line5
996
+ ];
849
997
  }
850
- if (day && !month && !year) {
851
- return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
998
+ return [
999
+ ...addressLines.filter(Boolean),
1000
+ postcode,
1001
+ country
1002
+ ];
1003
+ };
1004
+ var formatOriginalAddress = (originalAddress) => {
1005
+ const { lookup, manual, city, country, postcode } = originalAddress;
1006
+ return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
1007
+ };
1008
+ var formatChangedAddress = (changeBusinessAddress) => {
1009
+ if (!changeBusinessAddress.uprn) {
1010
+ return changeBusinessAddress;
852
1011
  }
853
- return null;
1012
+ const {
1013
+ pafOrganisationName,
1014
+ flatName,
1015
+ buildingName,
1016
+ buildingNumberRange,
1017
+ street,
1018
+ doubleDependentLocality,
1019
+ dependentLocality,
1020
+ city,
1021
+ county,
1022
+ country,
1023
+ postcode
1024
+ } = changeBusinessAddress;
1025
+ return {
1026
+ address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
1027
+ address2: buildStreetLine(buildingNumberRange, street),
1028
+ address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
1029
+ city: city ?? null,
1030
+ county: county ?? null,
1031
+ country: country ?? null,
1032
+ postcode: postcode ?? null
1033
+ };
854
1034
  };
855
- var makeError = (helpers, code, fields) => {
856
- const error = helpers.error(code);
857
- error.path = fields;
858
- error.isJoiError = true;
859
- return error;
1035
+ var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
1036
+ const displayAddresses = addresses.map((address) => ({
1037
+ value: `${address.uprn}${address.displayAddress}`,
1038
+ text: address.displayAddress,
1039
+ selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
1040
+ }));
1041
+ const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
1042
+ const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
1043
+ displayAddresses.unshift({
1044
+ value: "display",
1045
+ text,
1046
+ selected: !hasSelectedAddress
1047
+ });
1048
+ return displayAddresses;
860
1049
  };
861
1050
 
862
- // src/schemas/personal/personal-email-schema.js
863
- var personalEmailSchema = Joi.object({
864
- personalEmail: Joi.string().required().max(EMAIL_MAX).email({
865
- minDomainSegments: 2,
866
- tlds: {
867
- allow: true,
868
- min: 2
869
- }
870
- }).messages({
871
- "string.max": `Email address must be ${EMAIL_MAX} characters or less`,
872
- "string.empty": "Enter a personal email address",
873
- "string.email": "Enter an email address, like name@example.com",
874
- "string.noControlChars": "Personal email address must not contain invalid characters"
875
- })
876
- });
877
-
878
- // src/schemas/personal/personal-phone-schema.js
879
- var personalPhoneSchema = Joi.object({
880
- personalTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
881
- "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
882
- "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
883
- "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
884
- "string.noControlChars": "Personal telephone number must not contain invalid characters"
885
- }),
886
- personalMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
887
- "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
888
- "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
889
- "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
890
- "string.noControlChars": "Personal mobile phone number must not contain invalid characters"
891
- })
892
- }).or("personalTelephone", "personalMobile").messages({
893
- "object.missing": "Enter at least one phone number"
894
- });
895
-
896
- // src/schemas/personal/personal-schemas.js
897
- var personalSchemas = {
898
- name: personalNameSchema,
899
- dob: personalDobSchema,
900
- address: addressSchema,
901
- phone: personalPhoneSchema,
902
- email: personalEmailSchema
1051
+ // src/presenters/business-details-presenter.js
1052
+ var getActionText = (value) => {
1053
+ return value ? "Change" : "Add";
903
1054
  };
904
-
905
- // src/schemas/os-places/address-lookup-schema.js
906
- var addressLookupSchema = Joi.object({
907
- properties: Joi.object({
908
- UPRN: Joi.string().required(),
909
- ADDRESS: Joi.string().required(),
910
- ORGANISATION_NAME: Joi.string().allow(null),
911
- DEPARTMENT_NAME: Joi.string().allow(null),
912
- SUB_BUILDING_NAME: Joi.string().allow(null),
913
- BUILDING_NAME: Joi.string().allow(null),
914
- BUILDING_NUMBER: Joi.string().allow(null),
915
- DEPENDENT_THOROUGHFARE_NAME: Joi.string().allow(null),
916
- THOROUGHFARE_NAME: Joi.string().allow(null),
917
- DOUBLE_DEPENDENT_LOCALITY: Joi.string().allow(null),
918
- DEPENDENT_LOCALITY: Joi.string().allow(null),
919
- POST_TOWN: Joi.string().required(),
920
- POSTCODE: Joi.string().required(),
921
- LOCAL_CUSTODIAN_CODE_DESCRIPTION: Joi.string().allow(null),
922
- COUNTRY_CODE: Joi.string().required()
923
- }).unknown(true).required()
924
- }).unknown(true);
925
-
926
- // src/schemas/os-places/addresses-schema.js
927
- var CHOOSE_ADDRESS_ERROR = "Choose an address";
928
- var addressesSchema = Joi.object({
929
- addresses: Joi.string().invalid("display").required().messages({
930
- "any.required": CHOOSE_ADDRESS_ERROR,
931
- "string.empty": CHOOSE_ADDRESS_ERROR,
932
- "any.invalid": CHOOSE_ADDRESS_ERROR
933
- })
934
- });
935
-
936
- // src/schemas/os-places/uk-postcode-schema.js
937
- var ukPostcodeSchema = Joi.object({
938
- postcode: Joi.string().trim().required().uppercase().max(POSTCODE_MAX).pattern(/^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$/).messages({
939
- "any.required": "Enter a postcode",
940
- "string.empty": "Enter a postcode",
941
- "string.max": `Postal code must be ${POSTCODE_MAX} characters or less`,
942
- "string.pattern.base": "Enter a full UK postcode, like AA3 1AB"
943
- })
944
- });
945
-
946
- // src/schemas/os-places/os-places-schemas.js
947
- var osPlacesSchemas = {
948
- addressLookup: addressLookupSchema,
949
- addresses: addressesSchema,
950
- ukPostcode: ukPostcodeSchema
1055
+ var formatCph = (countyParishHoldings) => {
1056
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
1057
+ };
1058
+ var formatCphText = (count) => {
1059
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
1060
+ };
1061
+ var formatBusinessAddress = (businessAddress) => {
1062
+ var _a, _b;
1063
+ if (((_a = businessAddress == null ? void 0 : businessAddress.lookup) == null ? void 0 : _a.uprn) || ((_b = businessAddress == null ? void 0 : businessAddress.manual) == null ? void 0 : _b.line1)) {
1064
+ return formatDisplayAddress(businessAddress);
1065
+ }
1066
+ return [];
951
1067
  };
952
1068
 
953
- // src/schemas/schemas.js
954
- var schemas = {
955
- business: businessSchemas,
956
- customer: customerSchemas,
957
- personal: personalSchemas,
958
- osPlaces: osPlacesSchemas
1069
+ // src/presenters/presenters.js
1070
+ var presenters = {
1071
+ formatBackLink,
1072
+ formatNumber,
1073
+ formatDateInputValues,
1074
+ formatDisplayAddress,
1075
+ formatOriginalAddress,
1076
+ formatChangedAddress,
1077
+ formatDisplayAddresses,
1078
+ sortErrorsBySectionOrder,
1079
+ getActionText,
1080
+ formatCph,
1081
+ formatCphText,
1082
+ formatBusinessAddress,
1083
+ addressBackLink,
1084
+ addressChangeLink
959
1085
  };
960
1086
 
961
1087
  // src/utils/format-full-name.js
@@ -1010,6 +1136,308 @@ var utils = {
1010
1136
  buildUpdateBusinessEmailVariables,
1011
1137
  buildUpdateBusinessNameVariables
1012
1138
  };
1139
+
1140
+ // src/services/os-places/address-lookup-service.js
1141
+ var import_osdatahub = require("osdatahub");
1142
+
1143
+ // src/mock-data/mock-os-places-addresses.js
1144
+ var mockAddresses = [
1145
+ {
1146
+ properties: {
1147
+ UPRN: "10000001",
1148
+ ADDRESS: "APARTMENT 1, REDBRIDGE HOUSE, 1 ROSE COURT, LONDON, SW1A 1AA",
1149
+ SUB_BUILDING_NAME: "APARTMENT 1",
1150
+ BUILDING_NAME: "REDBRIDGE HOUSE",
1151
+ BUILDING_NUMBER: "1",
1152
+ THOROUGHFARE_NAME: "ROSE COURT",
1153
+ POST_TOWN: "LONDON",
1154
+ POSTCODE: "SW1A 1AA",
1155
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1156
+ COUNTRY_CODE: "E"
1157
+ }
1158
+ },
1159
+ {
1160
+ properties: {
1161
+ UPRN: "10000002",
1162
+ ADDRESS: "FLAT 2B, KINGSLEY APARTMENTS, 12 VICTORIA SQUARE, LONDON, SW1A 1AA",
1163
+ SUB_BUILDING_NAME: "FLAT 2B",
1164
+ BUILDING_NAME: "KINGSLEY APARTMENTS",
1165
+ BUILDING_NUMBER: "12",
1166
+ THOROUGHFARE_NAME: "VICTORIA SQUARE",
1167
+ DEPENDENT_LOCALITY: "",
1168
+ POST_TOWN: "LONDON",
1169
+ POSTCODE: "SW1A 1AA",
1170
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1171
+ COUNTRY_CODE: "E"
1172
+ }
1173
+ },
1174
+ {
1175
+ properties: {
1176
+ UPRN: "10000003",
1177
+ ADDRESS: "THE GROUND FLOOR, THE OLD GRANARY, 5 CHURCH LANE, LONDON, SW1A 1AA",
1178
+ ORGANISATION_NAME: "OLD GRANARY STUDIOS",
1179
+ SUB_BUILDING_NAME: "GROUND FLOOR",
1180
+ BUILDING_NAME: "THE OLD GRANARY",
1181
+ BUILDING_NUMBER: "5",
1182
+ THOROUGHFARE_NAME: "CHURCH LANE",
1183
+ POST_TOWN: "LONDON",
1184
+ POSTCODE: "SW1A 1AA",
1185
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1186
+ COUNTRY_CODE: "E"
1187
+ }
1188
+ },
1189
+ {
1190
+ properties: {
1191
+ UPRN: "10000004",
1192
+ ADDRESS: "SUITE 3, WELLINGTON HOUSE, 20 QUEEN STREET, LONDON, SW1A 1AA",
1193
+ ORGANISATION_NAME: "WELLINGTON CONSULTING LTD",
1194
+ DEPARTMENT_NAME: "CLIENT SERVICES",
1195
+ SUB_BUILDING_NAME: "SUITE 3",
1196
+ BUILDING_NAME: "WELLINGTON HOUSE",
1197
+ BUILDING_NUMBER: "20",
1198
+ THOROUGHFARE_NAME: "QUEEN STREET",
1199
+ POST_TOWN: "LONDON",
1200
+ POSTCODE: "SW1A 1AA",
1201
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1202
+ COUNTRY_CODE: "E"
1203
+ }
1204
+ },
1205
+ {
1206
+ properties: {
1207
+ UPRN: "10000005",
1208
+ ADDRESS: "UNIT 6, MARKET ROW, 3 MARKET YARD, LONDON, SW1A 1AA",
1209
+ ORGANISATION_NAME: "MARKET ROW TRADERS",
1210
+ SUB_BUILDING_NAME: "UNIT 6",
1211
+ BUILDING_NAME: "MARKET ROW",
1212
+ BUILDING_NUMBER: "3",
1213
+ THOROUGHFARE_NAME: "MARKET YARD",
1214
+ POST_TOWN: "LONDON",
1215
+ POSTCODE: "SW1A 1AA",
1216
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1217
+ COUNTRY_CODE: "E"
1218
+ }
1219
+ },
1220
+ {
1221
+ properties: {
1222
+ UPRN: "20000001",
1223
+ ADDRESS: "1 ORCHARD COTTAGES, WESTFIELD LANE, SHEPTON MALLET, BS14 8XX",
1224
+ BUILDING_NAME: "ORCHARD COTTAGES",
1225
+ BUILDING_NUMBER: "1",
1226
+ THOROUGHFARE_NAME: "WESTFIELD LANE",
1227
+ POST_TOWN: "SHEPTON MALLET",
1228
+ POSTCODE: "BS14 8XX",
1229
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "SOMERSET",
1230
+ COUNTRY_CODE: "E"
1231
+ }
1232
+ },
1233
+ {
1234
+ properties: {
1235
+ UPRN: "20000002",
1236
+ ADDRESS: "THE STABLES, MANOR FARM, HOLLOW ROAD, SHEPTON MALLET, BS14 8XX",
1237
+ ORGANISATION_NAME: "MANOR FARM",
1238
+ SUB_BUILDING_NAME: "THE STABLES",
1239
+ BUILDING_NAME: "MANOR FARM",
1240
+ THOROUGHFARE_NAME: "HOLLOW ROAD",
1241
+ POST_TOWN: "SHEPTON MALLET",
1242
+ POSTCODE: "BS14 8XX",
1243
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "SOMERSET",
1244
+ COUNTRY_CODE: "E"
1245
+ }
1246
+ },
1247
+ {
1248
+ properties: {
1249
+ UPRN: "30000001",
1250
+ ADDRESS: "FLAT 4, BISHOP COURT, 9 FLEET STREET, LONDON, EC1A 1BB",
1251
+ SUB_BUILDING_NAME: "FLAT 4",
1252
+ BUILDING_NAME: "BISHOP COURT",
1253
+ BUILDING_NUMBER: "9",
1254
+ THOROUGHFARE_NAME: "FLEET STREET",
1255
+ POST_TOWN: "LONDON",
1256
+ POSTCODE: "EC1A 1BB",
1257
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "CITY OF LONDON",
1258
+ COUNTRY_CODE: "E"
1259
+ }
1260
+ },
1261
+ {
1262
+ properties: {
1263
+ UPRN: "40000001",
1264
+ ADDRESS: "2A PICCADILLY ARCADE, MANCHESTER, M1 1AE",
1265
+ ORGANISATION_NAME: "PICCADILLY ARCADE LTD",
1266
+ SUB_BUILDING_NAME: "2A",
1267
+ BUILDING_NAME: "PICCADILLY ARCADE",
1268
+ BUILDING_NUMBER: "2A",
1269
+ THOROUGHFARE_NAME: "PICCADILLY",
1270
+ POST_TOWN: "MANCHESTER",
1271
+ POSTCODE: "M1 1AE",
1272
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER MANCHESTER",
1273
+ COUNTRY_CODE: "E"
1274
+ }
1275
+ },
1276
+ {
1277
+ properties: {
1278
+ UPRN: "50000001",
1279
+ ADDRESS: "3 WESTBOURNE TERRACE, LONDON, W1A 0AX",
1280
+ BUILDING_NAME: "WESTBOURNE TERRACE",
1281
+ BUILDING_NUMBER: "3",
1282
+ THOROUGHFARE_NAME: "WESTBOURNE TERRACE",
1283
+ POST_TOWN: "LONDON",
1284
+ POSTCODE: "W1A 0AX",
1285
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "WESTMINSTER",
1286
+ COUNTRY_CODE: "E"
1287
+ }
1288
+ }
1289
+ ];
1290
+
1291
+ // src/services/os-places/os-places-stub.js
1292
+ var mockPostcode = (postcode) => {
1293
+ const formattedPostcode = postcode == null ? void 0 : postcode.toUpperCase().replaceAll(" ", "");
1294
+ const matchingAddresses = mockAddresses.filter((address) => {
1295
+ var _a;
1296
+ const addressPostcode = (_a = address.properties.POSTCODE) == null ? void 0 : _a.toUpperCase().replaceAll(" ", "");
1297
+ return addressPostcode === formattedPostcode;
1298
+ });
1299
+ return {
1300
+ features: matchingAddresses
1301
+ };
1302
+ };
1303
+
1304
+ // src/services/os-places/address-lookup-service.js
1305
+ var addressLookupService = async (postcode, osPlacesConfig) => {
1306
+ const addresses = await fetchAddressesFromPostcodeLookup(postcode, osPlacesConfig);
1307
+ if (addresses.error) {
1308
+ return addresses;
1309
+ }
1310
+ if (!(addresses == null ? void 0 : addresses.length)) {
1311
+ return {
1312
+ error: [
1313
+ {
1314
+ message: "No addresses found for this postcode",
1315
+ path: ["postcode"]
1316
+ }
1317
+ ]
1318
+ };
1319
+ }
1320
+ const mappedAddresses = addressLookupMapper(addresses);
1321
+ return mappedAddresses;
1322
+ };
1323
+ var fetchAddressesFromPostcodeLookup = async (postcode, osPlacesConfig) => {
1324
+ const MAX_RETRIES = 3;
1325
+ const INITIAL_BACKOFF_MS = 100;
1326
+ for (let attemptNumber = 1; attemptNumber <= MAX_RETRIES; attemptNumber++) {
1327
+ try {
1328
+ const { clientId, osPlacesStub } = osPlacesConfig;
1329
+ if (osPlacesStub) {
1330
+ const response2 = mockPostcode(postcode);
1331
+ return response2.features ?? [];
1332
+ }
1333
+ const response = await fetchFromPlacesAPI(clientId, postcode);
1334
+ return response.features ?? [];
1335
+ } catch (error) {
1336
+ const shouldRetry = isRetryable(error) && attemptNumber < MAX_RETRIES;
1337
+ if (!shouldRetry) {
1338
+ return buildErrorResponse(error.message);
1339
+ }
1340
+ const backoffMs = calculateExponentialBackoff(attemptNumber, INITIAL_BACKOFF_MS);
1341
+ await delayBeforeRetry(backoffMs);
1342
+ }
1343
+ }
1344
+ };
1345
+ var calculateExponentialBackoff = (attemptNumber, baseMs) => {
1346
+ const exponent = attemptNumber - 1;
1347
+ const powerOf2 = Math.pow(2, exponent);
1348
+ const backoffDuration = baseMs * powerOf2;
1349
+ return backoffDuration;
1350
+ };
1351
+ function delayBeforeRetry(ms) {
1352
+ const callback = (resolve) => {
1353
+ setTimeout(resolve, ms);
1354
+ };
1355
+ return new Promise(callback);
1356
+ }
1357
+ var buildErrorResponse = (message) => {
1358
+ return {
1359
+ error: [
1360
+ {
1361
+ message: message || "Failed to fetch addresses",
1362
+ path: ["postcode"]
1363
+ }
1364
+ ]
1365
+ };
1366
+ };
1367
+ var fetchFromPlacesAPI = async (clientId, postcode) => {
1368
+ const response = await import_osdatahub.placesAPI.postcode(clientId, postcode, { limit: 150 });
1369
+ return response;
1370
+ };
1371
+ var isRetryable = (error) => {
1372
+ const networkErrors = ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"];
1373
+ if (networkErrors.includes(error.code)) {
1374
+ return true;
1375
+ }
1376
+ if (error.status && error.status >= INTERNAL_SERVER_ERROR) {
1377
+ return true;
1378
+ }
1379
+ return false;
1380
+ };
1381
+
1382
+ // src/services/build-address-variables-service.js
1383
+ var buildUprnAddress = (change) => {
1384
+ return {
1385
+ pafOrganisationName: nullIfUndefined(change.pafOrganisationName),
1386
+ buildingNumberRange: nullIfUndefined(change.buildingNumberRange),
1387
+ buildingName: nullIfUndefined(change.buildingName),
1388
+ flatName: nullIfUndefined(change.flatName),
1389
+ street: nullIfUndefined(change.street),
1390
+ city: nullIfUndefined(change.city),
1391
+ county: nullIfUndefined(change.county),
1392
+ postalCode: nullIfUndefined(change.postcode),
1393
+ country: nullIfUndefined(change.country),
1394
+ dependentLocality: nullIfUndefined(change.dependentLocality),
1395
+ doubleDependentLocality: nullIfUndefined(change.doubleDependentLocality),
1396
+ line1: null,
1397
+ line2: null,
1398
+ line3: null,
1399
+ line4: null,
1400
+ line5: null,
1401
+ uprn: change.uprn
1402
+ // required for DAL/v1
1403
+ };
1404
+ };
1405
+ var buildManualAddress = (change) => {
1406
+ return {
1407
+ pafOrganisationName: null,
1408
+ buildingNumberRange: null,
1409
+ buildingName: null,
1410
+ flatName: null,
1411
+ street: null,
1412
+ dependentLocality: null,
1413
+ doubleDependentLocality: null,
1414
+ county: null,
1415
+ uprn: null,
1416
+ line1: change.address1,
1417
+ // required for DAL/v1
1418
+ line2: nullIfUndefined(change.address2),
1419
+ line3: nullIfUndefined(change.address3),
1420
+ line4: nullIfUndefined(change.county),
1421
+ // manual county mapped for validation
1422
+ line5: null,
1423
+ city: change.city,
1424
+ // required for DAL/v1
1425
+ postalCode: change.postcode,
1426
+ // required for DAL/v1
1427
+ country: change.country
1428
+ // required for DAL/v1
1429
+ };
1430
+ };
1431
+ var nullIfUndefined = (value) => {
1432
+ return value ?? null;
1433
+ };
1434
+
1435
+ // src/services/services.js
1436
+ var services = {
1437
+ addressLookup: addressLookupService,
1438
+ buildUprnAddress,
1439
+ buildManualAddress
1440
+ };
1013
1441
  // Annotate the CommonJS export names for ESM import in node:
1014
1442
  0 && (module.exports = {
1015
1443
  constants,
@@ -1017,5 +1445,6 @@ var utils = {
1017
1445
  mutations,
1018
1446
  presenters,
1019
1447
  schemas,
1448
+ services,
1020
1449
  utils
1021
1450
  });