ti2-tourplan 1.0.41 → 1.0.43
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/index.js +74 -63
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -142,12 +142,14 @@ class Plugin {
|
|
|
142
142
|
if (!replyObj) {
|
|
143
143
|
// in case of error /xmlproxy, fallback to call tourplan directly
|
|
144
144
|
// and then use pyfilematch xml2json to parse the xml
|
|
145
|
-
const
|
|
145
|
+
const axiospayload = {
|
|
146
146
|
method: 'post',
|
|
147
147
|
url: endpoint,
|
|
148
148
|
data,
|
|
149
149
|
headers: getHeaders({ length: data.length }),
|
|
150
|
-
}
|
|
150
|
+
};
|
|
151
|
+
// console.log(axiospayload)
|
|
152
|
+
const reply = R.path(['data'], await axios(axiospayload));
|
|
151
153
|
if (this.xmlProxyUrl) {
|
|
152
154
|
try {
|
|
153
155
|
// using raw axios to avoid logging the large xml request
|
|
@@ -170,13 +172,14 @@ class Plugin {
|
|
|
170
172
|
}
|
|
171
173
|
const requestType = R.keys(model)[0];
|
|
172
174
|
if (!replyObj) throw new Error(`${requestType} failed: ${errorStr || 'no reply object'}`);
|
|
173
|
-
|
|
175
|
+
let error = replyObj.error || R.path(['Reply', 'ErrorReply', 'Error'], replyObj);
|
|
174
176
|
if (error) {
|
|
175
|
-
if (error.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
if (error.includes('DateFrom in the past')) {
|
|
178
|
+
error = '1002 - Date is in the past';
|
|
179
|
+
} else if (error.includes('1052 SCN')) {
|
|
180
|
+
error = '1052 - OptionId not found(Check if it is Internet Enabled)';
|
|
181
|
+
} else if (error.includes('SCN Server overloaded')) {
|
|
182
|
+
error = "2051 - The Tourplan server is unavailable. Please wait a minute and try again. If you keep getting this error, please contact your team's Tourplan administrator or Tourplan support."
|
|
180
183
|
}
|
|
181
184
|
throw new Error(`${requestType} failed: ${error}`);
|
|
182
185
|
}
|
|
@@ -212,15 +215,15 @@ class Plugin {
|
|
|
212
215
|
if (passengers && passengers.length && !noPaxList) {
|
|
213
216
|
RoomConfig.PaxList = passengers.map(p => {
|
|
214
217
|
const PaxDetails = {
|
|
215
|
-
Forename: p.firstName,
|
|
216
|
-
Surname: p.lastName,
|
|
218
|
+
Forename: this.escapeInvalidXmlChars(p.firstName),
|
|
219
|
+
Surname: this.escapeInvalidXmlChars(p.lastName),
|
|
217
220
|
PaxType: {
|
|
218
221
|
Adult: 'A',
|
|
219
222
|
Child: 'C',
|
|
220
223
|
Infant: 'I',
|
|
221
224
|
}[p.passengerType] || 'A',
|
|
222
225
|
};
|
|
223
|
-
if (p.salutation) PaxDetails.Title = p.salutation;
|
|
226
|
+
if (p.salutation) PaxDetails.Title = this.escapeInvalidXmlChars(p.salutation);
|
|
224
227
|
if (p.dob) PaxDetails.DateOfBirth = p.dob;
|
|
225
228
|
if (!R.isNil(p.age) && !isNaN(p.age)) {
|
|
226
229
|
if (!(p.passengerType === 'Adult' && p.age === 0)) {
|
|
@@ -236,7 +239,10 @@ class Plugin {
|
|
|
236
239
|
});
|
|
237
240
|
this.escapeInvalidXmlChars = str => {
|
|
238
241
|
if (!str) return '';
|
|
239
|
-
|
|
242
|
+
const convertAccentedChars = s => {
|
|
243
|
+
return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
244
|
+
}
|
|
245
|
+
return convertAccentedChars(str)
|
|
240
246
|
.replace(/’/g, "'")
|
|
241
247
|
.replace(/‘/g, "'")
|
|
242
248
|
.replace(/“/g, '"')
|
|
@@ -442,50 +448,57 @@ class Plugin {
|
|
|
442
448
|
xmlOptions: hostConnectXmlOptions,
|
|
443
449
|
};
|
|
444
450
|
// use cache if we are getting the full list
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
root => {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
451
|
+
// for example: for searchInput (backend search), we shouldn't get the full list from
|
|
452
|
+
// tourplan each time user search
|
|
453
|
+
const replyObj = optionId
|
|
454
|
+
? await this.callTourplan(payload)
|
|
455
|
+
: await this.cache.getOrExec({
|
|
456
|
+
fnParams: [model],
|
|
457
|
+
fn: () => this.callTourplan(payload),
|
|
458
|
+
ttl: 60 * 60 * 2, // 2 hours
|
|
459
|
+
forceRefresh: Boolean(forceRefresh),
|
|
460
|
+
});
|
|
461
|
+
const products = R.call(R.compose(
|
|
462
|
+
R.map(optionsGroupedBySupplierId => {
|
|
463
|
+
const OptGeneral = R.pathOr({}, [0, 'OptGeneral'], optionsGroupedBySupplierId);
|
|
464
|
+
const supplierData = {
|
|
465
|
+
supplierId: R.path(['SupplierId'], OptGeneral),
|
|
466
|
+
supplierName: R.path(['SupplierName'], OptGeneral),
|
|
467
|
+
supplierAddress: `${R.pathOr('', ['Address1'], OptGeneral)}, ${R.pathOr('', ['Address2'], OptGeneral)}, ${R.pathOr('', ['Address3'], OptGeneral)}, ${R.pathOr('', ['Address4'], OptGeneral)}, ${R.pathOr('', ['Address5'], OptGeneral)}`,
|
|
468
|
+
serviceTypes: R.uniq(optionsGroupedBySupplierId.map(R.path(['OptGeneral', 'ButtonName']))),
|
|
469
|
+
};
|
|
470
|
+
return translateTPOption({
|
|
471
|
+
supplierData,
|
|
472
|
+
optionsGroupedBySupplierId,
|
|
473
|
+
typeDefs: productTypeDefs,
|
|
474
|
+
query: productQuery,
|
|
475
|
+
});
|
|
476
|
+
}),
|
|
477
|
+
R.values,
|
|
478
|
+
R.groupBy(R.path(['OptGeneral', 'SupplierId'])),
|
|
479
|
+
root => {
|
|
480
|
+
if (!searchInput) return root;
|
|
481
|
+
const getFullSearchStr = o => {
|
|
482
|
+
const fullPptionName = `${R.path(['OptGeneral', 'Description'], o) || ''}-${R.path(['OptGeneral', 'Comment'], o) || ''}`;
|
|
483
|
+
return `${R.path(['OptGeneral', 'SupplierName'], o) || ''} ${fullPptionName} ${R.path(['Opt'], o)} ${R.path(['OptGeneral', 'SupplierId'], o) || ''}`;
|
|
484
|
+
};
|
|
485
|
+
const inputValueLower = searchInput.trim().toLowerCase();
|
|
486
|
+
const parts = inputValueLower.split(' ').filter(Boolean); // Filter out any empty strings just in case
|
|
487
|
+
return root.filter(option => {
|
|
488
|
+
const fullSearchStr = getFullSearchStr(option).toLowerCase();
|
|
489
|
+
return parts.every(part => fullSearchStr.includes(part));
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
root => {
|
|
493
|
+
const options = R.pathOr([], ['OptionInfoReply', 'Option'], root);
|
|
494
|
+
// due to the new parser, single option will be returned as an object
|
|
495
|
+
// instead of an array
|
|
496
|
+
if (Array.isArray(options)) return options;
|
|
497
|
+
return [options];
|
|
498
|
+
},
|
|
499
|
+
), replyObj);
|
|
500
|
+
if (!(products && products.length)) {
|
|
501
|
+
throw new Error('No products found');
|
|
489
502
|
}
|
|
490
503
|
return {
|
|
491
504
|
products,
|
|
@@ -673,8 +686,7 @@ class Plugin {
|
|
|
673
686
|
...(puInfo.time && puInfo.time.replace(/\D/g, '') ? {
|
|
674
687
|
puTime: puInfo.time.replace(/\D/g, ''),
|
|
675
688
|
} : {}),
|
|
676
|
-
puRemark: this.escapeInvalidXmlChars(`${puInfo.
|
|
677
|
-
${puInfo.location ? `Location: ${puInfo.location || 'NA'},` : ''}
|
|
689
|
+
puRemark: this.escapeInvalidXmlChars(`${puInfo.location ? `Location: ${puInfo.location || 'NA'},` : ''}
|
|
678
690
|
${puInfo.flightDetails ? `Flight: ${puInfo.flightDetails || 'NA'},` : ''}
|
|
679
691
|
`),
|
|
680
692
|
} : {}),
|
|
@@ -683,12 +695,11 @@ class Plugin {
|
|
|
683
695
|
...(doInfo.time && doInfo.time.replace(/\D/g, '') ? {
|
|
684
696
|
doTime: doInfo.time.replace(/\D/g, ''),
|
|
685
697
|
} : {}),
|
|
686
|
-
doRemark: this.escapeInvalidXmlChars(`${doInfo.
|
|
687
|
-
${doInfo.location ? `Location: ${doInfo.location || 'NA'},` : ''}
|
|
698
|
+
doRemark: this.escapeInvalidXmlChars(`${doInfo.location ? `Location: ${doInfo.location || 'NA'},` : ''}
|
|
688
699
|
${doInfo.flightDetails ? `Flight: ${doInfo.flightDetails || 'NA'},` : ''}
|
|
689
700
|
`),
|
|
690
701
|
} : {}),
|
|
691
|
-
Remarks: this.escapeInvalidXmlChars(`${notes || ''} ${extraText ? `\nExtras: ${extraText}` : ''}`).slice(0,
|
|
702
|
+
Remarks: this.escapeInvalidXmlChars(`${notes || ''} ${extraText ? `\nExtras: ${extraText}` : ''}`).slice(0, 220),
|
|
692
703
|
Opt: optionId,
|
|
693
704
|
DateFrom: startDate,
|
|
694
705
|
RateId: 'Default',
|
|
@@ -767,10 +778,10 @@ class Plugin {
|
|
|
767
778
|
});
|
|
768
779
|
let searchCriterias = [];
|
|
769
780
|
if (bookingId) {
|
|
770
|
-
searchCriterias = ['BookingId', 'Ref', 'AgentRef'].map(key => ({ [key]: bookingId }));
|
|
781
|
+
searchCriterias = ['BookingId', 'Ref', 'AgentRef'].map(key => ({ [key]: this.escapeInvalidXmlChars(bookingId) }));
|
|
771
782
|
}
|
|
772
783
|
if (name) {
|
|
773
|
-
searchCriterias.push({ NameContains: name });
|
|
784
|
+
searchCriterias.push({ NameContains: this.escapeInvalidXmlChars(name) });
|
|
774
785
|
}
|
|
775
786
|
const allSearches = searchCriterias.length
|
|
776
787
|
? searchCriterias.map(async keyObj => {
|