@pinelab/vendure-plugin-qls-fulfillment 1.0.1 → 1.1.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/CHANGELOG.md +7 -1
- package/README.md +20 -10
- package/dist/lib/client-types.d.ts +3 -1
- package/dist/services/qls-order-failed-event.d.ts +35 -0
- package/dist/services/qls-order-failed-event.js +44 -0
- package/dist/services/qls-order-failed-event.spec.d.ts +1 -0
- package/dist/services/qls-order-failed-event.spec.js +79 -0
- package/dist/services/qls-order.service.js +13 -3
- package/dist/types.d.ts +3 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
# 1.
|
|
1
|
+
# 1.1.0 (2026-01-07)
|
|
2
|
+
|
|
3
|
+
- Fix delivery options to be an array of objects with a tag property
|
|
4
|
+
- Emit event for failed order push
|
|
5
|
+
- Delay 10s before setting custom field `syncedToQls` to prevent race conditions.
|
|
6
|
+
|
|
7
|
+
# 1.0.0 (2025-11-07)
|
|
2
8
|
|
|
3
9
|
- Initial release
|
package/README.md
CHANGED
|
@@ -78,20 +78,30 @@ mutation {
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
## Monitoring
|
|
81
|
+
## Monitoring failed orders
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Whenever an order fails to be pushed to QLS, an event is emitted. You can listen to this event to monitor failed orders.
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
```ts
|
|
86
|
+
this.eventBus.ofType(QlsOrderFailedEvent).subscribe((event) => {
|
|
87
|
+
console.log('Order failed to be pushed to QLS:', event.order.code);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
Because a job can be retried, this event can be emitted multiple times for the same order. You can use the date field together with the order code to determine if you have already processed this event.
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
```ts
|
|
94
|
+
this.eventBus.ofType(QlsOrderFailedEvent).subscribe((event) => {
|
|
95
|
+
// Determine if we have already processed this event.
|
|
96
|
+
const uniqueEventId = `${event.order.code}_${
|
|
97
|
+
event.failedAt.toISOString().split('T')[0]
|
|
98
|
+
}`; // "JHD82JS8868_2026-01-01"
|
|
99
|
+
console.log('Order failed to be pushed to QLS:', event.order.code);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
## Manually pushing orders to QLS
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
This plugin adds a button `push to QLS` to the order detail page in the Admin UI. This will push the order to QLS again. If the order has been pushed before, you need to uncheck the checkbox `synced to QLS` in the order custom fields first.
|
|
96
106
|
|
|
97
|
-
|
|
107
|
+
Pushing an order to QLS again will not cancel the existing order in QLS. Cancel existing orders first via https://mijn.pakketdienstqls.nl/.
|
|
@@ -79,7 +79,9 @@ export interface FulfillmentOrderInput {
|
|
|
79
79
|
receiver_contact: FulfillmentOrderReceiverContactInput;
|
|
80
80
|
custom_values?: CustomValue[];
|
|
81
81
|
products: FulfillmentOrderLineInput[];
|
|
82
|
-
delivery_options:
|
|
82
|
+
delivery_options: {
|
|
83
|
+
tag: string;
|
|
84
|
+
}[];
|
|
83
85
|
}
|
|
84
86
|
export interface FulfillmentOrderReceiverContactInput {
|
|
85
87
|
name: string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Order, RequestContext, VendureEvent } from '@vendure/core';
|
|
2
|
+
export declare enum QLSOrderError {
|
|
3
|
+
INCORRECT_POSTAL_CODE = "INCORRECT_POSTAL_CODE",
|
|
4
|
+
INCORRECT_HOUSE_NUMBER = "INCORRECT_HOUSE_NUMBER",
|
|
5
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* This event is emitted when an order fails to be pushed to QLS.
|
|
9
|
+
*/
|
|
10
|
+
export declare class QlsOrderFailedEvent extends VendureEvent {
|
|
11
|
+
ctx: RequestContext;
|
|
12
|
+
order: Order;
|
|
13
|
+
/**
|
|
14
|
+
* The date and time the order failed to be pushed to QLS.
|
|
15
|
+
*/
|
|
16
|
+
failedAt: Date;
|
|
17
|
+
/**
|
|
18
|
+
* The full error response JSON from QLS.
|
|
19
|
+
*/
|
|
20
|
+
fullError: unknown;
|
|
21
|
+
/**
|
|
22
|
+
* The error code that caused the order to fail to be pushed to QLS.
|
|
23
|
+
*/
|
|
24
|
+
errorCode: QLSOrderError;
|
|
25
|
+
constructor(ctx: RequestContext, order: Order,
|
|
26
|
+
/**
|
|
27
|
+
* The date and time the order failed to be pushed to QLS.
|
|
28
|
+
*/
|
|
29
|
+
failedAt: Date,
|
|
30
|
+
/**
|
|
31
|
+
* The full error response JSON from QLS.
|
|
32
|
+
*/
|
|
33
|
+
fullError: unknown);
|
|
34
|
+
}
|
|
35
|
+
export declare function getQLSErrorCode(errorResponse: unknown): QLSOrderError;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QlsOrderFailedEvent = exports.QLSOrderError = void 0;
|
|
4
|
+
exports.getQLSErrorCode = getQLSErrorCode;
|
|
5
|
+
const core_1 = require("@vendure/core");
|
|
6
|
+
var QLSOrderError;
|
|
7
|
+
(function (QLSOrderError) {
|
|
8
|
+
QLSOrderError["INCORRECT_POSTAL_CODE"] = "INCORRECT_POSTAL_CODE";
|
|
9
|
+
QLSOrderError["INCORRECT_HOUSE_NUMBER"] = "INCORRECT_HOUSE_NUMBER";
|
|
10
|
+
QLSOrderError["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
11
|
+
})(QLSOrderError || (exports.QLSOrderError = QLSOrderError = {}));
|
|
12
|
+
/**
|
|
13
|
+
* This event is emitted when an order fails to be pushed to QLS.
|
|
14
|
+
*/
|
|
15
|
+
class QlsOrderFailedEvent extends core_1.VendureEvent {
|
|
16
|
+
constructor(ctx, order,
|
|
17
|
+
/**
|
|
18
|
+
* The date and time the order failed to be pushed to QLS.
|
|
19
|
+
*/
|
|
20
|
+
failedAt,
|
|
21
|
+
/**
|
|
22
|
+
* The full error response JSON from QLS.
|
|
23
|
+
*/
|
|
24
|
+
fullError) {
|
|
25
|
+
super();
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.order = order;
|
|
28
|
+
this.failedAt = failedAt;
|
|
29
|
+
this.fullError = fullError;
|
|
30
|
+
this.errorCode = getQLSErrorCode(fullError);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.QlsOrderFailedEvent = QlsOrderFailedEvent;
|
|
34
|
+
function getQLSErrorCode(errorResponse) {
|
|
35
|
+
const errorString = JSON.stringify(errorResponse);
|
|
36
|
+
if (errorString.includes('containsNumber')) {
|
|
37
|
+
return QLSOrderError.INCORRECT_HOUSE_NUMBER;
|
|
38
|
+
}
|
|
39
|
+
if (errorString.includes('validPostalCode') ||
|
|
40
|
+
errorString.includes('needPostalCodeVerification')) {
|
|
41
|
+
return QLSOrderError.INCORRECT_POSTAL_CODE;
|
|
42
|
+
}
|
|
43
|
+
return QLSOrderError.UNKNOWN_ERROR;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const qls_order_failed_event_1 = require("./qls-order-failed-event");
|
|
5
|
+
(0, vitest_1.describe)('QlsOrderFailedEvent', () => {
|
|
6
|
+
const mockCtx = {};
|
|
7
|
+
const mockOrder = { id: 1, code: 'ORDER-001' };
|
|
8
|
+
const mockFailedAt = new Date('2026-01-03T04:34:52.473Z');
|
|
9
|
+
(0, vitest_1.it)('sets errorCode to INCORRECT_HOUSE_NUMBER for containsNumber error', () => {
|
|
10
|
+
const fullError = {
|
|
11
|
+
errors: {
|
|
12
|
+
receiver_contact: {
|
|
13
|
+
housenumber: { containsNumber: 'Huisnummer bevat geen nummer' },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
pagination: null,
|
|
17
|
+
};
|
|
18
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
19
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.INCORRECT_HOUSE_NUMBER);
|
|
20
|
+
(0, vitest_1.expect)(event.ctx).toBe(mockCtx);
|
|
21
|
+
(0, vitest_1.expect)(event.order).toBe(mockOrder);
|
|
22
|
+
(0, vitest_1.expect)(event.failedAt).toBe(mockFailedAt);
|
|
23
|
+
(0, vitest_1.expect)(event.fullError).toBe(fullError);
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.it)('sets errorCode to INCORRECT_POSTAL_CODE for validPostalCode error', () => {
|
|
26
|
+
const fullError = {
|
|
27
|
+
errors: {
|
|
28
|
+
receiver_contact: {
|
|
29
|
+
postalcode: { validPostalCode: 'Ongeldige indeling (NNNN)' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
pagination: null,
|
|
33
|
+
};
|
|
34
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
35
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.INCORRECT_POSTAL_CODE);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)('sets errorCode to INCORRECT_POSTAL_CODE for needPostalCodeVerification error', () => {
|
|
38
|
+
const fullError = {
|
|
39
|
+
errors: {
|
|
40
|
+
receiver_contact: {
|
|
41
|
+
postalcode: { needPostalCodeVerification: 'Ongeldige postcode' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
pagination: null,
|
|
45
|
+
};
|
|
46
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
47
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.INCORRECT_POSTAL_CODE);
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)('sets errorCode to INCORRECT_POSTAL_CODE for combined postal code errors', () => {
|
|
50
|
+
const fullError = {
|
|
51
|
+
errors: {
|
|
52
|
+
receiver_contact: {
|
|
53
|
+
postalcode: {
|
|
54
|
+
needPostalCodeVerification: 'Ongeldige postcode',
|
|
55
|
+
validPostalCode: 'Ongeldige indeling (NNNN LL)',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
pagination: null,
|
|
60
|
+
};
|
|
61
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
62
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.INCORRECT_POSTAL_CODE);
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)('sets errorCode to UNKNOWN_ERROR for 502 Bad Gateway HTML response', () => {
|
|
65
|
+
const fullError = {
|
|
66
|
+
html: '<html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> <hr><center>nginx</center> </body> </html>',
|
|
67
|
+
};
|
|
68
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
69
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.UNKNOWN_ERROR);
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('sets errorCode to UNKNOWN_ERROR for unknown error format', () => {
|
|
72
|
+
const fullError = {
|
|
73
|
+
meta: { code: 500 },
|
|
74
|
+
errors: { unknown: 'Some other error' },
|
|
75
|
+
};
|
|
76
|
+
const event = new qls_order_failed_event_1.QlsOrderFailedEvent(mockCtx, mockOrder, mockFailedAt, fullError);
|
|
77
|
+
(0, vitest_1.expect)(event.errorCode).toBe(qls_order_failed_event_1.QLSOrderError.UNKNOWN_ERROR);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -23,6 +23,7 @@ const catch_unknown_1 = require("catch-unknown");
|
|
|
23
23
|
const util_1 = __importDefault(require("util"));
|
|
24
24
|
const constants_1 = require("../constants");
|
|
25
25
|
const qls_client_1 = require("../lib/qls-client");
|
|
26
|
+
const qls_order_failed_event_1 = require("./qls-order-failed-event");
|
|
26
27
|
let QlsOrderService = class QlsOrderService {
|
|
27
28
|
constructor(options, jobQueueService, eventBus, orderService, moduleRef) {
|
|
28
29
|
this.options = options;
|
|
@@ -157,15 +158,23 @@ let QlsOrderService = class QlsOrderService {
|
|
|
157
158
|
...(additionalOrderFields ?? {}),
|
|
158
159
|
};
|
|
159
160
|
const result = await client.createFulfillmentOrder(qlsOrder);
|
|
160
|
-
await this.orderService.updateCustomFields(ctx, orderId, {
|
|
161
|
-
syncedToQls: true,
|
|
162
|
-
});
|
|
163
161
|
core_2.Logger.info(`Successfully created order '${order.code}' in QLS with id '${result.id}'`, constants_1.loggerCtx);
|
|
164
162
|
await this.orderService.addNoteToOrder(ctx, {
|
|
165
163
|
id: orderId,
|
|
166
164
|
isPublic: false,
|
|
167
165
|
note: `Created order '${result.id}' in QLS`,
|
|
168
166
|
});
|
|
167
|
+
// Delay 10s to prevent race conditions: OrderPlacedEvent is emitted before transition to PaymentSettled is complete
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
this.orderService
|
|
170
|
+
.updateCustomFields(ctx, orderId, {
|
|
171
|
+
syncedToQls: true,
|
|
172
|
+
})
|
|
173
|
+
.catch((e) => {
|
|
174
|
+
const error = (0, catch_unknown_1.asError)(e);
|
|
175
|
+
core_2.Logger.error(`Error updating custom field 'syncedToQls: true' for order '${order.code}': ${error.message}`, constants_1.loggerCtx, error.stack);
|
|
176
|
+
});
|
|
177
|
+
}, 10000);
|
|
169
178
|
return `Order '${order.code}' created in QLS with id '${result.id}'`;
|
|
170
179
|
}
|
|
171
180
|
catch (e) {
|
|
@@ -175,6 +184,7 @@ let QlsOrderService = class QlsOrderService {
|
|
|
175
184
|
isPublic: false,
|
|
176
185
|
note: `Failed to create order '${order.code}' in QLS: ${error.message}`,
|
|
177
186
|
});
|
|
187
|
+
await this.eventBus.publish(new qls_order_failed_event_1.QlsOrderFailedEvent(ctx, order, new Date(), error.message));
|
|
178
188
|
throw error;
|
|
179
189
|
}
|
|
180
190
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -57,7 +57,9 @@ export type AdditionalVariantFields = Partial<FulfillmentProductInput & {
|
|
|
57
57
|
}>;
|
|
58
58
|
export interface AdditionalOrderFields {
|
|
59
59
|
servicepoint_code?: string;
|
|
60
|
-
delivery_options?:
|
|
60
|
+
delivery_options?: {
|
|
61
|
+
tag: string;
|
|
62
|
+
}[];
|
|
61
63
|
custom_values?: CustomValue[];
|
|
62
64
|
}
|
|
63
65
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinelab/vendure-plugin-qls-fulfillment",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Vendure plugin to fulfill orders via QLS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fulfillment",
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"catch-unknown": "^2.0.0"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "8f5d76680b66b12b9fb3732a4205b4ce20686076"
|
|
36
36
|
}
|