@defra/fcp-sfd-frontend-engine 0.4.0 → 0.6.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
@@ -87,11 +87,46 @@ var mapCustomerName = (name = {}) => {
87
87
  };
88
88
  };
89
89
 
90
+ // src/mappers/business-details-mapper.js
91
+ var asNullable = (value) => value ?? null;
92
+ var mapBusinessInfo = (business) => {
93
+ var _a, _b;
94
+ const info = business.info ?? {};
95
+ return {
96
+ sbi: business.sbi,
97
+ businessName: asNullable(info.name),
98
+ vat: asNullable(info.vat),
99
+ traderNumber: asNullable(info.traderNumber),
100
+ vendorNumber: asNullable(info.vendorNumber),
101
+ legalStatus: asNullable((_a = info.legalStatus) == null ? void 0 : _a.type),
102
+ type: asNullable((_b = info.type) == null ? void 0 : _b.type),
103
+ countyParishHoldingNumbers: business.countyParishHoldings ?? []
104
+ };
105
+ };
106
+ var mapBusinessContact = (businessInfo) => {
107
+ var _a, _b, _c;
108
+ return {
109
+ email: asNullable((_a = businessInfo.email) == null ? void 0 : _a.address),
110
+ landline: asNullable((_b = businessInfo.phone) == null ? void 0 : _b.landline),
111
+ mobile: asNullable((_c = businessInfo.phone) == null ? void 0 : _c.mobile)
112
+ };
113
+ };
114
+ var mapBusinessDetails = (value) => {
115
+ const business = (value == null ? void 0 : value.business) ?? {};
116
+ const businessInfo = business.info ?? {};
117
+ return {
118
+ info: mapBusinessInfo(business),
119
+ address: mapAddress(businessInfo.address),
120
+ contact: mapBusinessContact(businessInfo)
121
+ };
122
+ };
123
+
90
124
  // src/mappers/mappers.js
91
125
  var mappers = {
92
126
  personalBusinessDetails: mapPersonalBusinessDetails,
93
127
  address: mapAddress,
94
- customerName: mapCustomerName
128
+ customerName: mapCustomerName,
129
+ businessDetails: mapBusinessDetails
95
130
  };
96
131
 
97
132
  // src/schemas/business/business-sbi-schema.js
@@ -109,6 +144,7 @@ var import_joi2 = __toESM(require("joi"), 1);
109
144
  var FIRST_NAME_MAX = 100;
110
145
  var LAST_NAME_MAX = 100;
111
146
  var MIDDLE_NAMES_MAX = 100;
147
+ var BUSINESS_NAME_MAX = 160;
112
148
  var EMAIL_MAX = 254;
113
149
  var PHONE_NUMBER_MIN = 10;
114
150
  var PHONE_NUMBER_MAX = 50;
@@ -152,16 +188,77 @@ var addressSchema = import_joi2.default.object({
152
188
  })
153
189
  });
154
190
 
191
+ // src/schemas/business/business-name-schema.js
192
+ var import_joi3 = __toESM(require("joi"), 1);
193
+ var businessNameSchema = import_joi3.default.object({
194
+ businessName: import_joi3.default.string().required().max(BUSINESS_NAME_MAX).messages({
195
+ "string.empty": "Enter business name",
196
+ "string.max": `Business name must be ${BUSINESS_NAME_MAX} characters or less`,
197
+ "any.required": "Enter business name"
198
+ })
199
+ });
200
+
201
+ // src/schemas/business/business-email-schema.js
202
+ var import_joi4 = __toESM(require("joi"), 1);
203
+ var businessEmailSchema = import_joi4.default.object({
204
+ businessEmail: import_joi4.default.string().required().max(EMAIL_MAX).email({
205
+ minDomainSegments: 2,
206
+ tlds: {
207
+ allow: true
208
+ }
209
+ }).messages({
210
+ "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
211
+ "string.empty": "Enter business email address",
212
+ "string.email": "Enter an email address, like name@example.com"
213
+ })
214
+ });
215
+
216
+ // src/schemas/business/business-phone-schema.js
217
+ var import_joi5 = __toESM(require("joi"), 1);
218
+
219
+ // src/constants/patterns.js
220
+ var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
221
+
222
+ // src/schemas/business/business-phone-schema.js
223
+ var businessPhoneSchema = import_joi5.default.object({
224
+ businessTelephone: import_joi5.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
225
+ "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
226
+ "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
227
+ "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
228
+ }),
229
+ businessMobile: import_joi5.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
230
+ "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
231
+ "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
232
+ "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
233
+ })
234
+ }).or("businessTelephone", "businessMobile").messages({
235
+ "object.missing": "Enter at least one phone number"
236
+ });
237
+
238
+ // src/schemas/business/business-vat-schema.js
239
+ var import_joi6 = __toESM(require("joi"), 1);
240
+ var businessVatSchema = import_joi6.default.object({
241
+ vatNumber: import_joi6.default.string().pattern(/^\d{9}$/).allow("").optional().messages({
242
+ "string.pattern.base": "Enter a VAT registration number, like 123456789"
243
+ })
244
+ });
245
+
155
246
  // src/schemas/business/business-schemas.js
156
247
  var businessSchemas = {
157
248
  sbi: businessSbiSchema,
158
- address: addressSchema
249
+ details: {
250
+ name: businessNameSchema,
251
+ address: addressSchema,
252
+ phone: businessPhoneSchema,
253
+ email: businessEmailSchema,
254
+ vat: businessVatSchema
255
+ }
159
256
  };
160
257
 
161
258
  // src/schemas/customer/customer-crn-schema.js
162
- var import_joi3 = __toESM(require("joi"), 1);
163
- var customerCrnSchema = import_joi3.default.object({
164
- crn: import_joi3.default.string().pattern(/^\d{10}$/).allow("").optional().messages({
259
+ var import_joi7 = __toESM(require("joi"), 1);
260
+ var customerCrnSchema = import_joi7.default.object({
261
+ crn: import_joi7.default.string().pattern(/^\d{10}$/).allow("").optional().messages({
165
262
  "string.pattern.base": "Enter the full CRN"
166
263
  })
167
264
  });
@@ -172,28 +269,25 @@ var customerSchemas = {
172
269
  };
173
270
 
174
271
  // src/schemas/personal/personal-name-schema.js
175
- var import_joi4 = __toESM(require("joi"), 1);
176
- var personalNameSchema = import_joi4.default.object({
177
- first: import_joi4.default.string().required().max(FIRST_NAME_MAX).messages({
272
+ var import_joi8 = __toESM(require("joi"), 1);
273
+ var personalNameSchema = import_joi8.default.object({
274
+ first: import_joi8.default.string().required().max(FIRST_NAME_MAX).messages({
178
275
  "string.empty": "Enter first name",
179
276
  "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
180
277
  "any.required": "Enter first name"
181
278
  }),
182
- last: import_joi4.default.string().required().max(LAST_NAME_MAX).messages({
279
+ last: import_joi8.default.string().required().max(LAST_NAME_MAX).messages({
183
280
  "string.empty": "Enter last name",
184
281
  "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
185
282
  "any.required": "Enter last name"
186
283
  }),
187
- middle: import_joi4.default.string().allow("").max(MIDDLE_NAMES_MAX).messages({
284
+ middle: import_joi8.default.string().allow("").max(MIDDLE_NAMES_MAX).messages({
188
285
  "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
189
286
  })
190
287
  });
191
288
 
192
289
  // src/schemas/personal/personal-dob-schema.js
193
- var import_joi5 = __toESM(require("joi"), 1);
194
-
195
- // src/constants/patterns.js
196
- var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
290
+ var import_joi9 = __toESM(require("joi"), 1);
197
291
 
198
292
  // src/constants/month-map.js
199
293
  var MONTH_MAP = {
@@ -224,10 +318,10 @@ var MONTH_MAP = {
224
318
  };
225
319
 
226
320
  // src/schemas/personal/personal-dob-schema.js
227
- var personalDobSchema = import_joi5.default.object({
228
- day: import_joi5.default.string().allow(""),
229
- month: import_joi5.default.string().allow(""),
230
- year: import_joi5.default.string().allow("")
321
+ var personalDobSchema = import_joi9.default.object({
322
+ day: import_joi9.default.string().allow(""),
323
+ month: import_joi9.default.string().allow(""),
324
+ year: import_joi9.default.string().allow("")
231
325
  }).custom((value, helpers) => {
232
326
  const { day, month, year } = value;
233
327
  const missingFieldsError = checkMissingFields(day, month, year, helpers);
@@ -338,9 +432,9 @@ var makeError = (helpers, code, fields) => {
338
432
  };
339
433
 
340
434
  // src/schemas/personal/personal-email-schema.js
341
- var import_joi6 = __toESM(require("joi"), 1);
342
- var personalEmailSchema = import_joi6.default.object({
343
- personalEmail: import_joi6.default.string().required().max(EMAIL_MAX).email({
435
+ var import_joi10 = __toESM(require("joi"), 1);
436
+ var personalEmailSchema = import_joi10.default.object({
437
+ personalEmail: import_joi10.default.string().required().max(EMAIL_MAX).email({
344
438
  minDomainSegments: 2,
345
439
  tlds: {
346
440
  allow: true,
@@ -354,14 +448,14 @@ var personalEmailSchema = import_joi6.default.object({
354
448
  });
355
449
 
356
450
  // src/schemas/personal/personal-phone-schema.js
357
- var import_joi7 = __toESM(require("joi"), 1);
358
- var personalPhoneSchema = import_joi7.default.object({
359
- personalTelephone: import_joi7.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
451
+ var import_joi11 = __toESM(require("joi"), 1);
452
+ var personalPhoneSchema = import_joi11.default.object({
453
+ personalTelephone: import_joi11.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
360
454
  "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
361
455
  "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
362
456
  "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
363
457
  }),
364
- personalMobile: import_joi7.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
458
+ personalMobile: import_joi11.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
365
459
  "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
366
460
  "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
367
461
  "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
@@ -555,6 +649,24 @@ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDE
555
649
  return sortedErrors;
556
650
  };
557
651
 
652
+ // src/presenters/business-details-presenter.js
653
+ var getActionText = (value) => {
654
+ return value ? "Change" : "Add";
655
+ };
656
+ var formatCph = (countyParishHoldings) => {
657
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
658
+ };
659
+ var formatCphText = (count) => {
660
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
661
+ };
662
+ var formatBusinessAddress = (businessAddress) => {
663
+ var _a, _b;
664
+ 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)) {
665
+ return formatDisplayAddress(businessAddress);
666
+ }
667
+ return [];
668
+ };
669
+
558
670
  // src/presenters/presenters.js
559
671
  var presenters = {
560
672
  formatBackLink,
@@ -563,7 +675,11 @@ var presenters = {
563
675
  formatOriginalAddress,
564
676
  formatChangedAddress,
565
677
  formatDisplayAddresses,
566
- sortErrorsBySectionOrder
678
+ sortErrorsBySectionOrder,
679
+ getActionText,
680
+ formatCph,
681
+ formatCphText,
682
+ formatBusinessAddress
567
683
  };
568
684
  // Annotate the CommonJS export names for ESM import in node:
569
685
  0 && (module.exports = {
package/dist/index.js CHANGED
@@ -49,11 +49,46 @@ var mapCustomerName = (name = {}) => {
49
49
  };
50
50
  };
51
51
 
52
+ // src/mappers/business-details-mapper.js
53
+ var asNullable = (value) => value ?? null;
54
+ var mapBusinessInfo = (business) => {
55
+ var _a, _b;
56
+ const info = business.info ?? {};
57
+ return {
58
+ sbi: business.sbi,
59
+ businessName: asNullable(info.name),
60
+ vat: asNullable(info.vat),
61
+ traderNumber: asNullable(info.traderNumber),
62
+ vendorNumber: asNullable(info.vendorNumber),
63
+ legalStatus: asNullable((_a = info.legalStatus) == null ? void 0 : _a.type),
64
+ type: asNullable((_b = info.type) == null ? void 0 : _b.type),
65
+ countyParishHoldingNumbers: business.countyParishHoldings ?? []
66
+ };
67
+ };
68
+ var mapBusinessContact = (businessInfo) => {
69
+ var _a, _b, _c;
70
+ return {
71
+ email: asNullable((_a = businessInfo.email) == null ? void 0 : _a.address),
72
+ landline: asNullable((_b = businessInfo.phone) == null ? void 0 : _b.landline),
73
+ mobile: asNullable((_c = businessInfo.phone) == null ? void 0 : _c.mobile)
74
+ };
75
+ };
76
+ var mapBusinessDetails = (value) => {
77
+ const business = (value == null ? void 0 : value.business) ?? {};
78
+ const businessInfo = business.info ?? {};
79
+ return {
80
+ info: mapBusinessInfo(business),
81
+ address: mapAddress(businessInfo.address),
82
+ contact: mapBusinessContact(businessInfo)
83
+ };
84
+ };
85
+
52
86
  // src/mappers/mappers.js
53
87
  var mappers = {
54
88
  personalBusinessDetails: mapPersonalBusinessDetails,
55
89
  address: mapAddress,
56
- customerName: mapCustomerName
90
+ customerName: mapCustomerName,
91
+ businessDetails: mapBusinessDetails
57
92
  };
58
93
 
59
94
  // src/schemas/business/business-sbi-schema.js
@@ -71,6 +106,7 @@ import Joi2 from "joi";
71
106
  var FIRST_NAME_MAX = 100;
72
107
  var LAST_NAME_MAX = 100;
73
108
  var MIDDLE_NAMES_MAX = 100;
109
+ var BUSINESS_NAME_MAX = 160;
74
110
  var EMAIL_MAX = 254;
75
111
  var PHONE_NUMBER_MIN = 10;
76
112
  var PHONE_NUMBER_MAX = 50;
@@ -114,16 +150,77 @@ var addressSchema = Joi2.object({
114
150
  })
115
151
  });
116
152
 
153
+ // src/schemas/business/business-name-schema.js
154
+ import Joi3 from "joi";
155
+ var businessNameSchema = Joi3.object({
156
+ businessName: Joi3.string().required().max(BUSINESS_NAME_MAX).messages({
157
+ "string.empty": "Enter business name",
158
+ "string.max": `Business name must be ${BUSINESS_NAME_MAX} characters or less`,
159
+ "any.required": "Enter business name"
160
+ })
161
+ });
162
+
163
+ // src/schemas/business/business-email-schema.js
164
+ import Joi4 from "joi";
165
+ var businessEmailSchema = Joi4.object({
166
+ businessEmail: Joi4.string().required().max(EMAIL_MAX).email({
167
+ minDomainSegments: 2,
168
+ tlds: {
169
+ allow: true
170
+ }
171
+ }).messages({
172
+ "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
173
+ "string.empty": "Enter business email address",
174
+ "string.email": "Enter an email address, like name@example.com"
175
+ })
176
+ });
177
+
178
+ // src/schemas/business/business-phone-schema.js
179
+ import Joi5 from "joi";
180
+
181
+ // src/constants/patterns.js
182
+ var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
183
+
184
+ // src/schemas/business/business-phone-schema.js
185
+ var businessPhoneSchema = Joi5.object({
186
+ businessTelephone: Joi5.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
187
+ "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
188
+ "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
189
+ "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
190
+ }),
191
+ businessMobile: Joi5.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
192
+ "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
193
+ "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
194
+ "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
195
+ })
196
+ }).or("businessTelephone", "businessMobile").messages({
197
+ "object.missing": "Enter at least one phone number"
198
+ });
199
+
200
+ // src/schemas/business/business-vat-schema.js
201
+ import Joi6 from "joi";
202
+ var businessVatSchema = Joi6.object({
203
+ vatNumber: Joi6.string().pattern(/^\d{9}$/).allow("").optional().messages({
204
+ "string.pattern.base": "Enter a VAT registration number, like 123456789"
205
+ })
206
+ });
207
+
117
208
  // src/schemas/business/business-schemas.js
118
209
  var businessSchemas = {
119
210
  sbi: businessSbiSchema,
120
- address: addressSchema
211
+ details: {
212
+ name: businessNameSchema,
213
+ address: addressSchema,
214
+ phone: businessPhoneSchema,
215
+ email: businessEmailSchema,
216
+ vat: businessVatSchema
217
+ }
121
218
  };
122
219
 
123
220
  // src/schemas/customer/customer-crn-schema.js
124
- import Joi3 from "joi";
125
- var customerCrnSchema = Joi3.object({
126
- crn: Joi3.string().pattern(/^\d{10}$/).allow("").optional().messages({
221
+ import Joi7 from "joi";
222
+ var customerCrnSchema = Joi7.object({
223
+ crn: Joi7.string().pattern(/^\d{10}$/).allow("").optional().messages({
127
224
  "string.pattern.base": "Enter the full CRN"
128
225
  })
129
226
  });
@@ -134,28 +231,25 @@ var customerSchemas = {
134
231
  };
135
232
 
136
233
  // src/schemas/personal/personal-name-schema.js
137
- import Joi4 from "joi";
138
- var personalNameSchema = Joi4.object({
139
- first: Joi4.string().required().max(FIRST_NAME_MAX).messages({
234
+ import Joi8 from "joi";
235
+ var personalNameSchema = Joi8.object({
236
+ first: Joi8.string().required().max(FIRST_NAME_MAX).messages({
140
237
  "string.empty": "Enter first name",
141
238
  "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
142
239
  "any.required": "Enter first name"
143
240
  }),
144
- last: Joi4.string().required().max(LAST_NAME_MAX).messages({
241
+ last: Joi8.string().required().max(LAST_NAME_MAX).messages({
145
242
  "string.empty": "Enter last name",
146
243
  "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
147
244
  "any.required": "Enter last name"
148
245
  }),
149
- middle: Joi4.string().allow("").max(MIDDLE_NAMES_MAX).messages({
246
+ middle: Joi8.string().allow("").max(MIDDLE_NAMES_MAX).messages({
150
247
  "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
151
248
  })
152
249
  });
153
250
 
154
251
  // src/schemas/personal/personal-dob-schema.js
155
- import Joi5 from "joi";
156
-
157
- // src/constants/patterns.js
158
- var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
252
+ import Joi9 from "joi";
159
253
 
160
254
  // src/constants/month-map.js
161
255
  var MONTH_MAP = {
@@ -186,10 +280,10 @@ var MONTH_MAP = {
186
280
  };
187
281
 
188
282
  // src/schemas/personal/personal-dob-schema.js
189
- var personalDobSchema = Joi5.object({
190
- day: Joi5.string().allow(""),
191
- month: Joi5.string().allow(""),
192
- year: Joi5.string().allow("")
283
+ var personalDobSchema = Joi9.object({
284
+ day: Joi9.string().allow(""),
285
+ month: Joi9.string().allow(""),
286
+ year: Joi9.string().allow("")
193
287
  }).custom((value, helpers) => {
194
288
  const { day, month, year } = value;
195
289
  const missingFieldsError = checkMissingFields(day, month, year, helpers);
@@ -300,9 +394,9 @@ var makeError = (helpers, code, fields) => {
300
394
  };
301
395
 
302
396
  // src/schemas/personal/personal-email-schema.js
303
- import Joi6 from "joi";
304
- var personalEmailSchema = Joi6.object({
305
- personalEmail: Joi6.string().required().max(EMAIL_MAX).email({
397
+ import Joi10 from "joi";
398
+ var personalEmailSchema = Joi10.object({
399
+ personalEmail: Joi10.string().required().max(EMAIL_MAX).email({
306
400
  minDomainSegments: 2,
307
401
  tlds: {
308
402
  allow: true,
@@ -316,14 +410,14 @@ var personalEmailSchema = Joi6.object({
316
410
  });
317
411
 
318
412
  // src/schemas/personal/personal-phone-schema.js
319
- import Joi7 from "joi";
320
- var personalPhoneSchema = Joi7.object({
321
- personalTelephone: Joi7.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
413
+ import Joi11 from "joi";
414
+ var personalPhoneSchema = Joi11.object({
415
+ personalTelephone: Joi11.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
322
416
  "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
323
417
  "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
324
418
  "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
325
419
  }),
326
- personalMobile: Joi7.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
420
+ personalMobile: Joi11.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
327
421
  "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
328
422
  "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
329
423
  "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
@@ -517,6 +611,24 @@ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDE
517
611
  return sortedErrors;
518
612
  };
519
613
 
614
+ // src/presenters/business-details-presenter.js
615
+ var getActionText = (value) => {
616
+ return value ? "Change" : "Add";
617
+ };
618
+ var formatCph = (countyParishHoldings) => {
619
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
620
+ };
621
+ var formatCphText = (count) => {
622
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
623
+ };
624
+ var formatBusinessAddress = (businessAddress) => {
625
+ var _a, _b;
626
+ 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)) {
627
+ return formatDisplayAddress(businessAddress);
628
+ }
629
+ return [];
630
+ };
631
+
520
632
  // src/presenters/presenters.js
521
633
  var presenters = {
522
634
  formatBackLink,
@@ -525,7 +637,11 @@ var presenters = {
525
637
  formatOriginalAddress,
526
638
  formatChangedAddress,
527
639
  formatDisplayAddresses,
528
- sortErrorsBySectionOrder
640
+ sortErrorsBySectionOrder,
641
+ getActionText,
642
+ formatCph,
643
+ formatCphText,
644
+ formatBusinessAddress
529
645
  };
530
646
  export {
531
647
  mappers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/fcp-sfd-frontend-engine",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared frontend engine used by both the internal and external frontend applications.",
5
5
  "main": "./dist/index.cjs",
6
6
  "exports": {
@@ -1,6 +1,7 @@
1
1
  export const FIRST_NAME_MAX = 100
2
2
  export const LAST_NAME_MAX = 100
3
3
  export const MIDDLE_NAMES_MAX = 100
4
+ export const BUSINESS_NAME_MAX = 160
4
5
  export const EMAIL_MAX = 254
5
6
  export const PHONE_NUMBER_MIN = 10
6
7
  export const PHONE_NUMBER_MAX = 50
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Takes the raw business details data from the DAL and maps it to a more usable format.
3
+ *
4
+ * Maps the core `info`, `address`, and `contact` fields.
5
+ * Callers that need additional fields (e.g. `customer`) should extend the result.
6
+ *
7
+ * @param {Object} value - The raw data from the DAL
8
+ *
9
+ * @returns {Object} Mapped business details with `info`, `address`, and `contact`
10
+ */
11
+
12
+ import { mapAddress } from './address-mapper.js'
13
+
14
+ const asNullable = (value) => value ?? null
15
+
16
+ const mapBusinessInfo = (business) => {
17
+ const info = business.info ?? {}
18
+
19
+ return {
20
+ sbi: business.sbi,
21
+ businessName: asNullable(info.name),
22
+ vat: asNullable(info.vat),
23
+ traderNumber: asNullable(info.traderNumber),
24
+ vendorNumber: asNullable(info.vendorNumber),
25
+ legalStatus: asNullable(info.legalStatus?.type),
26
+ type: asNullable(info.type?.type),
27
+ countyParishHoldingNumbers: business.countyParishHoldings ?? []
28
+ }
29
+ }
30
+
31
+ const mapBusinessContact = (businessInfo) => {
32
+ return {
33
+ email: asNullable(businessInfo.email?.address),
34
+ landline: asNullable(businessInfo.phone?.landline),
35
+ mobile: asNullable(businessInfo.phone?.mobile)
36
+ }
37
+ }
38
+
39
+ export const mapBusinessDetails = (value) => {
40
+ const business = value?.business ?? {}
41
+ const businessInfo = business.info ?? {}
42
+
43
+ return {
44
+ info: mapBusinessInfo(business),
45
+ address: mapAddress(businessInfo.address),
46
+ contact: mapBusinessContact(businessInfo)
47
+ }
48
+ }
@@ -1,9 +1,11 @@
1
1
  import { mapPersonalBusinessDetails } from './personal-business-details-mapper.js'
2
2
  import { mapAddress } from './address-mapper.js'
3
3
  import { mapCustomerName } from './customer-name-mapper.js'
4
+ import { mapBusinessDetails } from './business-details-mapper.js'
4
5
 
5
6
  export const mappers = {
6
7
  personalBusinessDetails: mapPersonalBusinessDetails,
7
8
  address: mapAddress,
8
- customerName: mapCustomerName
9
+ customerName: mapCustomerName,
10
+ businessDetails: mapBusinessDetails
9
11
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared presenter helpers for the "View and update your business details" page.
3
+ *
4
+ * These functions are identical across the internal and external frontend
5
+ * applications and are provided here to avoid duplication.
6
+ */
7
+
8
+ import { formatDisplayAddress } from './base-presenter.js'
9
+
10
+ /**
11
+ * Returns the action text for a summary-list row.
12
+ *
13
+ * Returns 'Change' when a value is already present, or 'Add' when it is absent.
14
+ *
15
+ * @param {*} value - The current field value
16
+ *
17
+ * @returns {'Change'|'Add'}
18
+ */
19
+ export const getActionText = (value) => {
20
+ return value ? 'Change' : 'Add'
21
+ }
22
+
23
+ /**
24
+ * Extracts an array of CPH number strings from the raw DAL response.
25
+ *
26
+ * Filters out any objects that have a null or undefined `cphNumber` property.
27
+ *
28
+ * @param {Array<Object>} countyParishHoldings - Raw CPH objects from the DAL
29
+ *
30
+ * @returns {string[]} Array of CPH number strings
31
+ */
32
+ export const formatCph = (countyParishHoldings) => {
33
+ return (countyParishHoldings || [])
34
+ .filter((cph) => cph?.cphNumber)
35
+ .map((cph) => cph.cphNumber)
36
+ }
37
+
38
+ /**
39
+ * Returns the heading text for the CPH summary-list row.
40
+ *
41
+ * Uses the singular form when `count` is 1, plural otherwise.
42
+ *
43
+ * @param {number} count - Number of CPH numbers
44
+ *
45
+ * @returns {string}
46
+ */
47
+ export const formatCphText = (count) => {
48
+ return `County Parish Holding (CPH) number${count !== 1 ? 's' : ''}`
49
+ }
50
+
51
+ /**
52
+ * Formats a business address object into an array of display lines.
53
+ *
54
+ * Returns an empty array when the address contains no recognised content
55
+ * (i.e. neither a UPRN from a lookup nor a manually entered first line).
56
+ *
57
+ * @param {Object} businessAddress - The mapped address object
58
+ *
59
+ * @returns {string[]} Array of address lines, or an empty array if absent
60
+ */
61
+ export const formatBusinessAddress = (businessAddress) => {
62
+ if (businessAddress?.lookup?.uprn || businessAddress?.manual?.line1) {
63
+ return formatDisplayAddress(businessAddress)
64
+ }
65
+
66
+ return []
67
+ }
@@ -8,6 +8,13 @@ import {
8
8
  sortErrorsBySectionOrder
9
9
  } from './base-presenter.js'
10
10
 
11
+ import {
12
+ getActionText,
13
+ formatCph,
14
+ formatCphText,
15
+ formatBusinessAddress
16
+ } from './business-details-presenter.js'
17
+
11
18
  export const presenters = {
12
19
  formatBackLink,
13
20
  formatNumber,
@@ -15,5 +22,9 @@ export const presenters = {
15
22
  formatOriginalAddress,
16
23
  formatChangedAddress,
17
24
  formatDisplayAddresses,
18
- sortErrorsBySectionOrder
25
+ sortErrorsBySectionOrder,
26
+ getActionText,
27
+ formatCph,
28
+ formatCphText,
29
+ formatBusinessAddress
19
30
  }
@@ -0,0 +1,19 @@
1
+ import Joi from 'joi'
2
+ import { EMAIL_MAX } from '../../constants/validation-fields.js'
3
+
4
+ export const businessEmailSchema = Joi.object({
5
+ businessEmail: Joi.string()
6
+ .required()
7
+ .max(EMAIL_MAX)
8
+ .email({
9
+ minDomainSegments: 2,
10
+ tlds: {
11
+ allow: true
12
+ }
13
+ })
14
+ .messages({
15
+ 'string.max': `Business email address must be ${EMAIL_MAX} characters or less`,
16
+ 'string.empty': 'Enter business email address',
17
+ 'string.email': 'Enter an email address, like name@example.com'
18
+ })
19
+ })
@@ -0,0 +1,13 @@
1
+ import Joi from 'joi'
2
+ import { BUSINESS_NAME_MAX } from '../../constants/validation-fields.js'
3
+
4
+ export const businessNameSchema = Joi.object({
5
+ businessName: Joi.string()
6
+ .required()
7
+ .max(BUSINESS_NAME_MAX)
8
+ .messages({
9
+ 'string.empty': 'Enter business name',
10
+ 'string.max': `Business name must be ${BUSINESS_NAME_MAX} characters or less`,
11
+ 'any.required': 'Enter business name'
12
+ })
13
+ })
@@ -0,0 +1,33 @@
1
+ import Joi from 'joi'
2
+ import {
3
+ PHONE_NUMBER_MIN,
4
+ PHONE_NUMBER_MAX
5
+ } from '../../constants/validation-fields.js'
6
+ import { PHONE_NUMBER_PATTERN } from '../../constants/patterns.js'
7
+
8
+ export const businessPhoneSchema = Joi.object({
9
+ businessTelephone: Joi.string()
10
+ .empty('')
11
+ .min(PHONE_NUMBER_MIN)
12
+ .max(PHONE_NUMBER_MAX)
13
+ .pattern(PHONE_NUMBER_PATTERN)
14
+ .messages({
15
+ 'string.min': `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
16
+ 'string.max': `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
17
+ 'string.pattern.base': 'Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +'
18
+ }),
19
+ businessMobile: Joi.string()
20
+ .empty('')
21
+ .min(PHONE_NUMBER_MIN)
22
+ .max(PHONE_NUMBER_MAX)
23
+ .pattern(PHONE_NUMBER_PATTERN)
24
+ .messages({
25
+ 'string.min': `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
26
+ 'string.max': `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
27
+ 'string.pattern.base': 'Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +'
28
+ })
29
+ })
30
+ .or('businessTelephone', 'businessMobile')
31
+ .messages({
32
+ 'object.missing': 'Enter at least one phone number'
33
+ })
@@ -1,7 +1,23 @@
1
1
  import { businessSbiSchema } from './business-sbi-schema.js'
2
2
  import { addressSchema } from '../address-schema.js'
3
+ import { businessNameSchema } from './business-name-schema.js'
4
+ import { businessEmailSchema } from './business-email-schema.js'
5
+ import { businessPhoneSchema } from './business-phone-schema.js'
6
+ import { businessVatSchema } from './business-vat-schema.js'
3
7
 
8
+ /**
9
+ * The businessSchema object has a nested property of `details`.
10
+ * This is due to the interrupter journey. The journey required the business details to be validated in a single step,
11
+ * these include name, address, phone, email and vat. The `details` property is used to group these schemas together.
12
+ * The `sbi` property is separate as it is validated in a different step of the journey.
13
+ */
4
14
  export const businessSchemas = {
5
15
  sbi: businessSbiSchema,
6
- address: addressSchema
16
+ details: {
17
+ name: businessNameSchema,
18
+ address: addressSchema,
19
+ phone: businessPhoneSchema,
20
+ email: businessEmailSchema,
21
+ vat: businessVatSchema
22
+ }
7
23
  }
@@ -0,0 +1,11 @@
1
+ import Joi from 'joi'
2
+
3
+ export const businessVatSchema = Joi.object({
4
+ vatNumber: Joi.string()
5
+ .pattern(/^\d{9}$/)
6
+ .allow('')
7
+ .optional()
8
+ .messages({
9
+ 'string.pattern.base': 'Enter a VAT registration number, like 123456789'
10
+ })
11
+ })