@defra-fish/sales-api-service 1.61.0-rc.16 → 1.61.0-rc.18

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.16",
3
+ "version": "1.61.0-rc.18",
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.16",
39
- "@defra-fish/connectors-lib": "1.61.0-rc.16",
40
- "@defra-fish/dynamics-lib": "1.61.0-rc.16",
38
+ "@defra-fish/business-rules-lib": "1.61.0-rc.18",
39
+ "@defra-fish/connectors-lib": "1.61.0-rc.18",
40
+ "@defra-fish/dynamics-lib": "1.61.0-rc.18",
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": "a4d24d55c8279ed8fad6436a48312f8542adfada"
55
+ "gitHead": "e6a140250c115a25e37da3f1639e83df2e6ca886"
56
56
  }
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  dueRecurringPaymentsRequestParamsSchema,
3
3
  dueRecurringPaymentsResponseSchema,
4
- processRPResultRequestParamsSchema,
5
- linkRecurringPaymentsRequestParamsSchema
4
+ processRPResultRequestParamsSchema
6
5
  } from '../recurring-payments.schema.js'
7
6
 
8
7
  jest.mock('../validators/validators.js', () => ({
@@ -63,11 +62,6 @@ const getProcessRPResultSampleData = () => ({
63
62
  createdDate: '2025-01-01T00:00:00.000Z'
64
63
  })
65
64
 
66
- const getLinkRecurringPaymentsSampleData = () => ({
67
- existingRecurringPaymentId: 'ghi123',
68
- agreementId: 'jkl456'
69
- })
70
-
71
65
  describe('getDueRecurringPaymentsSchema', () => {
72
66
  it('validates expected object', async () => {
73
67
  expect(() => dueRecurringPaymentsResponseSchema.validateAsync(getResponseSampleData())).not.toThrow()
@@ -155,24 +149,3 @@ describe('processRPResultRequestParamsSchema', () => {
155
149
  expect(() => processRPResultRequestParamsSchema.validateAsync(sampleData).rejects.toThrow())
156
150
  })
157
151
  })
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,8 +29,3 @@ 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,10 +1,6 @@
1
1
  import recurringPayments from '../recurring-payments.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'
2
+ import { getRecurringPayments, processRPResult } from '../../../services/recurring-payments.service.js'
3
+ import { dueRecurringPaymentsRequestParamsSchema, processRPResultRequestParamsSchema } from '../../../schema/recurring-payments.schema.js'
8
4
 
9
5
  const [
10
6
  {
@@ -12,22 +8,17 @@ const [
12
8
  },
13
9
  {
14
10
  options: { handler: prpHandler }
15
- },
16
- {
17
- options: { handler: lrpHandler }
18
11
  }
19
12
  ] = recurringPayments
20
13
 
21
14
  jest.mock('../../../services/recurring-payments.service.js', () => ({
22
15
  getRecurringPayments: jest.fn(),
23
- processRPResult: jest.fn(),
24
- linkRecurringPayments: jest.fn()
16
+ processRPResult: jest.fn()
25
17
  }))
26
18
 
27
19
  jest.mock('../../../schema/recurring-payments.schema.js', () => ({
28
20
  dueRecurringPaymentsRequestParamsSchema: jest.fn(),
29
- processRPResultRequestParamsSchema: jest.fn(),
30
- linkRecurringPaymentsRequestParamsSchema: jest.fn()
21
+ processRPResultRequestParamsSchema: jest.fn()
31
22
  }))
32
23
 
33
24
  const getMockRequest = ({
@@ -95,28 +86,4 @@ describe('recurring payments', () => {
95
86
  expect(recurringPayments[1].options.validate.params).toBe(processRPResultRequestParamsSchema)
96
87
  })
97
88
  })
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
- })
122
89
  })
@@ -1,10 +1,9 @@
1
1
  import {
2
2
  dueRecurringPaymentsRequestParamsSchema,
3
3
  dueRecurringPaymentsResponseSchema,
4
- linkRecurringPaymentsRequestParamsSchema,
5
4
  processRPResultRequestParamsSchema
6
5
  } from '../../schema/recurring-payments.schema.js'
7
- import { getRecurringPayments, linkRecurringPayments, processRPResult } from '../../services/recurring-payments.service.js'
6
+ import { getRecurringPayments, processRPResult } from '../../services/recurring-payments.service.js'
8
7
 
9
8
  const SWAGGER_TAGS = ['api', 'recurring-payments']
10
9
 
@@ -56,29 +55,5 @@ export default [
56
55
  }
57
56
  }
58
57
  }
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
- }
83
58
  }
84
59
  ]
@@ -1,10 +1,17 @@
1
- import { executeQuery, findDueRecurringPayments, findRecurringPaymentsByAgreementId, Permission } from '@defra-fish/dynamics-lib'
1
+ import {
2
+ dynamicsClient,
3
+ executeQuery,
4
+ findDueRecurringPayments,
5
+ findRecurringPaymentsByAgreementId,
6
+ Permission,
7
+ RecurringPayment
8
+ } from '@defra-fish/dynamics-lib'
2
9
  import {
3
10
  getRecurringPayments,
4
11
  processRecurringPayment,
5
12
  generateRecurringPaymentRecord,
6
13
  processRPResult,
7
- linkRecurringPayments
14
+ findNewestExistingRecurringPaymentInCrm
8
15
  } from '../recurring-payments.service.js'
9
16
  import { calculateEndDate, generatePermissionNumber } from '../permissions.service.js'
10
17
  import { getObfuscatedDob } from '../contacts.service.js'
@@ -20,9 +27,11 @@ const { docClient, sqs } = AWS.mock.results[0].value
20
27
  jest.mock('@defra-fish/dynamics-lib', () => ({
21
28
  ...jest.requireActual('@defra-fish/dynamics-lib'),
22
29
  executeQuery: jest.fn(),
23
- findById: jest.fn(),
24
30
  findDueRecurringPayments: jest.fn(),
25
- findRecurringPaymentsByAgreementId: jest.fn(() => ['foo'])
31
+ findRecurringPaymentsByAgreementId: jest.fn(() => ({ toRetrieveRequest: () => {} })),
32
+ dynamicsClient: {
33
+ retrieveMultipleRequest: jest.fn(() => ({ value: [] }))
34
+ }
26
35
  }))
27
36
 
28
37
  jest.mock('@defra-fish/connectors-lib', () => ({
@@ -80,8 +89,6 @@ jest.mock('@defra-fish/business-rules-lib', () => ({
80
89
  }
81
90
  }))
82
91
 
83
- const dynamicsLib = jest.requireMock('@defra-fish/dynamics-lib')
84
-
85
92
  const getMockRecurringPayment = (overrides = {}) => ({
86
93
  name: 'Test Name',
87
94
  nextDueDate: '2019-12-14T00:00:00Z',
@@ -168,6 +175,54 @@ const getMockTransaction = (id = 'test-id') => ({
168
175
  ]
169
176
  })
170
177
 
178
+ const getMockResponse = () => ({
179
+ '@odata.context': 'https://ea-fish-devsandbox.crm4.dynamics.com/api/data/v9.1/$metadata#defra_recurringpayments',
180
+ value: [
181
+ {
182
+ '@odata.etag': 'W/"183695502"',
183
+ defra_recurringpaymentid: 'ed8e5dc7-d346-f011-877a-6045bde009c2',
184
+ defra_name: null,
185
+ statecode: 1,
186
+ defra_nextduedate: '2025-06-11T00:00:00Z',
187
+ defra_cancelleddate: null,
188
+ defra_cancelledreason: null,
189
+ defra_enddate: '2025-06-20T22:59:59Z',
190
+ defra_agreementid: 's6i8q2lcrgqene2s8u1qeaiel6',
191
+ defra_publicid: 'JIwepr5/XgxwLHF9u20F1veSdWqeMJz/dzVfjPKrflM=',
192
+ defra_lastdigitscardnumbers: null
193
+ },
194
+ {
195
+ '@odata.etag': 'W/"184173292"',
196
+ statecode: 1,
197
+ defra_recurringpaymentid: 'f5011f95-ac47-f011-877a-6045bde009c2',
198
+ defra_publicid: '/KTtr4kFQvLwSP3Xz/xAxh9jWbGuzW17ALni4rmip4k=',
199
+ defra_cancelleddate: null,
200
+ defra_inceptionmonth: null,
201
+ defra_cancelledreason: null,
202
+ defra_nextduedate: '2026-06-10T00:00:00Z',
203
+ defra_last_digits_card_number: null,
204
+ defra_enddate: '2026-06-20T22:59:59Z',
205
+ defra_agreementid: 's6i8q2lcrgqene2s8u1qeaiel6',
206
+ defra_lastdigitscardnumbers: null,
207
+ defra_name: null
208
+ },
209
+ {
210
+ '@odata.etag': 'W/"184173352"',
211
+ statecode: 1,
212
+ defra_recurringpaymentid: '0d021f95-ac47-f011-877a-6045bde009c2',
213
+ defra_publicid: 'y3vgHMkqiMyp0vyb3rrS9KvqvU+kUl6P4b74X62SFOU=',
214
+ defra_cancelleddate: null,
215
+ defra_cancelledreason: null,
216
+ defra_nextduedate: '2024-06-10T00:00:00Z',
217
+ defra_last_digits_card_number: null,
218
+ defra_enddate: '2024-06-20T22:59:59Z',
219
+ defra_agreementid: 's6i8q2lcrgqene2s8u1qeaiel6',
220
+ defra_lastdigitscardnumbers: null,
221
+ defra_name: null
222
+ }
223
+ ]
224
+ })
225
+
171
226
  describe('recurring payments service', () => {
172
227
  const createSimpleSampleTransactionRecord = () => ({ payment: { recurring: true }, permissions: [{}] })
173
228
  const createSamplePermission = overrides => {
@@ -197,7 +252,7 @@ describe('recurring payments service', () => {
197
252
  const mockContact = mockRecurringPayments[0].expanded.contact
198
253
  const mockPermission = mockRecurringPayments[0].expanded.activePermission
199
254
 
200
- dynamicsLib.executeQuery.mockResolvedValueOnce(mockRecurringPayments)
255
+ executeQuery.mockResolvedValueOnce(mockRecurringPayments)
201
256
 
202
257
  const result = await getRecurringPayments(new Date())
203
258
 
@@ -207,11 +262,11 @@ describe('recurring payments service', () => {
207
262
  it('executeQuery is called with findDueRecurringPayments with a date', async () => {
208
263
  const mockRecurringPayments = [getMockRecurringPayment()]
209
264
  const mockDate = new Date()
210
- dynamicsLib.executeQuery.mockResolvedValueOnce(mockRecurringPayments)
265
+ executeQuery.mockResolvedValueOnce(mockRecurringPayments)
211
266
 
212
267
  await getRecurringPayments(mockDate)
213
268
 
214
- expect(dynamicsLib.executeQuery).toHaveBeenCalledWith(findDueRecurringPayments(mockDate))
269
+ expect(executeQuery).toHaveBeenCalledWith(findDueRecurringPayments(mockDate))
215
270
  })
216
271
  })
217
272
 
@@ -616,42 +671,66 @@ describe('recurring payments service', () => {
616
671
  })
617
672
  })
618
673
 
619
- describe('linkRecurringPayments', () => {
620
- it('should call executeQuery with findRecurringPaymentsByAgreementId and the provided agreementId', async () => {
621
- const agreementId = 'abc123'
622
- await linkRecurringPayments('existing-recurring-payment-id', agreementId)
623
- expect(executeQuery).toHaveBeenCalledWith(findRecurringPaymentsByAgreementId(agreementId))
674
+ describe('findNewestExistingRecurringPaymentInCrm', () => {
675
+ it('passes agreement id to findRecurringPaymentsByAgreementId', async () => {
676
+ const agreementId = Symbol('agreement-id')
677
+ await findNewestExistingRecurringPaymentInCrm(agreementId)
678
+ expect(findRecurringPaymentsByAgreementId).toHaveBeenCalledWith(agreementId)
624
679
  })
625
680
 
626
- it('should log a RecurringPayment record when there is one match', async () => {
627
- const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
628
- const onlyRecord = { entity: getMockRecurringPayment() }
629
- dynamicsLib.executeQuery.mockReturnValueOnce([onlyRecord])
630
-
631
- await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
632
-
633
- expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', onlyRecord.id)
681
+ it('passes query created by findRecurringPaymentsByAgreementId to retrieveMultipleRequest', async () => {
682
+ const retrieveRequest = Symbol('retrieve request')
683
+ findRecurringPaymentsByAgreementId.mockReturnValueOnce({ toRetrieveRequest: () => retrieveRequest })
684
+ await findNewestExistingRecurringPaymentInCrm()
685
+ expect(dynamicsClient.retrieveMultipleRequest).toHaveBeenCalledWith(retrieveRequest)
634
686
  })
635
687
 
636
- it('should log the RecurringPayment record with the latest endDate when there are multiple matches', async () => {
637
- const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
638
- const oldestRecord = { entity: getMockRecurringPayment({ id: Symbol('oldest'), endDate: new Date('2021-12-15T00:00:00Z') }) }
639
- const newestRecord = { entity: getMockRecurringPayment({ id: Symbol('newest'), endDate: new Date('2023-12-15T00:00:00Z') }) }
640
- const middlestRecord = { entity: getMockRecurringPayment({ id: Symbol('middlest'), endDate: new Date('2022-12-15T00:00:00Z') }) }
641
- dynamicsLib.executeQuery.mockReturnValueOnce([oldestRecord, newestRecord, middlestRecord])
642
-
643
- await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
644
-
645
- expect(consoleLogSpy).toHaveBeenCalledWith('newRecurringPaymentId: ', newestRecord.id)
688
+ it('returns a Recurring Payment (not a plain object)', async () => {
689
+ jest.spyOn(RecurringPayment, 'fromResponse')
690
+ dynamicsClient.retrieveMultipleRequest.mockReturnValueOnce(getMockResponse())
691
+ const rcp = await findNewestExistingRecurringPaymentInCrm()
692
+ expect(RecurringPayment.fromResponse.mock.results[0].value).toBe(rcp)
646
693
  })
647
694
 
648
- it('should log no matches when there are no matches', async () => {
649
- const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
650
- dynamicsLib.executeQuery.mockReturnValueOnce([])
651
-
652
- await linkRecurringPayments('existing-recurring-payment-id', 'agreement-id')
695
+ it.each([
696
+ [
697
+ 'dataset of three with midpoint most recent',
698
+ [
699
+ { defra_recurringpaymentid: '1', defra_enddate: '2024-05-31T00:37:24.123' },
700
+ { defra_recurringpaymentid: '2', defra_enddate: '2025-05-31T00:37:24.456' },
701
+ { defra_recurringpaymentid: '3', defra_enddate: '2025-05-31T00:37:24.123' }
702
+ ],
703
+ '2'
704
+ ],
705
+ [
706
+ 'dataset of five with penultimate most recent',
707
+ [
708
+ { defra_recurringpaymentid: '111-aaa', defra_enddate: '2024-05-31T00:37:24.123' },
709
+ { defra_recurringpaymentid: '222-ddd', defra_enddate: '2022-05-31T00:37:24.456' },
710
+ { defra_recurringpaymentid: '304-jkj', defra_enddate: '2021-05-31T00:37:24.123' },
711
+ { defra_recurringpaymentid: 'abc-123', defra_enddate: '2025-05-31T00:37:24.123' },
712
+ { defra_recurringpaymentid: '908-oid', defra_enddate: '1876-05-30T00:37:24.123' }
713
+ ],
714
+ 'abc-123'
715
+ ],
716
+ [
717
+ 'dataset of two with last most recent',
718
+ [
719
+ { defra_recurringpaymentid: 'abc-123', defra_enddate: '1876-05-30T00:37:24.123' },
720
+ { defra_recurringpaymentid: '908-oid', defra_enddate: '2025-05-31T00:37:24.123' }
721
+ ],
722
+ '908-oid'
723
+ ]
724
+ ])('returns most recent existing recurring payment from %s', async (_desc, mockResponseData, expectedId) => {
725
+ dynamicsClient.retrieveMultipleRequest.mockReturnValueOnce({ value: mockResponseData })
726
+ const rcp = await findNewestExistingRecurringPaymentInCrm()
727
+ expect(rcp.id).toBe(expectedId)
728
+ })
653
729
 
654
- expect(consoleLogSpy).toHaveBeenCalledWith('No matches found')
730
+ it('returns boolean false if no recurring payments found', async () => {
731
+ dynamicsClient.retrieveMultipleRequest.mockReturnValueOnce({ value: [] })
732
+ const rcp = await findNewestExistingRecurringPaymentInCrm()
733
+ expect(rcp).toBeFalsy()
655
734
  })
656
735
  })
657
736
  })
@@ -1,4 +1,10 @@
1
- import { executeQuery, findDueRecurringPayments, findRecurringPaymentsByAgreementId, RecurringPayment } from '@defra-fish/dynamics-lib'
1
+ import {
2
+ executeQuery,
3
+ findDueRecurringPayments,
4
+ findRecurringPaymentsByAgreementId,
5
+ RecurringPayment,
6
+ dynamicsClient
7
+ } from '@defra-fish/dynamics-lib'
2
8
  import { calculateEndDate, generatePermissionNumber } from './permissions.service.js'
3
9
  import { getObfuscatedDob } from './contacts.service.js'
4
10
  import { createHash } from 'node:crypto'
@@ -123,22 +129,12 @@ export const processRPResult = async (transactionId, paymentId, createdDate) =>
123
129
  return { permission }
124
130
  }
125
131
 
126
- export const linkRecurringPayments = async (existingRecurringPaymentId, agreementId) => {
127
- console.log('existingRecurringPaymentId: ', existingRecurringPaymentId)
128
- console.log('agreementId: ', agreementId)
129
- const newRecurringPayment = await findNewestExistingRecurringPaymentInCrm(agreementId)
130
- if (newRecurringPayment) {
131
- console.log('newRecurringPaymentId: ', newRecurringPayment.id)
132
- } else {
133
- console.log('No matches found')
134
- }
135
- }
136
-
137
- const findNewestExistingRecurringPaymentInCrm = async agreementId => {
138
- const matchingRecords = await executeQuery(findRecurringPaymentsByAgreementId(agreementId))
139
- if (matchingRecords?.length) {
140
- return [...matchingRecords].sort((a, b) => a.entity.endDate - b.entity.endDate).pop()
141
- } else {
142
- return false
132
+ export const findNewestExistingRecurringPaymentInCrm = async agreementId => {
133
+ const query = findRecurringPaymentsByAgreementId(agreementId)
134
+ const response = await dynamicsClient.retrieveMultipleRequest(query.toRetrieveRequest())
135
+ if (response.value.length) {
136
+ const [rcpResponseData] = response.value.sort((a, b) => Date.parse(b.defra_enddate) - Date.parse(a.defra_enddate))
137
+ return RecurringPayment.fromResponse(rcpResponseData)
143
138
  }
139
+ return false
144
140
  }
@@ -1,14 +1,11 @@
1
1
  import { processQueue, getTransactionJournalRefNumber } from '../process-transaction-queue.js'
2
2
  import {
3
3
  persist,
4
- findById,
5
4
  ConcessionProof,
6
5
  Contact,
7
6
  FulfilmentRequest,
8
7
  Permission,
9
- PoclFile,
10
8
  RecurringPayment,
11
- RecurringPaymentInstruction,
12
9
  Transaction,
13
10
  TransactionJournal
14
11
  } from '@defra-fish/dynamics-lib'
@@ -25,7 +22,11 @@ import {
25
22
  import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '../../../config.js'
26
23
  import { POCL_DATA_SOURCE, DDE_DATA_SOURCE } from '@defra-fish/business-rules-lib'
27
24
  import moment from 'moment'
28
- import { processRecurringPayment, generateRecurringPaymentRecord } from '../../recurring-payments.service.js'
25
+ import {
26
+ processRecurringPayment,
27
+ generateRecurringPaymentRecord,
28
+ findNewestExistingRecurringPaymentInCrm
29
+ } from '../../recurring-payments.service.js'
29
30
  import { AWS } from '@defra-fish/connectors-lib'
30
31
  const { docClient } = AWS.mock.results[0].value
31
32
 
@@ -50,8 +51,7 @@ jest.mock('../../reference-data.service.js', () => ({
50
51
 
51
52
  jest.mock('@defra-fish/dynamics-lib', () => ({
52
53
  ...jest.requireActual('@defra-fish/dynamics-lib'),
53
- persist: jest.fn(),
54
- findById: jest.fn()
54
+ persist: jest.fn()
55
55
  }))
56
56
 
57
57
  jest.mock('../../contacts.service.js', () => ({
@@ -169,7 +169,36 @@ describe('transaction service', () => {
169
169
  expect.any(Contact),
170
170
  expect.any(Permission),
171
171
  expect.any(RecurringPayment),
172
- expect.any(RecurringPaymentInstruction),
172
+ expect.any(ConcessionProof)
173
+ ]
174
+ ],
175
+ [
176
+ 'licences with a previous recurring payment linked to new recurring payment',
177
+ () => {
178
+ processRecurringPayment.mockResolvedValueOnce({ recurringPayment: new RecurringPayment() })
179
+ findNewestExistingRecurringPaymentInCrm.mockResolvedValueOnce(new RecurringPayment())
180
+ const mockRecord = mockFinalisedTransactionRecord()
181
+ mockRecord.payment.recurring = {
182
+ name: 'Test name',
183
+ nextDueDate: new Date('2020/01/11'),
184
+ endDate: new Date('2022/01/16'),
185
+ agreementId: '123446jjng',
186
+ publicId: 'sdf-123',
187
+ status: 1,
188
+ activePermission: mockRecord.permissions[0],
189
+ contact: Object.assign(mockContactPayload(), { firstName: 'Esther' })
190
+ }
191
+ mockRecord.permissions[0].permitId = MOCK_12MONTH_SENIOR_PERMIT.id
192
+ return mockRecord
193
+ },
194
+ [
195
+ expect.any(Transaction),
196
+ expect.any(TransactionJournal),
197
+ expect.any(TransactionJournal),
198
+ expect.any(Contact),
199
+ expect.any(Permission),
200
+ expect.any(RecurringPayment),
201
+ expect.any(RecurringPayment),
173
202
  expect.any(ConcessionProof)
174
203
  ]
175
204
  ]
@@ -338,15 +367,13 @@ describe('transaction service', () => {
338
367
  expect(persistMockFirstAgument[0][4].isLicenceForYou).toBeUndefined()
339
368
  })
340
369
 
341
- it('handles requests which relate to an transaction file', async () => {
370
+ it('handles requests which relate to a transaction file', async () => {
342
371
  const transactionFilename = 'test-file.xml'
343
372
  const mockRecord = mockFinalisedTransactionRecord()
344
373
  mockRecord.transactionFile = transactionFilename
345
374
  docClient.get.mockResolvedValueOnce({ Item: mockRecord })
346
375
  const transactionToFileBindingSpy = jest.spyOn(Transaction.prototype, 'bindToAlternateKey')
347
376
  const permissionToFileBindingSpy = jest.spyOn(Permission.prototype, 'bindToAlternateKey')
348
- const testPoclFileEntity = new PoclFile()
349
- findById.mockResolvedValueOnce(testPoclFileEntity)
350
377
  await processQueue({ id: mockRecord.id })
351
378
  expect(transactionToFileBindingSpy).toHaveBeenCalledWith(Transaction.definition.relationships.poclFile, transactionFilename)
352
379
  expect(permissionToFileBindingSpy).toHaveBeenCalledWith(Permission.definition.relationships.poclFile, transactionFilename)
@@ -428,6 +455,22 @@ describe('transaction service', () => {
428
455
  await processQueue({ id: finalisedTransaction.id })
429
456
  expect(processRecurringPayment).toHaveBeenCalledWith(rprSymbol, expect.any(Contact))
430
457
  })
458
+
459
+ it('binds existing recurring payment to new recurring payment', async () => {
460
+ const mockExistingRecurringPayment = {
461
+ bindToEntity: jest.fn()
462
+ }
463
+ const mockNewRecurringPayment = new RecurringPayment()
464
+ const finalisedTransaction = mockFinalisedTransactionRecord()
465
+ findNewestExistingRecurringPaymentInCrm.mockReturnValueOnce(mockExistingRecurringPayment)
466
+ processRecurringPayment.mockReturnValueOnce({ recurringPayment: mockNewRecurringPayment })
467
+ docClient.get.mockResolvedValueOnce({ Item: finalisedTransaction })
468
+ await processQueue({ id: finalisedTransaction.id })
469
+ expect(mockExistingRecurringPayment.bindToEntity).toHaveBeenCalledWith(
470
+ RecurringPayment.definition.relationships.nextRecurringPayment,
471
+ mockNewRecurringPayment
472
+ )
473
+ })
431
474
  })
432
475
  })
433
476
 
@@ -8,11 +8,15 @@ import {
8
8
  Transaction,
9
9
  TransactionCurrency,
10
10
  TransactionJournal,
11
- RecurringPaymentInstruction
11
+ RecurringPayment
12
12
  } from '@defra-fish/dynamics-lib'
13
13
  import { DDE_DATA_SOURCE, FULFILMENT_SWITCHOVER_DATE, POCL_TRANSACTION_SOURCES } from '@defra-fish/business-rules-lib'
14
14
  import { getReferenceDataForEntityAndId, getGlobalOptionSetValue, getReferenceDataForEntity } from '../reference-data.service.js'
15
- import { generateRecurringPaymentRecord, processRecurringPayment } from '../recurring-payments.service.js'
15
+ import {
16
+ generateRecurringPaymentRecord,
17
+ processRecurringPayment,
18
+ findNewestExistingRecurringPaymentInCrm
19
+ } from '../recurring-payments.service.js'
16
20
  import { resolveContactPayload } from '../contacts.service.js'
17
21
  import { retrieveStagedTransaction } from './retrieve-transaction.js'
18
22
  import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '../../config.js'
@@ -77,11 +81,11 @@ export async function processQueue ({ id }) {
77
81
 
78
82
  if (recurringPayment && permit.isRecurringPaymentSupported) {
79
83
  entities.push(recurringPayment)
80
- const paymentInstruction = new RecurringPaymentInstruction()
81
- paymentInstruction.bindToEntity(RecurringPaymentInstruction.definition.relationships.licensee, contact)
82
- paymentInstruction.bindToEntity(RecurringPaymentInstruction.definition.relationships.permit, permit)
83
- paymentInstruction.bindToEntity(RecurringPaymentInstruction.definition.relationships.recurringPayment, recurringPayment)
84
- entities.push(paymentInstruction)
84
+ const existingRP = await findNewestExistingRecurringPaymentInCrm(recurringPayment.agreementId)
85
+ if (existingRP) {
86
+ existingRP.bindToEntity(RecurringPayment.definition.relationships.nextRecurringPayment, recurringPayment)
87
+ entities.push(existingRP)
88
+ }
85
89
  }
86
90
 
87
91
  for (const concession of concessions || []) {