@dotdev/harmony-sdk 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,7 @@ export declare class HarmonyAPI {
29
29
  mapOrderPayloadToXml(params: Partial<ProcessSaleOrderQueryParams>, sessionId: string, method?: "ProcessSalesOrder" | "ProcessSalesOrderWithoutPayment" | "ProcessInvoice"): Promise<string>;
30
30
  mapReturnPayloadToXml(params: ProcessReturnsQueryParams, sessionId: string): Promise<string>;
31
31
  processInvoice(params: ProcessInvoiceQueryParams, sessionId: string): Promise<boolean>;
32
+ processInvoiceSalesOrder(params: ProcessInvoiceQueryParams, sessionId: string): Promise<boolean>;
32
33
  processReturns(params: ProcessReturnsQueryParams, sessionId: string): Promise<boolean>;
33
34
  modifyExistingSalesOrder(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
34
35
  cancelExistingSalesOrder(params: ProcessSaleOrderQueryParams, sessionId: string): Promise<boolean>;
@@ -100,10 +100,15 @@ export class HarmonyAPI {
100
100
  async mapReturnPayloadToXml(params, sessionId) {
101
101
  return await this.pointOfSaleModule.mapReturnPayloadToXml(params, sessionId);
102
102
  }
103
- // Post fulfilled orders
103
+ // ProcessInvoice should have blank “order_no”, “order_deposit_total” should not be included or should be set to 0, “order_qty” should not be included.
104
104
  async processInvoice(params, sessionId) {
105
105
  return await this.pointOfSaleModule.processInvoice(params, sessionId);
106
106
  }
107
+ // Post fulfilled orders
108
+ // ProcessInvoice should have blank “order_no”, “order_deposit_total” should not be included or should be set to 0, “order_qty” should not be included.
109
+ async processInvoiceSalesOrder(params, sessionId) {
110
+ return await this.pointOfSaleModule.processInvoiceSalesOrder(params, sessionId);
111
+ }
107
112
  async processReturns(params, sessionId) {
108
113
  return await this.pointOfSaleModule.processReturns(params, sessionId);
109
114
  }
@@ -144,7 +149,7 @@ export class HarmonyAPI {
144
149
  return this.giftVoucherModule.giftVoucherVerificationKey(params, sessionId);
145
150
  }
146
151
  async giftVoucherReservation(params, sessionId) {
147
- return this.giftVoucherModule.giftVoucherVerificationKey(params, sessionId);
152
+ return this.giftVoucherModule.giftVoucherReservation(params, sessionId);
148
153
  }
149
154
  async giftVoucherUsed(params, sessionId) {
150
155
  return this.giftVoucherModule.giftVoucherUsed(params, sessionId);
@@ -99,21 +99,22 @@ export class ApiHelper {
99
99
  }
100
100
  static async parseError(error) {
101
101
  if (axios.isAxiosError(error)) {
102
+ const childLogger = logger.child({
103
+ error: {
104
+ config: error?.config,
105
+ request: error?.request,
106
+ data: error?.response?.data,
107
+ status: error?.response?.status,
108
+ },
109
+ });
102
110
  if (error?.response) {
103
111
  if (Utils.isValidXml(error?.response?.data)) {
104
112
  const parsedError = await ApiHelper.parseHarmonyErrorResponse(error?.response?.data);
113
+ childLogger.error(`Harmony request error`);
105
114
  return new RequestError(`API request failed with status ${error.response.status}: {code: ${parsedError?.code}, name: ${parsedError?.name}, message: ${parsedError?.message}}`);
106
115
  }
107
116
  else {
108
- logger.error(`API request error`);
109
- logger.child({
110
- error: {
111
- config: error?.config,
112
- request: error?.request,
113
- data: error?.response?.data,
114
- status: error?.response?.status,
115
- },
116
- });
117
+ childLogger.error(`API request error due to response not in Harmony XML format`);
117
118
  return new RequestError(`API request failed but error response is not in Harmony XML format. Please check the logs for more details.`);
118
119
  }
119
120
  }
@@ -124,10 +125,7 @@ export class ApiHelper {
124
125
  return new RequestError(`API request setup failed: ${error?.message}`);
125
126
  }
126
127
  }
127
- else {
128
- console.log(JSON.stringify(error));
129
- logger.error(`=============== Unexpected error occurred during API request ===============`);
130
- }
128
+ logger.error(error, `⛔️ Unexpected error occurred during API request`);
131
129
  return new RequestError(`Unexpected error occurred during API request`);
132
130
  }
133
131
  /**
@@ -161,7 +161,7 @@ export class Utils {
161
161
  static removeTabsAndNewLines(str) {
162
162
  if (!str)
163
163
  return "";
164
- return str.replace(/ |\r\n|\n|\r/gm, "");
164
+ return str.replace(/ |\r\n|\n|\r|\\t|\\r|\\n/gm, "");
165
165
  }
166
166
  }
167
167
  export const TEST_HEADERS = {
@@ -95,6 +95,11 @@ describe("Utils", () => {
95
95
  "2": "test",
96
96
  });
97
97
  });
98
+ test("removeTabsAndNewLines()", () => {
99
+ const input = " HelloWorld\n";
100
+ const expected = "HelloWorld";
101
+ expect(Utils.removeTabsAndNewLines(input)).toEqual(expected);
102
+ });
98
103
  });
99
104
  describe("Misc", () => {
100
105
  test("Axios should pass through request headers correctly", () => {
@@ -1,6 +1,7 @@
1
1
  import { ProcessInvoiceQueryParams, ProcessSaleOrderQueryDebtor, ProcessSaleOrderQueryExtra, ProcessSaleOrderQueryHeader, ProcessSaleOrderQueryParams, ProcessSaleOrderQueryStock, ProcessSaleOrderWithoutPaymentQueryParams } from "../types";
2
2
  export declare function mapProcessSaleOrderQueryParams(params: ProcessSaleOrderQueryParams): string;
3
3
  export declare function mapProcessInvoiceQueryParams(params: ProcessInvoiceQueryParams): string;
4
+ export declare function mapProcessInvoiceSalesOrderQueryParams(params: ProcessInvoiceQueryParams): string;
4
5
  export declare function mapProcessSaleOrderWithoutPaymentQueryParams(params: ProcessSaleOrderWithoutPaymentQueryParams): string;
5
6
  export declare function mapProcessSaleOrderBaseQueryParams(params: ProcessSaleOrderWithoutPaymentQueryParams): {
6
7
  mode: import("../types").PosQueryMode | undefined;
@@ -7,7 +7,7 @@ export function mapProcessSaleOrderQueryParams(params) {
7
7
  };
8
8
  return Utils.toXml(Utils.deleteEmptyProps(body));
9
9
  }
10
- export function mapProcessInvoiceQueryParams(params) {
10
+ function mapProcessInvoiceQueryParamsBase(params) {
11
11
  // XML body in JSON format, to be converted to XML string
12
12
  const body = {
13
13
  ...mapProcessSaleOrderBaseQueryParams(params),
@@ -16,6 +16,16 @@ export function mapProcessInvoiceQueryParams(params) {
16
16
  };
17
17
  return Utils.toXml(Utils.deleteEmptyProps(body));
18
18
  }
19
+ export function mapProcessInvoiceQueryParams(params) {
20
+ // for ProcessInvoice, payload should not include orderNo or orderDepositTotal
21
+ delete params.header.orderNo;
22
+ delete params.header.orderDepositTotal;
23
+ return mapProcessInvoiceQueryParamsBase(params);
24
+ }
25
+ export function mapProcessInvoiceSalesOrderQueryParams(params) {
26
+ // for ProcessInvoice, payload should not include orderNo or orderDepositTotal
27
+ return mapProcessInvoiceQueryParamsBase(params);
28
+ }
19
29
  export function mapProcessSaleOrderWithoutPaymentQueryParams(params) {
20
30
  return Utils.toXml(Utils.deleteEmptyProps(mapProcessSaleOrderBaseQueryParams(params)));
21
31
  }
@@ -39,6 +39,13 @@ export declare class PointOfSaleModule {
39
39
  * @param sessionId authenticated session ID string
40
40
  */
41
41
  processInvoice(params: ProcessInvoiceQueryParams, sessionId: string): Promise<boolean>;
42
+ /**
43
+ * Function to call the ProcessInvoiceSalesOrder endpoint in Harmony Point Of Sale service
44
+ * This is used to post orders that are already fulfilled (e.g. POS orders)
45
+ * @param params query params
46
+ * @param sessionId authenticated session ID string
47
+ */
48
+ processInvoiceSalesOrder(params: ProcessInvoiceQueryParams, sessionId: string): Promise<boolean>;
42
49
  /**
43
50
  * Function to call the ProcessReturns endpoint in Harmony Point Of Sale service
44
51
  *
@@ -1,9 +1,9 @@
1
1
  import { ApiHelper, Utils } from "../../helpers";
2
2
  import { ServiceAlias } from "../shared";
3
- import { mapPosInvoiceQuery, mapProcessInvoiceQueryParams, mapProcessSaleOrderQueryDebtor, mapProcessSaleOrderQueryExtra, mapProcessSaleOrderQueryHeaders, mapProcessSaleOrderQueryParams, mapProcessSaleOrderQueryStock, mapProcessSaleOrderWithoutPaymentQueryParams, } from "./mappings/process-sale-order.mapper";
3
+ import { mapPosInvoiceQuery, mapProcessInvoiceQueryParams, mapProcessInvoiceSalesOrderQueryParams, mapProcessSaleOrderQueryDebtor, mapProcessSaleOrderQueryExtra, mapProcessSaleOrderQueryHeaders, mapProcessSaleOrderQueryParams, mapProcessSaleOrderQueryStock, mapProcessSaleOrderWithoutPaymentQueryParams, } from "./mappings/process-sale-order.mapper";
4
4
  import { promisify } from "util";
5
5
  import { parseString } from "xml2js";
6
- import { mapProcessReturnsQueryExtra, mapProcessReturnsQueryHeaders, mapProcessReturnsQueryParams, mapProcessReturnsQueryStock, mapReturnsInvoiceQuery } from "./mappings/process-returns.mapper";
6
+ import { mapProcessReturnsQueryExtra, mapProcessReturnsQueryHeaders, mapProcessReturnsQueryParams, mapProcessReturnsQueryStock, mapReturnsInvoiceQuery, } from "./mappings/process-returns.mapper";
7
7
  const parseXml = promisify(parseString);
8
8
  export class PointOfSaleModule {
9
9
  axiosInstance;
@@ -94,7 +94,7 @@ export class PointOfSaleModule {
94
94
  extra: params?.extra ? mapProcessReturnsQueryExtra(params.extra) : "",
95
95
  stock: params?.stock?.map(mapProcessReturnsQueryStock),
96
96
  invoice: mapReturnsInvoiceQuery(params),
97
- debtor: (params?.debtor ?? []).map(mapProcessSaleOrderQueryDebtor)
97
+ debtor: (params?.debtor ?? []).map(mapProcessSaleOrderQueryDebtor),
98
98
  };
99
99
  const soapBodyJson = {};
100
100
  soapBodyJson[`${ServiceAlias.POINT_OF_SALE}:${bodyMethodName}`] =
@@ -148,6 +148,22 @@ export class PointOfSaleModule {
148
148
  throw await ApiHelper.parseError(error);
149
149
  }
150
150
  }
151
+ /**
152
+ * Function to call the ProcessInvoiceSalesOrder endpoint in Harmony Point Of Sale service
153
+ * This is used to post orders that are already fulfilled (e.g. POS orders)
154
+ * @param params query params
155
+ * @param sessionId authenticated session ID string
156
+ */
157
+ async processInvoiceSalesOrder(params, sessionId) {
158
+ try {
159
+ const requestBody = mapProcessInvoiceSalesOrderQueryParams(params);
160
+ const response = await ApiHelper.sendServiceRequest(this.SERVICE_URL, ["ProcessInvoiceSalesOrder", "Request"], sessionId, ServiceAlias.POINT_OF_SALE, this.axiosInstance, requestBody);
161
+ return Boolean(response?.result[0] ?? "false");
162
+ }
163
+ catch (error) {
164
+ throw await ApiHelper.parseError(error);
165
+ }
166
+ }
151
167
  /**
152
168
  * Function to call the ProcessReturns endpoint in Harmony Point Of Sale service
153
169
  *
@@ -376,7 +376,7 @@ describe("PointOfSaleModule", () => {
376
376
  <mode>U</mode>
377
377
  <audit_no>106942</audit_no>
378
378
  <header>
379
- <order_no>W00106943</order_no>
379
+ <invoice_no>IW00106943</invoice_no>
380
380
  <customer_order_no>W00106943</customer_order_no>
381
381
  <transaction_date>02-06-2015</transaction_date>
382
382
  <transaction_time_hour>11</transaction_time_hour>
@@ -391,7 +391,6 @@ describe("PointOfSaleModule", () => {
391
391
  <terminal_id>online</terminal_id>
392
392
  <user>WEB</user>
393
393
  <order_lines>0</order_lines>
394
- <order_deposit_total>119.95</order_deposit_total>
395
394
  <order_version>0</order_version>
396
395
  <originator_software>WEB</originator_software>
397
396
  <account_sale_flag>Y</account_sale_flag>
@@ -462,6 +461,7 @@ describe("PointOfSaleModule", () => {
462
461
  mode: PosQueryMode.UPDATE,
463
462
  auditNo: "106942",
464
463
  header: {
464
+ invoiceNo: "IW00106943",
465
465
  orderNo: "W00106943",
466
466
  customerOrderNo: "W00106943",
467
467
  transactionDate: "02-06-2015",
@@ -564,4 +564,207 @@ describe("PointOfSaleModule", () => {
564
564
  expect(Utils.removeEmptySpaces(actualPayload)).toEqual(Utils.removeEmptySpaces(expectedPayload));
565
565
  mAxios.post.mockClear();
566
566
  });
567
+ test("ProcessInvoiceSaleOrder should process the order correctly and return response", async () => {
568
+ const token = "test-token";
569
+ const ns = "S";
570
+ const expectedPayload = `
571
+ <${ns}:Envelope xmlns:${ns}="http://schemas.xmlsoap.org/soap/envelope/" xmlns:${ServiceAlias.POINT_OF_SALE}="http://${ServiceAlias.POINT_OF_SALE}.ws.fbsaust.com.au">
572
+ <${ns}:Header>
573
+ <${ServiceAlias.POINT_OF_SALE}:SessionId>${token}</${ServiceAlias.POINT_OF_SALE}:SessionId>
574
+ </${ns}:Header>
575
+ <${ns}:Body>
576
+ <${ServiceAlias.POINT_OF_SALE}:ProcessInvoiceSalesOrderRequest>
577
+ <mode>U</mode>
578
+ <audit_no>106942</audit_no>
579
+ <header>
580
+ <invoice_no>IW00106943</invoice_no>
581
+ <order_no>W00106943</order_no>
582
+ <customer_order_no>W00106943</customer_order_no>
583
+ <transaction_date>02-06-2015</transaction_date>
584
+ <transaction_time_hour>11</transaction_time_hour>
585
+ <transaction_time_min>52</transaction_time_min>
586
+ <transaction_time_sec>0</transaction_time_sec>
587
+ <shop_debtor>ABC</shop_debtor>
588
+ <diary>0005353</diary>
589
+ <diary_name_1>AMMY</diary_name_1>
590
+ <diary_name_2>WHITE</diary_name_2>
591
+ <warehouse>WEB</warehouse>
592
+ <agent_zone_1>1111</agent_zone_1>
593
+ <terminal_id>online</terminal_id>
594
+ <user>WEB</user>
595
+ <order_lines>0</order_lines>
596
+ <order_deposit_total>119.95</order_deposit_total>
597
+ <order_version>0</order_version>
598
+ <originator_software>WEB</originator_software>
599
+ <account_sale_flag>Y</account_sale_flag>
600
+ </header>
601
+ <extra>
602
+ <delivery_address_1>37</delivery_address_1>
603
+ <delivery_address_2>HOTHAM ST</delivery_address_2>
604
+ <delivery_address_3>ST KILDA EAST</delivery_address_3>
605
+ <delivery_address_4>VIC</delivery_address_4>
606
+ <delivery_postcode>3111</delivery_postcode>
607
+ <delivery_instruction_1>Enter backdoor</delivery_instruction_1>
608
+ <delivery_instruction_2>Then frontdoor</delivery_instruction_2>
609
+ <carrier>APOST</carrier>
610
+ <email>test@example.com</email>
611
+ <telephone_home>0444888885</telephone_home>
612
+ <telephone_mobile>0444888884</telephone_mobile>
613
+ <telephone_work>0444888883</telephone_work>
614
+ <billing_surname>DOE</billing_surname>
615
+ <billing_first_name>JOHN</billing_first_name>
616
+ <billing_address_1>37</billing_address_1>
617
+ <billing_address_2>HOTHAM ST</billing_address_2>
618
+ <billing_address_3>ST KILDA EAST</billing_address_3>
619
+ <billing_address_4>VIC</billing_address_4>
620
+ <billing_postcode>3111</billing_postcode>
621
+ <billing_telephone_home>0444888888</billing_telephone_home>
622
+ <billing_telephone_mobile>0444888887</billing_telephone_mobile>
623
+ <billing_telephone_work>0444888886</billing_telephone_work>
624
+ <customised_info>CUSTOM</customised_info>
625
+ </extra>
626
+ <stock>
627
+ <stock>7281VL.100</stock>
628
+ <stock_description>3/4 SLV CROSS OVER DRESS W/ TUCKS</stock_description>
629
+ <size>S</size>
630
+ <qdp>0</qdp>
631
+ <pdp>2</pdp>
632
+ <invoice_qty>0</invoice_qty>
633
+ <order_qty>1</order_qty>
634
+ <discount_price>119.95</discount_price>
635
+ <line_tax>0</line_tax>
636
+ <discount_rate>30</discount_rate>
637
+ <discount_reason>210</discount_reason>
638
+ <current_rrp_inc>149.95</current_rrp_inc>
639
+ </stock>
640
+ <invoice>
641
+ <invoice_price_inc>100.1</invoice_price_inc>
642
+ <rounding>1.1</rounding>
643
+ <total_tax>2.1</total_tax>
644
+ <cash_drawer_id>BIONLINE</cash_drawer_id>
645
+ </invoice>
646
+ <debtor>
647
+ <payment_type>4</payment_type>
648
+ <payment_amount>119.95</payment_amount>
649
+ <invoice_price_inc>0</invoice_price_inc>
650
+ <rounding>0</rounding>
651
+ <total_tax>0</total_tax>
652
+ <card_name>PPAL</card_name>
653
+ <card_no>ABC123</card_no>
654
+ <card_expiry_month>0</card_expiry_month>
655
+ <card_expiry_year>0</card_expiry_year>
656
+ <cash_drawer_id>ABC1</cash_drawer_id>
657
+ <reservation_history_no>0</reservation_history_no>
658
+ </debtor>
659
+ </${ServiceAlias.POINT_OF_SALE}:ProcessInvoiceSalesOrderRequest>
660
+ </${ns}:Body>
661
+ </${ns}:Envelope>
662
+ `;
663
+ const params = {
664
+ mode: PosQueryMode.UPDATE,
665
+ auditNo: "106942",
666
+ header: {
667
+ invoiceNo: "IW00106943",
668
+ orderNo: "W00106943",
669
+ customerOrderNo: "W00106943",
670
+ transactionDate: "02-06-2015",
671
+ transactionTimeHour: 11,
672
+ transactionTimeMin: 52,
673
+ transactionTimeSec: 0,
674
+ shopDebtor: "ABC",
675
+ diary: "0005353",
676
+ diaryName1: "AMMY",
677
+ diaryName2: "WHITE",
678
+ warehouse: "WEB",
679
+ agentZone1: 1111,
680
+ terminalId: "online",
681
+ user: "WEB",
682
+ orderLines: 0,
683
+ orderDepositTotal: 119.95,
684
+ orderVersion: 0,
685
+ originatorSoftware: "WEB",
686
+ accountSaleFlag: AccountSaleFlag.YES,
687
+ },
688
+ stock: [
689
+ {
690
+ stock: "7281VL.100",
691
+ stockDescription: "3/4 SLV CROSS OVER DRESS W/ TUCKS",
692
+ size: "S",
693
+ qdp: 0,
694
+ pdp: 2,
695
+ invoiceQty: 0.0,
696
+ orderQty: 1.0,
697
+ discountPrice: 119.95,
698
+ lineTax: 0.0,
699
+ discountRate: 30.0,
700
+ discountReason: "210",
701
+ currentRrpInc: 149.95,
702
+ },
703
+ ],
704
+ extra: {
705
+ deliveryAddress1: "37",
706
+ deliveryAddress2: "HOTHAM ST",
707
+ deliveryAddress3: "ST KILDA EAST",
708
+ deliveryAddress4: "VIC",
709
+ deliveryPostcode: "3111",
710
+ deliveryInstruction1: "Enter backdoor",
711
+ deliveryInstruction2: "Then frontdoor",
712
+ telephoneHome: "0444888885",
713
+ telephoneMobile: "0444888884",
714
+ telephoneWork: "0444888883",
715
+ email: "test@example.com",
716
+ billingAddress1: "37",
717
+ billingAddress2: "HOTHAM ST",
718
+ billingAddress3: "ST KILDA EAST",
719
+ billingAddress4: "VIC",
720
+ billingPostcode: "3111",
721
+ billingFirstname: "JOHN",
722
+ billingSurname: "DOE",
723
+ billingTelephoneHome: "0444888888",
724
+ billingTelephoneMobile: "0444888887",
725
+ billingTelephoneWork: "0444888886",
726
+ carrier: "APOST",
727
+ customisedInfo: "CUSTOM",
728
+ },
729
+ debtor: [
730
+ {
731
+ paymentType: PosPaymentType.CARD,
732
+ paymentAmount: 119.95,
733
+ invoicePriceInc: 0.0,
734
+ rounding: 0.0,
735
+ totalTax: 0.0,
736
+ cardName: CardName.PAYPAL,
737
+ cardNo: "ABC123",
738
+ cardExpiryMonth: 0,
739
+ cardExpiryYear: 0,
740
+ cashDrawerId: "ABC1",
741
+ reservationHistoryNo: 0,
742
+ },
743
+ ],
744
+ invoice: {
745
+ invoicePriceInc: 100.1,
746
+ rounding: 1.1,
747
+ totalTax: 2.1,
748
+ cashDrawerId: "BIONLINE",
749
+ },
750
+ };
751
+ const mockedResp = `
752
+ <?xml version='1.0' encoding='UTF-8'?>
753
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
754
+ <S:Body><ns2:ProcessInvoiceSalesOrderResponse xmlns:ns2="http://pos.ws.fbsaust.com.au">
755
+ <result>true</result>
756
+ </ns2:ProcessInvoiceSalesOrderResponse></S:Body>
757
+ </S:Envelope>
758
+ `;
759
+ const expectedResult = true;
760
+ mAxios.post.mockResolvedValueOnce({ data: mockedResp, ...TEST_HEADERS });
761
+ const response = await posModule.processInvoiceSalesOrder(params, token);
762
+ // Test that expected response is correct
763
+ expect(typeof response).toEqual("boolean");
764
+ expect(response).toEqual(expectedResult);
765
+ const actualPayload = mAxios.post.mock.calls[0][1];
766
+ // Test that actual payload sent in the API matches expected payload
767
+ expect(Utils.removeEmptySpaces(actualPayload)).toEqual(Utils.removeEmptySpaces(expectedPayload));
768
+ mAxios.post.mockClear();
769
+ });
567
770
  });
@@ -13,7 +13,7 @@ export interface ProcessSaleOrderWithoutPaymentQueryParams extends PosBaseQueryP
13
13
  }
14
14
  export interface ProcessSaleOrderQueryHeader extends PosQueryHeader {
15
15
  invoiceNo?: string;
16
- orderNo: string;
16
+ orderNo?: string;
17
17
  fulfillmentWarehouse?: string;
18
18
  cancelLaybyNo?: string;
19
19
  orderLines: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdev/harmony-sdk",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "description": "Harmony API SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",