ti2-tourplan 1.0.36 → 1.0.38

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 (2) hide show
  1. package/index.js +129 -53
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -201,7 +201,7 @@ class Plugin {
201
201
  Double: 'DB',
202
202
  Twin: 'TW',
203
203
  Triple: 'TR',
204
- Quad: 'QU',
204
+ Quad: 'QD',
205
205
  })[roomType];
206
206
  if (RoomType) RoomConfig.RoomType = RoomType;
207
207
  if (passengers && passengers.length && !noPaxList) {
@@ -229,6 +229,25 @@ class Plugin {
229
229
  }
230
230
  return { RoomConfig };
231
231
  });
232
+ this.escapeInvalidXmlChars = str => {
233
+ if (!str) return '';
234
+ return str.replace(/[^\x00-\x7F]+/g, '')
235
+ .replace(/’/g, "'")
236
+ .replace(/‘/g, "'")
237
+ .replace(/“/g, '"')
238
+ .replace(/”/g, '"')
239
+ .replace(/–/g, '-')
240
+ .replace(/&/g, '&')
241
+ .replace(/</g, '&lt;')
242
+ .replace(/>/g, '&gt;')
243
+ .replace(/"/g, '&quot;')
244
+ .replace(/'/g, '&apos;')
245
+ };
246
+
247
+ // this.errorPathsAxiosErrors = () => ([ // axios triggered errors
248
+ // ['response', 'data', 'error'],
249
+ // ]);
250
+ // this.errorPathsAxiosAny = () => ([]); // 200's that should be errors
232
251
  }
233
252
 
234
253
  async validateToken({
@@ -401,7 +420,7 @@ class Plugin {
401
420
  hostConnectAgentPassword,
402
421
  configuration,
403
422
  },
404
- payload: { optionId, forceRefresh },
423
+ payload: { optionId, forceRefresh, searchInput },
405
424
  }) {
406
425
  const model = {
407
426
  OptionInfoRequest: {
@@ -448,6 +467,19 @@ class Plugin {
448
467
  }),
449
468
  R.values,
450
469
  R.groupBy(R.path(['OptGeneral', 'SupplierId'])),
470
+ root => {
471
+ if (!searchInput) return root;
472
+ const getFullSearchStr = o => {
473
+ const fullPptionName = `${R.path(['OptGeneral', 'Description'], o) || ''}-${R.path(['OptGeneral', 'Comment'], o) || ''}`;
474
+ return `${R.path(['OptGeneral', 'SupplierName'], o) || ''} ${fullPptionName} ${R.path(['Opt'], o)} ${R.path(['OptGeneral', 'SupplierId'], o) || ''}`;
475
+ };
476
+ const inputValueLower = searchInput.trim().toLowerCase();
477
+ const parts = inputValueLower.split(' ').filter(Boolean); // Filter out any empty strings just in case
478
+ return root.filter(option => {
479
+ const fullSearchStr = getFullSearchStr(option).toLowerCase();
480
+ return parts.every(part => fullSearchStr.includes(part));
481
+ });
482
+ },
451
483
  root => {
452
484
  const options = R.pathOr([], ['OptionInfoReply', 'Option'], root);
453
485
  // due to the new parser, single option will be returned as an object
@@ -460,7 +492,7 @@ class Plugin {
460
492
  return {
461
493
  products,
462
494
  productFields: [],
463
- ...configuration,
495
+ ...(searchInput || optionId ? {} : configuration),
464
496
  };
465
497
  }
466
498
 
@@ -491,7 +523,7 @@ class Plugin {
491
523
  const model = {
492
524
  OptionInfoRequest: {
493
525
  Opt: optionId,
494
- Info: 'A',
526
+ Info: 'S',
495
527
  DateFrom: startDate,
496
528
  DateTo: startDate,
497
529
  RoomConfigs: this.getPaxConfigs(paxConfigs, true),
@@ -505,47 +537,84 @@ class Plugin {
505
537
  axios,
506
538
  xmlOptions: hostConnectXmlOptions,
507
539
  });
508
- const optAvail = R.path(['OptionInfoReply', 'Option', 'OptAvail'], replyObj);
509
540
  /*
510
- FROM TP DOCS:
511
- Each integer in the list gives the availability for one of the days in the range requested,
512
- from the start date through to the end date. The integer values are to be interpreted as
513
- follows:
514
- Greater than 0 means that inventory is available, with the integer specifying the
515
- number of units available. For options with a service type of Y , the inventory is in
516
- units of rooms. For other service types, the inventory is in units of pax.
517
- -1 Not available.
518
- -2 Available on free sell.
519
- -3 Available on request.
520
- Note: A return value of 0 or something less than -3 is impossible.
541
+ If not rates, optionInfoReply is null, meaning it's not bookable
542
+ otherwise, example data:
543
+ {
544
+ optionInfoReply: {
545
+ Option: {
546
+ Opt: 'LHRTRDAVIDSHABTVC',
547
+ OptStayResults: {
548
+ AgentPrice: '51175',
549
+ Availability: 'RQ',
550
+ CancelHours: '96',
551
+ CommissionPercent: '0.00',
552
+ Currency: 'GBP',
553
+ PeriodValueAdds: {
554
+ PeriodValueAdd: {
555
+ DateFrom: '2024-08-10',
556
+ DateTo: '2024-08-10',
557
+ RateName: 'Std-Mon Sun',
558
+ RateText: 'Std'
559
+ }
560
+ },
561
+ RateId: 'Default',
562
+ RateName: 'Std-Mon Sun',
563
+ RateText: 'Std',
564
+ SaleFrom: '2023-12-20',
565
+ TotalPrice: '51175'
566
+ },
567
+ OptionNumber: '70461'
568
+ }
569
+ }
570
+ }
521
571
  */
522
- if (optAvail === -1) {
523
- return {
524
- available: false,
525
- };
526
- }
527
- if (optAvail === -2) {
528
- return {
529
- available: true,
530
- type: 'free sell',
531
- };
532
- }
533
- if (optAvail === -3) {
534
- return {
535
- available: true,
536
- type: 'on request',
537
- };
538
- }
539
- if (optAvail > 0) {
540
- return {
541
- available: true,
542
- type: 'inventory',
543
- quantity: optAvail,
544
- };
545
- }
572
+ const optionInfoReply = R.path(['OptionInfoReply'], replyObj);
546
573
  return {
547
- available: false,
574
+ bookable: Boolean(optionInfoReply),
548
575
  };
576
+ // THE BELOW CODE is for A check, might still need it on user's future request
577
+ // const optAvail = parseInt(R.pathOr('-4', ['OptionInfoReply', 'Option', 'OptAvail'], replyObj), 10);
578
+ // /*
579
+ // FROM TP DOCS:
580
+ // Each integer in the list gives the availability for one of the days in the range requested,
581
+ // from the start date through to the end date. The integer values are to be interpreted as
582
+ // follows:
583
+ // Greater than 0 means that inventory is available, with the integer specifying the
584
+ // number of units available. For options with a service type of Y , the inventory is in
585
+ // units of rooms. For other service types, the inventory is in units of pax.
586
+ // -1 Not available.
587
+ // -2 Available on free sell.
588
+ // -3 Available on request.
589
+ // Note: A return value of 0 or something less than -3 is impossible.
590
+ // */
591
+ // if (optAvail === -1) {
592
+ // return {
593
+ // available: false,
594
+ // };
595
+ // }
596
+ // if (optAvail === -2) {
597
+ // return {
598
+ // available: true,
599
+ // type: 'free sell',
600
+ // };
601
+ // }
602
+ // if (optAvail === -3) {
603
+ // return {
604
+ // available: true,
605
+ // type: 'on request',
606
+ // };
607
+ // }
608
+ // if (optAvail > 0) {
609
+ // return {
610
+ // available: true,
611
+ // type: 'inventory',
612
+ // quantity: optAvail,
613
+ // };
614
+ // }
615
+ // return {
616
+ // available: false,
617
+ // };
549
618
  }
550
619
 
551
620
  async searchQuote({
@@ -590,31 +659,34 @@ class Plugin {
590
659
  ...(quoteId ? {
591
660
  ExistingBookingInfo: { BookingId: quoteId },
592
661
  } : {
593
- NewBookingInfo: { Name: quoteName, QB: 'Q' },
662
+ NewBookingInfo: {
663
+ Name: this.escapeInvalidXmlChars(quoteName),
664
+ QB: 'Q',
665
+ },
594
666
  }),
595
667
  ...(rateId ? {
596
668
  RateId: rateId,
597
669
  } : {}),
598
- ...(puInfo ? {
670
+ ...(puInfo && (puInfo.time || puInfo.location || puInfo.flightDetails) ? {
599
671
  ...(puInfo.time && puInfo.time.replace(/\D/g, '') ? {
600
672
  puTime: puInfo.time.replace(/\D/g, ''),
601
673
  } : {}),
602
- puRemark: `Time: ${puInfo.time || 'NA'},
603
- Location: ${puInfo.location || 'NA'},
604
- Flight: ${puInfo.flightDetails || 'NA'},
605
- `,
674
+ puRemark: this.escapeInvalidXmlChars(`${puInfo.time ? `Time: ${puInfo.time || 'NA'},` : ''}
675
+ ${puInfo.location ? `Location: ${puInfo.location || 'NA'},` : ''}
676
+ ${puInfo.flightDetails ? `Flight: ${puInfo.flightDetails || 'NA'},` : ''}
677
+ `),
606
678
  } : {}),
607
- ...(doInfo ? {
679
+ ...(doInfo && (doInfo.time || doInfo.location || doInfo.flightDetails) ? {
608
680
  // only get numbers from doInfo.time
609
681
  ...(doInfo.time && doInfo.time.replace(/\D/g, '') ? {
610
682
  doTime: doInfo.time.replace(/\D/g, ''),
611
683
  } : {}),
612
- doRemark: `Time: ${doInfo.time || 'NA'},
613
- Location: ${doInfo.location || 'NA'},
614
- Flight: ${doInfo.flightDetails || 'NA'},
615
- `,
684
+ doRemark: this.escapeInvalidXmlChars(`${doInfo.time ? `Time: ${doInfo.time || 'NA'},` : ''}
685
+ ${doInfo.location ? `Location: ${doInfo.location || 'NA'},` : ''}
686
+ ${doInfo.flightDetails ? `Flight: ${doInfo.flightDetails || 'NA'},` : ''}
687
+ `),
616
688
  } : {}),
617
- Remarks: `${notes || ''} ${extraText ? `\nExtras: ${extraText}` : ''}`,
689
+ Remarks: this.escapeInvalidXmlChars(`${notes || ''} ${extraText ? `\nExtras: ${extraText}` : ''}`).slice(0, 240),
618
690
  Opt: optionId,
619
691
  DateFrom: startDate,
620
692
  RateId: 'Default',
@@ -692,6 +764,10 @@ class Plugin {
692
764
  try {
693
765
  reply = await this.callTourplan(getPayload('ListBookingsRequest', { [key]: bookingId }));
694
766
  } catch (err) {
767
+ if (err.includes && err.includes('Request failed with status code')) {
768
+ throw Error(err);
769
+ }
770
+ // if it's not server error, we just considered as no booking is found
695
771
  console.log('error in searchBooking', err);
696
772
  reply = { ListBookingsReply: { BookingHeaders: { BookingHeader: [] } } };
697
773
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-tourplan",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "Tourplan's TI2 Plugin",
5
5
  "main": "index.js",
6
6
  "scripts": {