@defra-fish/sales-api-service 1.61.0-rc.6 → 1.61.0-rc.8

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.61.0-rc.6",
3
+ "version": "1.61.0-rc.8",
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.61.0-rc.6",
39
- "@defra-fish/connectors-lib": "1.61.0-rc.6",
40
- "@defra-fish/dynamics-lib": "1.61.0-rc.6",
38
+ "@defra-fish/business-rules-lib": "1.61.0-rc.8",
39
+ "@defra-fish/connectors-lib": "1.61.0-rc.8",
40
+ "@defra-fish/dynamics-lib": "1.61.0-rc.8",
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": "38a3dd7bb9c0b5331ef38530b7a2ddc0de8ecfa4"
55
+ "gitHead": "6ee197c0caa5d83c4b111f6a382eee271d1e4f61"
56
56
  }
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  dueRecurringPaymentsRequestParamsSchema,
3
3
  dueRecurringPaymentsResponseSchema,
4
- processRPResultRequestParamsSchema
4
+ processRPResultRequestParamsSchema,
5
+ linkRecurringPaymentsRequestParamsSchema
5
6
  } from '../recurring-payments.schema.js'
6
7
 
7
8
  jest.mock('../validators/validators.js', () => ({
@@ -62,6 +63,11 @@ const getProcessRPResultSampleData = () => ({
62
63
  createdDate: '2025-01-01T00:00:00.000Z'
63
64
  })
64
65
 
66
+ const getLinkRecurringPaymentsSampleData = () => ({
67
+ existingRecurringPaymentId: 'ghi123',
68
+ agreementId: 'jkl456'
69
+ })
70
+
65
71
  describe('getDueRecurringPaymentsSchema', () => {
66
72
  it('validates expected object', async () => {
67
73
  expect(() => dueRecurringPaymentsResponseSchema.validateAsync(getResponseSampleData())).not.toThrow()
@@ -149,3 +155,24 @@ describe('processRPResultRequestParamsSchema', () => {
149
155
  expect(() => processRPResultRequestParamsSchema.validateAsync(sampleData).rejects.toThrow())
150
156
  })
151
157
  })
158
+
159
+ describe('linkRecurringPaymentsRequestParamsSchema', () => {
160
+ it('validates expected object', async () => {
161
+ expect(() => linkRecurringPaymentsRequestParamsSchema.validateAsync(getLinkRecurringPaymentsSampleData())).not.toThrow()
162
+ })
163
+
164
+ it.each([['existingRecurringPaymentId'], ['agreementId']])('throws an error if %s is missing', async property => {
165
+ const sampleData = getLinkRecurringPaymentsSampleData()
166
+ delete sampleData[property]
167
+ expect(() => linkRecurringPaymentsRequestParamsSchema.validateAsync(sampleData).rejects.toThrow())
168
+ })
169
+
170
+ it.each([
171
+ ['existingRecurringPaymentId', 99],
172
+ ['agreementId', 99]
173
+ ])('throws an error if %s is not the correct type', async (property, value) => {
174
+ const sampleData = getLinkRecurringPaymentsSampleData()
175
+ sampleData[property] = value
176
+ expect(() => linkRecurringPaymentsRequestParamsSchema.validateAsync(sampleData).rejects.toThrow())
177
+ })
178
+ })
@@ -29,3 +29,8 @@ export const processRPResultRequestParamsSchema = Joi.object({
29
29
  paymentId: Joi.string().required(),
30
30
  createdDate: Joi.string().isoDate().required()
31
31
  })
32
+
33
+ export const linkRecurringPaymentsRequestParamsSchema = Joi.object({
34
+ existingRecurringPaymentId: Joi.string().required(),
35
+ agreementId: Joi.string().required()
36
+ })
@@ -1,6 +1,10 @@
1
1
  import recurringPayments from '../recurring-payments.js'
2
- import { getRecurringPayments, processRPResult } from '../../../services/recurring-payments.service.js'
3
- import { dueRecurringPaymentsRequestParamsSchema, processRPResultRequestParamsSchema } from '../../../schema/recurring-payments.schema.js'
2
+ import { getRecurringPayments, processRPResult, linkRecurringPayments } from '../../../services/recurring-payments.service.js'
3
+ import {
4
+ dueRecurringPaymentsRequestParamsSchema,
5
+ processRPResultRequestParamsSchema,
6
+ linkRecurringPaymentsRequestParamsSchema
7
+ } from '../../../schema/recurring-payments.schema.js'
4
8
 
5
9
  const [
6
10
  {
@@ -8,26 +12,33 @@ const [
8
12
  },
9
13
  {
10
14
  options: { handler: prpHandler }
15
+ },
16
+ {
17
+ options: { handler: lrpHandler }
11
18
  }
12
19
  ] = recurringPayments
13
20
 
14
21
  jest.mock('../../../services/recurring-payments.service.js', () => ({
15
22
  getRecurringPayments: jest.fn(),
16
- processRPResult: jest.fn()
23
+ processRPResult: jest.fn(),
24
+ linkRecurringPayments: jest.fn()
17
25
  }))
18
26
 
19
27
  jest.mock('../../../schema/recurring-payments.schema.js', () => ({
20
28
  dueRecurringPaymentsRequestParamsSchema: jest.fn(),
21
- processRPResultRequestParamsSchema: jest.fn()
29
+ processRPResultRequestParamsSchema: jest.fn(),
30
+ linkRecurringPaymentsRequestParamsSchema: jest.fn()
22
31
  }))
23
32
 
24
33
  const getMockRequest = ({
25
34
  date = '2023-10-19',
26
35
  transactionId = 'transaction-id',
27
36
  paymentId = 'payment-id',
28
- createdDate = 'created-date'
37
+ createdDate = 'created-date',
38
+ existingRecurringPaymentId = 'existing-recurring-payment-id',
39
+ agreementId = 'agreement-id'
29
40
  }) => ({
30
- params: { date, transactionId, paymentId, createdDate }
41
+ params: { date, transactionId, paymentId, createdDate, existingRecurringPaymentId, agreementId }
31
42
  })
32
43
 
33
44
  const getMockResponseToolkit = () => ({
@@ -75,11 +86,37 @@ describe('recurring payments', () => {
75
86
  expect(processRPResult).toHaveBeenCalledWith(transactionId, paymentId, createdDate)
76
87
  })
77
88
 
78
- it('should validate with dueRecurringPaymentsRequestParamsSchema', async () => {
79
- const date = Symbol('date')
80
- const request = getMockRequest({ date })
81
- await drpHandler(request, getMockResponseToolkit())
89
+ it('should validate with processRPResultRequestParamsSchema', async () => {
90
+ const transactionId = Symbol('transaction-id')
91
+ const paymentId = Symbol('payment-id')
92
+ const createdDate = Symbol('created-date')
93
+ const request = getMockRequest({ transactionId, paymentId, createdDate })
94
+ await prpHandler(request, getMockResponseToolkit())
82
95
  expect(recurringPayments[1].options.validate.params).toBe(processRPResultRequestParamsSchema)
83
96
  })
84
97
  })
98
+
99
+ describe('linkRecurringPayments', () => {
100
+ it('handler should return continue response', async () => {
101
+ const request = getMockRequest({})
102
+ const responseToolkit = getMockResponseToolkit()
103
+ expect(await lrpHandler(request, responseToolkit)).toEqual(responseToolkit.continue)
104
+ })
105
+
106
+ it('should call linkRecurringPayments with existingRecurringPaymentId and agreementId', async () => {
107
+ const existingRecurringPaymentId = Symbol('existing-recurring-payment')
108
+ const agreementId = Symbol('agreement-id')
109
+ const request = getMockRequest({ existingRecurringPaymentId, agreementId })
110
+ await lrpHandler(request, getMockResponseToolkit())
111
+ expect(linkRecurringPayments).toHaveBeenCalledWith(existingRecurringPaymentId, agreementId)
112
+ })
113
+
114
+ it('should validate with linkRecurringPaymentsRequestParamsSchema', async () => {
115
+ const existingRecurringPaymentId = Symbol('existing-recurring-payment')
116
+ const agreementId = Symbol('agreement-id')
117
+ const request = getMockRequest({ existingRecurringPaymentId, agreementId })
118
+ await lrpHandler(request, getMockResponseToolkit())
119
+ expect(recurringPayments[2].options.validate.params).toBe(linkRecurringPaymentsRequestParamsSchema)
120
+ })
121
+ })
85
122
  })
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  dueRecurringPaymentsRequestParamsSchema,
3
3
  dueRecurringPaymentsResponseSchema,
4
+ linkRecurringPaymentsRequestParamsSchema,
4
5
  processRPResultRequestParamsSchema
5
6
  } from '../../schema/recurring-payments.schema.js'
6
- import { getRecurringPayments, processRPResult } from '../../services/recurring-payments.service.js'
7
+ import { getRecurringPayments, linkRecurringPayments, processRPResult } from '../../services/recurring-payments.service.js'
8
+
9
+ const SWAGGER_TAGS = ['api', 'recurring-payments']
7
10
 
8
11
  export default [
9
12
  {
@@ -16,7 +19,7 @@ export default [
16
19
  return h.response(result)
17
20
  },
18
21
  description: 'Retrieve recurring payments due for the specified date',
19
- tags: ['api', 'recurring-payments'],
22
+ tags: SWAGGER_TAGS,
20
23
  validate: {
21
24
  params: dueRecurringPaymentsRequestParamsSchema
22
25
  },
@@ -40,7 +43,7 @@ export default [
40
43
  return h.response(result)
41
44
  },
42
45
  description: 'Generate a permission from a recurring payment record',
43
- tags: ['api', 'recurring-payments'],
46
+ tags: SWAGGER_TAGS,
44
47
  validate: {
45
48
  params: processRPResultRequestParamsSchema
46
49
  },
@@ -53,5 +56,29 @@ export default [
53
56
  }
54
57
  }
55
58
  }
59
+ },
60
+ {
61
+ method: 'GET',
62
+ path: '/linkRecurringPayments/{existingRecurringPaymentId}/{agreementId}',
63
+ options: {
64
+ handler: async (request, h) => {
65
+ const { existingRecurringPaymentId, agreementId } = request.params
66
+ const result = await linkRecurringPayments(existingRecurringPaymentId, agreementId)
67
+ return h.response(result)
68
+ },
69
+ description: 'Link an old RecurringPayment to its replacement',
70
+ tags: SWAGGER_TAGS,
71
+ validate: {
72
+ params: linkRecurringPaymentsRequestParamsSchema
73
+ },
74
+ plugins: {
75
+ 'hapi-swagger': {
76
+ responses: {
77
+ 200: { description: 'Old RecurringPayment linked to new RecurringPayment successfully' }
78
+ },
79
+ order: 3
80
+ }
81
+ }
82
+ }
56
83
  }
57
84
  ]
@@ -4,7 +4,7 @@ import {
4
4
  processRecurringPayment,
5
5
  generateRecurringPaymentRecord,
6
6
  processRPResult,
7
- findNewestExistingRecurringPaymentInCrm
7
+ linkRecurringPayments
8
8
  } from '../recurring-payments.service.js'
9
9
  import { calculateEndDate, generatePermissionNumber } from '../permissions.service.js'
10
10
  import { getObfuscatedDob } from '../contacts.service.js'
@@ -623,33 +623,42 @@ describe('recurring payments service', () => {
623
623
  })
624
624
  })
625
625
 
626
- describe('findNewestExistingRecurringPaymentInCrm', () => {
626
+ describe('linkRecurringPayments', () => {
627
627
  it('should call executeQuery with findRecurringPaymentsByAgreementId and the provided agreementId', async () => {
628
628
  const agreementId = 'abc123'
629
- await findNewestExistingRecurringPaymentInCrm(agreementId)
629
+ await linkRecurringPayments('existing-recurring-payment-id', agreementId)
630
630
  expect(executeQuery).toHaveBeenCalledWith(findRecurringPaymentsByAgreementId(agreementId))
631
631
  })
632
632
 
633
- it('should return a RecurringPayment record when there is one match', async () => {
634
- const onlyRecord = getMockRecurringPayment()
633
+ it('should log a RecurringPayment record when there is one match', async () => {
634
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
635
+ const onlyRecord = { entity: getMockRecurringPayment() }
635
636
  dynamicsLib.executeQuery.mockReturnValueOnce([onlyRecord])
636
- const returnedRecord = await findNewestExistingRecurringPaymentInCrm('agreementId')
637
- expect(returnedRecord).toBe(onlyRecord)
637
+
638
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
639
+
640
+ expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', onlyRecord.id)
638
641
  })
639
642
 
640
- it('should return the RecurringPayment record with the latest endDate when there are multiple matches', async () => {
641
- const oldestRecord = getMockRecurringPayment({ endDate: '2021-12-15T00:00:00Z' })
642
- const newestRecord = getMockRecurringPayment({ endDate: '2023-12-15T00:00:00Z' })
643
- const middlestRecord = getMockRecurringPayment({ endDate: '2022-12-15T00:00:00Z' })
643
+ it('should log the RecurringPayment record with the latest endDate when there are multiple matches', async () => {
644
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
645
+ const oldestRecord = { entity: getMockRecurringPayment({ id: Symbol('oldest'), endDate: new Date('2021-12-15T00:00:00Z') }) }
646
+ const newestRecord = { entity: getMockRecurringPayment({ id: Symbol('newest'), endDate: new Date('2023-12-15T00:00:00Z') }) }
647
+ const middlestRecord = { entity: getMockRecurringPayment({ id: Symbol('middlest'), endDate: new Date('2022-12-15T00:00:00Z') }) }
644
648
  dynamicsLib.executeQuery.mockReturnValueOnce([oldestRecord, newestRecord, middlestRecord])
645
- const returnedRecord = await findNewestExistingRecurringPaymentInCrm('agreementId')
646
- expect(returnedRecord).toBe(newestRecord)
649
+
650
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
651
+
652
+ expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', newestRecord.id)
647
653
  })
648
654
 
649
- it('should return false when there are no matches', async () => {
655
+ it('should log no matches when there are no matches', async () => {
656
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
650
657
  dynamicsLib.executeQuery.mockReturnValueOnce([])
651
- const returnedRecord = await findNewestExistingRecurringPaymentInCrm('agreementId')
652
- expect(returnedRecord).toBe(false)
658
+
659
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
660
+
661
+ expect(consoleLogSpy).toHaveBeenCalledWith('No matches found')
653
662
  })
654
663
  })
655
664
  })
@@ -127,10 +127,21 @@ export const processRPResult = async (transactionId, paymentId, createdDate) =>
127
127
  return { permission }
128
128
  }
129
129
 
130
- export const findNewestExistingRecurringPaymentInCrm = async agreementId => {
130
+ export const linkRecurringPayments = async (existingRecurringPaymentId, agreementId) => {
131
+ console.log('existingRecurringPaymentId: ', existingRecurringPaymentId)
132
+ console.log('agreementId: ', agreementId)
133
+ const newRecurringPayment = await findNewestExistingRecurringPaymentInCrm(agreementId)
134
+ if (newRecurringPayment) {
135
+ console.log('newRecurringPaymentId: ', newRecurringPayment.id)
136
+ } else {
137
+ console.log('No matches found')
138
+ }
139
+ }
140
+
141
+ const findNewestExistingRecurringPaymentInCrm = async agreementId => {
131
142
  const matchingRecords = await executeQuery(findRecurringPaymentsByAgreementId(agreementId))
132
143
  if (matchingRecords?.length) {
133
- return [...matchingRecords].sort((a, b) => a.endDate.localeCompare(b.endDate)).pop()
144
+ return [...matchingRecords].sort((a, b) => a.entity.endDate - b.entity.endDate).pop()
134
145
  } else {
135
146
  return false
136
147
  }
@@ -26,11 +26,7 @@ import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '..
26
26
  import AwsMock from 'aws-sdk'
27
27
  import { POCL_DATA_SOURCE, DDE_DATA_SOURCE } from '@defra-fish/business-rules-lib'
28
28
  import moment from 'moment'
29
- import {
30
- findNewestExistingRecurringPaymentInCrm,
31
- processRecurringPayment,
32
- generateRecurringPaymentRecord
33
- } from '../../recurring-payments.service.js'
29
+ import { processRecurringPayment, generateRecurringPaymentRecord } from '../../recurring-payments.service.js'
34
30
 
35
31
  jest.mock('../../reference-data.service.js', () => ({
36
32
  ...jest.requireActual('../../reference-data.service.js'),
@@ -418,57 +414,6 @@ describe('transaction service', () => {
418
414
  await processQueue({ id: finalisedTransaction.id })
419
415
  expect(processRecurringPayment).toHaveBeenCalledWith(rprSymbol, expect.any(Contact))
420
416
  })
421
-
422
- it('checks for existing recurring payments with the same agreementId', async () => {
423
- const agreementId = Symbol('agreementId')
424
- const mockRecurringPayment = { agreementId }
425
- const finalisedTransaction = mockFinalisedTransactionRecord()
426
- processRecurringPayment.mockResolvedValueOnce({ recurringPayment: mockRecurringPayment })
427
- AwsMock.DynamoDB.DocumentClient.__setResponse('get', { Item: finalisedTransaction })
428
-
429
- await processQueue({ id: finalisedTransaction.id })
430
-
431
- expect(findNewestExistingRecurringPaymentInCrm).toHaveBeenCalledWith(agreementId)
432
- })
433
-
434
- it("assigns the new RecurringPayment as the existing RecurringPayment's nextRecurringPayment", async () => {
435
- const agreementId = Symbol('agreementId')
436
- const mockRecurringPayment = { agreementId }
437
- const mockExistingRecurringPayment = { bindToEntity: jest.fn() }
438
- const finalisedTransaction = mockFinalisedTransactionRecord()
439
- processRecurringPayment.mockResolvedValueOnce({ recurringPayment: mockRecurringPayment })
440
- AwsMock.DynamoDB.DocumentClient.__setResponse('get', { Item: finalisedTransaction })
441
- findNewestExistingRecurringPaymentInCrm.mockReturnValueOnce(mockExistingRecurringPayment)
442
-
443
- await processQueue({ id: finalisedTransaction.id })
444
-
445
- expect(mockExistingRecurringPayment.bindToEntity).toHaveBeenCalledWith(
446
- RecurringPayment.definition.relationships.nextRecurringPayment,
447
- mockRecurringPayment
448
- )
449
- })
450
-
451
- it('does not attempt to update multiple recurring payment entities if no pre-existing recurring payment is found', async () => {
452
- const agreementId = Symbol('agreementId')
453
- const mockRecurringPayment = { agreementId }
454
- const finalisedTransaction = mockFinalisedTransactionRecord()
455
- processRecurringPayment.mockResolvedValueOnce({ recurringPayment: mockRecurringPayment })
456
- AwsMock.DynamoDB.DocumentClient.__setResponse('get', { Item: finalisedTransaction })
457
- findNewestExistingRecurringPaymentInCrm.mockReturnValueOnce(false)
458
-
459
- await processQueue({ id: finalisedTransaction.id })
460
-
461
- expect(persist.mock.calls[0][0]).toEqual([
462
- expect.objectContaining({ referenceNumber: finalisedTransaction.id }),
463
- expect.objectContaining({ referenceNumber: finalisedTransaction.id }),
464
- expect.objectContaining({ referenceNumber: finalisedTransaction.id }),
465
- expect.objectContaining({ firstName: finalisedTransaction.permissions[0].licensee.firstName }),
466
- expect.objectContaining({ referenceNumber: finalisedTransaction.permissions[0].referenceNumber }),
467
- expect.objectContaining({ agreementId }),
468
- expect.objectContaining({}),
469
- expect.objectContaining({ referenceNumber: finalisedTransaction.permissions[0].concessions[0].proof.referenceNumber })
470
- ])
471
- })
472
417
  })
473
418
  })
474
419
 
@@ -8,16 +8,11 @@ import {
8
8
  Transaction,
9
9
  TransactionCurrency,
10
10
  TransactionJournal,
11
- RecurringPayment,
12
11
  RecurringPaymentInstruction
13
12
  } from '@defra-fish/dynamics-lib'
14
13
  import { DDE_DATA_SOURCE, FULFILMENT_SWITCHOVER_DATE, POCL_TRANSACTION_SOURCES } from '@defra-fish/business-rules-lib'
15
14
  import { getReferenceDataForEntityAndId, getGlobalOptionSetValue, getReferenceDataForEntity } from '../reference-data.service.js'
16
- import {
17
- findNewestExistingRecurringPaymentInCrm,
18
- generateRecurringPaymentRecord,
19
- processRecurringPayment
20
- } from '../recurring-payments.service.js'
15
+ import { generateRecurringPaymentRecord, processRecurringPayment } from '../recurring-payments.service.js'
21
16
  import { resolveContactPayload } from '../contacts.service.js'
22
17
  import { retrieveStagedTransaction } from './retrieve-transaction.js'
23
18
  import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '../../config.js'
@@ -87,12 +82,6 @@ export async function processQueue ({ id }) {
87
82
  paymentInstruction.bindToEntity(RecurringPaymentInstruction.definition.relationships.permit, permit)
88
83
  paymentInstruction.bindToEntity(RecurringPaymentInstruction.definition.relationships.recurringPayment, recurringPayment)
89
84
  entities.push(paymentInstruction)
90
-
91
- const existingRecurringPayment = await findNewestExistingRecurringPaymentInCrm(recurringPayment.agreementId)
92
- if (existingRecurringPayment) {
93
- existingRecurringPayment.bindToEntity(RecurringPayment.definition.relationships.nextRecurringPayment, recurringPayment)
94
- entities.push(existingRecurringPayment)
95
- }
96
85
  }
97
86
 
98
87
  for (const concession of concessions || []) {