@pinelab/vendure-plugin-qls-fulfillment 1.0.0-beta.11 → 1.0.0-beta.13

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/README.md CHANGED
@@ -51,6 +51,33 @@ This plugin only uses Vendure's default stock location, that means you should ei
51
51
 
52
52
  Vendure assumes the first created stock location is the default stock location.
53
53
 
54
+ ## Service Points
55
+
56
+ You can use the query `qlsServicePoints(postalCode: String!): [QlsServicePoint!]!` to get the service points for a given postal code. You can use the `setOrderCustomFields` mutation to set the service point on an order.
57
+
58
+ ```graphql
59
+ mutation {
60
+ setOrderCustomFields(
61
+ input: {
62
+ customFields: {
63
+ qlsServicePointId: "12232" # This is the ID of one of the points returned by the query above
64
+ qlsServicePointDetails: "Some details about the service point for admin users" # This is just for admin users in Vendure
65
+ }
66
+ }
67
+ ) {
68
+ __typename
69
+ ... on Order {
70
+ id
71
+ code
72
+ customFields {
73
+ qlsServicePointId
74
+ qlsServicePointDetails
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
54
81
  ## Monitoring
55
82
 
56
83
  Make sure to monitor failed jobs: A job that failed after its retries were exhausted, means:
@@ -1 +1,2 @@
1
1
  export declare const adminApiExtensions: import("graphql").DocumentNode;
2
+ export declare const shopApiExtensions: import("graphql").DocumentNode;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.adminApiExtensions = void 0;
6
+ exports.shopApiExtensions = exports.adminApiExtensions = void 0;
7
7
  const graphql_tag_1 = __importDefault(require("graphql-tag"));
8
8
  exports.adminApiExtensions = (0, graphql_tag_1.default) `
9
9
  extend type Mutation {
@@ -18,3 +18,47 @@ exports.adminApiExtensions = (0, graphql_tag_1.default) `
18
18
  pushOrderToQls(orderId: ID!): String!
19
19
  }
20
20
  `;
21
+ exports.shopApiExtensions = (0, graphql_tag_1.default) `
22
+ type QlsServicePoint {
23
+ servicepoint_code: String!
24
+ name: String!
25
+ address: QlsServicePointAddress!
26
+ geo: QlsServicePointGeo!
27
+ times: [QlsServicePointTime!]!
28
+ needsPostNumber: Boolean!
29
+ productId: Int!
30
+ productName: String!
31
+ }
32
+
33
+ type QlsServicePointAddress {
34
+ country: String!
35
+ postalcode: String!
36
+ locality: String!
37
+ street: String!
38
+ housenumber: String!
39
+ }
40
+
41
+ type QlsServicePointGeo {
42
+ lat: Float!
43
+ long: Float!
44
+ }
45
+
46
+ type QlsServicePointTime {
47
+ weekday: Int!
48
+ formatted: String!
49
+ from: String!
50
+ to: String!
51
+ }
52
+
53
+ input QlsServicePointSearchInput {
54
+ countryCode: String!
55
+ postalCode: String!
56
+ }
57
+
58
+ extend type Query {
59
+ """
60
+ Get the service points for a given postal code
61
+ """
62
+ qlsServicePoints(input: QlsServicePointSearchInput!): [QlsServicePoint!]!
63
+ }
64
+ `;
@@ -29,3 +29,46 @@ export type Mutation = {
29
29
  export type MutationPushOrderToQlsArgs = {
30
30
  orderId: Scalars['ID'];
31
31
  };
32
+ export type QlsServicePoint = {
33
+ __typename?: 'QlsServicePoint';
34
+ address: QlsServicePointAddress;
35
+ geo: QlsServicePointGeo;
36
+ name: Scalars['String'];
37
+ needsPostNumber: Scalars['Boolean'];
38
+ productId: Scalars['Int'];
39
+ productName: Scalars['String'];
40
+ servicepoint_code: Scalars['String'];
41
+ times: Array<QlsServicePointTime>;
42
+ };
43
+ export type QlsServicePointAddress = {
44
+ __typename?: 'QlsServicePointAddress';
45
+ country: Scalars['String'];
46
+ housenumber: Scalars['String'];
47
+ locality: Scalars['String'];
48
+ postalcode: Scalars['String'];
49
+ street: Scalars['String'];
50
+ };
51
+ export type QlsServicePointGeo = {
52
+ __typename?: 'QlsServicePointGeo';
53
+ lat: Scalars['Float'];
54
+ long: Scalars['Float'];
55
+ };
56
+ export type QlsServicePointSearchInput = {
57
+ countryCode: Scalars['String'];
58
+ postalCode: Scalars['String'];
59
+ };
60
+ export type QlsServicePointTime = {
61
+ __typename?: 'QlsServicePointTime';
62
+ formatted: Scalars['String'];
63
+ from: Scalars['String'];
64
+ to: Scalars['String'];
65
+ weekday: Scalars['Int'];
66
+ };
67
+ export type Query = {
68
+ __typename?: 'Query';
69
+ /** Get the service points for a given postal code */
70
+ qlsServicePoints: Array<QlsServicePoint>;
71
+ };
72
+ export type QueryQlsServicePointsArgs = {
73
+ input: QlsServicePointSearchInput;
74
+ };
@@ -3,9 +3,9 @@ import { QlsProductService } from '../services/qls-product.service';
3
3
  import { MutationPushOrderToQlsArgs } from './generated/graphql';
4
4
  import { QlsOrderService } from '../services/qls-order.service';
5
5
  export declare class QlsAdminResolver {
6
- private qlsService;
6
+ private qlsProductService;
7
7
  private qlsOrderService;
8
- constructor(qlsService: QlsProductService, qlsOrderService: QlsOrderService);
8
+ constructor(qlsProductService: QlsProductService, qlsOrderService: QlsOrderService);
9
9
  triggerQlsProductSync(ctx: RequestContext): Promise<boolean>;
10
10
  pushOrderToQls(ctx: RequestContext, input: MutationPushOrderToQlsArgs): Promise<string>;
11
11
  }
@@ -19,12 +19,12 @@ const qls_product_service_1 = require("../services/qls-product.service");
19
19
  const qls_order_service_1 = require("../services/qls-order.service");
20
20
  const permissions_1 = require("../config/permissions");
21
21
  let QlsAdminResolver = class QlsAdminResolver {
22
- constructor(qlsService, qlsOrderService) {
23
- this.qlsService = qlsService;
22
+ constructor(qlsProductService, qlsOrderService) {
23
+ this.qlsProductService = qlsProductService;
24
24
  this.qlsOrderService = qlsOrderService;
25
25
  }
26
26
  async triggerQlsProductSync(ctx) {
27
- await this.qlsService.triggerFullSync(ctx);
27
+ await this.qlsProductService.triggerFullSync(ctx);
28
28
  return true;
29
29
  }
30
30
  async pushOrderToQls(ctx, input) {
@@ -0,0 +1,8 @@
1
+ import { RequestContext } from '@vendure/core';
2
+ import { QlsOrderService } from '../services/qls-order.service';
3
+ import { QlsServicePoint, QlsServicePointSearchInput } from './generated/graphql';
4
+ export declare class QlsShopResolver {
5
+ private qlsOrderService;
6
+ constructor(qlsOrderService: QlsOrderService);
7
+ qlsServicePoints(ctx: RequestContext, input: QlsServicePointSearchInput): Promise<QlsServicePoint[]>;
8
+ }
@@ -0,0 +1,39 @@
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.QlsShopResolver = void 0;
16
+ const graphql_1 = require("@nestjs/graphql");
17
+ const core_1 = require("@vendure/core");
18
+ const qls_order_service_1 = require("../services/qls-order.service");
19
+ let QlsShopResolver = class QlsShopResolver {
20
+ constructor(qlsOrderService) {
21
+ this.qlsOrderService = qlsOrderService;
22
+ }
23
+ async qlsServicePoints(ctx, input) {
24
+ return await this.qlsOrderService.getServicePoints(ctx, input);
25
+ }
26
+ };
27
+ exports.QlsShopResolver = QlsShopResolver;
28
+ __decorate([
29
+ (0, graphql_1.Query)(),
30
+ __param(0, (0, core_1.Ctx)()),
31
+ __param(1, (0, graphql_1.Args)('input')),
32
+ __metadata("design:type", Function),
33
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
34
+ __metadata("design:returntype", Promise)
35
+ ], QlsShopResolver.prototype, "qlsServicePoints", null);
36
+ exports.QlsShopResolver = QlsShopResolver = __decorate([
37
+ (0, graphql_1.Resolver)(),
38
+ __metadata("design:paramtypes", [qls_order_service_1.QlsOrderService])
39
+ ], QlsShopResolver);
@@ -3,5 +3,10 @@ declare module '@vendure/core' {
3
3
  interface CustomProductVariantFields {
4
4
  qlsProductId?: string;
5
5
  }
6
+ interface CustomOrderFields {
7
+ qlsServicePointId?: string;
8
+ qlsServicePointDetails?: string;
9
+ }
6
10
  }
7
11
  export declare const variantCustomFields: CustomFieldConfig[];
12
+ export declare const orderCustomFields: CustomFieldConfig[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.variantCustomFields = void 0;
3
+ exports.orderCustomFields = exports.variantCustomFields = void 0;
4
4
  const core_1 = require("@vendure/core");
5
5
  exports.variantCustomFields = [
6
6
  {
@@ -13,3 +13,31 @@ exports.variantCustomFields = [
13
13
  ui: { tab: 'QLS' },
14
14
  },
15
15
  ];
16
+ exports.orderCustomFields = [
17
+ {
18
+ name: 'qlsServicePointId',
19
+ type: 'string',
20
+ label: [{ value: 'QLS Service Point ID', languageCode: core_1.LanguageCode.en }],
21
+ nullable: true,
22
+ public: true,
23
+ readonly: false,
24
+ ui: { tab: 'QLS' },
25
+ },
26
+ {
27
+ name: 'qlsServicePointDetails',
28
+ type: 'string',
29
+ label: [
30
+ { value: 'QLS Service Point Details', languageCode: core_1.LanguageCode.en },
31
+ ],
32
+ description: [
33
+ {
34
+ value: 'Only used for display purposes.',
35
+ languageCode: core_1.LanguageCode.en,
36
+ },
37
+ ],
38
+ nullable: true,
39
+ public: true,
40
+ readonly: false,
41
+ ui: { tab: 'QLS' },
42
+ },
43
+ ];
@@ -1,6 +1,7 @@
1
1
  import { RequestContext } from '@vendure/core';
2
2
  import { QlsClientConfig, QlsPluginOptions } from '../types';
3
3
  import type { FulfillmentOrder, FulfillmentOrderInput, FulfillmentProduct, FulfillmentProductInput, QlsApiResponse } from './client-types';
4
+ import { QlsServicePoint } from '../api/generated/graphql';
4
5
  export declare function getQlsClient(ctx: RequestContext, pluginOptions: QlsPluginOptions): Promise<QlsClient | undefined>;
5
6
  /**
6
7
  * Wrapper around the QLS Rest API.
@@ -30,5 +31,6 @@ export declare class QlsClient {
30
31
  * Add an extra barcode to a fulfillment product in QLS
31
32
  */
32
33
  removeBarcode(productId: string, barcodeId: number): Promise<void>;
33
- rawRequest<T>(method: 'POST' | 'GET' | 'PUT' | 'DELETE', action: string, data?: unknown): Promise<QlsApiResponse<T>>;
34
+ getServicePoints(countryCode: string, postalCode: string): Promise<QlsServicePoint[]>;
35
+ rawRequest<T>(method: 'POST' | 'GET' | 'PUT' | 'DELETE', action: string, data?: unknown): Promise<QlsApiResponse<T | undefined>>;
34
36
  }
@@ -26,7 +26,7 @@ class QlsClient {
26
26
  */
27
27
  async getFulfillmentProductBySku(sku) {
28
28
  const result = await this.rawRequest('GET', `fulfillment/products?filter%5Bsku%5D=${encodeURIComponent(sku)}`);
29
- if (result.data.length === 0) {
29
+ if (!result.data || result.data.length === 0) {
30
30
  return undefined;
31
31
  }
32
32
  if (result.data.length > 1) {
@@ -57,10 +57,16 @@ class QlsClient {
57
57
  }
58
58
  async createFulfillmentProduct(data) {
59
59
  const response = await this.rawRequest('POST', 'fulfillment/products', data);
60
+ if (!response.data) {
61
+ throw new Error('Failed to create fulfillment product. Got empty response.');
62
+ }
60
63
  return response.data;
61
64
  }
62
65
  async updateFulfillmentProduct(fulfillmentProductId, data) {
63
66
  const response = await this.rawRequest('PUT', `fulfillment/products/${fulfillmentProductId}`, data);
67
+ if (!response.data) {
68
+ throw new Error('Failed to update fulfillment product. Got empty response.');
69
+ }
64
70
  return response.data;
65
71
  }
66
72
  async createFulfillmentOrder(data) {
@@ -68,6 +74,9 @@ class QlsClient {
68
74
  ...data,
69
75
  brand_id: this.config.brandId,
70
76
  });
77
+ if (!response.data) {
78
+ throw new Error('Failed to create fulfillment order. Got empty response.');
79
+ }
71
80
  return response.data;
72
81
  }
73
82
  /**
@@ -84,6 +93,11 @@ class QlsClient {
84
93
  async removeBarcode(productId, barcodeId) {
85
94
  await this.rawRequest('DELETE', `fulfillment/products/${productId}/barcodes/${barcodeId}`);
86
95
  }
96
+ async getServicePoints(countryCode, postalCode) {
97
+ countryCode = countryCode.toUpperCase();
98
+ const result = await this.rawRequest('GET', `service-points/${countryCode}/${postalCode}`);
99
+ return result.data ?? [];
100
+ }
87
101
  async rawRequest(method, action, data) {
88
102
  // Set headers
89
103
  const headers = {
@@ -98,6 +112,15 @@ class QlsClient {
98
112
  headers,
99
113
  body,
100
114
  });
115
+ if (response.status === 204) {
116
+ // 204 No Content
117
+ return {
118
+ data: undefined,
119
+ meta: {
120
+ code: response.status,
121
+ },
122
+ };
123
+ }
101
124
  if (!response.ok) {
102
125
  const errorText = await response.text();
103
126
  // Log error including the request body
@@ -15,6 +15,7 @@ const core_1 = require("@vendure/core");
15
15
  const path_1 = __importDefault(require("path"));
16
16
  const api_extensions_1 = require("./api/api-extensions");
17
17
  const qls_admin_resolver_1 = require("./api/qls-admin.resolver");
18
+ const qls_shop_resolver_1 = require("./api/qls-shop.resolver");
18
19
  const qls_webhooks_controller_1 = require("./api/qls-webhooks-controller");
19
20
  const permissions_1 = require("./config/permissions");
20
21
  const constants_1 = require("./constants");
@@ -49,6 +50,7 @@ exports.QlsPlugin = QlsPlugin = QlsPlugin_1 = __decorate([
49
50
  config.authOptions.customPermissions.push(permissions_1.qlsFullSyncPermission);
50
51
  config.authOptions.customPermissions.push(permissions_1.qlsPushOrderPermission);
51
52
  config.customFields.ProductVariant.push(...custom_fields_1.variantCustomFields);
53
+ config.customFields.Order.push(...custom_fields_1.orderCustomFields);
52
54
  return config;
53
55
  },
54
56
  compatibility: '>=3.2.0',
@@ -56,5 +58,9 @@ exports.QlsPlugin = QlsPlugin = QlsPlugin_1 = __decorate([
56
58
  schema: api_extensions_1.adminApiExtensions,
57
59
  resolvers: [qls_admin_resolver_1.QlsAdminResolver],
58
60
  },
61
+ shopApiExtensions: {
62
+ schema: api_extensions_1.shopApiExtensions,
63
+ resolvers: [qls_shop_resolver_1.QlsShopResolver],
64
+ },
59
65
  })
60
66
  ], QlsPlugin);
@@ -3,6 +3,7 @@ import { ModuleRef } from '@nestjs/core';
3
3
  import { EventBus, ID, Job, JobQueueService, OrderService, RequestContext, TransactionalConnection } from '@vendure/core';
4
4
  import { IncomingOrderWebhook } from '../lib/client-types';
5
5
  import { QlsOrderJobData, QlsPluginOptions } from '../types';
6
+ import { QlsServicePoint, QlsServicePointSearchInput } from '../api/generated/graphql';
6
7
  export declare class QlsOrderService implements OnModuleInit, OnApplicationBootstrap {
7
8
  private connection;
8
9
  private options;
@@ -25,5 +26,6 @@ export declare class QlsOrderService implements OnModuleInit, OnApplicationBoots
25
26
  */
26
27
  handleOrderStatusUpdate(ctx: RequestContext, body: IncomingOrderWebhook): Promise<void>;
27
28
  triggerPushOrder(ctx: RequestContext, orderId: ID, orderCode?: string): Promise<Job<QlsOrderJobData> | undefined>;
29
+ getServicePoints(ctx: RequestContext, input: QlsServicePointSearchInput): Promise<QlsServicePoint[]>;
28
30
  private getVendureOrderState;
29
31
  }
@@ -115,12 +115,12 @@ let QlsOrderService = class QlsOrderService {
115
115
  const qlsOrder = {
116
116
  customer_reference: order.code,
117
117
  processable: new Date().toISOString(), // Processable starting now
118
- servicepoint_code: additionalOrderFields?.servicepoint_code,
118
+ servicepoint_code: order.customFields?.qlsServicePointId,
119
119
  delivery_options: additionalOrderFields?.delivery_options ?? [],
120
120
  total_price: order.totalWithTax,
121
121
  receiver_contact: {
122
122
  name: order.shippingAddress.fullName || customerName,
123
- companyname: order.shippingAddress.company,
123
+ companyname: order.shippingAddress.company ?? customerName,
124
124
  street: order.shippingAddress.streetLine1,
125
125
  housenumber: order.shippingAddress.streetLine2,
126
126
  postalcode: order.shippingAddress.postalCode,
@@ -185,6 +185,13 @@ let QlsOrderService = class QlsOrderService {
185
185
  orderId,
186
186
  }, { retries: 5 });
187
187
  }
188
+ async getServicePoints(ctx, input) {
189
+ const client = await (0, qls_client_1.getQlsClient)(ctx, this.options);
190
+ if (!client) {
191
+ throw new core_2.UserInputError(`QLS not enabled for channel ${ctx.channel.token}`);
192
+ }
193
+ return await client.getServicePoints(input.countryCode, input.postalCode);
194
+ }
188
195
  getVendureOrderState(body) {
189
196
  if (body.cancelled) {
190
197
  return 'Cancelled';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinelab/vendure-plugin-qls-fulfillment",
3
- "version": "1.0.0-beta.11",
3
+ "version": "1.0.0-beta.13",
4
4
  "description": "Vendure plugin to fulfill orders via QLS.",
5
5
  "keywords": [
6
6
  "fulfillment",