@pinelab/vendure-plugin-qls-fulfillment 1.0.0-beta.1

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 ADDED
@@ -0,0 +1,3 @@
1
+ # 1.0.0 (2025-11-07) ## CHANGE THIS DATE AND VERSION
2
+
3
+ - Initial release
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Vendure plugin to fulfill orders via QLS
2
+
3
+ Vendure plugin to fulfill orders via QLS. This uses QLS as fulfillment center and does not support shipments only via QLS.
4
+
5
+ [Official documentation here](https://plugins.pinelab.studio/plugin/vendure-plugin-qls-fulfillment)
6
+
7
+ This plugin contains:
8
+
9
+ ## Product Sync
10
+
11
+ - Full product sync (via Admin UI):
12
+ - Fetches all products from QLS
13
+ - Ensures corresponding Vendure variants exist
14
+ - Updates Vendure stock levels from QLS
15
+ - Partial product sync: Creates/updates products in QLS when products change in Vendure
16
+ - Partial stock sync: Updates Vendure stock based on incoming QLS webhooks
17
+
18
+ ## Order Sync
19
+
20
+ - Pushes orders to QLS automatically on order placement
21
+ - Supports manual order push to QLS via Admin UI
22
+ - Updates order status in Vendure based on QLS webhooks
23
+
24
+ ## Getting started
25
+
26
+ // TODO
27
+
28
+ 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
+
30
+ ## Webhooks
31
+
32
+ You should set up webhooks for the following events:
33
+
34
+ - `fulfillment_order.cancelled`
35
+ - `fulfillment_product.stock`
36
+ - `fulfillment_order.status`
37
+ - `fulfillment_order.completed`
38
+
39
+ The URL for all these events should be `https://<YOUR_VENDURE_HOST>/qls/webhook/<CHANNEL_TOKEN>?secret=<PLUGIN_SECRET>`. E.g. `https://example.com/qls/webhook/your-channel-token?secret=121231`.
40
+
41
+ - `<YOUR_VENDURE_HOST>` is the URL of your Vendure instance.
42
+ - `<CHANNEL_TOKEN>` is the token of the channel you want to use.
43
+ - `<PLUGIN_SECRET>` is the webhook secret you've passed into the plugin's `init()` function.
44
+
45
+ ### Stock location setup
46
+
47
+ This plugin only uses Vendure's default stock location, that means you should either:
48
+
49
+ 1. Remove all but one stock location in Vendure
50
+ 2. Or, remove all stock from other stock locations than the default in Vendure
51
+
52
+ Vendure assumes the first created stock location is the default stock location.
53
+
54
+ ## Monitoring
55
+
56
+ Make sure to monitor failed jobs: A job that failed after its retries were exhausted, means:
57
+
58
+ 1. An order was not pushed to QLS
59
+ 2. A product was not synced to QLS
60
+
61
+ Monitor your logs for the string `QLS webhook error`. This means an incoming stock update webhook was not processed correctly.
62
+
63
+ ## Cancelling orders and manually pushing orders to QLS
64
+
65
+ // TODO: Push will just create a new order in QLS, it will not cancel the existing order in QLS. Cancel existing order first via https://mijn.pakketdienstqls.nl/
@@ -0,0 +1 @@
1
+ export declare const adminApiExtensions: import("graphql").DocumentNode;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.adminApiExtensions = void 0;
7
+ const graphql_tag_1 = __importDefault(require("graphql-tag"));
8
+ exports.adminApiExtensions = (0, graphql_tag_1.default) `
9
+ extend type Mutation {
10
+ """
11
+ Trigger a sync to create or update all products in Vendure to QLS, and pull in stock levels from QLS.
12
+ """
13
+ triggerQlsProductSync: Boolean!
14
+
15
+ """
16
+ Manually push an order to QLS (again)
17
+ """
18
+ pushOrderToQls(orderId: ID!): String!
19
+ }
20
+ `;
@@ -0,0 +1,31 @@
1
+ export type Maybe<T> = T | null;
2
+ export type InputMaybe<T> = Maybe<T>;
3
+ export type Exact<T extends {
4
+ [key: string]: unknown;
5
+ }> = {
6
+ [K in keyof T]: T[K];
7
+ };
8
+ export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
9
+ [SubKey in K]?: Maybe<T[SubKey]>;
10
+ };
11
+ export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
12
+ [SubKey in K]: Maybe<T[SubKey]>;
13
+ };
14
+ /** All built-in and custom scalars, mapped to their actual values */
15
+ export type Scalars = {
16
+ ID: string | number;
17
+ String: string;
18
+ Boolean: boolean;
19
+ Int: number;
20
+ Float: number;
21
+ };
22
+ export type Mutation = {
23
+ __typename?: 'Mutation';
24
+ /** Manually push an order to QLS (again) */
25
+ pushOrderToQls: Scalars['String'];
26
+ /** Trigger a sync to create or update all products in Vendure to QLS, and pull in stock levels from QLS. */
27
+ triggerQlsProductSync: Scalars['Boolean'];
28
+ };
29
+ export type MutationPushOrderToQlsArgs = {
30
+ orderId: Scalars['ID'];
31
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import { RequestContext } from '@vendure/core';
2
+ import { QlsProductService } from '../services/qls-product.service';
3
+ import { MutationPushOrderToQlsArgs } from './generated/graphql';
4
+ import { QlsOrderService } from '../services/qls-order.service';
5
+ export declare class QlsAdminResolver {
6
+ private qlsService;
7
+ private qlsOrderService;
8
+ constructor(qlsService: QlsProductService, qlsOrderService: QlsOrderService);
9
+ triggerQlsProductSync(ctx: RequestContext): Promise<boolean>;
10
+ pushOrderToQls(ctx: RequestContext, input: MutationPushOrderToQlsArgs): Promise<string>;
11
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.QlsAdminResolver = void 0;
16
+ const graphql_1 = require("@nestjs/graphql");
17
+ const core_1 = require("@vendure/core");
18
+ const qls_product_service_1 = require("../services/qls-product.service");
19
+ const qls_order_service_1 = require("../services/qls-order.service");
20
+ const permissions_1 = require("../config/permissions");
21
+ let QlsAdminResolver = class QlsAdminResolver {
22
+ constructor(qlsService, qlsOrderService) {
23
+ this.qlsService = qlsService;
24
+ this.qlsOrderService = qlsOrderService;
25
+ }
26
+ async triggerQlsProductSync(ctx) {
27
+ await this.qlsService.triggerFullSync(ctx);
28
+ return true;
29
+ }
30
+ async pushOrderToQls(ctx, input) {
31
+ return await this.qlsOrderService.pushOrderToQls(ctx, input.orderId);
32
+ }
33
+ };
34
+ exports.QlsAdminResolver = QlsAdminResolver;
35
+ __decorate([
36
+ (0, graphql_1.Mutation)(),
37
+ (0, core_1.Transaction)(),
38
+ (0, core_1.Allow)(permissions_1.qlsFullSyncPermission.Permission),
39
+ __param(0, (0, core_1.Ctx)()),
40
+ __metadata("design:type", Function),
41
+ __metadata("design:paramtypes", [core_1.RequestContext]),
42
+ __metadata("design:returntype", Promise)
43
+ ], QlsAdminResolver.prototype, "triggerQlsProductSync", null);
44
+ __decorate([
45
+ (0, graphql_1.Mutation)(),
46
+ (0, core_1.Transaction)(),
47
+ (0, core_1.Allow)(permissions_1.qlsPushOrderPermission.Permission),
48
+ (0, core_1.Allow)(core_1.Permission.UpdateAdministrator),
49
+ __param(0, (0, core_1.Ctx)()),
50
+ __param(1, (0, graphql_1.Args)()),
51
+ __metadata("design:type", Function),
52
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
53
+ __metadata("design:returntype", Promise)
54
+ ], QlsAdminResolver.prototype, "pushOrderToQls", null);
55
+ exports.QlsAdminResolver = QlsAdminResolver = __decorate([
56
+ (0, graphql_1.Resolver)(),
57
+ __metadata("design:paramtypes", [qls_product_service_1.QlsProductService,
58
+ qls_order_service_1.QlsOrderService])
59
+ ], QlsAdminResolver);
@@ -0,0 +1,18 @@
1
+ import { ChannelService } from '@vendure/core';
2
+ import { Request } from 'express';
3
+ import { QlsProductService } from '../services/qls-product.service';
4
+ import { QlsPluginOptions } from '../types';
5
+ import { QlsOrderService } from '../services/qls-order.service';
6
+ import { IncomingOrderWebhook, IncomingStockWebhook } from '../lib/client-types';
7
+ export declare class QlsWebhooksController {
8
+ private channelService;
9
+ private qlsProductService;
10
+ private qlsOrderService;
11
+ private options;
12
+ constructor(channelService: ChannelService, qlsProductService: QlsProductService, qlsOrderService: QlsOrderService, options: QlsPluginOptions);
13
+ /**
14
+ * Endpoint for all incoming webhooks
15
+ */
16
+ events(channelToken: string, webhookSecret: string, request: Request, body: IncomingStockWebhook | IncomingOrderWebhook): Promise<void>;
17
+ private getCtxForChannel;
18
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.QlsWebhooksController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@vendure/core");
18
+ const catch_unknown_1 = require("catch-unknown");
19
+ const qls_product_service_1 = require("../services/qls-product.service");
20
+ const constants_1 = require("../constants");
21
+ const qls_order_service_1 = require("../services/qls-order.service");
22
+ let QlsWebhooksController = class QlsWebhooksController {
23
+ constructor(channelService, qlsProductService, qlsOrderService, options) {
24
+ this.channelService = channelService;
25
+ this.qlsProductService = qlsProductService;
26
+ this.qlsOrderService = qlsOrderService;
27
+ this.options = options;
28
+ }
29
+ /**
30
+ * Endpoint for all incoming webhooks
31
+ */
32
+ async events(channelToken, webhookSecret, request, body) {
33
+ if (webhookSecret !== this.options.webhookSecret) {
34
+ return core_1.Logger.warn(`Incoming webhook with invalid secret for channel '${channelToken}' to '${request.url}'`, constants_1.loggerCtx);
35
+ }
36
+ try {
37
+ const ctx = await this.getCtxForChannel(channelToken);
38
+ if (!ctx) {
39
+ return core_1.Logger.error(`Incoming webhook with invalid channel token for channel '${channelToken}' to '${request.url}'`, constants_1.loggerCtx, JSON.stringify(body));
40
+ }
41
+ if (isStockWebhook(body)) {
42
+ await this.qlsProductService.updateStockBySku(ctx, body.sku, body.amount_available);
43
+ }
44
+ else if (isOrderWebhook(body)) {
45
+ await this.qlsOrderService.handleOrderStatusUpdate(ctx, body);
46
+ }
47
+ else {
48
+ throw Error(`Invalid webhook body: ${JSON.stringify(body)}`);
49
+ }
50
+ }
51
+ catch (error) {
52
+ core_1.Logger.error(`QLS webhook error: ${(0, catch_unknown_1.asError)(error).message}`);
53
+ throw error;
54
+ }
55
+ }
56
+ async getCtxForChannel(token) {
57
+ const channel = await this.channelService.getChannelFromToken(token);
58
+ if (token !== channel.token) {
59
+ // This validation is needed. Vendure returns the default channel when a non-existing channel token is provided.
60
+ return undefined;
61
+ }
62
+ return new core_1.RequestContext({
63
+ apiType: 'admin',
64
+ authorizedAsOwnerOnly: false,
65
+ channel,
66
+ isAuthorized: true,
67
+ });
68
+ }
69
+ };
70
+ exports.QlsWebhooksController = QlsWebhooksController;
71
+ __decorate([
72
+ (0, common_1.Post)('/webhook/:channelToken'),
73
+ __param(0, (0, common_1.Param)('channelToken')),
74
+ __param(1, (0, common_1.Query)('secret')),
75
+ __param(2, (0, common_1.Req)()),
76
+ __param(3, (0, common_1.Body)()),
77
+ __metadata("design:type", Function),
78
+ __metadata("design:paramtypes", [String, String, Object, Object]),
79
+ __metadata("design:returntype", Promise)
80
+ ], QlsWebhooksController.prototype, "events", null);
81
+ exports.QlsWebhooksController = QlsWebhooksController = __decorate([
82
+ (0, common_1.Controller)('qls'),
83
+ __param(3, (0, common_1.Inject)(constants_1.PLUGIN_INIT_OPTIONS)),
84
+ __metadata("design:paramtypes", [core_1.ChannelService,
85
+ qls_product_service_1.QlsProductService,
86
+ qls_order_service_1.QlsOrderService, Object])
87
+ ], QlsWebhooksController);
88
+ function isStockWebhook(body) {
89
+ return 'amount_available' in body;
90
+ }
91
+ function isOrderWebhook(body) {
92
+ return 'customer_reference' in body && 'status' in body;
93
+ }
@@ -0,0 +1,2 @@
1
+ import { ScheduledTask } from '@vendure/core';
2
+ export declare const fullProductSyncTask: ScheduledTask<{}>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fullProductSyncTask = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ const qls_product_service_1 = require("../services/qls-product.service");
6
+ const constants_1 = require("../constants");
7
+ exports.fullProductSyncTask = new core_1.ScheduledTask({
8
+ id: 'full-sync-qls-products',
9
+ description: 'Trigger a full sync of products to QLS',
10
+ params: {},
11
+ schedule: (cron) => cron.everyDayAt(3, 0),
12
+ async execute({ injector }) {
13
+ const qlsProductService = injector.get(qls_product_service_1.QlsProductService);
14
+ // Verify for what channels we need to trigger QLS full sync
15
+ const ctx = await injector.get(core_1.RequestContextService).create({
16
+ apiType: 'admin',
17
+ });
18
+ const channels = await injector.get(core_1.ChannelService).findAll(ctx);
19
+ let triggeredFullSync = 0;
20
+ for (const channel of channels.items) {
21
+ // Create ctx for channel
22
+ const channelCtx = new core_1.RequestContext({
23
+ apiType: 'admin',
24
+ channel: channel,
25
+ isAuthorized: true,
26
+ authorizedAsOwnerOnly: false,
27
+ });
28
+ const options = injector
29
+ .get(constants_1.PLUGIN_INIT_OPTIONS)
30
+ .getConfig(channelCtx);
31
+ if (!options) {
32
+ core_1.Logger.info(`QLS not enabled for channel ${channel.token}`, constants_1.loggerCtx);
33
+ continue;
34
+ }
35
+ await qlsProductService.triggerFullSync(ctx);
36
+ triggeredFullSync += 1;
37
+ }
38
+ return {
39
+ message: `Triggered full sync for ${triggeredFullSync} channels`,
40
+ };
41
+ },
42
+ });
@@ -0,0 +1,3 @@
1
+ import { PermissionDefinition } from '@vendure/core';
2
+ export declare const qlsFullSyncPermission: PermissionDefinition;
3
+ export declare const qlsPushOrderPermission: PermissionDefinition;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.qlsPushOrderPermission = exports.qlsFullSyncPermission = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ exports.qlsFullSyncPermission = new core_1.PermissionDefinition({
6
+ name: 'QLSFullSync',
7
+ description: 'Allows triggering QLS full sync',
8
+ });
9
+ exports.qlsPushOrderPermission = new core_1.PermissionDefinition({
10
+ name: 'QLSPushOrder',
11
+ description: 'Allows pushing orders to QLS',
12
+ });
@@ -0,0 +1,2 @@
1
+ export declare const loggerCtx = "QlsPlugin";
2
+ export declare const PLUGIN_INIT_OPTIONS: unique symbol;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_INIT_OPTIONS = exports.loggerCtx = void 0;
4
+ exports.loggerCtx = 'QlsPlugin';
5
+ exports.PLUGIN_INIT_OPTIONS = Symbol('PLUGIN_OPTIONS');
@@ -0,0 +1,7 @@
1
+ import { CustomFieldConfig } from '@vendure/core';
2
+ declare module '@vendure/core' {
3
+ interface CustomProductVariantFields {
4
+ qlsProductId?: string;
5
+ }
6
+ }
7
+ export declare const variantCustomFields: CustomFieldConfig[];
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.variantCustomFields = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ exports.variantCustomFields = [
6
+ {
7
+ name: 'qlsProductId',
8
+ type: 'string',
9
+ label: [{ value: 'QLS Product ID', languageCode: core_1.LanguageCode.en }],
10
+ nullable: true,
11
+ public: false,
12
+ readonly: true,
13
+ ui: { tab: 'QLS' },
14
+ },
15
+ ];
@@ -0,0 +1,10 @@
1
+ export * from './api/generated/graphql';
2
+ export * from './config/full-product-sync-task';
3
+ export * from './custom-fields';
4
+ export * from './lib/client-types';
5
+ export * from './lib/qls-client';
6
+ export * from './qls-plugin';
7
+ export * from './services/qls-order.service';
8
+ export * from './services/qls-product.service';
9
+ export * from './config/permissions';
10
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./api/generated/graphql"), exports);
18
+ __exportStar(require("./config/full-product-sync-task"), exports);
19
+ __exportStar(require("./custom-fields"), exports);
20
+ __exportStar(require("./lib/client-types"), exports);
21
+ __exportStar(require("./lib/qls-client"), exports);
22
+ __exportStar(require("./qls-plugin"), exports);
23
+ __exportStar(require("./services/qls-order.service"), exports);
24
+ __exportStar(require("./services/qls-product.service"), exports);
25
+ __exportStar(require("./config/permissions"), exports);
26
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,151 @@
1
+ /**
2
+ * QLS API types only
3
+ */
4
+ export interface QlsApiResponse<T> {
5
+ meta: {
6
+ code: number;
7
+ };
8
+ data: T;
9
+ pagination?: {
10
+ page: number;
11
+ limit: number;
12
+ count: number;
13
+ pageCount: number;
14
+ nextPage: boolean;
15
+ prevPage: boolean;
16
+ };
17
+ }
18
+ export type FulfillmentProductInput = {
19
+ name: string;
20
+ ean?: string;
21
+ sku: string;
22
+ image_url?: string;
23
+ price_cost?: number;
24
+ price_store?: number;
25
+ order_unit?: number;
26
+ };
27
+ export type FulfillmentProduct = {
28
+ id: string;
29
+ company_id: string;
30
+ collection_id: string | null;
31
+ dimensions: unknown;
32
+ article_number: string | null;
33
+ ean: string;
34
+ name: string;
35
+ sku: string;
36
+ image_url: string | null;
37
+ country_code_of_origin: string | null;
38
+ hs_code: string | null;
39
+ need_barcode_picking: boolean;
40
+ need_best_before_stock: boolean;
41
+ need_serial_number: boolean;
42
+ amount_available: number;
43
+ amount_reserved: number;
44
+ amount_total: number;
45
+ price_cost: number | null;
46
+ price_store: number | null;
47
+ weight: number;
48
+ created: string;
49
+ modified: string;
50
+ foldable: boolean;
51
+ fulfillment_product_brand_id: string | null;
52
+ description: string | null;
53
+ amount_blocked: number;
54
+ status: string | null;
55
+ suppliers: unknown[];
56
+ barcodes: string[];
57
+ image_url_handheld: string | null;
58
+ barcodes_and_ean: string[];
59
+ };
60
+ export interface FulfillmentOrderInput {
61
+ brand_id: string;
62
+ customer_reference: string;
63
+ processable: string;
64
+ servicepoint_code?: string;
65
+ total_price: number;
66
+ receiver_contact: FulfillmentOrderReceiverContactInput;
67
+ custom_values?: CustomValue[];
68
+ products: FulfillmentOrderLineInput[];
69
+ delivery_options: string[];
70
+ }
71
+ export interface FulfillmentOrderReceiverContactInput {
72
+ name: string;
73
+ companyname?: string;
74
+ street: string;
75
+ housenumber: string;
76
+ address2?: string | null;
77
+ postalcode: string;
78
+ locality: string;
79
+ country: string;
80
+ email?: string | null;
81
+ phone?: string | null;
82
+ vat?: string | null;
83
+ eori?: string | null;
84
+ oss?: string | null;
85
+ }
86
+ export interface CustomValue {
87
+ key: string;
88
+ value: string;
89
+ }
90
+ export interface FulfillmentOrderLineInput {
91
+ amount_ordered: number;
92
+ product_id: string;
93
+ name: string;
94
+ custom_values?: CustomValue[];
95
+ }
96
+ export type QlsOrderStatus = 'concept' | 'error_validation' | 'received' | 'pending' | 'partically_sent' | 'sent';
97
+ export interface FulfillmentOrder {
98
+ id: string;
99
+ customer_reference: string;
100
+ amount_delivered: number | null;
101
+ amount_reserved: number | null;
102
+ amount_total: number;
103
+ status: QlsOrderStatus;
104
+ created: string;
105
+ modified: string;
106
+ cancelled: boolean | null;
107
+ hold: boolean | null;
108
+ processable: string;
109
+ shop_integration_id: string | null;
110
+ shop_integration_reference: string | null;
111
+ need_customs_information: boolean | null;
112
+ brand: string | null;
113
+ receiver_contact: {
114
+ companyName: string | null;
115
+ name: string;
116
+ phone: string | null;
117
+ email: string | null;
118
+ street: string;
119
+ houseNumber: string | null;
120
+ address2: string | null;
121
+ postalCode: string | null;
122
+ locality: string;
123
+ country: string;
124
+ };
125
+ delivery_options: string[];
126
+ products: Array<{
127
+ id: string;
128
+ order_id: string;
129
+ product_id: string;
130
+ name: string;
131
+ shop_integration_reference: string | null;
132
+ status: string | null;
133
+ amount_open: number | null;
134
+ amount_ordered: number;
135
+ amount_reserved: number | null;
136
+ amount_delivered: number | null;
137
+ amount_original: number | null;
138
+ country_code_of_origin: string | null;
139
+ hs_code: string | null;
140
+ price_per_unit: number | null;
141
+ custom1: string | null;
142
+ special_handling: string | null;
143
+ weight_per_unit: number | null;
144
+ company_id: string;
145
+ created: string;
146
+ modified: string;
147
+ product: unknown;
148
+ }>;
149
+ }
150
+ export type IncomingStockWebhook = Pick<FulfillmentProduct, 'sku' | 'amount_available'>;
151
+ export type IncomingOrderWebhook = Pick<FulfillmentOrder, 'customer_reference' | 'status' | 'cancelled' | 'amount_delivered' | 'amount_total'>;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * QLS API types only
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ import { RequestContext } from '@vendure/core';
2
+ import { QlsClientConfig, QlsPluginOptions } from '../types';
3
+ import type { FulfillmentOrder, FulfillmentOrderInput, FulfillmentProduct, FulfillmentProductInput, QlsApiResponse } from './client-types';
4
+ export declare function getQlsClient(ctx: RequestContext, pluginOptions: QlsPluginOptions): Promise<QlsClient | undefined>;
5
+ /**
6
+ * Wrapper around the QLS Rest API.
7
+ */
8
+ export declare class QlsClient {
9
+ private readonly config;
10
+ private baseUrl;
11
+ constructor(config: QlsClientConfig);
12
+ /**
13
+ * Find a product by SKU.
14
+ * Returns the first product found, or undefined if no product is found.
15
+ */
16
+ getFulfillmentProductBySku(sku: string): Promise<FulfillmentProduct | undefined>;
17
+ /**
18
+ * Get stock for all fulfillment products.
19
+ * Might require multiple requests if the result is paginated.
20
+ */
21
+ getAllFulfillmentProducts(): Promise<FulfillmentProduct[]>;
22
+ createFulfillmentProduct(data: FulfillmentProductInput): Promise<FulfillmentProduct>;
23
+ updateFulfillmentProduct(fulfillmentProductId: string, data: FulfillmentProductInput): Promise<FulfillmentProduct>;
24
+ createFulfillmentOrder(data: Omit<FulfillmentOrderInput, 'brand_id'>): Promise<FulfillmentOrder>;
25
+ rawRequest<T>(method: 'POST' | 'GET' | 'PUT' | 'DELETE', action: string, data?: unknown): Promise<QlsApiResponse<T>>;
26
+ }