@defra-fish/sales-api-service 1.61.0-rc.0 → 1.61.0-rc.10

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.0",
3
+ "version": "1.61.0-rc.10",
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.0",
39
- "@defra-fish/connectors-lib": "1.61.0-rc.0",
40
- "@defra-fish/dynamics-lib": "1.61.0-rc.0",
38
+ "@defra-fish/business-rules-lib": "1.61.0-rc.10",
39
+ "@defra-fish/connectors-lib": "1.61.0-rc.10",
40
+ "@defra-fish/dynamics-lib": "1.61.0-rc.10",
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": "67185f8a721c75f38052b63bdb92a5549622367c"
55
+ "gitHead": "27650c5d19c86f1621c7c1367fafb7d090da3d02"
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
  ]
@@ -1,9 +1,10 @@
1
- import { findDueRecurringPayments, Permission } from '@defra-fish/dynamics-lib'
1
+ import { executeQuery, findDueRecurringPayments, findRecurringPaymentsByAgreementId, Permission } from '@defra-fish/dynamics-lib'
2
2
  import {
3
3
  getRecurringPayments,
4
4
  processRecurringPayment,
5
5
  generateRecurringPaymentRecord,
6
- processRPResult
6
+ processRPResult,
7
+ linkRecurringPayments
7
8
  } from '../recurring-payments.service.js'
8
9
  import { calculateEndDate, generatePermissionNumber } from '../permissions.service.js'
9
10
  import { getObfuscatedDob } from '../contacts.service.js'
@@ -20,7 +21,8 @@ jest.mock('@defra-fish/dynamics-lib', () => ({
20
21
  ...jest.requireActual('@defra-fish/dynamics-lib'),
21
22
  executeQuery: jest.fn(),
22
23
  findById: jest.fn(),
23
- findDueRecurringPayments: jest.fn()
24
+ findDueRecurringPayments: jest.fn(),
25
+ findRecurringPaymentsByAgreementId: jest.fn(() => ['foo'])
24
26
  }))
25
27
 
26
28
  jest.mock('@defra-fish/connectors-lib', () => {
@@ -77,7 +79,7 @@ jest.mock('@defra-fish/business-rules-lib', () => ({
77
79
 
78
80
  const dynamicsLib = jest.requireMock('@defra-fish/dynamics-lib')
79
81
 
80
- const getMockRecurringPayment = () => ({
82
+ const getMockRecurringPayment = (overrides = {}) => ({
81
83
  name: 'Test Name',
82
84
  nextDueDate: '2019-12-14T00:00:00Z',
83
85
  cancelledDate: null,
@@ -93,7 +95,8 @@ const getMockRecurringPayment = () => ({
93
95
  activePermission: {
94
96
  entity: getMockPermission()
95
97
  }
96
- }
98
+ },
99
+ ...overrides
97
100
  })
98
101
 
99
102
  const getMockRPContactPermission = (contact, permission) => ({
@@ -619,4 +622,43 @@ describe('recurring payments service', () => {
619
622
  })
620
623
  })
621
624
  })
625
+
626
+ describe('linkRecurringPayments', () => {
627
+ it('should call executeQuery with findRecurringPaymentsByAgreementId and the provided agreementId', async () => {
628
+ const agreementId = 'abc123'
629
+ await linkRecurringPayments('existing-recurring-payment-id', agreementId)
630
+ expect(executeQuery).toHaveBeenCalledWith(findRecurringPaymentsByAgreementId(agreementId))
631
+ })
632
+
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() }
636
+ dynamicsLib.executeQuery.mockReturnValueOnce([onlyRecord])
637
+
638
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
639
+
640
+ expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', onlyRecord.id)
641
+ })
642
+
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') }) }
648
+ dynamicsLib.executeQuery.mockReturnValueOnce([oldestRecord, newestRecord, middlestRecord])
649
+
650
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
651
+
652
+ expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', newestRecord.id)
653
+ })
654
+
655
+ it('should log no matches when there are no matches', async () => {
656
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
657
+ dynamicsLib.executeQuery.mockReturnValueOnce([])
658
+
659
+ await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
660
+
661
+ expect(consoleLogSpy).toHaveBeenCalledWith('No matches found')
662
+ })
663
+ })
622
664
  })
@@ -1,4 +1,4 @@
1
- import { executeQuery, findDueRecurringPayments, RecurringPayment } from '@defra-fish/dynamics-lib'
1
+ import { executeQuery, findDueRecurringPayments, findRecurringPaymentsByAgreementId, RecurringPayment } from '@defra-fish/dynamics-lib'
2
2
  import { calculateEndDate, generatePermissionNumber } from './permissions.service.js'
3
3
  import { getObfuscatedDob } from './contacts.service.js'
4
4
  import { createHash } from 'node:crypto'
@@ -126,3 +126,23 @@ export const processRPResult = async (transactionId, paymentId, createdDate) =>
126
126
 
127
127
  return { permission }
128
128
  }
129
+
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 => {
142
+ const matchingRecords = await executeQuery(findRecurringPaymentsByAgreementId(agreementId))
143
+ if (matchingRecords?.length) {
144
+ return [...matchingRecords].sort((a, b) => a.entity.endDate - b.entity.endDate).pop()
145
+ } else {
146
+ return false
147
+ }
148
+ }
@@ -7,10 +7,10 @@ import {
7
7
  FulfilmentRequest,
8
8
  Permission,
9
9
  PoclFile,
10
+ RecurringPayment,
10
11
  RecurringPaymentInstruction,
11
12
  Transaction,
12
- TransactionJournal,
13
- RecurringPayment
13
+ TransactionJournal
14
14
  } from '@defra-fish/dynamics-lib'
15
15
  import {
16
16
  mockFinalisedTransactionRecord,
@@ -65,7 +65,11 @@ jest.mock('@defra-fish/business-rules-lib', () => ({
65
65
  START_AFTER_PAYMENT_MINUTES: 30
66
66
  }))
67
67
 
68
- jest.mock('../../recurring-payments.service.js')
68
+ jest.mock('../../recurring-payments.service.js', () => ({
69
+ findNewestExistingRecurringPaymentInCrm: jest.fn(),
70
+ generateRecurringPaymentRecord: jest.fn(),
71
+ processRecurringPayment: jest.fn()
72
+ }))
69
73
 
70
74
  describe('transaction service', () => {
71
75
  beforeAll(() => {