@pinelab/vendure-plugin-qls-fulfillment 1.9.0 → 2.0.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 +6 -0
- package/README.md +46 -1
- package/dist/lib/client-types.d.ts +392 -0
- package/dist/lib/client-types.js +1 -0
- package/dist/lib/qls-client.d.ts +2 -2
- package/dist/qls-plugin.js +10 -4
- package/dist/services/qls-order.service.js +14 -6
- package/dist/services/qls-product.service.js +6 -5
- package/dist/types.d.ts +68 -51
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# 2.0.0 (2026-06-10)
|
|
2
|
+
|
|
3
|
+
- Restructure plugin options into nested `orderSync` and `productSync` objects. This is a **breaking change**: all plugin options must now be organized under `orderSync` (for order-related hooks) and `productSync` (for product-related hooks).
|
|
4
|
+
- Renamed `getAdditionalOrderFields` to `pushAdditionalOrderFields` in `orderSync` options.
|
|
5
|
+
- Added `pullAdditionalOrderFields` hook in `orderSync` options, called after an order is successfully created in QLS. Errors in this hook are caught and logged, and will not fail the order push job.
|
|
6
|
+
|
|
1
7
|
# 1.9.0 (2026-08-05)
|
|
2
8
|
|
|
3
9
|
- Upgraded to Vendure 3.6.3
|
package/README.md
CHANGED
|
@@ -23,7 +23,52 @@ This plugin contains:
|
|
|
23
23
|
|
|
24
24
|
## Getting started
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
```ts
|
|
27
|
+
import { QlsPlugin } from '@pinelab/vendure-plugin-qls-fulfillment';
|
|
28
|
+
|
|
29
|
+
// In your VendureConfig:
|
|
30
|
+
plugins: [
|
|
31
|
+
QlsPlugin.init({
|
|
32
|
+
getConfig: (ctx) => {
|
|
33
|
+
// Return config for the current channel, or undefined to disable QLS for this channel
|
|
34
|
+
return {
|
|
35
|
+
username: 'qls-username',
|
|
36
|
+
password: 'qls-password',
|
|
37
|
+
companyId: 'your-company-id',
|
|
38
|
+
brandId: 'your-brand-id',
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
webhookSecret: 'your-random-secret',
|
|
42
|
+
productSync: {
|
|
43
|
+
// Required: map Vendure variants to QLS product fields
|
|
44
|
+
getAdditionalVariantFields: (ctx, variant) => ({
|
|
45
|
+
ean: variant.sku,
|
|
46
|
+
image_url: `https://your-cdn.com/${variant.featuredAsset?.preview}`,
|
|
47
|
+
additionalEANs: ['1234567890'],
|
|
48
|
+
}),
|
|
49
|
+
// Optional: exclude certain variants from being synced to QLS
|
|
50
|
+
excludeVariantFromSync: (ctx, injector, variant) => {
|
|
51
|
+
return variant.sku.startsWith('DONOTSYNC');
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
orderSync: {
|
|
55
|
+
// Optional: push additional fields to QLS when creating an order
|
|
56
|
+
pushAdditionalOrderFields: (ctx, injector, order) => ({
|
|
57
|
+
custom_values: [
|
|
58
|
+
{
|
|
59
|
+
key: 'vendureOrder',
|
|
60
|
+
value: `https://your-admin.com/admin/orders/${order.code}`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}),
|
|
64
|
+
// Optional: delay order processing in QLS by 2 hours
|
|
65
|
+
processOrderFrom: (ctx, order) => {
|
|
66
|
+
return new Date(Date.now() + 1000 * 60 * 60 * 2);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
];
|
|
71
|
+
```
|
|
27
72
|
|
|
28
73
|
This plugin requires the default order process to be configured with `checkFulfillmentStates: false`, so that orders can be transitioned to Shipped and Delivered without the need of fulfillment. Fulfillment is the responsibility of Picqer, so we won't handle that in Vendure when using this plugin.
|
|
29
74
|
|
|
@@ -165,3 +165,395 @@ export interface FulfillmentOrder {
|
|
|
165
165
|
}
|
|
166
166
|
export type IncomingStockWebhook = Pick<FulfillmentProduct, 'sku' | 'amount_available'>;
|
|
167
167
|
export type IncomingOrderWebhook = Pick<FulfillmentOrder, 'customer_reference' | 'status' | 'cancelled' | 'amount_delivered' | 'amount_total'>;
|
|
168
|
+
/**
|
|
169
|
+
* This is the full data returend by the QLS API when fetching a fulfillment product by ID
|
|
170
|
+
* Getting via list, sku or filtering does NOT return this full object
|
|
171
|
+
*/
|
|
172
|
+
export interface FulfillmentProductDetail {
|
|
173
|
+
data: Data;
|
|
174
|
+
meta: Meta;
|
|
175
|
+
errors: any[];
|
|
176
|
+
pagination: any;
|
|
177
|
+
}
|
|
178
|
+
export interface Data {
|
|
179
|
+
id: string;
|
|
180
|
+
company_id: string;
|
|
181
|
+
collection_id: any;
|
|
182
|
+
dimensions: Dimensions;
|
|
183
|
+
article_number: any;
|
|
184
|
+
ean: string;
|
|
185
|
+
name: string;
|
|
186
|
+
sku: string;
|
|
187
|
+
image_url: string;
|
|
188
|
+
country_code_of_origin: any;
|
|
189
|
+
hs_code: any;
|
|
190
|
+
need_barcode_picking: boolean;
|
|
191
|
+
need_best_before_stock: boolean;
|
|
192
|
+
need_serial_number: boolean;
|
|
193
|
+
amount_available: number;
|
|
194
|
+
amount_reserved: number;
|
|
195
|
+
amount_total: number;
|
|
196
|
+
price_cost: number;
|
|
197
|
+
price_store: any;
|
|
198
|
+
weight: number;
|
|
199
|
+
created: string;
|
|
200
|
+
modified: string;
|
|
201
|
+
foldable: boolean;
|
|
202
|
+
fulfillment_product_brand_id: string;
|
|
203
|
+
description: any;
|
|
204
|
+
amount_blocked: number;
|
|
205
|
+
status: any;
|
|
206
|
+
bundles: any[];
|
|
207
|
+
product_brand: ProductBrand;
|
|
208
|
+
product_values: ProductValue[];
|
|
209
|
+
suppliers: Supplier[];
|
|
210
|
+
vas: Va[];
|
|
211
|
+
product_master_cartons: any[];
|
|
212
|
+
product_measurements: ProductMeasurement[];
|
|
213
|
+
barcodes: any[];
|
|
214
|
+
warehouse_stock_transfers: WarehouseStockTransfer[];
|
|
215
|
+
bundle_products: any[];
|
|
216
|
+
warehouse_stocks: WarehouseStock[];
|
|
217
|
+
mappings: Mapping[];
|
|
218
|
+
order_unit: any;
|
|
219
|
+
image_url_handheld: string;
|
|
220
|
+
barcodes_and_ean: string[];
|
|
221
|
+
}
|
|
222
|
+
export interface Dimensions {
|
|
223
|
+
length: string;
|
|
224
|
+
width: string;
|
|
225
|
+
height: string;
|
|
226
|
+
}
|
|
227
|
+
export interface ProductBrand {
|
|
228
|
+
id: string;
|
|
229
|
+
company_id: string;
|
|
230
|
+
name: string;
|
|
231
|
+
}
|
|
232
|
+
export interface ProductValue {
|
|
233
|
+
id: number;
|
|
234
|
+
fulfillment_product_id: string;
|
|
235
|
+
key: string;
|
|
236
|
+
value: string;
|
|
237
|
+
}
|
|
238
|
+
export interface Supplier {
|
|
239
|
+
id: string;
|
|
240
|
+
company_id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
_joinData: JoinData;
|
|
243
|
+
}
|
|
244
|
+
export interface JoinData {
|
|
245
|
+
id: number;
|
|
246
|
+
fulfillment_product_id: string;
|
|
247
|
+
supplier_id: string;
|
|
248
|
+
supplier_code: any;
|
|
249
|
+
}
|
|
250
|
+
export interface Va {
|
|
251
|
+
id: number;
|
|
252
|
+
fulfillment_product_id: string;
|
|
253
|
+
vas_id: number;
|
|
254
|
+
value_added_service: ValueAddedService;
|
|
255
|
+
}
|
|
256
|
+
export interface ValueAddedService {
|
|
257
|
+
id: number;
|
|
258
|
+
short: string;
|
|
259
|
+
name: string;
|
|
260
|
+
type_id: number;
|
|
261
|
+
price: number;
|
|
262
|
+
description: string;
|
|
263
|
+
entity_type?: string;
|
|
264
|
+
entity_id?: number;
|
|
265
|
+
company_id: any;
|
|
266
|
+
enabled: boolean;
|
|
267
|
+
parent_id: any;
|
|
268
|
+
sort: number;
|
|
269
|
+
visible: boolean;
|
|
270
|
+
setting: Setting;
|
|
271
|
+
vas_type: VasType;
|
|
272
|
+
}
|
|
273
|
+
export interface Setting {
|
|
274
|
+
use_for_tote_hash: number;
|
|
275
|
+
}
|
|
276
|
+
export interface VasType {
|
|
277
|
+
id: number;
|
|
278
|
+
short: string;
|
|
279
|
+
name: string;
|
|
280
|
+
description: any;
|
|
281
|
+
type: string;
|
|
282
|
+
info_text?: string;
|
|
283
|
+
}
|
|
284
|
+
export interface ProductMeasurement {
|
|
285
|
+
id: string;
|
|
286
|
+
product_id: string;
|
|
287
|
+
dimensions: Dimensions2;
|
|
288
|
+
weight: number;
|
|
289
|
+
product_measurementscol: any;
|
|
290
|
+
creator: string;
|
|
291
|
+
creator_company_id?: string;
|
|
292
|
+
creator_user_id?: string;
|
|
293
|
+
created: string;
|
|
294
|
+
creator_user?: CreatorUser;
|
|
295
|
+
}
|
|
296
|
+
export interface Dimensions2 {
|
|
297
|
+
length: string;
|
|
298
|
+
width: string;
|
|
299
|
+
height: string;
|
|
300
|
+
}
|
|
301
|
+
export interface CreatorUser {
|
|
302
|
+
id: string;
|
|
303
|
+
email: string;
|
|
304
|
+
firstname: string;
|
|
305
|
+
lastname: string;
|
|
306
|
+
role: string;
|
|
307
|
+
permissions: any;
|
|
308
|
+
created: string;
|
|
309
|
+
modified: string;
|
|
310
|
+
language_id: number;
|
|
311
|
+
personnel_number: string;
|
|
312
|
+
has_beta_version: boolean;
|
|
313
|
+
password_changed: string;
|
|
314
|
+
remember_token: string;
|
|
315
|
+
email_verified_at: any;
|
|
316
|
+
}
|
|
317
|
+
export interface WarehouseStockTransfer {
|
|
318
|
+
id: string;
|
|
319
|
+
company_id: string;
|
|
320
|
+
product_id: string;
|
|
321
|
+
source_stock_id: any;
|
|
322
|
+
amount_total: number;
|
|
323
|
+
amount_transferred: number;
|
|
324
|
+
remarks: any;
|
|
325
|
+
source: string;
|
|
326
|
+
status: string;
|
|
327
|
+
created: string;
|
|
328
|
+
modified: string;
|
|
329
|
+
best_before: any;
|
|
330
|
+
fulfillment_product_batch_id: any;
|
|
331
|
+
creator_user_id: string;
|
|
332
|
+
purchase_order_label_product_id: any;
|
|
333
|
+
purchase_order_label_product: any;
|
|
334
|
+
warehouse_stock_transfer_box_mappings: WarehouseStockTransferBoxMapping[];
|
|
335
|
+
creator: Creator;
|
|
336
|
+
company: Company;
|
|
337
|
+
amount_pending: number;
|
|
338
|
+
}
|
|
339
|
+
export interface WarehouseStockTransferBoxMapping {
|
|
340
|
+
id: string;
|
|
341
|
+
box_id: string;
|
|
342
|
+
transfer_id: string;
|
|
343
|
+
created: string;
|
|
344
|
+
modified: string;
|
|
345
|
+
deleted: any;
|
|
346
|
+
warehouse_box: WarehouseBox;
|
|
347
|
+
}
|
|
348
|
+
export interface WarehouseBox {
|
|
349
|
+
id: string;
|
|
350
|
+
company_id: string;
|
|
351
|
+
trolley_id: any;
|
|
352
|
+
warehouse_id: number;
|
|
353
|
+
type_id: any;
|
|
354
|
+
code: string;
|
|
355
|
+
position: any;
|
|
356
|
+
created: string;
|
|
357
|
+
modified: string;
|
|
358
|
+
deleted: any;
|
|
359
|
+
prefix: string;
|
|
360
|
+
}
|
|
361
|
+
export interface Creator {
|
|
362
|
+
id: string;
|
|
363
|
+
email: string;
|
|
364
|
+
firstname: string;
|
|
365
|
+
lastname: string;
|
|
366
|
+
role: string;
|
|
367
|
+
permissions: any;
|
|
368
|
+
created: string;
|
|
369
|
+
modified: string;
|
|
370
|
+
language_id: number;
|
|
371
|
+
personnel_number: any;
|
|
372
|
+
has_beta_version: boolean;
|
|
373
|
+
password_changed: string;
|
|
374
|
+
remember_token: string;
|
|
375
|
+
email_verified_at: any;
|
|
376
|
+
}
|
|
377
|
+
export interface Company {
|
|
378
|
+
id: string;
|
|
379
|
+
tenant_id: string;
|
|
380
|
+
affiliate_id: any;
|
|
381
|
+
name: string;
|
|
382
|
+
anonymous_name: string;
|
|
383
|
+
invoice_contact_id: string;
|
|
384
|
+
active: boolean;
|
|
385
|
+
shipments_last_7_days: number;
|
|
386
|
+
legacy_shipments_last_7_days: number;
|
|
387
|
+
heavy_shipments: boolean;
|
|
388
|
+
default_user_role_id: string;
|
|
389
|
+
financial_relation_id: string;
|
|
390
|
+
has_employee: boolean;
|
|
391
|
+
has_fulfillment: boolean;
|
|
392
|
+
has_route: boolean;
|
|
393
|
+
has_signed_gdpr: boolean;
|
|
394
|
+
pickup_timeframe_start: any;
|
|
395
|
+
pickup_timeframe_end: any;
|
|
396
|
+
fulfillment_average_handling_time: any;
|
|
397
|
+
outstanding_invoices_status: string;
|
|
398
|
+
outstanding_invoices_amount: number;
|
|
399
|
+
legal_companyname: any;
|
|
400
|
+
legal_coc: any;
|
|
401
|
+
legal_vat: any;
|
|
402
|
+
onboarding_status: string;
|
|
403
|
+
status: string;
|
|
404
|
+
created: string;
|
|
405
|
+
modified: string;
|
|
406
|
+
deleted: any;
|
|
407
|
+
price_change: any;
|
|
408
|
+
auto_secondwave: boolean;
|
|
409
|
+
priority_support: boolean;
|
|
410
|
+
mollie_customer_id: any;
|
|
411
|
+
company_group_id: any;
|
|
412
|
+
test: boolean;
|
|
413
|
+
enable_software_charge: boolean;
|
|
414
|
+
}
|
|
415
|
+
export interface WarehouseStock {
|
|
416
|
+
id: number;
|
|
417
|
+
product_id: string;
|
|
418
|
+
autostore_bin_id: string;
|
|
419
|
+
zone_id: number;
|
|
420
|
+
row_number: string;
|
|
421
|
+
rack_number: string;
|
|
422
|
+
shelf_number: string;
|
|
423
|
+
number: string;
|
|
424
|
+
amount_current: number;
|
|
425
|
+
amount_reserved: number;
|
|
426
|
+
best_before: any;
|
|
427
|
+
created: string;
|
|
428
|
+
modified: string;
|
|
429
|
+
deleted: any;
|
|
430
|
+
best_before_cutoff: any;
|
|
431
|
+
blocked: any;
|
|
432
|
+
autostore_bin: AutostoreBin;
|
|
433
|
+
warehouse_zone: WarehouseZone;
|
|
434
|
+
product_batch: any[];
|
|
435
|
+
amount_available: number;
|
|
436
|
+
code: string;
|
|
437
|
+
code_formatted: string;
|
|
438
|
+
is_changed_for_replenishment: boolean;
|
|
439
|
+
location: string;
|
|
440
|
+
}
|
|
441
|
+
export interface AutostoreBin {
|
|
442
|
+
id: string;
|
|
443
|
+
autostore_id: string;
|
|
444
|
+
autostore_bin_id: number;
|
|
445
|
+
autostore_bin_layout_id: string;
|
|
446
|
+
zone_id: number;
|
|
447
|
+
content_code: string;
|
|
448
|
+
bin_type: number;
|
|
449
|
+
created?: string;
|
|
450
|
+
modified: string;
|
|
451
|
+
deleted: any;
|
|
452
|
+
depth: number;
|
|
453
|
+
xpos: number;
|
|
454
|
+
ypos: number;
|
|
455
|
+
transferred_in: string;
|
|
456
|
+
mode: string;
|
|
457
|
+
}
|
|
458
|
+
export interface WarehouseZone {
|
|
459
|
+
id: number;
|
|
460
|
+
company_id: string;
|
|
461
|
+
name: string;
|
|
462
|
+
pickable: boolean;
|
|
463
|
+
flip_row_numbers: boolean;
|
|
464
|
+
warehouse_id: number;
|
|
465
|
+
batchable: boolean;
|
|
466
|
+
isolated: boolean;
|
|
467
|
+
autostore_id: string;
|
|
468
|
+
has_pallets: boolean;
|
|
469
|
+
block_reservation_after: any;
|
|
470
|
+
warehouse_zone_group_id: any;
|
|
471
|
+
has_quarantine: boolean;
|
|
472
|
+
partially_replenishment_allowed: boolean;
|
|
473
|
+
allow_automatic_replenishment: boolean;
|
|
474
|
+
company: Company2;
|
|
475
|
+
}
|
|
476
|
+
export interface Company2 {
|
|
477
|
+
id: string;
|
|
478
|
+
tenant_id: string;
|
|
479
|
+
affiliate_id: any;
|
|
480
|
+
name: string;
|
|
481
|
+
anonymous_name: string;
|
|
482
|
+
invoice_contact_id: string;
|
|
483
|
+
active: boolean;
|
|
484
|
+
shipments_last_7_days: number;
|
|
485
|
+
legacy_shipments_last_7_days: number;
|
|
486
|
+
heavy_shipments: boolean;
|
|
487
|
+
default_user_role_id: string;
|
|
488
|
+
financial_relation_id: string;
|
|
489
|
+
has_employee: boolean;
|
|
490
|
+
has_fulfillment: boolean;
|
|
491
|
+
has_route: boolean;
|
|
492
|
+
has_signed_gdpr: boolean;
|
|
493
|
+
pickup_timeframe_start: any;
|
|
494
|
+
pickup_timeframe_end: any;
|
|
495
|
+
fulfillment_average_handling_time: any;
|
|
496
|
+
outstanding_invoices_status: string;
|
|
497
|
+
outstanding_invoices_amount: number;
|
|
498
|
+
legal_companyname: any;
|
|
499
|
+
legal_coc: any;
|
|
500
|
+
legal_vat: any;
|
|
501
|
+
onboarding_status: string;
|
|
502
|
+
status: string;
|
|
503
|
+
created: string;
|
|
504
|
+
modified: string;
|
|
505
|
+
deleted: any;
|
|
506
|
+
price_change: any;
|
|
507
|
+
auto_secondwave: boolean;
|
|
508
|
+
priority_support: boolean;
|
|
509
|
+
mollie_customer_id: any;
|
|
510
|
+
company_group_id: any;
|
|
511
|
+
test: boolean;
|
|
512
|
+
enable_software_charge: boolean;
|
|
513
|
+
}
|
|
514
|
+
export interface Mapping {
|
|
515
|
+
id: string;
|
|
516
|
+
product_id: string;
|
|
517
|
+
shop_integration_id: string;
|
|
518
|
+
shop_integration_reference: string;
|
|
519
|
+
shop_integration_reference2: string;
|
|
520
|
+
stock: number;
|
|
521
|
+
sync_stock: boolean;
|
|
522
|
+
created: string;
|
|
523
|
+
modified: string;
|
|
524
|
+
deleted: any;
|
|
525
|
+
synced: string;
|
|
526
|
+
exception_count: number;
|
|
527
|
+
name: string;
|
|
528
|
+
ean: string;
|
|
529
|
+
sku: string;
|
|
530
|
+
shop_integration: ShopIntegration;
|
|
531
|
+
}
|
|
532
|
+
export interface ShopIntegration {
|
|
533
|
+
id: string;
|
|
534
|
+
company_id: string;
|
|
535
|
+
brand_id: string;
|
|
536
|
+
type_id: number;
|
|
537
|
+
name: string;
|
|
538
|
+
created: string;
|
|
539
|
+
modified: string;
|
|
540
|
+
deleted: any;
|
|
541
|
+
last_imported: string;
|
|
542
|
+
last_imported_order_insights: any;
|
|
543
|
+
last_imported_shipment_insights: any;
|
|
544
|
+
last_imported_purchase_order: any;
|
|
545
|
+
fulfillment_sync_orders: boolean;
|
|
546
|
+
fulfillment_sync_products: boolean;
|
|
547
|
+
fulfillment_sync_stock: boolean;
|
|
548
|
+
fulfillment_sync_purchase_orders: boolean;
|
|
549
|
+
fulfillment_product_create_new: boolean;
|
|
550
|
+
fulfillment_product_match_by_name: boolean;
|
|
551
|
+
fulfillment_product_match_by_ean: boolean;
|
|
552
|
+
fulfillment_product_match_by_sku: boolean;
|
|
553
|
+
fulfillment_product_match_fields: string;
|
|
554
|
+
last_imported_products: any;
|
|
555
|
+
exception_count_importing_products: number;
|
|
556
|
+
}
|
|
557
|
+
export interface Meta {
|
|
558
|
+
code: number;
|
|
559
|
+
}
|
package/dist/lib/client-types.js
CHANGED
package/dist/lib/qls-client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RequestContext } from '@vendure/core';
|
|
2
2
|
import { QlsClientConfig, QlsPluginOptions } from '../types';
|
|
3
|
-
import type { FulfillmentOrder, FulfillmentOrderInput, FulfillmentProduct, FulfillmentProductInput, QlsApiResponse } from './client-types';
|
|
3
|
+
import type { FulfillmentOrder, FulfillmentOrderInput, FulfillmentProduct, FulfillmentProductDetail, FulfillmentProductInput, QlsApiResponse } from './client-types';
|
|
4
4
|
import { QlsServicePoint } from '../api/generated/graphql';
|
|
5
5
|
export declare function getQlsClient(ctx: RequestContext, pluginOptions: QlsPluginOptions): Promise<QlsClient | undefined>;
|
|
6
6
|
/**
|
|
@@ -15,7 +15,7 @@ export declare class QlsClient {
|
|
|
15
15
|
* Returns the first product found, or undefined if no product is found.
|
|
16
16
|
*/
|
|
17
17
|
getFulfillmentProductBySku(sku: string): Promise<FulfillmentProduct | undefined>;
|
|
18
|
-
getFulfillmentProductById(fulfillmentProductId: string): Promise<
|
|
18
|
+
getFulfillmentProductById(fulfillmentProductId: string): Promise<FulfillmentProductDetail | undefined>;
|
|
19
19
|
/**
|
|
20
20
|
* Get stock for all fulfillment products.
|
|
21
21
|
* Might require multiple requests if the result is paginated.
|
package/dist/qls-plugin.js
CHANGED
|
@@ -26,10 +26,16 @@ const qls_order_entity_entity_1 = require("./entities/qls-order-entity.entity");
|
|
|
26
26
|
let QlsPlugin = QlsPlugin_1 = class QlsPlugin {
|
|
27
27
|
static init(options) {
|
|
28
28
|
this.options = {
|
|
29
|
-
synchronizeStockLevels: true,
|
|
30
|
-
autoPushOrders: true,
|
|
31
|
-
qlsProductIdUiTab: 'QLS',
|
|
32
29
|
...options,
|
|
30
|
+
orderSync: {
|
|
31
|
+
autoPushOrders: true,
|
|
32
|
+
...options.orderSync,
|
|
33
|
+
},
|
|
34
|
+
productSync: {
|
|
35
|
+
synchronizeStockLevels: true,
|
|
36
|
+
qlsProductIdUiTab: 'QLS',
|
|
37
|
+
...options.productSync,
|
|
38
|
+
},
|
|
33
39
|
};
|
|
34
40
|
return QlsPlugin_1;
|
|
35
41
|
}
|
|
@@ -55,7 +61,7 @@ exports.QlsPlugin = QlsPlugin = QlsPlugin_1 = __decorate([
|
|
|
55
61
|
configuration: (config) => {
|
|
56
62
|
config.authOptions.customPermissions.push(permissions_1.qlsFullSyncPermission);
|
|
57
63
|
config.authOptions.customPermissions.push(permissions_1.qlsPushOrderPermission);
|
|
58
|
-
config.customFields.ProductVariant.push(...(0, custom_fields_1.getVariantCustomFields)(QlsPlugin.options?.qlsProductIdUiTab));
|
|
64
|
+
config.customFields.ProductVariant.push(...(0, custom_fields_1.getVariantCustomFields)(QlsPlugin.options?.productSync?.qlsProductIdUiTab));
|
|
59
65
|
config.customFields.Order.push(...custom_fields_1.orderCustomFields);
|
|
60
66
|
return config;
|
|
61
67
|
},
|
|
@@ -37,7 +37,7 @@ let QlsOrderService = class QlsOrderService {
|
|
|
37
37
|
onApplicationBootstrap() {
|
|
38
38
|
// Listen for OrderPlacedEvent and add a job to the queue
|
|
39
39
|
this.eventBus.ofType(core_2.OrderPlacedEvent).subscribe((event) => {
|
|
40
|
-
if (!this.options.autoPushOrders) {
|
|
40
|
+
if (!this.options.orderSync.autoPushOrders) {
|
|
41
41
|
core_2.Logger.info(`Auto push orders disabled, not triggering push order job for order ${event.order.code}`, constants_1.loggerCtx);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
@@ -111,7 +111,7 @@ let QlsOrderService = class QlsOrderService {
|
|
|
111
111
|
const qlsProducts = [];
|
|
112
112
|
await Promise.all(order.lines.map(async (line) => {
|
|
113
113
|
// Check if product variant should be excluded from sync
|
|
114
|
-
if (await this.options.excludeVariantFromSync?.(ctx, new core_2.Injector(this.moduleRef), line.productVariant)) {
|
|
114
|
+
if (await this.options.productSync.excludeVariantFromSync?.(ctx, new core_2.Injector(this.moduleRef), line.productVariant)) {
|
|
115
115
|
core_2.Logger.info(`Product variant '${line.productVariant.sku}' not sent to QLS in order '${order.code}' because it is excluded from sync.`, constants_1.loggerCtx);
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
@@ -127,7 +127,7 @@ let QlsOrderService = class QlsOrderService {
|
|
|
127
127
|
}));
|
|
128
128
|
// Add additional order items, if any
|
|
129
129
|
try {
|
|
130
|
-
const additionalOrderItems = await this.options.addAdditionalOrderItems?.(ctx, new core_2.Injector(this.moduleRef), order);
|
|
130
|
+
const additionalOrderItems = await this.options.orderSync.addAdditionalOrderItems?.(ctx, new core_2.Injector(this.moduleRef), order);
|
|
131
131
|
if (additionalOrderItems) {
|
|
132
132
|
qlsProducts.push(...additionalOrderItems);
|
|
133
133
|
}
|
|
@@ -142,7 +142,7 @@ let QlsOrderService = class QlsOrderService {
|
|
|
142
142
|
core_2.Logger.info(message, constants_1.loggerCtx);
|
|
143
143
|
return message;
|
|
144
144
|
}
|
|
145
|
-
const additionalOrderFields = await this.options.
|
|
145
|
+
const additionalOrderFields = await this.options.orderSync.pushAdditionalOrderFields?.(ctx, new core_2.Injector(this.moduleRef), order);
|
|
146
146
|
const customerName = [order.customer?.firstName, order.customer?.lastName]
|
|
147
147
|
.filter(Boolean)
|
|
148
148
|
.join(' ');
|
|
@@ -160,8 +160,9 @@ let QlsOrderService = class QlsOrderService {
|
|
|
160
160
|
!order.shippingAddress.countryCode) {
|
|
161
161
|
throw new Error(`Shipping address for order '${order.code}' is missing one of required fields: streetLine1, postalCode, city, streetLine2, countryCode. Can not push order to QLS.`);
|
|
162
162
|
}
|
|
163
|
-
const processable = (await this.options.processOrderFrom?.(ctx, order)) ??
|
|
164
|
-
|
|
163
|
+
const processable = (await this.options.orderSync.processOrderFrom?.(ctx, order)) ??
|
|
164
|
+
new Date();
|
|
165
|
+
const receiverContact = this.options.orderSync.getReceiverContact?.(ctx, order);
|
|
165
166
|
const qlsOrder = {
|
|
166
167
|
customer_reference: order.code,
|
|
167
168
|
processable: processable.toISOString(),
|
|
@@ -206,6 +207,13 @@ let QlsOrderService = class QlsOrderService {
|
|
|
206
207
|
const error = (0, catch_unknown_1.asError)(e);
|
|
207
208
|
core_2.Logger.error(`Error saving QLS order entity for order '${order.code}': ${error.message}`, constants_1.loggerCtx, error.stack);
|
|
208
209
|
});
|
|
210
|
+
try {
|
|
211
|
+
await this.options.orderSync.pullAdditionalOrderFields?.(ctx, new core_2.Injector(this.moduleRef), order, result);
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
const error = (0, catch_unknown_1.asError)(e);
|
|
215
|
+
core_2.Logger.error(`Error in pullAdditionalOrderFields for order '${order.code}': ${error.message}`, constants_1.loggerCtx, error.stack);
|
|
216
|
+
}
|
|
209
217
|
return `Order '${order.code}' created in QLS with id '${result.id}'`;
|
|
210
218
|
}
|
|
211
219
|
catch (e) {
|
|
@@ -218,11 +218,12 @@ let QlsProductService = class QlsProductService {
|
|
|
218
218
|
else if (result.status === 'updated') {
|
|
219
219
|
updatedInQls.push(variant);
|
|
220
220
|
}
|
|
221
|
-
if (result.qlsProductId &&
|
|
221
|
+
if (result.qlsProductId &&
|
|
222
|
+
this.options.productSync.saveAdditionalVariantData) {
|
|
222
223
|
try {
|
|
223
224
|
const qlsProduct = await client.getFulfillmentProductById(result.qlsProductId);
|
|
224
225
|
if (qlsProduct) {
|
|
225
|
-
await this.options.
|
|
226
|
+
await this.options.productSync.saveAdditionalVariantData(ctx, new core_1.Injector(this.moduleRef), qlsProduct, variant);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
catch (e) {
|
|
@@ -289,7 +290,7 @@ let QlsProductService = class QlsProductService {
|
|
|
289
290
|
* Determines if a product needs to be created or updated in QLS based on the given variant and existing QLS product.
|
|
290
291
|
*/
|
|
291
292
|
async createOrUpdateProductInQls(ctx, client, variant, existingProduct) {
|
|
292
|
-
if (await this.options.excludeVariantFromSync?.(ctx, new core_1.Injector(this.moduleRef), variant)) {
|
|
293
|
+
if (await this.options.productSync.excludeVariantFromSync?.(ctx, new core_1.Injector(this.moduleRef), variant)) {
|
|
293
294
|
core_1.Logger.info(`Variant '${variant.sku}' excluded from sync to QLS.`, constants_1.loggerCtx);
|
|
294
295
|
return { status: 'not-changed' };
|
|
295
296
|
}
|
|
@@ -305,7 +306,7 @@ let QlsProductService = class QlsProductService {
|
|
|
305
306
|
}
|
|
306
307
|
let qlsProduct = existingProduct;
|
|
307
308
|
let createdOrUpdated = 'not-changed';
|
|
308
|
-
const { additionalEANs, ...additionalVariantFields } = this.options.getAdditionalVariantFields(ctx, variant);
|
|
309
|
+
const { additionalEANs, ...additionalVariantFields } = this.options.productSync.getAdditionalVariantFields(ctx, variant);
|
|
309
310
|
if (!existingProduct) {
|
|
310
311
|
const result = await client.createFulfillmentProduct({
|
|
311
312
|
name: variant.name,
|
|
@@ -430,7 +431,7 @@ let QlsProductService = class QlsProductService {
|
|
|
430
431
|
* Update stock level for a variant based on the given available stock
|
|
431
432
|
*/
|
|
432
433
|
async updateStock(ctx, variantId, availableStock) {
|
|
433
|
-
if (!this.options.synchronizeStockLevels) {
|
|
434
|
+
if (!this.options.productSync.synchronizeStockLevels) {
|
|
434
435
|
core_1.Logger.warn(`Stock sync disabled. Not updating stock for variant '${variantId}'`, constants_1.loggerCtx);
|
|
435
436
|
return;
|
|
436
437
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,69 +1,86 @@
|
|
|
1
1
|
import { ID, Injector, Order, ProductVariant, RequestContext, SerializedRequestContext } from '@vendure/core';
|
|
2
|
-
import { CustomValue, FulfillmentOrderInput, FulfillmentOrderLineInput,
|
|
2
|
+
import { CustomValue, FulfillmentOrder, FulfillmentOrderInput, FulfillmentOrderLineInput, FulfillmentProductDetail, FulfillmentProductInput } from './lib/client-types';
|
|
3
3
|
export interface QlsPluginOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Get the QLS client config for the current channel based on given context
|
|
6
6
|
*/
|
|
7
7
|
getConfig: (ctx: RequestContext) => QlsClientConfig | undefined | Promise<QlsClientConfig | undefined>;
|
|
8
|
-
/**
|
|
9
|
-
* Required to get the EAN and image URL for a product variant.
|
|
10
|
-
* Also allows you to override other product attributes like name, price, etc.
|
|
11
|
-
*/
|
|
12
|
-
getAdditionalVariantFields: (ctx: RequestContext, variant: ProductVariant) => AdditionalVariantFields;
|
|
13
|
-
/**
|
|
14
|
-
* Function to get the set service point code for an order.
|
|
15
|
-
* Return undefined to not use a service point at all.
|
|
16
|
-
*/
|
|
17
|
-
getAdditionalOrderFields?: (ctx: RequestContext, injector: Injector, order: Order) => Promise<AdditionalOrderFields | undefined> | AdditionalOrderFields | undefined;
|
|
18
8
|
/**
|
|
19
9
|
* A key used to verify if the caller is authorized to call the webhook.
|
|
20
10
|
* Set this to a random string
|
|
21
11
|
*/
|
|
22
12
|
webhookSecret: string;
|
|
23
13
|
/**
|
|
24
|
-
*
|
|
25
|
-
* Defaults to true.
|
|
26
|
-
*/
|
|
27
|
-
synchronizeStockLevels?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Allows you to disable the automatic pushing of orders to QLS. You can still push orders manually via the Admin UI.
|
|
30
|
-
* Defaults to true.
|
|
31
|
-
*/
|
|
32
|
-
autoPushOrders?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Allows you to define a date from when the order should be processed by QLS.
|
|
35
|
-
* You can for example make orders processable 2 hours from now, so that you can still edit the order in QLS
|
|
36
|
-
* Defaults to now.
|
|
37
|
-
*/
|
|
38
|
-
processOrderFrom?: (ctx: RequestContext, order: Order) => Date | Promise<Date>;
|
|
39
|
-
/**
|
|
40
|
-
* Optional function to determine if a product variant should be excluded from syncing to QLS.
|
|
41
|
-
* Return true to exclude the variant from sync, false or undefined to include it.
|
|
42
|
-
*/
|
|
43
|
-
excludeVariantFromSync?: (ctx: RequestContext, injector: Injector, variant: ProductVariant) => boolean | Promise<boolean>;
|
|
44
|
-
/**
|
|
45
|
-
* Optional function to customize the receiver contact details when creating a QLS order.
|
|
46
|
-
* Allows you to set different fields or override default mapping from the order's shipping address and customer.
|
|
47
|
-
* If not provided, default mapping will be used.
|
|
48
|
-
*/
|
|
49
|
-
getReceiverContact?: (ctx: RequestContext, order: Order) => FulfillmentOrderInput['receiver_contact'] | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* Admin UI tab name where the QLS Product ID custom field is shown on ProductVariant.
|
|
52
|
-
* `null` will show the custom field on the default tab.
|
|
53
|
-
* Defaults to 'QLS'.
|
|
54
|
-
*/
|
|
55
|
-
qlsProductIdUiTab?: string | null;
|
|
56
|
-
/**
|
|
57
|
-
* Optional hook called after syncing a variant to QLS.
|
|
58
|
-
* Receives the full QLS product, a RequestContext, and an Injector, allowing
|
|
59
|
-
* you to save any additional data to your own database. The saving itself
|
|
60
|
-
* happens inside this hook — if not provided, nothing is persisted.
|
|
14
|
+
* Options related to order pushing to QLS
|
|
61
15
|
*/
|
|
62
|
-
|
|
16
|
+
orderSync: {
|
|
17
|
+
/**
|
|
18
|
+
* Function to get additional order fields when pushing an order to QLS.
|
|
19
|
+
* Return undefined to not use additional fields at all.
|
|
20
|
+
*/
|
|
21
|
+
pushAdditionalOrderFields?: (ctx: RequestContext, injector: Injector, order: Order) => Promise<AdditionalOrderFields | undefined> | AdditionalOrderFields | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Allows you to disable the automatic pushing of orders to QLS. You can still push orders manually via the Admin UI.
|
|
24
|
+
* Defaults to true.
|
|
25
|
+
*/
|
|
26
|
+
autoPushOrders?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Allows you to define a date from when the order should be processed by QLS.
|
|
29
|
+
* You can for example make orders processable 2 hours from now, so that you can still edit the order in QLS
|
|
30
|
+
* Defaults to now.
|
|
31
|
+
*/
|
|
32
|
+
processOrderFrom?: (ctx: RequestContext, order: Order) => Date | Promise<Date>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional function to customize the receiver contact details when creating a QLS order.
|
|
35
|
+
* Allows you to set different fields or override default mapping from the order's shipping address and customer.
|
|
36
|
+
* If not provided, default mapping will be used.
|
|
37
|
+
*/
|
|
38
|
+
getReceiverContact?: (ctx: RequestContext, order: Order) => FulfillmentOrderInput['receiver_contact'] | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Optional hook called after an order has been successfully created in QLS.
|
|
41
|
+
* Receives the Vendure order, the created QLS order, a RequestContext, and an Injector.
|
|
42
|
+
* Errors in this hook are caught and logged, and will not fail the order push job.
|
|
43
|
+
*/
|
|
44
|
+
pullAdditionalOrderFields?: (ctx: RequestContext, injector: Injector, order: Order, createdQlsOrder: FulfillmentOrder) => Promise<void> | void;
|
|
45
|
+
/**
|
|
46
|
+
* Additional order items to add to the QLS order.
|
|
47
|
+
*/
|
|
48
|
+
addAdditionalOrderItems?: (ctx: RequestContext, injector: Injector, order: Order) => FulfillmentOrderLineInput[] | Promise<FulfillmentOrderLineInput[]>;
|
|
49
|
+
};
|
|
63
50
|
/**
|
|
64
|
-
*
|
|
51
|
+
* Options related to product syncing to QLS
|
|
65
52
|
*/
|
|
66
|
-
|
|
53
|
+
productSync: {
|
|
54
|
+
/**
|
|
55
|
+
* Required to get the EAN and image URL for a product variant.
|
|
56
|
+
* Also allows you to override other product attributes like name, price, etc.
|
|
57
|
+
*/
|
|
58
|
+
getAdditionalVariantFields: (ctx: RequestContext, variant: ProductVariant) => AdditionalVariantFields;
|
|
59
|
+
/**
|
|
60
|
+
* Allows you to disable the pulling in of stock levels from QLS. When disabled, stock in Vendure will not be modified based on QLS stock levels.
|
|
61
|
+
* Defaults to true.
|
|
62
|
+
*/
|
|
63
|
+
synchronizeStockLevels?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Optional function to determine if a product variant should be excluded from syncing to QLS.
|
|
66
|
+
* Return true to exclude the variant from sync, false or undefined to include it.
|
|
67
|
+
* This is also used during order pushing: variants that are excluded from sync will not be pushed to QLS in an order.
|
|
68
|
+
*/
|
|
69
|
+
excludeVariantFromSync?: (ctx: RequestContext, injector: Injector, variant: ProductVariant) => boolean | Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Optional hook called after syncing a variant to QLS.
|
|
72
|
+
* Receives the full QLS product, a RequestContext, and an Injector, allowing
|
|
73
|
+
* you to save any additional data to your own database. The saving itself
|
|
74
|
+
* happens inside this hook — if not provided, nothing is persisted.
|
|
75
|
+
*/
|
|
76
|
+
saveAdditionalVariantData?: (ctx: RequestContext, injector: Injector, qlsProduct: FulfillmentProductDetail, variant: ProductVariant) => Promise<void> | void;
|
|
77
|
+
/**
|
|
78
|
+
* Admin UI tab name where the QLS Product ID custom field is shown on ProductVariant.
|
|
79
|
+
* `null` will show the custom field on the default tab.
|
|
80
|
+
* Defaults to 'QLS'.
|
|
81
|
+
*/
|
|
82
|
+
qlsProductIdUiTab?: string | null;
|
|
83
|
+
};
|
|
67
84
|
}
|
|
68
85
|
/**
|
|
69
86
|
* Additional fields for a product variant that are used to create or update a product in QLS
|