@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 +3 -0
- package/README.md +65 -0
- package/dist/api/api-extensions.d.ts +1 -0
- package/dist/api/api-extensions.js +20 -0
- package/dist/api/generated/graphql.d.ts +31 -0
- package/dist/api/generated/graphql.js +2 -0
- package/dist/api/qls-admin.resolver.d.ts +11 -0
- package/dist/api/qls-admin.resolver.js +59 -0
- package/dist/api/qls-webhooks-controller.d.ts +18 -0
- package/dist/api/qls-webhooks-controller.js +93 -0
- package/dist/config/full-product-sync-task.d.ts +2 -0
- package/dist/config/full-product-sync-task.js +42 -0
- package/dist/config/permissions.d.ts +3 -0
- package/dist/config/permissions.js +12 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +5 -0
- package/dist/custom-fields.d.ts +7 -0
- package/dist/custom-fields.js +15 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +26 -0
- package/dist/lib/client-types.d.ts +151 -0
- package/dist/lib/client-types.js +5 -0
- package/dist/lib/qls-client.d.ts +26 -0
- package/dist/lib/qls-client.js +99 -0
- package/dist/qls-plugin.d.ts +8 -0
- package/dist/qls-plugin.js +60 -0
- package/dist/services/qls-order.service.d.ts +29 -0
- package/dist/services/qls-order.service.js +210 -0
- package/dist/services/qls-product.service.d.ts +69 -0
- package/dist/services/qls-product.service.js +309 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/package.json +35 -0
package/CHANGELOG.md
ADDED
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,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,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,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,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
|
+
];
|
package/dist/index.d.ts
ADDED
|
@@ -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,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
|
+
}
|