@defra-fish/pocl-job 1.35.0-rc.8 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra-fish/pocl-job",
|
|
3
|
-
"version": "1.35.0
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Post Office Counter Licence sales processor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@defra-fish/business-rules-lib": "1.35.0
|
|
39
|
-
"@defra-fish/connectors-lib": "1.35.0
|
|
38
|
+
"@defra-fish/business-rules-lib": "1.35.0",
|
|
39
|
+
"@defra-fish/connectors-lib": "1.35.0",
|
|
40
40
|
"commander": "^7.2.0",
|
|
41
41
|
"debug": "^4.3.3",
|
|
42
42
|
"filesize": "^6.4.0",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"sax-stream": "^1.3.0",
|
|
47
47
|
"ssh2-sftp-client": "^6.0.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "be7f472adf337fc2e5b16431f2767defcab16d92"
|
|
50
50
|
}
|
|
@@ -14,7 +14,7 @@ Object {
|
|
|
14
14
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
15
15
|
"licensee": Object {
|
|
16
16
|
"birthDate": "1989-07-01",
|
|
17
|
-
"country": "GB",
|
|
17
|
+
"country": "GB-ENG",
|
|
18
18
|
"email": "daniel-ricc@example.couk",
|
|
19
19
|
"firstName": "Daniel",
|
|
20
20
|
"lastName": "Ricciardo",
|
|
@@ -55,7 +55,7 @@ Array [
|
|
|
55
55
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
56
56
|
"licensee": Object {
|
|
57
57
|
"birthDate": "1989-07-01",
|
|
58
|
-
"country": "GB",
|
|
58
|
+
"country": "GB-ENG",
|
|
59
59
|
"email": "daniel-ricc@example.couk",
|
|
60
60
|
"firstName": "Daniel",
|
|
61
61
|
"lastName": "Ricciardo",
|
|
@@ -123,7 +123,7 @@ Object {
|
|
|
123
123
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
124
124
|
"licensee": Object {
|
|
125
125
|
"birthDate": "1989-07-01",
|
|
126
|
-
"country": "GB",
|
|
126
|
+
"country": "GB-ENG",
|
|
127
127
|
"email": "daniel-ricc@example.couk",
|
|
128
128
|
"firstName": "Daniel",
|
|
129
129
|
"lastName": "Ricciardo",
|
|
@@ -164,7 +164,7 @@ Array [
|
|
|
164
164
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
165
165
|
"licensee": Object {
|
|
166
166
|
"birthDate": "1989-07-01",
|
|
167
|
-
"country": "GB",
|
|
167
|
+
"country": "GB-ENG",
|
|
168
168
|
"email": "daniel-ricc@example.couk",
|
|
169
169
|
"firstName": "Daniel",
|
|
170
170
|
"lastName": "Ricciardo",
|
|
@@ -216,7 +216,7 @@ Object {
|
|
|
216
216
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
217
217
|
"licensee": Object {
|
|
218
218
|
"birthDate": "1989-07-01",
|
|
219
|
-
"country": "GB",
|
|
219
|
+
"country": "GB-ENG",
|
|
220
220
|
"email": "daniel-ricc@example.co.uk",
|
|
221
221
|
"firstName": "Daniel",
|
|
222
222
|
"lastName": "Ricciardo",
|
|
@@ -273,7 +273,7 @@ Array [
|
|
|
273
273
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
274
274
|
"licensee": Object {
|
|
275
275
|
"birthDate": "1989-07-01",
|
|
276
|
-
"country": "GB",
|
|
276
|
+
"country": "GB-ENG",
|
|
277
277
|
"email": "daniel-ricc@example.co.uk",
|
|
278
278
|
"firstName": "Daniel",
|
|
279
279
|
"lastName": "Ricciardo",
|
|
@@ -328,7 +328,7 @@ Array [
|
|
|
328
328
|
"issueDate": "2020-01-01T14:00:00Z",
|
|
329
329
|
"licensee": Object {
|
|
330
330
|
"birthDate": "1989-07-01",
|
|
331
|
-
"country": "GB",
|
|
331
|
+
"country": "GB-ENG",
|
|
332
332
|
"email": "daniel-ricc@example.co.uk",
|
|
333
333
|
"firstName": "Daniel",
|
|
334
334
|
"lastName": "Ricciardo",
|
|
@@ -4,13 +4,13 @@ import { salesApi } from '@defra-fish/connectors-lib'
|
|
|
4
4
|
jest.mock('@defra-fish/connectors-lib', () => ({
|
|
5
5
|
salesApi: {
|
|
6
6
|
getPoclValidationErrorsForProcessing: jest.fn(),
|
|
7
|
-
createTransactions: jest.fn(),
|
|
7
|
+
createTransactions: jest.fn(() => [{ statusCode: 201, response: { id: 'test-response-id' } }]),
|
|
8
8
|
finaliseTransaction: jest.fn(),
|
|
9
9
|
updatePoclValidationError: jest.fn()
|
|
10
10
|
}
|
|
11
11
|
}))
|
|
12
12
|
|
|
13
|
-
const getPoclValidationError =
|
|
13
|
+
const getPoclValidationError = overrides => ({
|
|
14
14
|
id: 'test-pocl-validation-error-id',
|
|
15
15
|
dataSource: { label: 'Post Office Sales' },
|
|
16
16
|
serialNumber: '12345-ABCDE',
|
|
@@ -21,7 +21,7 @@ const getPoclValidationError = () => ({
|
|
|
21
21
|
street: 'Eastmead Lane',
|
|
22
22
|
town: 'Bristol',
|
|
23
23
|
postcode: 'BS9 1HJ',
|
|
24
|
-
country: 'GB',
|
|
24
|
+
country: { label: 'GB-ENG' },
|
|
25
25
|
birthDate: '1989-07-01',
|
|
26
26
|
email: 'daniel-ricc@example.co.uk',
|
|
27
27
|
mobilePhone: '07722 123456',
|
|
@@ -31,14 +31,17 @@ const getPoclValidationError = () => ({
|
|
|
31
31
|
postalFulfilment: true,
|
|
32
32
|
concessions: '[{"type":"Blue Badge","referenceNumber":"123456789"}]',
|
|
33
33
|
startDate: '2021-06-15',
|
|
34
|
+
startDateUnvalidated: '2021-06-15',
|
|
34
35
|
permitId: 'test-permit-id',
|
|
35
36
|
transactionDate: '2020-01-01T14:00:00Z',
|
|
36
37
|
amount: 30,
|
|
37
|
-
paymentSource: 'Post Office Sales',
|
|
38
|
+
paymentSource: { label: 'Post Office Sales' },
|
|
39
|
+
paymentSourceUnvalidated: 'Post Office Sales',
|
|
38
40
|
channelId: '948594',
|
|
39
41
|
methodOfPayment: { label: 'Cash' },
|
|
40
42
|
status: { label: 'Ready for Processing' },
|
|
41
|
-
transactionFile: 'test-pocl-file.xml'
|
|
43
|
+
transactionFile: 'test-pocl-file.xml',
|
|
44
|
+
...overrides
|
|
42
45
|
})
|
|
43
46
|
|
|
44
47
|
const getFinalisationError = (status, error) => ({
|
|
@@ -59,46 +62,118 @@ describe('pocl-validation-errors', () => {
|
|
|
59
62
|
})
|
|
60
63
|
|
|
61
64
|
describe('when no validation errors are returned from Sales Api', () => {
|
|
62
|
-
|
|
63
|
-
beforeEach(async () => {
|
|
65
|
+
beforeAll(() => {
|
|
64
66
|
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue(null)
|
|
65
|
-
result = await processPoclValidationErrors()
|
|
66
67
|
})
|
|
67
68
|
|
|
68
|
-
it('returns undefined', () => {
|
|
69
|
-
|
|
69
|
+
it('returns undefined', async () => {
|
|
70
|
+
const result = await processPoclValidationErrors()
|
|
71
|
+
expect(result).toBeUndefined()
|
|
70
72
|
})
|
|
71
73
|
|
|
72
|
-
it('does not attempt to create transactions', () => {
|
|
74
|
+
it('does not attempt to create transactions', async () => {
|
|
75
|
+
await processPoclValidationErrors()
|
|
73
76
|
expect(salesApi.createTransactions).not.toBeCalled()
|
|
74
77
|
})
|
|
75
78
|
|
|
76
|
-
it('does not finalise transactions which have failed', () => {
|
|
79
|
+
it('does not finalise transactions which have failed', async () => {
|
|
80
|
+
await processPoclValidationErrors()
|
|
77
81
|
expect(salesApi.finaliseTransaction).not.toBeCalled()
|
|
78
82
|
})
|
|
79
83
|
})
|
|
80
84
|
|
|
81
85
|
describe('processes successfully fixed validation errors', () => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
beforeAll(() => {
|
|
87
|
+
salesApi.getPoclValidationErrorsForProcessing.mockImplementation(() => [getPoclValidationError()])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('creates transaction in the Sales Api', async () => {
|
|
87
91
|
await processPoclValidationErrors()
|
|
92
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
93
|
+
expect(createTransactionPayload).toMatchSnapshot()
|
|
88
94
|
})
|
|
89
95
|
|
|
90
|
-
it('
|
|
91
|
-
const
|
|
92
|
-
|
|
96
|
+
it('uses startDate if this is provided', async () => {
|
|
97
|
+
const startDate = '2023-07-15'
|
|
98
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([getPoclValidationError({ startDate })])
|
|
99
|
+
await processPoclValidationErrors()
|
|
100
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
101
|
+
expect(createTransactionPayload.permissions[0].startDate).toBe(startDate)
|
|
93
102
|
})
|
|
94
103
|
|
|
95
|
-
it(
|
|
96
|
-
const
|
|
104
|
+
it("uses startDateUnvalidated if startDate isn't provided", async () => {
|
|
105
|
+
const startDateUnvalidated = '2023-07-15'
|
|
106
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([
|
|
107
|
+
getPoclValidationError({ startDateUnvalidated, startDate: undefined })
|
|
108
|
+
])
|
|
109
|
+
await processPoclValidationErrors()
|
|
110
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
111
|
+
expect(createTransactionPayload.permissions[0].startDate).toBe(startDateUnvalidated)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('uses country if this is provided', async () => {
|
|
115
|
+
const country = { label: 'GB-ENG' }
|
|
116
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([getPoclValidationError({ country })])
|
|
117
|
+
await processPoclValidationErrors()
|
|
118
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
119
|
+
expect(createTransactionPayload.permissions[0].licensee.country).toBe(country.label)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it("uses countryUnvalidated if country isn't provided", async () => {
|
|
123
|
+
const countryUnvalidated = 'England'
|
|
124
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([
|
|
125
|
+
getPoclValidationError({ countryUnvalidated, country: undefined })
|
|
126
|
+
])
|
|
127
|
+
await processPoclValidationErrors()
|
|
128
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
129
|
+
expect(createTransactionPayload.permissions[0].licensee.country).toBe(countryUnvalidated)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('uses paymentSource label if this is provided', async () => {
|
|
133
|
+
const paymentSource = { label: 'Post Office Sales' }
|
|
134
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([getPoclValidationError({ paymentSource })])
|
|
135
|
+
await processPoclValidationErrors()
|
|
136
|
+
const finaliseTransaction = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
137
|
+
expect(finaliseTransaction.payment.source).toBe(paymentSource.label)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("uses paymentSourceUnvalidated if paymentSource isn't provided", async () => {
|
|
141
|
+
const paymentSourceUnvalidated = 'Fire Sales'
|
|
142
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([
|
|
143
|
+
getPoclValidationError({ paymentSourceUnvalidated, paymentSource: undefined })
|
|
144
|
+
])
|
|
145
|
+
await processPoclValidationErrors()
|
|
146
|
+
const finaliseTransaction = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
147
|
+
expect(finaliseTransaction.payment.source).toBe(paymentSourceUnvalidated)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('populates the channelId with N/A if not provided', async () => {
|
|
151
|
+
const error = getPoclValidationError()
|
|
152
|
+
delete error.channelId
|
|
153
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([error])
|
|
154
|
+
await processPoclValidationErrors()
|
|
155
|
+
const finaliseTransaction = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
156
|
+
expect(finaliseTransaction.payment.channelId).toBe('N/A')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("omits transaction file from payload if one isn't provided", async () => {
|
|
160
|
+
const error = getPoclValidationError()
|
|
161
|
+
delete error.transactionFile
|
|
162
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([error])
|
|
163
|
+
await processPoclValidationErrors()
|
|
164
|
+
const finaliseTransaction = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
165
|
+
expect(finaliseTransaction.transactionFile).toBeUndefined()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('finalises the transaction in the Sales Api', async () => {
|
|
169
|
+
await processPoclValidationErrors()
|
|
170
|
+
const [record] = salesApi.finaliseTransaction.mock.calls
|
|
97
171
|
expect(record).toMatchSnapshot()
|
|
98
172
|
})
|
|
99
173
|
|
|
100
|
-
it('updates POCL validation error record in the Sales Api', () => {
|
|
101
|
-
|
|
174
|
+
it('updates POCL validation error record in the Sales Api', async () => {
|
|
175
|
+
await processPoclValidationErrors()
|
|
176
|
+
const [record] = salesApi.updatePoclValidationError.mock.calls
|
|
102
177
|
expect(record).toMatchSnapshot()
|
|
103
178
|
})
|
|
104
179
|
|
|
@@ -106,60 +181,197 @@ describe('pocl-validation-errors', () => {
|
|
|
106
181
|
salesApi.finaliseTransaction.mockRejectedValueOnce(getFinalisationError(410, 'Gone'))
|
|
107
182
|
await processPoclValidationErrors()
|
|
108
183
|
|
|
109
|
-
const record = salesApi.updatePoclValidationError.mock.calls
|
|
184
|
+
const [record] = salesApi.updatePoclValidationError.mock.calls
|
|
110
185
|
expect(record).toMatchSnapshot()
|
|
111
186
|
})
|
|
112
187
|
})
|
|
113
188
|
|
|
114
189
|
describe('processes failed fixes of validation errors', () => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
salesApi.createTransactions.mockResolvedValue([{ statusCode: 422, message: '"permissions[0].licensee.email" must be a valid email' }])
|
|
121
|
-
await processPoclValidationErrors()
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([getPoclValidationError({ email: 'daniel-ricc@example.couk' })])
|
|
192
|
+
salesApi.createTransactions.mockResolvedValueOnce([
|
|
193
|
+
{ statusCode: 422, message: '"permissions[0].licensee.email" must be a valid email' }
|
|
194
|
+
])
|
|
122
195
|
})
|
|
123
196
|
|
|
124
|
-
it('creates transaction in the Sales Api', () => {
|
|
125
|
-
|
|
126
|
-
|
|
197
|
+
it('creates transaction in the Sales Api', async () => {
|
|
198
|
+
await processPoclValidationErrors()
|
|
199
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
200
|
+
expect(createTransactionPayload).toMatchSnapshot()
|
|
127
201
|
})
|
|
128
202
|
|
|
129
|
-
it('does not finalise transactions which have failed', () => {
|
|
203
|
+
it('does not finalise transactions which have failed', async () => {
|
|
204
|
+
await processPoclValidationErrors()
|
|
130
205
|
expect(salesApi.finaliseTransaction).not.toBeCalled()
|
|
131
206
|
})
|
|
132
207
|
|
|
133
|
-
it('updates POCL validation error record in the Sales Api', () => {
|
|
134
|
-
|
|
208
|
+
it('updates POCL validation error record in the Sales Api', async () => {
|
|
209
|
+
await processPoclValidationErrors()
|
|
210
|
+
const [record] = salesApi.updatePoclValidationError.mock.calls
|
|
135
211
|
expect(record).toMatchSnapshot()
|
|
136
212
|
})
|
|
137
213
|
})
|
|
138
214
|
|
|
139
215
|
describe('processes records which failed during finalising', () => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
salesApi.createTransactions.mockResolvedValue([{ statusCode: 201, response: { id: 'test-response-id' } }])
|
|
216
|
+
beforeAll(() => {
|
|
217
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([getPoclValidationError({ email: 'daniel-ricc@example.couk' })])
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('creates transaction in the Sales Api', async () => {
|
|
146
221
|
salesApi.finaliseTransaction.mockRejectedValueOnce(getFinalisationError(500, 'Internal server error'))
|
|
147
222
|
await processPoclValidationErrors()
|
|
223
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
224
|
+
expect(createTransactionPayload).toMatchSnapshot()
|
|
148
225
|
})
|
|
149
226
|
|
|
150
|
-
it('
|
|
151
|
-
|
|
152
|
-
|
|
227
|
+
it('attempts to finalise transaction in the Sales Api', async () => {
|
|
228
|
+
salesApi.finaliseTransaction.mockRejectedValueOnce(getFinalisationError(500, 'Internal server error'))
|
|
229
|
+
await processPoclValidationErrors()
|
|
230
|
+
const [record] = salesApi.finaliseTransaction.mock.calls
|
|
231
|
+
expect(record).toMatchSnapshot()
|
|
153
232
|
})
|
|
154
233
|
|
|
155
|
-
it('
|
|
156
|
-
|
|
234
|
+
it('updates POCL validation error record in the Sales Api', async () => {
|
|
235
|
+
salesApi.finaliseTransaction.mockRejectedValueOnce(getFinalisationError(500, 'Internal server error'))
|
|
236
|
+
await processPoclValidationErrors()
|
|
237
|
+
const [record] = salesApi.updatePoclValidationError.mock.calls
|
|
157
238
|
expect(record).toMatchSnapshot()
|
|
158
239
|
})
|
|
240
|
+
})
|
|
159
241
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
242
|
+
describe('Create transaction payload datasource', () => {
|
|
243
|
+
it("is set to 'Postal Order Sales' when payment source is 'Postal Order'", async () => {
|
|
244
|
+
const poclValidationError = getPoclValidationError({ dataSource: null, paymentSource: { label: 'Postal Order' } })
|
|
245
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([poclValidationError])
|
|
246
|
+
await processPoclValidationErrors()
|
|
247
|
+
|
|
248
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
249
|
+
expect(createTransactionPayload.dataSource).toBe('Postal Order Sales')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it("is undefined when payment source is not 'Postal Order'", async () => {
|
|
253
|
+
const poclValidationError = getPoclValidationError({ dataSource: null, paymentSource: { label: 'Magic beans' } })
|
|
254
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([poclValidationError])
|
|
255
|
+
await processPoclValidationErrors()
|
|
256
|
+
|
|
257
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
258
|
+
expect(createTransactionPayload.dataSource).toBeUndefined()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('is undefined when payment source is null', async () => {
|
|
262
|
+
const poclValidationError = getPoclValidationError({ dataSource: null, paymentSource: null })
|
|
263
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([poclValidationError])
|
|
264
|
+
await processPoclValidationErrors()
|
|
265
|
+
|
|
266
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
267
|
+
expect(createTransactionPayload.dataSource).toBeUndefined()
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Create transaction payload serial number', () => {
|
|
272
|
+
it("is set to 'Postal Order Sales' when paymentSource is 'Postal Order'", async () => {
|
|
273
|
+
const poclValidationError = getPoclValidationError({ serialNumber: null, paymentSource: { label: 'Postal Order' } })
|
|
274
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([poclValidationError])
|
|
275
|
+
|
|
276
|
+
await processPoclValidationErrors()
|
|
277
|
+
|
|
278
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
279
|
+
expect(createTransactionPayload.serialNumber).toBe('Postal Order Sales')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it("is undefined if payment source is not 'Postal Order'", async () => {
|
|
283
|
+
const poclValidationError = getPoclValidationError({ serialNumber: null, paymentSource: { label: 'Magic Beans' } })
|
|
284
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValueOnce([poclValidationError])
|
|
285
|
+
|
|
286
|
+
await processPoclValidationErrors()
|
|
287
|
+
|
|
288
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
289
|
+
expect(createTransactionPayload.serialNumber).toBeUndefined()
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('is undefined if payment source is not set', async () => {
|
|
293
|
+
const poclValidationError = getPoclValidationError({ serialNumber: null, paymentSource: null })
|
|
294
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
295
|
+
|
|
296
|
+
await processPoclValidationErrors()
|
|
297
|
+
|
|
298
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
299
|
+
expect(createTransactionPayload.serialNumber).toBeUndefined()
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('Finalise transaction payload payment method', () => {
|
|
304
|
+
it("is 'Other' if payment source is 'Postal Order'", async () => {
|
|
305
|
+
const poclValidationError = getPoclValidationError({ methodOfPayment: null, paymentSource: { label: 'Postal Order' } })
|
|
306
|
+
poclValidationError.methodOfPayment = null
|
|
307
|
+
poclValidationError.paymentSource = { label: 'Postal Order' }
|
|
308
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
309
|
+
|
|
310
|
+
await processPoclValidationErrors()
|
|
311
|
+
|
|
312
|
+
const finaliseTransactionPayload = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
313
|
+
expect(finaliseTransactionPayload.payment.method).toBe('Other')
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it("is undefined if payment source is not 'Postal Order'", async () => {
|
|
317
|
+
const poclValidationError = getPoclValidationError({ methodOfPayment: null, paymentSource: { label: 'Magic Beans' } })
|
|
318
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
319
|
+
|
|
320
|
+
await processPoclValidationErrors()
|
|
321
|
+
|
|
322
|
+
const finaliseTransactionPayload = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
323
|
+
expect(finaliseTransactionPayload.payment.method).toBeUndefined()
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('is undefined if payment source is not set', async () => {
|
|
327
|
+
const poclValidationError = getPoclValidationError({ methodOfPayment: null, paymentSource: null })
|
|
328
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
329
|
+
|
|
330
|
+
await processPoclValidationErrors()
|
|
331
|
+
|
|
332
|
+
const finaliseTransactionPayload = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
333
|
+
expect(finaliseTransactionPayload.payment.method).toBeUndefined()
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
describe('when a date is not in ISO format', () => {
|
|
338
|
+
it('converts the issueDate to ISO format without milliseconds', async () => {
|
|
339
|
+
const poclValidationError = getPoclValidationError()
|
|
340
|
+
poclValidationError.transactionDate = '01/01/2023'
|
|
341
|
+
|
|
342
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
343
|
+
salesApi.createTransactions.mockResolvedValue([{ statusCode: 201, response: { id: 'test-response-id' } }])
|
|
344
|
+
await processPoclValidationErrors()
|
|
345
|
+
|
|
346
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
347
|
+
const expectedDate = '2023-01-01T00:00:00Z'
|
|
348
|
+
expect(createTransactionPayload.permissions[0].issueDate).toBe(expectedDate)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('converts the startDate to ISO format without milliseconds', async () => {
|
|
352
|
+
const poclValidationError = getPoclValidationError()
|
|
353
|
+
poclValidationError.startDate = '01/01/2023'
|
|
354
|
+
|
|
355
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
356
|
+
salesApi.createTransactions.mockResolvedValue([{ statusCode: 201, response: { id: 'test-response-id' } }])
|
|
357
|
+
await processPoclValidationErrors()
|
|
358
|
+
|
|
359
|
+
const [[[createTransactionPayload]]] = salesApi.createTransactions.mock.calls
|
|
360
|
+
const expectedDate = '2023-01-01T00:00:00Z'
|
|
361
|
+
expect(createTransactionPayload.permissions[0].startDate).toBe(expectedDate)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('converts the payment timestamp to ISO format without milliseconds', async () => {
|
|
365
|
+
const poclValidationError = getPoclValidationError()
|
|
366
|
+
poclValidationError.transactionDate = '01/01/2023'
|
|
367
|
+
|
|
368
|
+
salesApi.getPoclValidationErrorsForProcessing.mockResolvedValue([poclValidationError])
|
|
369
|
+
salesApi.createTransactions.mockResolvedValue([{ statusCode: 201, response: { id: 'test-response-id' } }])
|
|
370
|
+
await processPoclValidationErrors()
|
|
371
|
+
|
|
372
|
+
const finaliseTransactionPayload = salesApi.finaliseTransaction.mock.calls[0][1]
|
|
373
|
+
const expectedDate = '2023-01-01T00:00:00Z'
|
|
374
|
+
expect(finaliseTransactionPayload.payment.timestamp).toBe(expectedDate)
|
|
163
375
|
})
|
|
164
376
|
})
|
|
165
377
|
})
|
package/src/staging/constants.js
CHANGED
|
@@ -4,6 +4,9 @@ export const MAX_FINALISE_TRANSACTION_BATCH_SIZE = 10
|
|
|
4
4
|
export const POST_OFFICE_DATASOURCE = 'Post Office Sales'
|
|
5
5
|
export const DIRECT_DEBIT_DATASOURCE = 'DDE File'
|
|
6
6
|
export const DIRECT_DEBIT_PAYMENTSOURCE = 'Direct Debit'
|
|
7
|
+
export const POSTAL_ORDER_DATASOURCE = 'Postal Order Sales'
|
|
8
|
+
export const POSTAL_ORDER_PAYMENTSOURCE = 'Postal Order'
|
|
9
|
+
export const POSTAL_ORDER_PAYMENTMETHOD = 'Other'
|
|
7
10
|
|
|
8
11
|
export const FILE_STAGE = {
|
|
9
12
|
Pending: 'Received and Pending',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { salesApi } from '@defra-fish/connectors-lib'
|
|
2
|
+
import { POSTAL_ORDER_DATASOURCE, POSTAL_ORDER_PAYMENTSOURCE, POSTAL_ORDER_PAYMENTMETHOD } from './constants.js'
|
|
3
|
+
import moment from 'moment'
|
|
2
4
|
import db from 'debug'
|
|
3
5
|
const debug = db('pocl:validation-errors')
|
|
4
6
|
|
|
@@ -6,8 +8,8 @@ const mapRecords = records =>
|
|
|
6
8
|
records.map(record => ({
|
|
7
9
|
poclValidationErrorId: record.id,
|
|
8
10
|
createTransactionPayload: {
|
|
9
|
-
dataSource: record
|
|
10
|
-
serialNumber: record
|
|
11
|
+
dataSource: backfillDataSource(record),
|
|
12
|
+
serialNumber: backfillSerialNumber(record),
|
|
11
13
|
permissions: [
|
|
12
14
|
{
|
|
13
15
|
licensee: {
|
|
@@ -22,38 +24,88 @@ const mapRecords = records =>
|
|
|
22
24
|
locality: record.locality,
|
|
23
25
|
town: record.town,
|
|
24
26
|
postcode: record.postcode,
|
|
25
|
-
country: record
|
|
27
|
+
country: getCountryValue(record),
|
|
26
28
|
preferredMethodOfConfirmation: record.preferredMethodOfConfirmation.label,
|
|
27
29
|
preferredMethodOfNewsletter: record.preferredMethodOfNewsletter.label,
|
|
28
30
|
preferredMethodOfReminder: record.preferredMethodOfReminder.label,
|
|
29
31
|
postalFulfilment: record.postalFulfilment
|
|
30
32
|
},
|
|
31
|
-
issueDate: record.transactionDate,
|
|
32
|
-
startDate: record.startDate,
|
|
33
|
-
permitId: record.permitId,
|
|
33
|
+
issueDate: formatDateToShortenedISO(record.transactionDate),
|
|
34
|
+
startDate: formatDateToShortenedISO(record.startDate || record.startDateUnvalidated),
|
|
35
|
+
permitId: record.permitId.replace(/(^\{|\}$)/g, '').toLowerCase(),
|
|
34
36
|
...(record.concessions && { concessions: JSON.parse(record.concessions) })
|
|
35
37
|
}
|
|
36
38
|
]
|
|
37
39
|
},
|
|
38
40
|
finaliseTransactionPayload: {
|
|
39
|
-
transactionFile: record.transactionFile,
|
|
41
|
+
...(record.transactionFile ? { transactionFile: record.transactionFile } : {}),
|
|
40
42
|
payment: {
|
|
41
|
-
timestamp: record.transactionDate,
|
|
43
|
+
timestamp: formatDateToShortenedISO(record.transactionDate),
|
|
42
44
|
amount: record.amount,
|
|
43
|
-
source: record
|
|
44
|
-
channelId: record.channelId,
|
|
45
|
-
method: record.methodOfPayment.
|
|
45
|
+
source: getSourceValue(record),
|
|
46
|
+
channelId: record.channelId || 'N/A',
|
|
47
|
+
method: backfillPaymentMethod(record.methodOfPayment, record.paymentSource)
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
}))
|
|
49
51
|
|
|
52
|
+
const backfillDataSource = record => {
|
|
53
|
+
if (record.dataSource) {
|
|
54
|
+
return record.dataSource.label
|
|
55
|
+
} else if (record.paymentSource && record.paymentSource.label === POSTAL_ORDER_PAYMENTSOURCE) {
|
|
56
|
+
return POSTAL_ORDER_DATASOURCE
|
|
57
|
+
}
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const backfillSerialNumber = record => {
|
|
62
|
+
if (record.serialNumber) {
|
|
63
|
+
return record.serialNumber
|
|
64
|
+
} else if (record.paymentSource && record.paymentSource.label === POSTAL_ORDER_PAYMENTSOURCE) {
|
|
65
|
+
return POSTAL_ORDER_DATASOURCE
|
|
66
|
+
}
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const backfillPaymentMethod = (method, paymentSource) => {
|
|
71
|
+
if (method) {
|
|
72
|
+
return method.label
|
|
73
|
+
} else if (paymentSource && paymentSource.label === POSTAL_ORDER_PAYMENTSOURCE) {
|
|
74
|
+
return POSTAL_ORDER_PAYMENTMETHOD
|
|
75
|
+
}
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const getCountryValue = record => {
|
|
80
|
+
if (record.country && record.country.label) {
|
|
81
|
+
return record.country.label
|
|
82
|
+
}
|
|
83
|
+
return record.countryUnvalidated
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const getSourceValue = record => {
|
|
87
|
+
if (record.paymentSource && record.paymentSource.label) {
|
|
88
|
+
return record.paymentSource.label
|
|
89
|
+
}
|
|
90
|
+
return record.paymentSourceUnvalidated
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const formatDateToShortenedISO = date => {
|
|
94
|
+
const manuallyEnteredDateFormat = /[0-9]{2}\/[0-9]{2}\/[0-9]{4}/
|
|
95
|
+
|
|
96
|
+
if (date.match(manuallyEnteredDateFormat)) {
|
|
97
|
+
return moment(date, 'DD/MM/YYYY').toDate().toISOString().split('.')[0] + 'Z'
|
|
98
|
+
}
|
|
99
|
+
return date
|
|
100
|
+
}
|
|
101
|
+
|
|
50
102
|
/**
|
|
51
103
|
* Calls Sales Api to update Dynamics record
|
|
52
104
|
* @param {Array<Object>} failed
|
|
53
105
|
*/
|
|
54
106
|
const processFailed = async failed => {
|
|
55
107
|
for (const { record, result } of failed) {
|
|
56
|
-
debug('Failed when reprocessing record: %
|
|
108
|
+
debug('Failed when reprocessing record: %s', JSON.stringify(record))
|
|
57
109
|
await salesApi.updatePoclValidationError(record.poclValidationErrorId, { ...record, errorMessage: result.message })
|
|
58
110
|
}
|
|
59
111
|
}
|
|
@@ -78,26 +130,36 @@ const createTransactions = async records => {
|
|
|
78
130
|
return { succeeded, failed }
|
|
79
131
|
}
|
|
80
132
|
|
|
133
|
+
const finaliseTransaction = async rec => {
|
|
134
|
+
const payment = rec.record.finaliseTransactionPayload.payment
|
|
135
|
+
const finaliseTransactionPayload = {
|
|
136
|
+
payment: { method: backfillPaymentMethod(payment.method, payment.paymentSource) },
|
|
137
|
+
...rec.record.finaliseTransactionPayload
|
|
138
|
+
}
|
|
139
|
+
debug('finalising transaction: %o', finaliseTransactionPayload)
|
|
140
|
+
return salesApi.finaliseTransaction(rec.result.response.id, finaliseTransactionPayload)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const isFulfilled = result => (result?.status === 'fulfilled' || result?.status?.id === 'FINALISED') === true
|
|
144
|
+
|
|
81
145
|
const finaliseTransactions = async records => {
|
|
82
146
|
const { succeeded: created, failed } = records
|
|
83
|
-
const finalisationResults = await Promise.allSettled(
|
|
84
|
-
created.map(rec => salesApi.finaliseTransaction(rec.result.response.id, rec.record.finaliseTransactionPayload))
|
|
85
|
-
)
|
|
147
|
+
const finalisationResults = await Promise.allSettled(created.map(rec => finaliseTransaction(rec)))
|
|
86
148
|
|
|
87
149
|
const succeeded = []
|
|
88
150
|
created.forEach(({ record }, idx) => {
|
|
89
151
|
const result = finalisationResults[idx]
|
|
90
|
-
if (result
|
|
152
|
+
if (isFulfilled(result)) {
|
|
91
153
|
succeeded.push({ record, result: result.value })
|
|
92
|
-
} else if (result
|
|
154
|
+
} else if (result?.reason?.status === 410) {
|
|
93
155
|
/*
|
|
94
156
|
HTTP-410 errors indicate that the record has already been finalised. This can occur if the process is terminated while finalising records
|
|
95
157
|
(between the API call and the database update.) As the transaction has already been finalised, treat these as successful. The data for the
|
|
96
158
|
previously finalised record is returned under the data key of the error structure returned by the Sales API
|
|
97
159
|
*/
|
|
98
|
-
succeeded.push({ record, result: result
|
|
160
|
+
succeeded.push({ record, result: result?.reason?.body?.data })
|
|
99
161
|
} else {
|
|
100
|
-
failed.push({ record, result: result
|
|
162
|
+
failed.push({ record, result: result?.reason })
|
|
101
163
|
}
|
|
102
164
|
})
|
|
103
165
|
|