@defra/fcp-sfd-frontend-engine 0.4.0 → 0.5.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,78 @@ 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
+ address: addressSchema,
250
+ details: {
251
+ name: businessNameSchema,
252
+ address: addressSchema,
253
+ phone: businessPhoneSchema,
254
+ email: businessEmailSchema,
255
+ vat: businessVatSchema
256
+ }
159
257
  };
160
258
 
161
259
  // 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({
260
+ var import_joi7 = __toESM(require("joi"), 1);
261
+ var customerCrnSchema = import_joi7.default.object({
262
+ crn: import_joi7.default.string().pattern(/^\d{10}$/).allow("").optional().messages({
165
263
  "string.pattern.base": "Enter the full CRN"
166
264
  })
167
265
  });
@@ -172,28 +270,25 @@ var customerSchemas = {
172
270
  };
173
271
 
174
272
  // 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({
273
+ var import_joi8 = __toESM(require("joi"), 1);
274
+ var personalNameSchema = import_joi8.default.object({
275
+ first: import_joi8.default.string().required().max(FIRST_NAME_MAX).messages({
178
276
  "string.empty": "Enter first name",
179
277
  "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
180
278
  "any.required": "Enter first name"
181
279
  }),
182
- last: import_joi4.default.string().required().max(LAST_NAME_MAX).messages({
280
+ last: import_joi8.default.string().required().max(LAST_NAME_MAX).messages({
183
281
  "string.empty": "Enter last name",
184
282
  "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
185
283
  "any.required": "Enter last name"
186
284
  }),
187
- middle: import_joi4.default.string().allow("").max(MIDDLE_NAMES_MAX).messages({
285
+ middle: import_joi8.default.string().allow("").max(MIDDLE_NAMES_MAX).messages({
188
286
  "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
189
287
  })
190
288
  });
191
289
 
192
290
  // 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 ()]+$/;
291
+ var import_joi9 = __toESM(require("joi"), 1);
197
292
 
198
293
  // src/constants/month-map.js
199
294
  var MONTH_MAP = {
@@ -224,10 +319,10 @@ var MONTH_MAP = {
224
319
  };
225
320
 
226
321
  // 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("")
322
+ var personalDobSchema = import_joi9.default.object({
323
+ day: import_joi9.default.string().allow(""),
324
+ month: import_joi9.default.string().allow(""),
325
+ year: import_joi9.default.string().allow("")
231
326
  }).custom((value, helpers) => {
232
327
  const { day, month, year } = value;
233
328
  const missingFieldsError = checkMissingFields(day, month, year, helpers);
@@ -338,9 +433,9 @@ var makeError = (helpers, code, fields) => {
338
433
  };
339
434
 
340
435
  // 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({
436
+ var import_joi10 = __toESM(require("joi"), 1);
437
+ var personalEmailSchema = import_joi10.default.object({
438
+ personalEmail: import_joi10.default.string().required().max(EMAIL_MAX).email({
344
439
  minDomainSegments: 2,
345
440
  tlds: {
346
441
  allow: true,
@@ -354,14 +449,14 @@ var personalEmailSchema = import_joi6.default.object({
354
449
  });
355
450
 
356
451
  // 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({
452
+ var import_joi11 = __toESM(require("joi"), 1);
453
+ var personalPhoneSchema = import_joi11.default.object({
454
+ personalTelephone: import_joi11.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
360
455
  "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
361
456
  "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
362
457
  "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
363
458
  }),
364
- personalMobile: import_joi7.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
459
+ personalMobile: import_joi11.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
365
460
  "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
366
461
  "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
367
462
  "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
@@ -555,6 +650,24 @@ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDE
555
650
  return sortedErrors;
556
651
  };
557
652
 
653
+ // src/presenters/business-details-presenter.js
654
+ var getActionText = (value) => {
655
+ return value ? "Change" : "Add";
656
+ };
657
+ var formatCph = (countyParishHoldings) => {
658
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
659
+ };
660
+ var formatCphText = (count) => {
661
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
662
+ };
663
+ var formatBusinessAddress = (businessAddress) => {
664
+ var _a, _b;
665
+ 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)) {
666
+ return formatDisplayAddress(businessAddress);
667
+ }
668
+ return [];
669
+ };
670
+
558
671
  // src/presenters/presenters.js
559
672
  var presenters = {
560
673
  formatBackLink,
@@ -563,7 +676,11 @@ var presenters = {
563
676
  formatOriginalAddress,
564
677
  formatChangedAddress,
565
678
  formatDisplayAddresses,
566
- sortErrorsBySectionOrder
679
+ sortErrorsBySectionOrder,
680
+ getActionText,
681
+ formatCph,
682
+ formatCphText,
683
+ formatBusinessAddress
567
684
  };
568
685
  // Annotate the CommonJS export names for ESM import in node:
569
686
  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,78 @@ 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
+ address: addressSchema,
212
+ details: {
213
+ name: businessNameSchema,
214
+ address: addressSchema,
215
+ phone: businessPhoneSchema,
216
+ email: businessEmailSchema,
217
+ vat: businessVatSchema
218
+ }
121
219
  };
122
220
 
123
221
  // 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({
222
+ import Joi7 from "joi";
223
+ var customerCrnSchema = Joi7.object({
224
+ crn: Joi7.string().pattern(/^\d{10}$/).allow("").optional().messages({
127
225
  "string.pattern.base": "Enter the full CRN"
128
226
  })
129
227
  });
@@ -134,28 +232,25 @@ var customerSchemas = {
134
232
  };
135
233
 
136
234
  // 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({
235
+ import Joi8 from "joi";
236
+ var personalNameSchema = Joi8.object({
237
+ first: Joi8.string().required().max(FIRST_NAME_MAX).messages({
140
238
  "string.empty": "Enter first name",
141
239
  "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
142
240
  "any.required": "Enter first name"
143
241
  }),
144
- last: Joi4.string().required().max(LAST_NAME_MAX).messages({
242
+ last: Joi8.string().required().max(LAST_NAME_MAX).messages({
145
243
  "string.empty": "Enter last name",
146
244
  "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
147
245
  "any.required": "Enter last name"
148
246
  }),
149
- middle: Joi4.string().allow("").max(MIDDLE_NAMES_MAX).messages({
247
+ middle: Joi8.string().allow("").max(MIDDLE_NAMES_MAX).messages({
150
248
  "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
151
249
  })
152
250
  });
153
251
 
154
252
  // 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 ()]+$/;
253
+ import Joi9 from "joi";
159
254
 
160
255
  // src/constants/month-map.js
161
256
  var MONTH_MAP = {
@@ -186,10 +281,10 @@ var MONTH_MAP = {
186
281
  };
187
282
 
188
283
  // 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("")
284
+ var personalDobSchema = Joi9.object({
285
+ day: Joi9.string().allow(""),
286
+ month: Joi9.string().allow(""),
287
+ year: Joi9.string().allow("")
193
288
  }).custom((value, helpers) => {
194
289
  const { day, month, year } = value;
195
290
  const missingFieldsError = checkMissingFields(day, month, year, helpers);
@@ -300,9 +395,9 @@ var makeError = (helpers, code, fields) => {
300
395
  };
301
396
 
302
397
  // 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({
398
+ import Joi10 from "joi";
399
+ var personalEmailSchema = Joi10.object({
400
+ personalEmail: Joi10.string().required().max(EMAIL_MAX).email({
306
401
  minDomainSegments: 2,
307
402
  tlds: {
308
403
  allow: true,
@@ -316,14 +411,14 @@ var personalEmailSchema = Joi6.object({
316
411
  });
317
412
 
318
413
  // 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({
414
+ import Joi11 from "joi";
415
+ var personalPhoneSchema = Joi11.object({
416
+ personalTelephone: Joi11.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
322
417
  "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
323
418
  "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
324
419
  "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
325
420
  }),
326
- personalMobile: Joi7.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
421
+ personalMobile: Joi11.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
327
422
  "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
328
423
  "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
329
424
  "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
@@ -517,6 +612,24 @@ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDE
517
612
  return sortedErrors;
518
613
  };
519
614
 
615
+ // src/presenters/business-details-presenter.js
616
+ var getActionText = (value) => {
617
+ return value ? "Change" : "Add";
618
+ };
619
+ var formatCph = (countyParishHoldings) => {
620
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
621
+ };
622
+ var formatCphText = (count) => {
623
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
624
+ };
625
+ var formatBusinessAddress = (businessAddress) => {
626
+ var _a, _b;
627
+ 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)) {
628
+ return formatDisplayAddress(businessAddress);
629
+ }
630
+ return [];
631
+ };
632
+
520
633
  // src/presenters/presenters.js
521
634
  var presenters = {
522
635
  formatBackLink,
@@ -525,7 +638,11 @@ var presenters = {
525
638
  formatOriginalAddress,
526
639
  formatChangedAddress,
527
640
  formatDisplayAddresses,
528
- sortErrorsBySectionOrder
641
+ sortErrorsBySectionOrder,
642
+ getActionText,
643
+ formatCph,
644
+ formatCphText,
645
+ formatBusinessAddress
529
646
  };
530
647
  export {
531
648
  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.5.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,18 @@
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
 
4
8
  export const businessSchemas = {
5
9
  sbi: businessSbiSchema,
6
- address: addressSchema
10
+ address: addressSchema,
11
+ details: {
12
+ name: businessNameSchema,
13
+ address: addressSchema,
14
+ phone: businessPhoneSchema,
15
+ email: businessEmailSchema,
16
+ vat: businessVatSchema
17
+ }
7
18
  }
@@ -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
+ })