@defra-fish/sales-api-service 1.49.0-rc.5 → 1.49.0-rc.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/sales-api-service",
3
- "version": "1.49.0-rc.5",
3
+ "version": "1.49.0-rc.7",
4
4
  "description": "Rod Licensing Sales API",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,9 +35,9 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/business-rules-lib": "1.49.0-rc.5",
39
- "@defra-fish/connectors-lib": "1.49.0-rc.5",
40
- "@defra-fish/dynamics-lib": "1.49.0-rc.5",
38
+ "@defra-fish/business-rules-lib": "1.49.0-rc.7",
39
+ "@defra-fish/connectors-lib": "1.49.0-rc.7",
40
+ "@defra-fish/dynamics-lib": "1.49.0-rc.7",
41
41
  "@hapi/boom": "^9.1.2",
42
42
  "@hapi/hapi": "^20.1.3",
43
43
  "@hapi/inert": "^6.0.3",
@@ -52,5 +52,5 @@
52
52
  "moment-timezone": "^0.5.34",
53
53
  "uuid": "^8.3.2"
54
54
  },
55
- "gitHead": "62fcbb4485a825c2d8e285a15f452de249d859fe"
55
+ "gitHead": "a933cacf4f8f557e16ff0d0ee9d8b001723e292c"
56
56
  }
@@ -130,9 +130,7 @@ const validatePermissionConcession = async (permission, transaction) => {
130
130
  if (
131
131
  concessionsRequiredForPermit.length &&
132
132
  !hasConcessionProofs &&
133
- transaction.dataSource !== 'Post Office Sales' &&
134
- transaction.dataSource !== 'DDE File' &&
135
- transaction.dataSource !== 'Postal Order Sales'
133
+ !['Post Office Sales', 'DDE File', 'Postal Order Sales'].includes(transaction.dataSource)
136
134
  ) {
137
135
  throw new Error(`The permit '${permission.permitId}' requires proof of concession however none were supplied`)
138
136
  } else if (!concessionsRequiredForPermit.length && hasConcessionProofs) {
@@ -453,3 +453,90 @@ Object {
453
453
  },
454
454
  }
455
455
  `;
456
+
457
+ exports[`permissionRenewalData should call preparePermissionDataForRenewal with the expected data 1`] = `
458
+ [MockFunction] {
459
+ "calls": Array [
460
+ Array [
461
+ Object {
462
+ "concessions": Array [
463
+ Object {
464
+ "id": "d0ece997-ef65-e611-80dc-c4346bad4004",
465
+ "name": "Senior",
466
+ },
467
+ ],
468
+ "dataSource": Object {
469
+ "description": "Web Sales",
470
+ "id": 910400003,
471
+ "label": "Web Sales",
472
+ },
473
+ "endDate": "2020-12-13T23:59:59Z",
474
+ "id": "347a9083-361e-ea11-a810-000d3a25c5d6",
475
+ "issueDate": "2019-12-13T09:00:00Z",
476
+ "licensee": Object {
477
+ "birthDate": "1946-01-01",
478
+ "country": Object {
479
+ "description": "GB-ENG",
480
+ "id": 910400195,
481
+ "label": "England",
482
+ },
483
+ "email": "fester@tester.com",
484
+ "firstName": "Fester",
485
+ "id": "1329a866-d175-ea11-a811-000d3a64905b",
486
+ "lastName": "Tester",
487
+ "locality": "Testville",
488
+ "mobilePhone": "01234 567890",
489
+ "organisation": "Test Organisation",
490
+ "postalFulfilment": true,
491
+ "postcode": "AB12 3CD",
492
+ "preferredMethodOfConfirmation": Object {
493
+ "description": "Email",
494
+ "id": 910400000,
495
+ "label": "Email",
496
+ },
497
+ "premises": "1",
498
+ "street": "Tester Avenue",
499
+ "town": "Tersterton",
500
+ },
501
+ "permit": Object {
502
+ "availableFrom": "2017-03-31T23:00:00Z",
503
+ "availableTo": "2021-03-31T22:59:00Z",
504
+ "cost": 6,
505
+ "description": "Coarse 1 day 2 Rod Licence (Senior)",
506
+ "durationDesignator": Object {
507
+ "description": "D",
508
+ "id": 910400000,
509
+ "label": "Day(s)",
510
+ },
511
+ "durationMagnitude": 1,
512
+ "id": "9f1b34a0-0c66-e611-80dc-c4346bad0190",
513
+ "isCounterSales": true,
514
+ "isForFulfilment": false,
515
+ "isRecurringPaymentSupported": false,
516
+ "itemId": "42290",
517
+ "numberOfRods": 2,
518
+ "permitSubtype": Object {
519
+ "description": "C",
520
+ "id": 910400001,
521
+ "label": "Trout and coarse",
522
+ },
523
+ "permitType": Object {
524
+ "description": "Rod Fishing Licence",
525
+ "id": 910400000,
526
+ "label": "Rod Fishing Licence",
527
+ },
528
+ },
529
+ "referenceNumber": "00000000-2WC3FDR-CD379B",
530
+ "stagingId": "71ad9a25-2a03-406b-a0e3-f4ff37799374",
531
+ "startDate": "2019-12-14T00:00:00Z",
532
+ },
533
+ ],
534
+ ],
535
+ "results": Array [
536
+ Object {
537
+ "type": "return",
538
+ "value": undefined,
539
+ },
540
+ ],
541
+ }
542
+ `;
@@ -16,6 +16,7 @@ jest.mock('../../../services/renewals/renewals.service.js', () => ({
16
16
 
17
17
  jest.mock('@defra-fish/dynamics-lib', () => ({
18
18
  ...jest.requireActual('@defra-fish/dynamics-lib'),
19
+ concessionsByIds: jest.fn(),
19
20
  permissionForFullReferenceNumber: jest.fn(),
20
21
  executeQuery: jest.fn()
21
22
  }))
@@ -32,16 +33,25 @@ const permissionForFullReferenceNumberMock = () => ({
32
33
  entity: MOCK_EXISTING_PERMISSION_ENTITY,
33
34
  expanded: {
34
35
  licensee: { entity: MOCK_EXISTING_CONTACT_ENTITY, expanded: {} },
35
- concessionProofs: [{ entity: MOCK_CONCESSION_PROOF_ENTITY, expanded: { concession: { entity: MOCK_CONCESSION } } }],
36
+ concessionProofs: [{ entity: MOCK_CONCESSION_PROOF_ENTITY }],
36
37
  permit: { entity: MOCK_1DAY_SENIOR_PERMIT_ENTITY, expanded: {} }
37
38
  }
38
39
  })
39
40
 
41
+ const concessionMock = () => ({
42
+ expanded: {
43
+ concession: {
44
+ entity: MOCK_CONCESSION
45
+ }
46
+ }
47
+ })
48
+
40
49
  describe('permissionRenewalData', () => {
41
50
  beforeEach(jest.clearAllMocks)
42
51
 
43
52
  it('should call permissionForFullReferenceNumber with referenceNumber', async () => {
44
53
  executeQuery.mockResolvedValueOnce([permissionForFullReferenceNumberMock()])
54
+ executeQuery.mockResolvedValueOnce([concessionMock()])
45
55
 
46
56
  const referenceNumber = 'REFERENCE123'
47
57
  const request = getMockRequest(referenceNumber)
@@ -53,22 +63,27 @@ describe('permissionRenewalData', () => {
53
63
  })
54
64
 
55
65
  it('should call preparePermissionDataForRenewal with the expected data', async () => {
66
+ executeQuery.mockResolvedValueOnce([permissionForFullReferenceNumberMock()])
67
+ executeQuery.mockResolvedValueOnce([concessionMock()])
68
+
69
+ await permissionRenewalData[0].options.handler(getMockRequest(), getMockResponseToolkit())
70
+
71
+ expect(preparePermissionDataForRenewal).toMatchSnapshot()
72
+ })
73
+
74
+ it('should set concessions to an empty array if concessions is empty', async () => {
56
75
  const permissionMock = permissionForFullReferenceNumberMock()
76
+ permissionMock.expanded.concessionProofs = []
57
77
  executeQuery.mockResolvedValueOnce([permissionMock])
58
78
 
59
79
  await permissionRenewalData[0].options.handler(getMockRequest(), getMockResponseToolkit())
60
80
 
61
- const expectedData = {
62
- ...permissionMock.entity.toJSON(),
63
- licensee: permissionMock.expanded.licensee.entity.toJSON(),
64
- concessions: [],
65
- permit: permissionMock.expanded.permit.entity.toJSON()
66
- }
67
- expect(preparePermissionDataForRenewal).toHaveBeenCalledWith(expectedData)
81
+ expect(preparePermissionDataForRenewal.mock.calls[0][0].concessions).toEqual([])
68
82
  })
69
83
 
70
84
  it('should return continue response', async () => {
71
85
  executeQuery.mockResolvedValueOnce([permissionForFullReferenceNumberMock()])
86
+ executeQuery.mockResolvedValueOnce([concessionMock()])
72
87
  const request = getMockRequest({})
73
88
  const responseToolkit = getMockResponseToolkit()
74
89
 
@@ -2,7 +2,7 @@ import Boom from '@hapi/boom'
2
2
  import { preparePermissionDataForRenewal } from '../../services/renewals/renewals.service.js'
3
3
  import { permissionRenewalDataRequestParamsSchema, permissionRenewalDataResponseSchema } from '../../schema/renewals.schema.js'
4
4
  import db from 'debug'
5
- import { permissionForFullReferenceNumber, executeQuery } from '@defra-fish/dynamics-lib'
5
+ import { concessionsByIds, permissionForFullReferenceNumber, executeQuery } from '@defra-fish/dynamics-lib'
6
6
  const debug = db('sales:permission-renewal-data')
7
7
 
8
8
  const executeWithErrorLog = async query => {
@@ -14,6 +14,14 @@ const executeWithErrorLog = async query => {
14
14
  }
15
15
  }
16
16
 
17
+ const getConcessions = async permission => {
18
+ if (permission.expanded.concessionProofs.length) {
19
+ const concessionProofs = await executeWithErrorLog(concessionsByIds(permission.expanded.concessionProofs.map(cp => cp.entity.id)))
20
+ return concessionProofs.map(cp => cp.expanded.concession.entity.toJSON())
21
+ }
22
+ return []
23
+ }
24
+
17
25
  export default [
18
26
  {
19
27
  method: 'GET',
@@ -23,11 +31,12 @@ export default [
23
31
  const results = await executeWithErrorLog(permissionForFullReferenceNumber(request.params.referenceNumber))
24
32
 
25
33
  if (results.length === 1) {
26
- const permissionData = preparePermissionDataForRenewal({
27
- ...results[0].entity.toJSON(),
28
- licensee: results[0].expanded.licensee.entity.toJSON(),
29
- concessions: [],
30
- permit: results[0].expanded.permit.entity.toJSON()
34
+ const [permission] = results
35
+ const permissionData = await preparePermissionDataForRenewal({
36
+ ...permission.entity.toJSON(),
37
+ licensee: permission.expanded.licensee.entity.toJSON(),
38
+ concessions: await getConcessions(permission),
39
+ permit: permission.expanded.permit.entity.toJSON()
31
40
  })
32
41
  return h.response(permissionData)
33
42
  } else if (results.length === 0) {
@@ -0,0 +1,315 @@
1
+ import * as concessionService from '../concession.service.js'
2
+ import { CONCESSION, CONCESSION_PROOF } from '../constants.js'
3
+ import { getReferenceDataForEntity, getReferenceDataForEntityAndId } from '../reference-data.service.js'
4
+
5
+ jest.mock('@defra-fish/connectors-lib')
6
+ jest.mock('../reference-data.service.js')
7
+
8
+ getReferenceDataForEntity.mockImplementation(() => [
9
+ {
10
+ id: 'aaa-111-bbb-222',
11
+ name: CONCESSION.JUNIOR
12
+ },
13
+ {
14
+ id: 'ccc-333-ddd-444',
15
+ name: CONCESSION.SENIOR
16
+ },
17
+ {
18
+ id: 'eee-555-fff-666',
19
+ name: CONCESSION.DISABLED
20
+ }
21
+ ])
22
+
23
+ getReferenceDataForEntityAndId.mockImplementation((_e, id) => {
24
+ if (id === 'aaa-111-bbb-222') {
25
+ return {
26
+ id,
27
+ name: CONCESSION.JUNIOR
28
+ }
29
+ }
30
+ if (id === 'ccc-333-ddd-444') {
31
+ return {
32
+ id,
33
+ name: CONCESSION.SENIOR
34
+ }
35
+ }
36
+ if (id === 'eee-555-fff-666') {
37
+ return {
38
+ id,
39
+ name: CONCESSION.DISABLED
40
+ }
41
+ }
42
+ throw Error('Unrecognised concession id')
43
+ })
44
+
45
+ const getMockPermission = () => ({})
46
+
47
+ const getJuniorConcession = () => ({
48
+ id: 'aaa-111-bbb-222',
49
+ name: CONCESSION.JUNIOR,
50
+ proof: {
51
+ type: CONCESSION_PROOF.none
52
+ }
53
+ })
54
+
55
+ const getSeniorConcession = () => ({
56
+ id: 'ccc-333-ddd-444',
57
+ name: CONCESSION.SENIOR,
58
+ proof: {
59
+ type: CONCESSION_PROOF.none
60
+ }
61
+ })
62
+
63
+ const getDisabledBlueBadgeConcession = () => ({
64
+ id: 'eee-555-fff-666',
65
+ name: CONCESSION.DISABLED,
66
+ proof: {
67
+ type: CONCESSION_PROOF.blueBadge,
68
+ referenceNumber: 'blue-badge-123'
69
+ }
70
+ })
71
+
72
+ const getDisabledNiConcession = () => ({
73
+ id: 'eee-555-fff-666',
74
+ name: CONCESSION.DISABLED,
75
+ proof: {
76
+ type: CONCESSION_PROOF.NI,
77
+ referenceNumber: 'national-insurance-456'
78
+ }
79
+ })
80
+
81
+ describe('preparePermissionDataForRenewal', () => {
82
+ describe('Disabled', () => {
83
+ describe('addDisabled', () => {
84
+ it('add blue badge', async () => {
85
+ const permission = getMockPermission()
86
+ await concessionService.addDisabled(permission, CONCESSION_PROOF.blueBadge, 'bb-111')
87
+ expect(permission.concessions).toContainEqual(
88
+ expect.objectContaining({
89
+ id: 'eee-555-fff-666',
90
+ name: CONCESSION.DISABLED,
91
+ proof: {
92
+ type: CONCESSION_PROOF.blueBadge,
93
+ referenceNumber: 'bb-111'
94
+ }
95
+ })
96
+ )
97
+ })
98
+
99
+ it('add NI', async () => {
100
+ const permission = getMockPermission()
101
+ await concessionService.addDisabled(permission, CONCESSION_PROOF.NI, 'national-insurance-456')
102
+ expect(permission.concessions).toContainEqual(expect.objectContaining(getDisabledNiConcession()))
103
+ })
104
+
105
+ it('add blue badge to senior licence', async () => {
106
+ const permission = getMockPermission()
107
+ const seniorConcession = getSeniorConcession()
108
+ permission.concessions = [seniorConcession]
109
+ await concessionService.addDisabled(permission, CONCESSION_PROOF.blueBadge, 'blue-badge-123')
110
+ expect(permission.concessions).toEqual(
111
+ expect.arrayContaining([expect.objectContaining(getDisabledBlueBadgeConcession()), expect.objectContaining(seniorConcession)])
112
+ )
113
+ })
114
+
115
+ it('add NI to senior licence', async () => {
116
+ const permission = getMockPermission()
117
+ const seniorConcession = getSeniorConcession()
118
+ permission.concessions = [seniorConcession]
119
+ await concessionService.addDisabled(permission, CONCESSION_PROOF.NI, 'national-insurance-456')
120
+ expect(permission.concessions).toEqual(
121
+ expect.arrayContaining([expect.objectContaining(getDisabledNiConcession()), expect.objectContaining(seniorConcession)])
122
+ )
123
+ })
124
+ })
125
+
126
+ describe('removeDisabled', () => {
127
+ it.each([
128
+ ['blue badge', []],
129
+ ['blue badge and senior', [getSeniorConcession()]]
130
+ ])('remove disabled concession from array with %s concession(s)', async (_d, additionalConcessions) => {
131
+ const permission = getMockPermission()
132
+ const disabledConcession = getDisabledBlueBadgeConcession()
133
+ permission.concessions = [disabledConcession, ...additionalConcessions]
134
+ await concessionService.removeDisabled(permission)
135
+ expect(permission.concessions).toEqual(expect.not.arrayContaining([expect.objectContaining(disabledConcession)]))
136
+ })
137
+
138
+ it.each([
139
+ ['NI', []],
140
+ ['NI and senior', [getSeniorConcession()]]
141
+ ])('remove disabled concession from array with %s concession(s)', async (_d, additionalConcessions) => {
142
+ const permission = getMockPermission()
143
+ const disabledConcession = getDisabledNiConcession()
144
+ permission.concessions = [disabledConcession, ...additionalConcessions]
145
+ await concessionService.removeDisabled(permission)
146
+ expect(permission.concessions).toEqual(expect.not.arrayContaining([expect.objectContaining(disabledConcession)]))
147
+ })
148
+
149
+ it.each([
150
+ ['senior', 'blue badge', getSeniorConcession(), getDisabledBlueBadgeConcession()],
151
+ ['junior', 'blue badge', getJuniorConcession(), getDisabledBlueBadgeConcession()],
152
+ ['senior', 'NI', getSeniorConcession(), getDisabledNiConcession()],
153
+ ['junior', 'NI', getJuniorConcession(), getDisabledNiConcession()]
154
+ ])('leaves remaining %s concession when removing a disabled %s concession', async (_cd, _dcd, concession, disabledConcession) => {
155
+ const permission = getMockPermission()
156
+ permission.concessions = [disabledConcession, concession]
157
+ await concessionService.removeDisabled(permission)
158
+ expect(permission.concessions).toEqual(expect.arrayContaining([expect.objectContaining(concession)]))
159
+ })
160
+ })
161
+
162
+ describe('hasDisabled', () => {
163
+ it('returns true if have disabled blue badge', async () => {
164
+ const permission = getMockPermission()
165
+ permission.concessions = [getDisabledBlueBadgeConcession()]
166
+ const result = await concessionService.hasDisabled(permission)
167
+ expect(result).toBe(true)
168
+ })
169
+
170
+ it('returns true if have disabled NI', async () => {
171
+ const permission = getMockPermission()
172
+ permission.concessions = [getDisabledNiConcession()]
173
+ const result = await concessionService.hasDisabled(permission)
174
+ expect(result).toBe(true)
175
+ })
176
+
177
+ it('returns false if do not have disabled', async () => {
178
+ const permission = getMockPermission()
179
+ permission.concessions = []
180
+ const result = await concessionService.hasDisabled(permission)
181
+ expect(result).toBe(false)
182
+ })
183
+ })
184
+ })
185
+
186
+ describe('Senior', () => {
187
+ describe('addSenior', () => {
188
+ it('add concession', async () => {
189
+ const permission = getMockPermission()
190
+ await concessionService.addSenior(permission)
191
+ expect(permission.concessions).toContainEqual(getSeniorConcession())
192
+ })
193
+
194
+ it("doesn't add second senior concession if one's already present", async () => {
195
+ const seniorConcession = getSeniorConcession()
196
+ const permission = getMockPermission()
197
+ permission.concessions = [seniorConcession]
198
+ await concessionService.addSenior(permission)
199
+ expect(permission.concessions).toEqual([expect.objectContaining(seniorConcession)])
200
+ })
201
+
202
+ it('add senior to blue badge', async () => {
203
+ const permission = getMockPermission()
204
+ const disabledConcession = getDisabledNiConcession()
205
+ permission.concessions = [disabledConcession]
206
+ await concessionService.addSenior(permission)
207
+ expect(permission.concessions).toEqual([disabledConcession, getSeniorConcession()])
208
+ })
209
+
210
+ it('add senior to NI', async () => {
211
+ const permission = getMockPermission()
212
+ const disabledConcession = getDisabledBlueBadgeConcession()
213
+ permission.concessions = [disabledConcession]
214
+ await concessionService.addSenior(permission)
215
+ expect(permission.concessions).toEqual([disabledConcession, getSeniorConcession()])
216
+ })
217
+ })
218
+
219
+ describe('removeSenior', () => {
220
+ it.each([
221
+ ['has only a senior concession', [getSeniorConcession()], []],
222
+ [
223
+ 'has senior and disabled concessions',
224
+ [getSeniorConcession(), getDisabledBlueBadgeConcession()],
225
+ [getDisabledBlueBadgeConcession()]
226
+ ]
227
+ ])('removes senior concession if permission %s', async (_desc, concessions, expectedConcessions) => {
228
+ const permission = getMockPermission()
229
+ permission.concessions = concessions
230
+ await concessionService.removeSenior(permission)
231
+ expect(permission.concessions).toEqual(expectedConcessions)
232
+ })
233
+
234
+ it.each([
235
+ ['is empty', []],
236
+ ['has junior concession', [getJuniorConcession()]],
237
+ ['has junior and disabled concession', [getJuniorConcession(), getDisabledBlueBadgeConcession()]],
238
+ ['has disabled concession', [getDisabledNiConcession()]]
239
+ ])('leaves concessions unmodified if senior concession not present and concessions %s', async (_desc, concessions) => {
240
+ const permission = getMockPermission()
241
+ permission.concessions = concessions
242
+ await concessionService.removeSenior(permission)
243
+ expect(permission.concessions).toEqual(concessions)
244
+ })
245
+ })
246
+
247
+ describe('hasSenior', () => {
248
+ it.each(['senior'])('is true if licensee has senior concession', async () => {
249
+ const permission = getMockPermission()
250
+ permission.concessions = [getSeniorConcession()]
251
+ const isSenior = await concessionService.hasSenior(permission)
252
+ expect(isSenior).toBe(true)
253
+ })
254
+
255
+ it.each([
256
+ ['empty array', []],
257
+ ['array with disabled concession', [getDisabledBlueBadgeConcession()]]
258
+ ])("is false if licensee doesn't have senior concession in %s", async (_d, concessions) => {
259
+ const permission = getMockPermission()
260
+ permission.concessions = concessions
261
+ const isSenior = await concessionService.hasSenior(permission)
262
+ expect(isSenior).toBe(false)
263
+ })
264
+ })
265
+ })
266
+
267
+ describe('Junior', () => {
268
+ describe('removeJunior', () => {
269
+ it.each([
270
+ ['has only a junior concession', [getJuniorConcession()], []],
271
+ [
272
+ 'has junior and disabled concessions',
273
+ [getJuniorConcession(), getDisabledBlueBadgeConcession()],
274
+ [getDisabledBlueBadgeConcession()]
275
+ ]
276
+ ])('removes junior concession if concessions %s', async (_desc, concessions, expectedConcessions) => {
277
+ const permission = getMockPermission()
278
+ permission.concessions = concessions
279
+ await concessionService.removeJunior(permission)
280
+ expect(permission.concessions).toEqual(expectedConcessions)
281
+ })
282
+
283
+ it.each([
284
+ ['is empty', []],
285
+ ['has senior concession', [getSeniorConcession()]],
286
+ ['has senior and disabled concession', [getSeniorConcession(), getDisabledBlueBadgeConcession()]],
287
+ ['has disabled concession', [getDisabledNiConcession()]]
288
+ ])('leaves concessions unmodified if junior concession not present and concessions %s', async (_desc, concessions) => {
289
+ const permission = getMockPermission()
290
+ permission.concessions = concessions
291
+ await concessionService.removeJunior(permission)
292
+ expect(permission.concessions).toEqual(concessions)
293
+ })
294
+ })
295
+
296
+ describe('hasJunior', () => {
297
+ it('returns true if permission has a junior concession', async () => {
298
+ const permission = getMockPermission()
299
+ permission.concessions = [getJuniorConcession()]
300
+ const result = await concessionService.hasJunior(permission)
301
+ expect(result).toBe(true)
302
+ })
303
+
304
+ it.each([
305
+ ['empty array', []],
306
+ ['array with disabled concession', [getDisabledBlueBadgeConcession()]]
307
+ ])("is false if licensee doesn't have junior concession in %s", async (_d, concessions) => {
308
+ const permission = getMockPermission()
309
+ permission.concessions = concessions
310
+ const isJunior = await concessionService.hasJunior(permission)
311
+ expect(isJunior).toBe(false)
312
+ })
313
+ })
314
+ })
315
+ })
@@ -0,0 +1,131 @@
1
+ import { findPermit } from '../permit.service.js'
2
+ import { Permit, Concession, PermitConcession } from '@defra-fish/dynamics-lib'
3
+ import { getReferenceDataForEntity } from '../reference-data.service.js'
4
+
5
+ jest.mock('../reference-data.service.js')
6
+
7
+ const getSamplePermits = () => {
8
+ const samplePermits = [
9
+ {
10
+ id: 'prm-111',
11
+ numberOfRods: 1,
12
+ durationMagnitude: '12',
13
+ durationDesignator: { description: 'M' },
14
+ permitSubtype: { label: 'type-1' }
15
+ },
16
+ {
17
+ id: 'prm-222',
18
+ numberOfRods: 3,
19
+ durationMagnitude: '12',
20
+ durationDesignator: { description: 'M' },
21
+ permitSubtype: { label: 'type-2' }
22
+ },
23
+ {
24
+ id: 'prm-333',
25
+ numberOfRods: 1,
26
+ durationMagnitude: '12',
27
+ durationDesignator: { description: 'M' },
28
+ permitSubtype: { label: 'type-2' }
29
+ },
30
+ {
31
+ id: 'prm-444',
32
+ numberOfRods: 1,
33
+ durationMagnitude: '12',
34
+ durationDesignator: { description: 'M' },
35
+ permitSubtype: { label: 'type-3' }
36
+ }
37
+ ]
38
+ return samplePermits.map(sp => ({
39
+ ...sp,
40
+ toJSON: function () {
41
+ const props = {
42
+ ...this
43
+ }
44
+ delete props.toJSON
45
+ return props
46
+ }
47
+ }))
48
+ }
49
+
50
+ const getSamplePermitConcessions = () => [{ permitId: 'prm-111', concessionId: 'con-111' }]
51
+
52
+ const getSampleConcessions = () => [
53
+ { id: 'con-111', name: 'concession-type-1' },
54
+ { id: 'con-222', name: 'concession-type-2' }
55
+ ]
56
+
57
+ describe('findPermit', () => {
58
+ beforeAll(() => {
59
+ getReferenceDataForEntity.mockImplementation(async entity => {
60
+ if (entity === Permit) {
61
+ return getSamplePermits()
62
+ }
63
+ if (entity === PermitConcession) {
64
+ return getSamplePermitConcessions()
65
+ }
66
+ if (entity === Concession) {
67
+ return getSampleConcessions()
68
+ }
69
+ return []
70
+ })
71
+ })
72
+ const getSamplePermission = overrides => ({
73
+ permit: {
74
+ numberOfRods: '1',
75
+ permitSubtype: {
76
+ label: 'type-3'
77
+ }
78
+ },
79
+ durationMagnitude: '12',
80
+ durationDesignator: { description: 'M' },
81
+ ...overrides
82
+ })
83
+ const getSamplePermissionWithConcession = () => ({
84
+ concessions: [{ name: 'concession-type-1', id: 'abc-123', proof: {} }],
85
+ permit: {
86
+ numberOfRods: '1',
87
+ permitSubtype: {
88
+ label: 'type-1'
89
+ }
90
+ }
91
+ })
92
+
93
+ const createExpectedPermit = ({
94
+ numberOfRods = 1,
95
+ durationMagnitude = '12',
96
+ durationDescription = 'M',
97
+ permitSubtypeLabel = 'type-3',
98
+ ...overrides
99
+ } = {}) => ({
100
+ numberOfRods,
101
+ durationMagnitude,
102
+ durationDesignator: { description: durationDescription },
103
+ permitSubtype: { label: permitSubtypeLabel },
104
+ ...overrides
105
+ })
106
+
107
+ it.each`
108
+ description | expectedPermit | permission
109
+ ${'concessions'} | ${createExpectedPermit({ id: 'prm-111', permitSubtypeLabel: 'type-1' })} | ${getSamplePermissionWithConcession()}
110
+ ${'licence type'} | ${createExpectedPermit({ id: 'prm-444', permitSubtypeLabel: 'type-3' })} | ${getSamplePermission()}
111
+ ${'number of rods'} | ${createExpectedPermit({ id: 'prm-222', numberOfRods: 3, permitSubtypeLabel: 'type-2' })} | ${getSamplePermission({ permit: { permitSubtype: { label: 'type-2' }, numberOfRods: '3' } })}
112
+ `('matches a permission to a permit on $description', async ({ expectedPermit, permission }) => {
113
+ const permit = await findPermit(permission)
114
+ expect(permit).toStrictEqual(expect.objectContaining(expectedPermit))
115
+ })
116
+
117
+ it.each([
118
+ ['with one element', [{ type: 'concession-type-1', id: 'con-111', proof: {} }]],
119
+ [
120
+ 'with two elements, one',
121
+ [
122
+ { name: 'concession-type-1', id: 'con-111', proof: {} },
123
+ { type: 'concession-type-2', id: 'abc-123', proof: {} }
124
+ ]
125
+ ]
126
+ ])('throws an error if concessions array %s containing type rather than name', async (_d, concessions) => {
127
+ const perm = getSamplePermissionWithConcession()
128
+ perm.concessions = concessions
129
+ await expect(() => findPermit(perm)).rejects.toThrow("Concession should contain 'name' not 'type'")
130
+ })
131
+ })
@@ -0,0 +1,61 @@
1
+ import { CONCESSION, CONCESSION_PROOF } from './constants.js'
2
+ import { getReferenceDataForEntity } from './reference-data.service.js'
3
+ import { Concession } from '@defra-fish/dynamics-lib'
4
+
5
+ const getTypeConcessionId = async type => {
6
+ const concessions = await getReferenceDataForEntity(Concession)
7
+ const { id } = concessions.find(c => c.name === type)
8
+ return id
9
+ }
10
+
11
+ const hasConcessionType = async (permission, type) => {
12
+ const id = await getTypeConcessionId(type)
13
+ return !!permission.concessions && permission.concessions.some(c => c.id === id)
14
+ }
15
+
16
+ const removeConcessionType = async (permission, type) => {
17
+ if (await hasConcessionType(permission, type)) {
18
+ const id = await getTypeConcessionId(type)
19
+ permission.concessions = permission.concessions.filter(c => c.id !== id)
20
+ }
21
+ }
22
+
23
+ export const addDisabled = async (permission, concessionProof, referenceNumber) => {
24
+ await removeDisabled(permission)
25
+ if (!permission.concessions) {
26
+ permission.concessions = []
27
+ }
28
+ permission.concessions.push({
29
+ id: await getTypeConcessionId(CONCESSION.DISABLED),
30
+ name: CONCESSION.DISABLED,
31
+ proof: {
32
+ type: concessionProof,
33
+ referenceNumber
34
+ }
35
+ })
36
+ }
37
+
38
+ export const removeDisabled = permission => removeConcessionType(permission, CONCESSION.DISABLED)
39
+ export const hasDisabled = permission => hasConcessionType(permission, CONCESSION.DISABLED)
40
+
41
+ export const addSenior = async permission => {
42
+ if (!(await hasSenior(permission))) {
43
+ await removeJunior(permission)
44
+ if (!permission.concessions) {
45
+ permission.concessions = []
46
+ }
47
+ permission.concessions.push({
48
+ id: await getTypeConcessionId(CONCESSION.SENIOR),
49
+ name: CONCESSION.SENIOR,
50
+ proof: {
51
+ type: CONCESSION_PROOF.none
52
+ }
53
+ })
54
+ }
55
+ }
56
+
57
+ export const removeSenior = permission => removeConcessionType(permission, CONCESSION.SENIOR)
58
+ export const hasSenior = permission => hasConcessionType(permission, CONCESSION.SENIOR)
59
+
60
+ export const removeJunior = permission => removeConcessionType(permission, CONCESSION.JUNIOR)
61
+ export const hasJunior = permission => hasConcessionType(permission, CONCESSION.JUNIOR)
@@ -0,0 +1,13 @@
1
+ const CONCESSION = {
2
+ SENIOR: 'Senior',
3
+ JUNIOR: 'Junior',
4
+ DISABLED: 'Disabled'
5
+ }
6
+
7
+ const CONCESSION_PROOF = {
8
+ none: 'No Proof',
9
+ blueBadge: 'Blue Badge',
10
+ NI: 'National Insurance Number'
11
+ }
12
+
13
+ export { CONCESSION, CONCESSION_PROOF }
@@ -0,0 +1,41 @@
1
+ import { getReferenceDataForEntity } from './reference-data.service.js'
2
+ import { Permit, Concession, PermitConcession } from '@defra-fish/dynamics-lib'
3
+
4
+ export const findPermit = async existingPermission => {
5
+ const licenseeConcessions = existingPermission.concessions || []
6
+ const permitsJoinPermitConcessions = await getPermitsJoinPermitConcessions()
7
+
8
+ if (licenseeConcessions.some(c => !c.name && !!c.type)) {
9
+ throw new Error("Concession should contain 'name' not 'type'")
10
+ }
11
+
12
+ // Filter the joined list to include every and only those concessions in licenseeConcessions
13
+ const filteredPermitsJoinPermitConcessions = permitsJoinPermitConcessions.filter(
14
+ pjpc =>
15
+ licenseeConcessions.map(lc => lc.name).every(t => pjpc.concessions.map(c => c.name).includes(t)) &&
16
+ pjpc.concessions.length === licenseeConcessions.length
17
+ )
18
+
19
+ // Filter by the licence length
20
+ const byLicenceLength = filteredPermitsJoinPermitConcessions.filter(
21
+ p => String(p.durationMagnitude + p.durationDesignator.description) === '12M'
22
+ )
23
+
24
+ // Filter by the licence (sub) type
25
+ const byLicenceType = byLicenceLength.filter(p => p.permitSubtype.label === existingPermission.permit.permitSubtype.label)
26
+
27
+ // Filter by the number of rods
28
+ const byNumberOfRods = byLicenceType.filter(r => String(r.numberOfRods) === String(existingPermission.permit.numberOfRods))
29
+
30
+ return byNumberOfRods[0]
31
+ }
32
+
33
+ const getPermitsJoinPermitConcessions = async () => {
34
+ const permits = await getReferenceDataForEntity(Permit)
35
+ const permitConcessions = await getReferenceDataForEntity(PermitConcession)
36
+ const concessions = await getReferenceDataForEntity(Concession)
37
+ return permits.map(p => ({
38
+ ...p.toJSON(),
39
+ concessions: permitConcessions.filter(pc => pc.permitId === p.id).map(pc => concessions.find(c => c.id === pc.concessionId))
40
+ }))
41
+ }
@@ -1,5 +1,30 @@
1
1
  import moment from 'moment'
2
2
  import { preparePermissionDataForRenewal } from '../renewals.service.js'
3
+ import { findPermit } from '../../permit.service.js'
4
+ import { getReferenceDataForEntity } from '../../reference-data.service.js'
5
+ import { CONCESSION, CONCESSION_PROOF } from '../../constants.js'
6
+
7
+ jest.mock('@defra-fish/connectors-lib')
8
+ jest.mock('../../reference-data.service.js')
9
+
10
+ getReferenceDataForEntity.mockResolvedValue([
11
+ {
12
+ id: '3230c68f-ef65-e611-80dc-c4346bad4004',
13
+ name: 'Junior'
14
+ },
15
+ {
16
+ id: 'd0ece997-ef65-e611-80dc-c4346bad4004',
17
+ name: 'Senior'
18
+ },
19
+ {
20
+ id: 'd1ece997-ef65-e611-80dc-c4346bad4004',
21
+ name: 'Disabled'
22
+ }
23
+ ])
24
+
25
+ jest.mock('../../permit.service.js', () => ({
26
+ findPermit: jest.fn(() => ({ id: '123456' }))
27
+ }))
3
28
 
4
29
  describe('preparePermissionDataForRenewal', () => {
5
30
  const existingPermission = overrides => ({
@@ -37,9 +62,18 @@ describe('preparePermissionDataForRenewal', () => {
37
62
  },
38
63
  numberOfRods: 1
39
64
  },
65
+ concessions: [],
40
66
  ...overrides
41
67
  })
42
68
 
69
+ const existingSeniorPermission = () =>
70
+ existingPermission({
71
+ licensee: {
72
+ ...existingPermission().licensee,
73
+ birthDate: '1958-01-01'
74
+ }
75
+ })
76
+
43
77
  it('should assign the correct data to the base permission', async () => {
44
78
  const expectedData = {
45
79
  isRenewal: true,
@@ -49,7 +83,7 @@ describe('preparePermissionDataForRenewal', () => {
49
83
  isLicenceForYou: true,
50
84
  permitId: '123456'
51
85
  }
52
- expect(preparePermissionDataForRenewal(existingPermission())).toEqual(expect.objectContaining(expectedData))
86
+ expect(await preparePermissionDataForRenewal(existingPermission())).toEqual(expect.objectContaining(expectedData))
53
87
  })
54
88
 
55
89
  it('should copy the relevant licensee data', async () => {
@@ -67,20 +101,23 @@ describe('preparePermissionDataForRenewal', () => {
67
101
  preferredMethodOfConfirmation: 'Text',
68
102
  preferredMethodOfReminder: 'Letter'
69
103
  }
70
- expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.objectContaining(expectedData))
104
+ const permission = await preparePermissionDataForRenewal(existingPermission())
105
+ expect(permission.licensee).toEqual(expect.objectContaining(expectedData))
71
106
  })
72
107
 
73
108
  it('should not assign shortTermPreferredMethodOfConfirmation to the licensee', async () => {
74
- const licenseeData = preparePermissionDataForRenewal(existingPermission()).licensee
75
- expect(licenseeData.shortTermPreferredMethodOfConfirmation).toBeUndefined()
109
+ const permission = await preparePermissionDataForRenewal(existingPermission())
110
+ expect(permission.licensee.shortTermPreferredMethodOfConfirmation).toBeUndefined()
76
111
  })
77
112
 
78
113
  it('should remove null values from the licensee object', async () => {
79
- expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.not.objectContaining({ mobilePhone: null }))
114
+ const permission = await preparePermissionDataForRenewal(existingPermission())
115
+ expect(permission.licensee).toEqual(expect.not.objectContaining({ mobilePhone: null }))
80
116
  })
81
117
 
82
118
  it('should keep false values on the licensee object', async () => {
83
- expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.objectContaining({ postalFulfilment: false }))
119
+ const permission = await preparePermissionDataForRenewal(existingPermission())
120
+ expect(permission.licensee).toEqual(expect.objectContaining({ postalFulfilment: false }))
84
121
  })
85
122
 
86
123
  describe('when the original permission has expired', () => {
@@ -93,7 +130,7 @@ describe('preparePermissionDataForRenewal', () => {
93
130
  licenceStartTime: 0,
94
131
  renewedEndDate: endDate.toISOString()
95
132
  }
96
- expect(preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
133
+ expect(await preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
97
134
  })
98
135
  })
99
136
 
@@ -107,7 +144,61 @@ describe('preparePermissionDataForRenewal', () => {
107
144
  licenceStartTime: endDate.hours(),
108
145
  renewedEndDate: endDate.toISOString()
109
146
  }
110
- expect(preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
147
+ expect(await preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
148
+ })
149
+ })
150
+
151
+ describe('prepareConcessionsData', () => {
152
+ it('should add senior concession if the licensee is senior', async () => {
153
+ const samplePermission = existingSeniorPermission()
154
+ const ppd = await preparePermissionDataForRenewal(samplePermission)
155
+ const senior = { name: 'Senior', id: 'd0ece997-ef65-e611-80dc-c4346bad4004', proof: { type: 'No Proof' } }
156
+ expect(ppd.concessions[0]).toEqual(senior)
157
+ })
158
+
159
+ it("doesn't add senior concession if the licensee is not senior", async () => {
160
+ const permission = await preparePermissionDataForRenewal(existingPermission())
161
+ expect(permission.concessions).toEqual([])
162
+ })
163
+
164
+ it('should remove noLicenceRequired from licensee', async () => {
165
+ const permission = existingPermission()
166
+ permission.licensee.noLicenceRequired = true
167
+ const preparedPermission = await preparePermissionDataForRenewal(existingPermission())
168
+ expect(preparedPermission.licensee.noLicenceRequired).toBeUndefined()
169
+ })
170
+
171
+ it.each([
172
+ ['adult', existingPermission()],
173
+ ['senior', existingSeniorPermission()]
174
+ ])('should leave disabled concession unmodified on %s permission', async (_d, permission) => {
175
+ const disabledConcession = {
176
+ id: 'eee-555-fff-666',
177
+ name: CONCESSION.DISABLED,
178
+ proof: {
179
+ type: CONCESSION_PROOF.blueBadge,
180
+ referenceNumber: 'blue-badge-123'
181
+ }
182
+ }
183
+ permission.concessions = [disabledConcession]
184
+
185
+ const preparedPermission = await preparePermissionDataForRenewal(permission)
186
+ expect(preparedPermission.concessions).toEqual(expect.arrayContaining([expect.objectContaining(disabledConcession)]))
187
+ })
188
+ })
189
+
190
+ describe('preparePermit', () => {
191
+ it('permitId should match the return of findPermit.id', async () => {
192
+ const mockPermit = {
193
+ id: '101010',
194
+ permitSubtype: {
195
+ label: 'Salmon and sea trout'
196
+ },
197
+ numberOfRods: 1
198
+ }
199
+ findPermit.mockResolvedValueOnce(mockPermit)
200
+ const permission = await preparePermissionDataForRenewal(existingPermission())
201
+ expect(permission.permitId).toEqual(mockPermit.id)
111
202
  })
112
203
  })
113
204
  })
@@ -1,5 +1,7 @@
1
- import { SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
1
+ import { isSenior, SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
2
2
  import moment from 'moment-timezone'
3
+ import { addSenior } from '../concession.service.js'
4
+ import { findPermit } from '../permit.service.js'
3
5
 
4
6
  // Replicated from GAFL - need to decide whether to move
5
7
  const cacheDateFormat = 'YYYY-MM-DD'
@@ -8,12 +10,17 @@ const licenceToStart = {
8
10
  ANOTHER_DATE: 'another-date'
9
11
  }
10
12
 
11
- export const preparePermissionDataForRenewal = existingPermission => ({
12
- ...prepareBasePermissionData(existingPermission),
13
- ...prepareDateData(existingPermission),
14
- licensee: prepareLicenseeData(existingPermission),
15
- permitId: preparePermitId(existingPermission)
16
- })
13
+ export const preparePermissionDataForRenewal = async existingPermission => {
14
+ const dateData = prepareDateData(existingPermission)
15
+ const concessions = await prepareConcessionsData(existingPermission, dateData)
16
+ return {
17
+ ...prepareBasePermissionData(existingPermission),
18
+ ...dateData,
19
+ licensee: prepareLicenseeData(existingPermission),
20
+ concessions,
21
+ permitId: await preparePermit(existingPermission)
22
+ }
23
+ }
17
24
 
18
25
  const prepareBasePermissionData = existingPermission => ({
19
26
  isRenewal: true,
@@ -83,4 +90,20 @@ const dateDataIfNotExpired = endDateMoment => {
83
90
  }
84
91
  }
85
92
 
86
- const preparePermitId = existingPermission => existingPermission.permit.id
93
+ const preparePermit = async existingPermission => {
94
+ const permit = await findPermit(existingPermission)
95
+ return permit.id
96
+ }
97
+
98
+ const prepareConcessionsData = async (existingPermission, dateData) => {
99
+ delete existingPermission.licensee.noLicenceRequired
100
+ const ageAtLicenceStartDate = moment(dateData.licenceStartDate)
101
+ .add(1, 'year')
102
+ .diff(moment(existingPermission.licensee.birthDate), 'years')
103
+
104
+ if (isSenior(ageAtLicenceStartDate)) {
105
+ await addSenior(existingPermission)
106
+ }
107
+
108
+ return existingPermission.concessions
109
+ }