@dotdev/harmony-sdk 1.0.6

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.
Files changed (124) hide show
  1. package/README.md +88 -0
  2. package/dist/HarmonyAPI.d.ts +37 -0
  3. package/dist/HarmonyAPI.js +113 -0
  4. package/dist/errors/index.d.ts +15 -0
  5. package/dist/errors/index.js +18 -0
  6. package/dist/helpers/index.d.ts +50 -0
  7. package/dist/helpers/index.js +167 -0
  8. package/dist/helpers/index.spec.d.ts +1 -0
  9. package/dist/helpers/index.spec.js +147 -0
  10. package/dist/helpers/mapper.d.ts +2 -0
  11. package/dist/helpers/mapper.js +28 -0
  12. package/dist/helpers/utils.d.ts +71 -0
  13. package/dist/helpers/utils.js +133 -0
  14. package/dist/helpers/utils.spec.d.ts +1 -0
  15. package/dist/helpers/utils.spec.js +96 -0
  16. package/dist/index.d.ts +5 -0
  17. package/dist/index.js +5 -0
  18. package/dist/modules/auth/auth.module.d.ts +7 -0
  19. package/dist/modules/auth/auth.module.js +42 -0
  20. package/dist/modules/auth/auth.module.spec.d.ts +1 -0
  21. package/dist/modules/auth/auth.module.spec.js +55 -0
  22. package/dist/modules/auth/index.d.ts +2 -0
  23. package/dist/modules/auth/index.js +2 -0
  24. package/dist/modules/auth/types/index.d.ts +7 -0
  25. package/dist/modules/auth/types/index.js +1 -0
  26. package/dist/modules/carrier/carrier.module.d.ts +10 -0
  27. package/dist/modules/carrier/carrier.module.js +39 -0
  28. package/dist/modules/carrier/carrier.module.spec.d.ts +1 -0
  29. package/dist/modules/carrier/carrier.module.spec.js +187 -0
  30. package/dist/modules/carrier/index.d.ts +3 -0
  31. package/dist/modules/carrier/index.js +3 -0
  32. package/dist/modules/carrier/mappings/index.d.ts +5 -0
  33. package/dist/modules/carrier/mappings/index.js +36 -0
  34. package/dist/modules/carrier/types/index.d.ts +42 -0
  35. package/dist/modules/carrier/types/index.js +2 -0
  36. package/dist/modules/diary/diary.module.d.ts +9 -0
  37. package/dist/modules/diary/diary.module.js +28 -0
  38. package/dist/modules/diary/index.d.ts +3 -0
  39. package/dist/modules/diary/index.js +3 -0
  40. package/dist/modules/diary/mappings/diary.mapper.d.ts +4 -0
  41. package/dist/modules/diary/mappings/diary.mapper.js +101 -0
  42. package/dist/modules/diary/mappings/diary.mapper.spec.d.ts +1 -0
  43. package/dist/modules/diary/mappings/diary.mapper.spec.js +18 -0
  44. package/dist/modules/diary/mappings/index.d.ts +1 -0
  45. package/dist/modules/diary/mappings/index.js +1 -0
  46. package/dist/modules/diary/types/diary.interface.d.ts +138 -0
  47. package/dist/modules/diary/types/diary.interface.js +24 -0
  48. package/dist/modules/diary/types/index.d.ts +1 -0
  49. package/dist/modules/diary/types/index.js +1 -0
  50. package/dist/modules/gift-voucher/gift-voucher.module.d.ts +12 -0
  51. package/dist/modules/gift-voucher/gift-voucher.module.js +55 -0
  52. package/dist/modules/gift-voucher/gift-voucher.module.spec.d.ts +1 -0
  53. package/dist/modules/gift-voucher/gift-voucher.module.spec.js +296 -0
  54. package/dist/modules/gift-voucher/index.d.ts +1 -0
  55. package/dist/modules/gift-voucher/index.js +1 -0
  56. package/dist/modules/gift-voucher/mappings/index.d.ts +6 -0
  57. package/dist/modules/gift-voucher/mappings/index.js +70 -0
  58. package/dist/modules/gift-voucher/mappings/index.spec.d.ts +1 -0
  59. package/dist/modules/gift-voucher/mappings/index.spec.js +21 -0
  60. package/dist/modules/gift-voucher/types/index.d.ts +92 -0
  61. package/dist/modules/gift-voucher/types/index.js +1 -0
  62. package/dist/modules/index.d.ts +8 -0
  63. package/dist/modules/index.js +8 -0
  64. package/dist/modules/point-of-sale/index.d.ts +3 -0
  65. package/dist/modules/point-of-sale/index.js +3 -0
  66. package/dist/modules/point-of-sale/mappings/index.d.ts +1 -0
  67. package/dist/modules/point-of-sale/mappings/index.js +1 -0
  68. package/dist/modules/point-of-sale/mappings/process-sale-order.mapper.d.ts +6 -0
  69. package/dist/modules/point-of-sale/mappings/process-sale-order.mapper.js +142 -0
  70. package/dist/modules/point-of-sale/point-of-sale.module.d.ts +43 -0
  71. package/dist/modules/point-of-sale/point-of-sale.module.js +64 -0
  72. package/dist/modules/point-of-sale/point-of-sale.module.spec.d.ts +1 -0
  73. package/dist/modules/point-of-sale/point-of-sale.module.spec.js +205 -0
  74. package/dist/modules/point-of-sale/types/cancel-existing-sales-order.interface.d.ts +13 -0
  75. package/dist/modules/point-of-sale/types/cancel-existing-sales-order.interface.js +1 -0
  76. package/dist/modules/point-of-sale/types/index.d.ts +124 -0
  77. package/dist/modules/point-of-sale/types/index.js +31 -0
  78. package/dist/modules/point-of-sale/types/modify-existing-sales-order.interface.d.ts +12 -0
  79. package/dist/modules/point-of-sale/types/modify-existing-sales-order.interface.js +1 -0
  80. package/dist/modules/point-of-sale/types/process-returns.interface.d.ts +17 -0
  81. package/dist/modules/point-of-sale/types/process-returns.interface.js +1 -0
  82. package/dist/modules/point-of-sale/types/process-sale-order.interface.d.ts +67 -0
  83. package/dist/modules/point-of-sale/types/process-sale-order.interface.js +1 -0
  84. package/dist/modules/shared/index.d.ts +1 -0
  85. package/dist/modules/shared/index.js +1 -0
  86. package/dist/modules/shared/types/index.d.ts +93 -0
  87. package/dist/modules/shared/types/index.js +53 -0
  88. package/dist/modules/stock-level-lookup/index.d.ts +3 -0
  89. package/dist/modules/stock-level-lookup/index.js +3 -0
  90. package/dist/modules/stock-level-lookup/mappings/index.d.ts +1 -0
  91. package/dist/modules/stock-level-lookup/mappings/index.js +1 -0
  92. package/dist/modules/stock-level-lookup/mappings/stock-level-lookup.mapper.d.ts +4 -0
  93. package/dist/modules/stock-level-lookup/mappings/stock-level-lookup.mapper.js +78 -0
  94. package/dist/modules/stock-level-lookup/mappings/stock-level-lookup.mapper.spec.d.ts +1 -0
  95. package/dist/modules/stock-level-lookup/mappings/stock-level-lookup.mapper.spec.js +57 -0
  96. package/dist/modules/stock-level-lookup/stock-level-lookup.module.d.ts +10 -0
  97. package/dist/modules/stock-level-lookup/stock-level-lookup.module.js +39 -0
  98. package/dist/modules/stock-level-lookup/stock-level-lookup.module.spec.d.ts +1 -0
  99. package/dist/modules/stock-level-lookup/stock-level-lookup.module.spec.js +317 -0
  100. package/dist/modules/stock-level-lookup/types/index.d.ts +1 -0
  101. package/dist/modules/stock-level-lookup/types/index.js +1 -0
  102. package/dist/modules/stock-level-lookup/types/stock-level-lookup.interface.d.ts +162 -0
  103. package/dist/modules/stock-level-lookup/types/stock-level-lookup.interface.js +61 -0
  104. package/dist/modules/stock-lookup/index.d.ts +3 -0
  105. package/dist/modules/stock-lookup/index.js +3 -0
  106. package/dist/modules/stock-lookup/mappings/index.d.ts +1 -0
  107. package/dist/modules/stock-lookup/mappings/index.js +1 -0
  108. package/dist/modules/stock-lookup/mappings/stock-lookup.mapper.d.ts +22 -0
  109. package/dist/modules/stock-lookup/mappings/stock-lookup.mapper.js +156 -0
  110. package/dist/modules/stock-lookup/mappings/stock-lookup.mapper.spec.d.ts +1 -0
  111. package/dist/modules/stock-lookup/mappings/stock-lookup.mapper.spec.js +92 -0
  112. package/dist/modules/stock-lookup/stock-lookup.module.d.ts +65 -0
  113. package/dist/modules/stock-lookup/stock-lookup.module.js +141 -0
  114. package/dist/modules/stock-lookup/stock-lookup.module.spec.d.ts +1 -0
  115. package/dist/modules/stock-lookup/stock-lookup.module.spec.js +419 -0
  116. package/dist/modules/stock-lookup/types/index.d.ts +1 -0
  117. package/dist/modules/stock-lookup/types/index.js +1 -0
  118. package/dist/modules/stock-lookup/types/stock-lookup.interface.d.ts +242 -0
  119. package/dist/modules/stock-lookup/types/stock-lookup.interface.js +82 -0
  120. package/dist/modules/stock-lookup/types/stock-lookup.interface.spec.d.ts +1 -0
  121. package/dist/modules/stock-lookup/types/stock-lookup.interface.spec.js +76 -0
  122. package/dist/types/index.d.ts +5 -0
  123. package/dist/types/index.js +6 -0
  124. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Harmony API SDK
2
+
3
+ Harmony API SDK is a TypeScript library for interacting with the Harmony API. It provides a simple and type-safe way to use various Harmony services.
4
+
5
+ ## Endpoints
6
+
7
+ - Authentication
8
+ - Stock lookup
9
+ - [Other main features...]
10
+
11
+ ## Installation (Pending package publication)
12
+
13
+ As this package is currently developed as part of a monorepo, you need to clone the entire repository and install dependencies locally.
14
+
15
+ ```bash
16
+ git clone [your monorepo repository URL]
17
+ cd [your monorepo directory]
18
+ pnpm install
19
+ ```
20
+
21
+ ## Local Build
22
+
23
+ To build the SDK locally, run the following command:
24
+
25
+ 1. Navigate to the SDK directory
26
+
27
+ ```bash
28
+ cd packages/harmony-sdk
29
+ ```
30
+
31
+ 2. Run the build command - This will generate compiled JavaScript files and type declaration files in the `dist` directory.
32
+
33
+ ```bash
34
+ pnpm run build
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ Here's a basic example of how to use the Harmony SDK:
40
+
41
+ ```typescript
42
+ import { HarmonyAPI } from "@dotapparel/harmony-sdk";
43
+
44
+ const api = new HarmonyAPI("hostname", "port");
45
+
46
+ async function main() {
47
+ try {
48
+ const token = await api.authenticate({
49
+ username: "your-username",
50
+ method: "AGENT",
51
+ password: "your-password",
52
+ });
53
+
54
+ const stockInfo = await api.stockLookup(token, {
55
+ SearchType: "Barcode",
56
+ SearchValue: "9338726242178",
57
+ });
58
+
59
+ console.log("Stock info:", stockInfo);
60
+ } catch (error) {
61
+ console.error("An error occurred:", error);
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Development
67
+
68
+ To contribute to this package:
69
+
70
+ 1. Make your changes in the src directory.
71
+ 1. Write tests for your changes in the tests directory.
72
+ 1. Run tests:
73
+
74
+ ```bash
75
+ pnpm run test
76
+ ```
77
+
78
+ 4. Build the SDK:
79
+
80
+ ```bash
81
+ pnpm run build
82
+ ```
83
+
84
+ 5. Commit your changes and open a pull request.
85
+
86
+ ## Publishing
87
+
88
+ TODO: Add publishing instructions
@@ -0,0 +1,37 @@
1
+ import { AuthenticationToken, Carrier, CarrierShipment, CreateDiaryQueryParams, Diary, GetCarrierShipmentByTimeQueryParams, GetDiaryQueryParams, ProcessSaleOrderQueryParams, SizeGrid, Stock, StockBarcode, StockCategory, StockClassification, StockClassificationType, StockColor, StockLevel, StockLevelLookupQueryParams, StockLookupRequestParams, Warehouse } from "./modules";
2
+ import { GiftVoucher, GiftVoucherLookupQueryParams, GiftVoucherReservationQueryParams, GiftVoucherUsedQueryParams, GiftVoucherVerificationKeyQueryParams } from "./modules/gift-voucher/types";
3
+ export declare class HarmonyAPI {
4
+ private baseUrl;
5
+ private axiosInstance;
6
+ private authModule;
7
+ private stockLookupModule;
8
+ private stockLevelLookupModule;
9
+ private pointOfSaleModule;
10
+ private carrierModule;
11
+ private diaryModule;
12
+ private giftVoucherModule;
13
+ constructor(url: string);
14
+ authenticate(authToken: AuthenticationToken): Promise<string>;
15
+ stockLookup(params: StockLookupRequestParams, sessionId: string): Promise<Stock[]>;
16
+ stockCategoryLookup(sessionId: string): Promise<StockCategory[]>;
17
+ stockBarcodeLookup(params: StockLookupRequestParams, sessionId: string): Promise<StockBarcode[]>;
18
+ stockColorLookup(sessionId: string): Promise<StockColor[]>;
19
+ sizeGridLookup(sessionId: string): Promise<SizeGrid[]>;
20
+ stockClassificationLookup(sessionId: string, searchType?: StockClassificationType): Promise<StockClassification[]>;
21
+ warehouseLookup(sessionId: string): Promise<Warehouse[]>;
22
+ stockLevelLookup(params: StockLevelLookupQueryParams, sessionId: string): Promise<StockLevel[]>;
23
+ stockLevelLookupByWarehouseQuickView(params: StockLevelLookupQueryParams, sessionId: string): Promise<StockLevel[]>;
24
+ processSalesOrder(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
25
+ processReturns(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
26
+ modifyExistingSalesOrder(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
27
+ cancelExistingSalesOrder(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
28
+ getCarrier(sessionId: string): Promise<Carrier[]>;
29
+ getCarrierShipmentByOrderNo(orderNo: string, sessionId: string): Promise<CarrierShipment[]>;
30
+ getCarrierBySysTime(params: GetCarrierShipmentByTimeQueryParams, sessionId: string): Promise<CarrierShipment[]>;
31
+ getDiaryBy(params: GetDiaryQueryParams, sessionId: string): Promise<Diary[]>;
32
+ createDiary(params: CreateDiaryQueryParams, sessionId: string): Promise<boolean>;
33
+ giftVoucherLookup(params: GiftVoucherLookupQueryParams, sessionId: string): Promise<GiftVoucher[]>;
34
+ giftVoucherVerificationKey(params: GiftVoucherVerificationKeyQueryParams, sessionId: string): Promise<string>;
35
+ giftVoucherReservation(params: GiftVoucherReservationQueryParams, sessionId: string): Promise<string>;
36
+ giftVoucherUsed(params: GiftVoucherUsedQueryParams, sessionId: string): Promise<number>;
37
+ }
@@ -0,0 +1,113 @@
1
+ import axios from "axios";
2
+ import * as https from "https";
3
+ import { promisify } from "util";
4
+ import { parseString } from "xml2js";
5
+ import { AuthModule, CarrierModule, DiaryModule, GiftVoucherModule, PointOfSaleModule, StockClassificationType, StockLevelLookupModule, StockLookupModule, } from "./modules";
6
+ require("tls").DEFAULT_MIN_VERSION = "TLSv1";
7
+ const parseXml = promisify(parseString);
8
+ export class HarmonyAPI {
9
+ baseUrl;
10
+ axiosInstance;
11
+ authModule;
12
+ stockLookupModule;
13
+ stockLevelLookupModule;
14
+ pointOfSaleModule;
15
+ carrierModule;
16
+ diaryModule;
17
+ giftVoucherModule;
18
+ constructor(url) {
19
+ this.baseUrl = url;
20
+ this.axiosInstance = axios.create({
21
+ baseURL: this.baseUrl,
22
+ headers: {
23
+ "Content-Type": "text/xml;charset=UTF-8",
24
+ },
25
+ httpsAgent: new https.Agent({
26
+ rejectUnauthorized: false,
27
+ }),
28
+ });
29
+ // Initialise modules that correspond to Harmony API services
30
+ this.authModule = new AuthModule(this.axiosInstance);
31
+ this.stockLookupModule = new StockLookupModule(this.axiosInstance);
32
+ this.stockLevelLookupModule = new StockLevelLookupModule(this.axiosInstance);
33
+ this.pointOfSaleModule = new PointOfSaleModule(this.axiosInstance);
34
+ this.carrierModule = new CarrierModule(this.axiosInstance);
35
+ this.diaryModule = new DiaryModule(this.axiosInstance);
36
+ this.giftVoucherModule = new GiftVoucherModule(this.axiosInstance);
37
+ }
38
+ async authenticate(authToken) {
39
+ return await this.authModule.authenticate(authToken);
40
+ }
41
+ // Stock lookup module
42
+ async stockLookup(params, sessionId) {
43
+ return await this.stockLookupModule.stockLookup(params, sessionId);
44
+ }
45
+ async stockCategoryLookup(sessionId) {
46
+ return await this.stockLookupModule.stockCategoryLookup(sessionId);
47
+ }
48
+ async stockBarcodeLookup(params, sessionId) {
49
+ return await this.stockLookupModule.stockBarcodeLookup(params, sessionId);
50
+ }
51
+ async stockColorLookup(sessionId) {
52
+ return await this.stockLookupModule.stockColorLookup(sessionId);
53
+ }
54
+ async sizeGridLookup(sessionId) {
55
+ return await this.stockLookupModule.sizeGridLookup(sessionId);
56
+ }
57
+ async stockClassificationLookup(sessionId, searchType = StockClassificationType.STK1) {
58
+ return await this.stockLookupModule.stockClassificationLookup(sessionId, searchType);
59
+ }
60
+ // Stock level lookup module
61
+ async warehouseLookup(sessionId) {
62
+ return await this.stockLevelLookupModule.warehouseLookup(sessionId);
63
+ }
64
+ async stockLevelLookup(params, sessionId) {
65
+ return await this.stockLevelLookupModule.stockLevelLookup(params, sessionId);
66
+ }
67
+ async stockLevelLookupByWarehouseQuickView(params, sessionId) {
68
+ return await this.stockLevelLookupModule.stockLevelLookupByWarehouseQuickView(params, sessionId);
69
+ }
70
+ // Point of sale module
71
+ async processSalesOrder(params, sessionId) {
72
+ return await this.pointOfSaleModule.processSalesOrder(params, sessionId);
73
+ }
74
+ async processReturns(params, sessionId) {
75
+ return await this.pointOfSaleModule.processReturns(params, sessionId);
76
+ }
77
+ async modifyExistingSalesOrder(params, sessionId) {
78
+ return await this.pointOfSaleModule.modifyExistingSalesOrder(params, sessionId);
79
+ }
80
+ async cancelExistingSalesOrder(params, sessionId) {
81
+ return await this.pointOfSaleModule.cancelExistingSalesOrder(params, sessionId);
82
+ }
83
+ // Carrier module
84
+ async getCarrier(sessionId) {
85
+ return await this.carrierModule.getCarrier(sessionId);
86
+ }
87
+ async getCarrierShipmentByOrderNo(orderNo, sessionId) {
88
+ return await this.carrierModule.getCarrierShipmentByOrderNumber(orderNo, sessionId);
89
+ }
90
+ async getCarrierBySysTime(params, sessionId) {
91
+ return await this.carrierModule.getCarrierShipmentBySysTime(params, sessionId);
92
+ }
93
+ // Diary module
94
+ async getDiaryBy(params, sessionId) {
95
+ return await this.diaryModule.getDiaryBy(params, sessionId);
96
+ }
97
+ async createDiary(params, sessionId) {
98
+ return await this.diaryModule.createDiary(params, sessionId);
99
+ }
100
+ // Gift voucher module
101
+ async giftVoucherLookup(params, sessionId) {
102
+ return this.giftVoucherModule.giftVoucherLookup(params, sessionId);
103
+ }
104
+ async giftVoucherVerificationKey(params, sessionId) {
105
+ return this.giftVoucherModule.giftVoucherVerificationKey(params, sessionId);
106
+ }
107
+ async giftVoucherReservation(params, sessionId) {
108
+ return this.giftVoucherModule.giftVoucherVerificationKey(params, sessionId);
109
+ }
110
+ async giftVoucherUsed(params, sessionId) {
111
+ return this.giftVoucherModule.giftVoucherUsed(params, sessionId);
112
+ }
113
+ }
@@ -0,0 +1,15 @@
1
+ export declare class HarmonyAPIError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class AuthenticationError extends HarmonyAPIError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class RequestError extends HarmonyAPIError {
8
+ constructor(message: string);
9
+ }
10
+ export interface ApiError {
11
+ status?: string;
12
+ code?: string;
13
+ name?: string;
14
+ message?: string;
15
+ }
@@ -0,0 +1,18 @@
1
+ export class HarmonyAPIError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "HarmonyAPIError";
5
+ }
6
+ }
7
+ export class AuthenticationError extends HarmonyAPIError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "AuthenticationError";
11
+ }
12
+ }
13
+ export class RequestError extends HarmonyAPIError {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "RequestError";
17
+ }
18
+ }
@@ -0,0 +1,50 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { ApiError, RequestError } from "../errors";
3
+ import { ParamLimits, ServiceAlias } from "../modules";
4
+ export * from "./utils";
5
+ export declare abstract class ApiHelper {
6
+ static sendSoapRequest(endpoint: string, axiosInstance: AxiosInstance, requestBody?: string, type?: "GET" | "POST"): Promise<any>;
7
+ /**
8
+ * Helper function to create SOAP request body for Harmony API
9
+ *
10
+ * @static
11
+ * @param {(string | string[])} methodName
12
+ * @param {string} sessionId
13
+ * @param {ServiceAlias} serviceAlias
14
+ * @param {?string} [body]
15
+ * @returns {string}
16
+ */
17
+ static createServiceRequestBody(methodName: string | string[], sessionId: string, serviceAlias: ServiceAlias, body?: string): string;
18
+ /**
19
+ * Private method to send SOAP request to different endpoint in Point Of Sale service in Harmony API
20
+ *
21
+ * @private
22
+ * @async
23
+ * @template R
24
+ * @param {string} methodName
25
+ * @param {string} sessionId
26
+ * @returns {Promise<R>}
27
+ */
28
+ static sendServiceRequest<R>(serviceName: string, methodName: string | string[], sessionId: string, serviceAlias: ServiceAlias, axios: AxiosInstance, body?: string): Promise<R>;
29
+ static parseError(error: any): Promise<RequestError>;
30
+ /**
31
+ * Extract error response from Harmony in XML format and convert it to JSON format for ease of debugging
32
+ *
33
+ * @static
34
+ * @async
35
+ * @param {*} error XML error string
36
+ * @returns {Promise<ApiError>} JSON error object
37
+ */
38
+ static parseHarmonyErrorResponse(error: any): Promise<ApiError>;
39
+ }
40
+ /**
41
+ * Check whether the provided parameters meet the specified limits. If any parameter exceeds the limit, an error will be thrown.
42
+ * If all checks passed, return true.
43
+ *
44
+ * @export
45
+ * @template T
46
+ * @param {T} params set of parameters to be checked
47
+ * @param {ParamLimits<T>} limits limit object with same properties as the parameter object, with value of each key being a number or a function that returns a boolean
48
+ * @returns {boolean} returns true if all checks passed
49
+ */
50
+ export declare function checkParamLimits<T>(params: T, limits: ParamLimits<T>): boolean;
@@ -0,0 +1,167 @@
1
+ import axios from "axios";
2
+ import { promisify } from "util";
3
+ import { parseString } from "xml2js";
4
+ import { RequestError } from "../errors";
5
+ import { Utils } from "./utils";
6
+ export * from "./utils";
7
+ const parseXml = promisify(parseString);
8
+ export class ApiHelper {
9
+ static async sendSoapRequest(endpoint, axiosInstance, requestBody, type = "POST") {
10
+ const url = `${endpoint}?wsdl`;
11
+ try {
12
+ let response;
13
+ switch (type) {
14
+ case "GET":
15
+ response = await axiosInstance.get(url, {
16
+ params: {},
17
+ });
18
+ break;
19
+ case "POST":
20
+ default:
21
+ response = await axiosInstance.post(url, requestBody, {
22
+ // headers: {
23
+ // 'SOAPAction': request.action
24
+ // }
25
+ });
26
+ break;
27
+ }
28
+ const result = (await parseXml(response?.data)); // fix type
29
+ return result["S:Envelope"]["S:Body"][0];
30
+ }
31
+ catch (error) {
32
+ throw await ApiHelper.parseError(error);
33
+ }
34
+ }
35
+ /**
36
+ * Helper function to create SOAP request body for Harmony API
37
+ *
38
+ * @static
39
+ * @param {(string | string[])} methodName
40
+ * @param {string} sessionId
41
+ * @param {ServiceAlias} serviceAlias
42
+ * @param {?string} [body]
43
+ * @returns {string}
44
+ */
45
+ static createServiceRequestBody(methodName, sessionId, serviceAlias, body) {
46
+ const soapNamespace = "S";
47
+ // Extract method name and construct corresponding properties for the body of the SOAP request and the response from the API
48
+ const bodyMethodName = Array.isArray(methodName)
49
+ ? methodName.join("")
50
+ : methodName;
51
+ const soapBody = `<${serviceAlias}:${bodyMethodName}>${body ?? ""}</${serviceAlias}:${bodyMethodName}>`;
52
+ const soapHeader = `
53
+ <${serviceAlias}:SessionId>${sessionId}</${serviceAlias}:SessionId>
54
+ `;
55
+ return `
56
+ <${soapNamespace}:Envelope xmlns:${soapNamespace}="http://schemas.xmlsoap.org/soap/envelope/" xmlns:${serviceAlias}="http://${serviceAlias}.ws.fbsaust.com.au">
57
+ <${soapNamespace}:Header>
58
+ ${soapHeader}
59
+ </${soapNamespace}:Header>
60
+ <${soapNamespace}:Body>
61
+ ${soapBody}
62
+ </${soapNamespace}:Body>
63
+ </${soapNamespace}:Envelope>
64
+ `;
65
+ }
66
+ /**
67
+ * Private method to send SOAP request to different endpoint in Point Of Sale service in Harmony API
68
+ *
69
+ * @private
70
+ * @async
71
+ * @template R
72
+ * @param {string} methodName
73
+ * @param {string} sessionId
74
+ * @returns {Promise<R>}
75
+ */
76
+ static async sendServiceRequest(serviceName, methodName, sessionId, serviceAlias, axios, body) {
77
+ // Extract method name and construct corresponding properties for the body of the SOAP request and the response from the API
78
+ const responseMethodName = Array.isArray(methodName)
79
+ ? `ns2:${methodName[0]}Response`
80
+ : `ns2:${methodName}Response`;
81
+ const requestBody = ApiHelper.createServiceRequestBody(methodName, sessionId, serviceAlias, body);
82
+ const response = await ApiHelper.sendSoapRequest(serviceName, axios, requestBody);
83
+ return response[responseMethodName][0];
84
+ }
85
+ static async parseError(error) {
86
+ if (axios.isAxiosError(error)) {
87
+ if (error?.response) {
88
+ const parsedError = await ApiHelper.parseHarmonyErrorResponse(error?.response?.data);
89
+ return new RequestError(`API request failed with status ${error.response.status}: {code: ${parsedError?.code}, name: ${parsedError?.name}, message: ${parsedError?.message}}`);
90
+ }
91
+ else if (error?.request) {
92
+ return new RequestError("No response received from the server during API call");
93
+ }
94
+ else {
95
+ return new RequestError(`API request setup failed: ${error?.message}`);
96
+ }
97
+ }
98
+ else {
99
+ return new RequestError(`An unexpected error occurred during API request: ${JSON.stringify(error.message)}`);
100
+ }
101
+ }
102
+ /**
103
+ * Extract error response from Harmony in XML format and convert it to JSON format for ease of debugging
104
+ *
105
+ * @static
106
+ * @async
107
+ * @param {*} error XML error string
108
+ * @returns {Promise<ApiError>} JSON error object
109
+ */
110
+ static async parseHarmonyErrorResponse(error) {
111
+ if (error == null)
112
+ return { code: "", name: "", message: "" };
113
+ const result = (await parseXml(error));
114
+ const errorDetails = result["S:Envelope"]["S:Body"][0]["S:Fault"][0];
115
+ // Extract error response body
116
+ const code = Utils.nonEmptyArray(errorDetails["faultcode"])
117
+ ? errorDetails["faultcode"][0]
118
+ : "";
119
+ const name = Utils.nonEmptyArray(errorDetails["faultstring"])
120
+ ? errorDetails["faultstring"][0]
121
+ : "";
122
+ // extract error message
123
+ const detail = errorDetails["detail"];
124
+ const fault = Utils.nonEmptyArray(detail)
125
+ ? detail[0]["ns2:ServiceFault"]
126
+ : [];
127
+ const message = Utils.nonEmptyArray(fault) ? fault[0]["errorDetails"] : [];
128
+ return {
129
+ code,
130
+ name,
131
+ message: message[0] ?? "",
132
+ };
133
+ }
134
+ }
135
+ /**
136
+ * Check whether the provided parameters meet the specified limits. If any parameter exceeds the limit, an error will be thrown.
137
+ * If all checks passed, return true.
138
+ *
139
+ * @export
140
+ * @template T
141
+ * @param {T} params set of parameters to be checked
142
+ * @param {ParamLimits<T>} limits limit object with same properties as the parameter object, with value of each key being a number or a function that returns a boolean
143
+ * @returns {boolean} returns true if all checks passed
144
+ */
145
+ export function checkParamLimits(params, limits) {
146
+ for (const key in params) {
147
+ const val = params[key];
148
+ const limit = limits[key];
149
+ // Only perform checks if both param value and limit are defined
150
+ if (val && limit) {
151
+ // If limit type is a number a.k.a length limit, check if provided string value exceeds the length limit
152
+ if (typeof limit === "number") {
153
+ const valStr = typeof val === "string" ? val : val.toString();
154
+ if (valStr.length > limit) {
155
+ throw new Error(`Provided value for "${key}" exceeds limit of ${limit}`);
156
+ }
157
+ // Otherwise call limit function to determine whether value meets the condition
158
+ }
159
+ else {
160
+ if (!limit(val)) {
161
+ throw new Error(`Invalid value for parameter "${key}": ${val}`);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return true;
167
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,147 @@
1
+ import axios from "axios";
2
+ import { ApiHelper, checkParamLimits, Utils } from ".";
3
+ import { ServiceAlias } from "../modules/shared/types";
4
+ // Increase timeout due to Harmony API having long response time
5
+ jest.setTimeout(999999);
6
+ jest.mock("axios");
7
+ const mAxios = axios;
8
+ describe("ApiHelper", () => {
9
+ test("ApiHelper.sendSoapRequest", async () => {
10
+ const endpoint = "/StockLookupService/StockLookupService";
11
+ const sessionId = "test-session-id";
12
+ const requestBody = `
13
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:stk="http://stk.ws.fbsaust.com.au">
14
+ <S:Header>
15
+ <stk:SessionId>${sessionId}</stk:SessionId>
16
+ </S:Header>
17
+ <S:Body>
18
+ <stk:StockClassificationLookup>
19
+ <SearchType>STK00</SearchType>
20
+ </stk:StockClassificationLookup>
21
+ </S:Body>
22
+ </S:Envelope>
23
+ `;
24
+ mAxios.post.mockResolvedValueOnce({
25
+ data: `
26
+ <?xml version='1.0' encoding='UTF-8'?>
27
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
28
+ <S:Body>
29
+ <ns2:StockClassificationLookupResponse xmlns:ns2="http://stk.ws.fbsaust.com.au">
30
+ <StockClassification>
31
+ <code>BACK</code>
32
+ <description>TEST</description>
33
+ </StockClassification>
34
+ </ns2:StockClassificationLookupResponse>
35
+ </S:Body>
36
+ </S:Envelope>
37
+ `,
38
+ });
39
+ const response = await ApiHelper.sendSoapRequest(endpoint, mAxios, requestBody);
40
+ expect(response["ns2:StockClassificationLookupResponse"][0]
41
+ .StockClassification).toEqual([
42
+ {
43
+ code: ["BACK"],
44
+ description: ["TEST"],
45
+ },
46
+ ]);
47
+ });
48
+ test("createServiceRequestBody should return correct SOAP request body", () => {
49
+ const expected = `
50
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:stk="http://stk.ws.fbsaust.com.au">
51
+ <S:Header>
52
+ <stk:SessionId>ABC</stk:SessionId>
53
+ </S:Header>
54
+ <S:Body>
55
+ <stk:StockClassificationLookup>
56
+ <SearchType>STK99</SearchType>
57
+ </stk:StockClassificationLookup>
58
+ </S:Body>
59
+ </S:Envelope>
60
+ `;
61
+ const actual = ApiHelper.createServiceRequestBody("StockClassificationLookup", "ABC", ServiceAlias.STOCK_LOOKUP, "<SearchType>STK99</SearchType>");
62
+ expect(Utils.removeEmptySpaces(actual)).toEqual(Utils.removeEmptySpaces(expected));
63
+ });
64
+ });
65
+ describe("Other functions", () => {
66
+ test("checkParamLimit", () => {
67
+ const params = {
68
+ param1: 123,
69
+ param2: "abc",
70
+ param3: true,
71
+ };
72
+ // Matching param limit
73
+ const matchedParamLimit = {
74
+ param1: 3,
75
+ param2: 3,
76
+ param3: (val) => typeof val === "boolean" && val === true,
77
+ };
78
+ expect(checkParamLimits(params, matchedParamLimit)).toBe(true);
79
+ // Exceeded param limit
80
+ const exceededParamLimit = {
81
+ param1: 3,
82
+ param2: 2,
83
+ param3: (val) => typeof val === "boolean" && val === true,
84
+ };
85
+ expect(() => checkParamLimits(params, exceededParamLimit)).toThrow(`Provided value for "param2" exceeds limit of 2`);
86
+ // Limit using function return false
87
+ const falseParamLimit = {
88
+ param1: 3,
89
+ param2: 3,
90
+ param3: (val) => val === false,
91
+ };
92
+ expect(() => checkParamLimits(params, falseParamLimit)).toThrow(`Invalid value for parameter "param3": true`);
93
+ });
94
+ test("checkParamLimit with enum", () => {
95
+ let TestEnum;
96
+ (function (TestEnum) {
97
+ TestEnum[TestEnum["A"] = 0] = "A";
98
+ TestEnum[TestEnum["B"] = 1] = "B";
99
+ })(TestEnum || (TestEnum = {}));
100
+ const params = {
101
+ param1: TestEnum.A,
102
+ };
103
+ const wrongParams = {
104
+ param1: "C",
105
+ };
106
+ // Matching param limit
107
+ const matchedParamLimit = {
108
+ param1: (val) => Object.values(TestEnum).includes(val),
109
+ };
110
+ expect(checkParamLimits(params, matchedParamLimit)).toBe(true);
111
+ expect(() => checkParamLimits(wrongParams, matchedParamLimit)).toThrow(`Invalid value for parameter "param1": C`);
112
+ });
113
+ test("checkParamLimit with no limit", () => {
114
+ const params = {
115
+ param1: "asdf",
116
+ };
117
+ // Matching param limit
118
+ const matchedParamLimit = {};
119
+ expect(checkParamLimits(params, matchedParamLimit)).toBe(true);
120
+ });
121
+ });
122
+ describe("parseRequestError", () => {
123
+ it("should handle error correctly and return the error string", async () => {
124
+ const errResp = `
125
+ <?xml version='1.0' encoding='UTF-8'?>
126
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
127
+ <S:Body>
128
+ <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
129
+ <faultcode>S:Server</faultcode>
130
+ <faultstring>Failed Test Request</faultstring>
131
+ <detail>
132
+ <ns2:ServiceFault xmlns:ns2="http://pos.ws.fbsaust.com.au">
133
+ <errorDetails>[204-104-2408071537-0000851206] (Invalid value)</errorDetails>
134
+ </ns2:ServiceFault>
135
+ </detail>
136
+ </S:Fault>
137
+ </S:Body>
138
+ </S:Envelope>
139
+ `;
140
+ const expected = {
141
+ code: "S:Server",
142
+ name: "Failed Test Request",
143
+ message: "[204-104-2408071537-0000851206] (Invalid value)",
144
+ };
145
+ expect(await ApiHelper.parseHarmonyErrorResponse(errResp)).toEqual(expected);
146
+ });
147
+ });
@@ -0,0 +1,2 @@
1
+ import { OptionalStockQueryParams, OptionalStockRequestBody } from "../modules/shared/types";
2
+ export declare function mapOptionalStockQueryParams(src: OptionalStockQueryParams): OptionalStockRequestBody;
@@ -0,0 +1,28 @@
1
+ import { OptionalStockRequestBody, } from "../modules/shared/types";
2
+ export function mapOptionalStockQueryParams(src) {
3
+ return new OptionalStockRequestBody({
4
+ StockCategoryFr: src?.stockCategoryFr ?? "",
5
+ StockCategoryTo: src?.stockCategoryTo ?? "",
6
+ StockClassFr1: src?.stockClassFr1 ?? "",
7
+ StockClassTo1: src?.stockClassTo1 ?? "",
8
+ StockClassFr2: src?.stockClassFr2 ?? "",
9
+ StockClassTo2: src?.stockClassTo2 ?? "",
10
+ StockClassFr3: src?.stockClassFr3 ?? "",
11
+ StockClassTo3: src?.stockClassTo3 ?? "",
12
+ StockExtraClassFr1: src?.stockExtraClassFr1 ?? "",
13
+ StockExtraClassTo1: src?.stockExtraClassTo1 ?? "",
14
+ StockExtraClassFr2: src?.stockExtraClassFr2 ?? "",
15
+ StockExtraClassTo2: src?.stockExtraClassTo2 ?? "",
16
+ StockExtraClassFr3: src?.stockExtraClassFr3 ?? "",
17
+ StockExtraClassTo3: src?.stockExtraClassTo3 ?? "",
18
+ StockExtraClassFr4: src?.stockExtraClassFr4 ?? "",
19
+ StockExtraClassTo4: src?.stockExtraClassTo4 ?? "",
20
+ StockExtraClassFr5: src?.stockExtraClassFr5 ?? "",
21
+ StockExtraClassTo5: src?.stockExtraClassTo5 ?? "",
22
+ SysModTimeFr: src?.sysModTimeFr ?? "",
23
+ SysModTimeTo: src?.sysModTimeTo ?? "",
24
+ AlternateNamekeyFr: src?.alternateNamekeyFr ?? "",
25
+ AlternateNamekeyTo: src?.alternateNamekeyTo ?? "",
26
+ StockActive: src?.stockActive?.map(String)?.join("") ?? "",
27
+ });
28
+ }