ti2-tourplan 1.0.18 → 1.0.19
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 +77 -28
- package/package.json +5 -2
- package/resolvers/product.js +18 -18
package/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
const Promise = require('bluebird');
|
|
1
|
+
const axiosRaw = require('axios');
|
|
2
|
+
// const Promise = require('bluebird');
|
|
3
3
|
const R = require('ramda');
|
|
4
4
|
const assert = require('assert');
|
|
5
5
|
const moment = require('moment');
|
|
6
6
|
const js2xmlparser = require('js2xmlparser');
|
|
7
7
|
const xml2js = require('xml2js');
|
|
8
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
8
9
|
const { translateTPOption } = require('./resolvers/product');
|
|
9
10
|
|
|
10
11
|
const Normalizer = require('./normalizer');
|
|
11
12
|
|
|
12
13
|
const xmlParser = new xml2js.Parser();
|
|
14
|
+
const fastParser = new XMLParser();
|
|
13
15
|
|
|
14
16
|
const defaultXmlOptions = {
|
|
15
17
|
prettyPrinting: { enabled: false },
|
|
@@ -25,7 +27,6 @@ const hostConnectXmlOptions = {
|
|
|
25
27
|
name: 'hostConnect_4_06_009.dtd',
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
|
-
|
|
29
30
|
const getHeaders = ({ length }) => ({
|
|
30
31
|
Accept: 'application/xml',
|
|
31
32
|
'Content-Type': 'application/xml; charset=utf-8',
|
|
@@ -82,16 +83,58 @@ class Plugin {
|
|
|
82
83
|
js2xmlparser.parse('Request', model, xmlOptions),
|
|
83
84
|
);
|
|
84
85
|
data = data.replace(xmlOptions.dtd.name, `Request SYSTEM "${xmlOptions.dtd.name}"`);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
let replyObj;
|
|
87
|
+
let errorStr;
|
|
88
|
+
if (this.xmlProxyUrl) {
|
|
89
|
+
// use pyfilematch xmlproxy
|
|
90
|
+
try {
|
|
91
|
+
replyObj = R.path(['data'], await axios({
|
|
92
|
+
method: 'post',
|
|
93
|
+
url: `${this.xmlProxyUrl}/xmlproxy`,
|
|
94
|
+
data: {
|
|
95
|
+
url: endpoint,
|
|
96
|
+
data,
|
|
97
|
+
headers: getHeaders({ length: `${data.length}` }),
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.log('error in calling pyfilematch xmlproxy', err);
|
|
102
|
+
errorStr = `error in calling pyfilematch xmlproxy: ${err}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!replyObj) {
|
|
106
|
+
// in case of error /xmlproxy, fallback to call tourplan directly
|
|
107
|
+
// and then use pyfilematch xml2json to parse the xml
|
|
108
|
+
const reply = R.path(['data'], await axios({
|
|
109
|
+
method: 'post',
|
|
110
|
+
url: endpoint,
|
|
111
|
+
data,
|
|
112
|
+
headers: getHeaders({ length: data.length }),
|
|
113
|
+
}));
|
|
114
|
+
if (this.xmlProxyUrl) {
|
|
115
|
+
try {
|
|
116
|
+
// using raw axios to avoid logging the large xml request
|
|
117
|
+
({ data: replyObj } = await axiosRaw({
|
|
118
|
+
method: 'post',
|
|
119
|
+
url: `${this.xmlProxyUrl}/xml2json`,
|
|
120
|
+
data: { xml: reply },
|
|
121
|
+
maxContentLength: Infinity,
|
|
122
|
+
maxBodyLength: Infinity,
|
|
123
|
+
}));
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.log('error in calling pyfilematch xml2json', R.pathOr('Nada', ['response', 'data', 'error'], err));
|
|
126
|
+
errorStr = `error in calling pyfilematch xml2json: ${R.pathOr('Nada', ['response', 'data', 'error'], err)}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// in case of error from /xml2json, fallback to fast-xml-parser
|
|
130
|
+
if (!replyObj) {
|
|
131
|
+
replyObj = fastParser.parse(reply);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const requestType = R.keys(model)[0];
|
|
135
|
+
if (!replyObj) throw new Error(`${requestType} failed: ${errorStr || 'no reply object'}`);
|
|
136
|
+
const error = replyObj.error || R.path(['Reply', 'ErrorReply', 'Error'], replyObj);
|
|
93
137
|
if (error) {
|
|
94
|
-
const requestType = R.keys(model)[0];
|
|
95
138
|
if (error.indexOf('2050 SCN Request denied for TEST connecting from') > -1
|
|
96
139
|
&& requestType === 'OptionInfoRequest'
|
|
97
140
|
&& endpoint.indexOf('actour') > -1
|
|
@@ -130,7 +173,7 @@ class Plugin {
|
|
|
130
173
|
axios,
|
|
131
174
|
xmlOptions: hostConnectXmlOptions,
|
|
132
175
|
});
|
|
133
|
-
assert(R.path(['AgentInfoReply',
|
|
176
|
+
assert(R.path(['AgentInfoReply', 'Currency'], replyObj));
|
|
134
177
|
return true;
|
|
135
178
|
}
|
|
136
179
|
const model = {
|
|
@@ -142,7 +185,7 @@ class Plugin {
|
|
|
142
185
|
const replyObj = await this.callTourplan({
|
|
143
186
|
model, endpoint, axios, xmlOptions: defaultXmlOptions,
|
|
144
187
|
});
|
|
145
|
-
assert(R.path(['AuthenticationReply'
|
|
188
|
+
assert(R.path(['AuthenticationReply'], replyObj) === '');
|
|
146
189
|
return true;
|
|
147
190
|
} catch (err) {
|
|
148
191
|
return false;
|
|
@@ -293,7 +336,7 @@ class Plugin {
|
|
|
293
336
|
: await this.cache.getOrExec({
|
|
294
337
|
fnParams: [model],
|
|
295
338
|
fn: () => this.callTourplan(payload),
|
|
296
|
-
ttl: 60 * 60 *
|
|
339
|
+
ttl: 60 * 60 * 24, // 24 hours
|
|
297
340
|
forceRefresh: false,
|
|
298
341
|
});
|
|
299
342
|
let products = [];
|
|
@@ -303,8 +346,8 @@ class Plugin {
|
|
|
303
346
|
products = R.call(R.compose(
|
|
304
347
|
R.map(optionsGroupedBySupplierId => {
|
|
305
348
|
const supplierData = {
|
|
306
|
-
supplierId: R.path([0, 'OptGeneral',
|
|
307
|
-
supplierName: R.path([0, 'OptGeneral',
|
|
349
|
+
supplierId: R.path([0, 'OptGeneral', 'SupplierId'], optionsGroupedBySupplierId),
|
|
350
|
+
supplierName: R.path([0, 'OptGeneral', 'SupplierName'], optionsGroupedBySupplierId),
|
|
308
351
|
};
|
|
309
352
|
return translateTPOption({
|
|
310
353
|
supplierData,
|
|
@@ -314,20 +357,26 @@ class Plugin {
|
|
|
314
357
|
});
|
|
315
358
|
}),
|
|
316
359
|
R.values,
|
|
317
|
-
R.groupBy(R.path(['OptGeneral',
|
|
360
|
+
R.groupBy(R.path(['OptGeneral', 'SupplierId'])),
|
|
318
361
|
productName ? R.filter(o => {
|
|
319
362
|
const str = `${
|
|
320
|
-
R.path(['OptGeneral',
|
|
363
|
+
R.path(['OptGeneral', 'Description'], o)
|
|
321
364
|
} ${
|
|
322
|
-
R.path(['Opt'
|
|
365
|
+
R.path(['Opt'], o)
|
|
323
366
|
} ${
|
|
324
|
-
R.path(['OptGeneral',
|
|
367
|
+
R.path(['OptGeneral', 'SupplierId'], o)
|
|
325
368
|
} ${
|
|
326
|
-
R.path(['OptGeneral',
|
|
369
|
+
R.path(['OptGeneral', 'SupplierName'], o)
|
|
327
370
|
}`;
|
|
328
371
|
return wildcardMatch(productName, str);
|
|
329
372
|
}) : R.identity,
|
|
330
|
-
|
|
373
|
+
root => {
|
|
374
|
+
const options = R.pathOr([], ['OptionInfoReply', 'Option'], root);
|
|
375
|
+
// due to the new parser, single option will be returned as an object
|
|
376
|
+
// instead of an array
|
|
377
|
+
if (Array.isArray(options)) return options;
|
|
378
|
+
return [options];
|
|
379
|
+
},
|
|
331
380
|
), replyObj);
|
|
332
381
|
}
|
|
333
382
|
const productFields = [{
|
|
@@ -715,13 +764,13 @@ class Plugin {
|
|
|
715
764
|
});
|
|
716
765
|
console.log('replyObj', replyObj);
|
|
717
766
|
return {
|
|
718
|
-
message: R.path(['AddServiceReply',
|
|
767
|
+
message: R.path(['AddServiceReply', 'Status'], replyObj)
|
|
719
768
|
=== 'NO' ? 'Service cannot be added to quote' : '',
|
|
720
769
|
quote: {
|
|
721
|
-
id: R.path(['AddServiceReply',
|
|
722
|
-
reference: R.path(['AddServiceReply',
|
|
723
|
-
linePrice: R.path(['AddServiceReply',
|
|
724
|
-
lineId: R.path(['AddServiceReply',
|
|
770
|
+
id: R.path(['AddServiceReply', 'BookingId'], replyObj),
|
|
771
|
+
reference: R.path(['AddServiceReply', 'Ref'], replyObj),
|
|
772
|
+
linePrice: R.path(['AddServiceReply', 'Services', 'Service', 'LinePrice'], replyObj),
|
|
773
|
+
lineId: R.path(['AddServiceReply', 'ServiceLineId'], replyObj),
|
|
725
774
|
},
|
|
726
775
|
};
|
|
727
776
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ti2-tourplan",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Tourplan's TI2 Plugin",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -27,14 +27,17 @@
|
|
|
27
27
|
"testTimeout": 10000
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"axios": "^0.27.2",
|
|
30
31
|
"@graphql-tools/schema": "^8.5.1",
|
|
31
|
-
"graphql": "^16.5.0",
|
|
32
32
|
"bluebird": "^3.7.2",
|
|
33
|
+
"fast-xml-parser": "^4.3.4",
|
|
34
|
+
"graphql": "^16.5.0",
|
|
33
35
|
"js2xmlparser": "^4.0.2",
|
|
34
36
|
"jsonwebtoken": "^8.5.1",
|
|
35
37
|
"moment": "^2.29.1",
|
|
36
38
|
"ramda": "^0.27.1",
|
|
37
39
|
"underscore": "^1.13.4",
|
|
40
|
+
"xml-js": "^1.6.11",
|
|
38
41
|
"xml2js": "^0.4.23"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
package/resolvers/product.js
CHANGED
|
@@ -39,12 +39,12 @@ const translateTPOption = ({ optionsGroupedBySupplierId, supplierData }) => {
|
|
|
39
39
|
productId: supplierData.supplierId,
|
|
40
40
|
productName: supplierData.supplierName,
|
|
41
41
|
// Guides, Accommodation, Transfers, Entrance Fees, Meals, Other
|
|
42
|
-
// category: R.path([0, 'OptGeneral',
|
|
42
|
+
// category: R.path([0, 'OptGeneral', 'ButtonName'], optionsGroupedBySupplierId),
|
|
43
43
|
options: optionsGroupedBySupplierId.map(option => {
|
|
44
|
-
const comment = R.path(['OptGeneral',
|
|
44
|
+
const comment = R.path(['OptGeneral', 'Comment'], option);
|
|
45
45
|
const keyData = {
|
|
46
|
-
optionId: R.path(['Opt'
|
|
47
|
-
optionName: `${R.path(['OptGeneral',
|
|
46
|
+
optionId: R.path(['Opt'], option),
|
|
47
|
+
optionName: `${R.path(['OptGeneral', 'Description'], option)}${
|
|
48
48
|
comment ? `-${comment}` : ''
|
|
49
49
|
}`,
|
|
50
50
|
};
|
|
@@ -60,44 +60,44 @@ const translateTPOption = ({ optionsGroupedBySupplierId, supplierData }) => {
|
|
|
60
60
|
nights is not specified).
|
|
61
61
|
*/
|
|
62
62
|
const units = (() => {
|
|
63
|
-
if (R.path(['OptGeneral',
|
|
63
|
+
if (R.path(['OptGeneral', 'SType'], option) === 'N') {
|
|
64
64
|
return [{
|
|
65
65
|
unitId: 'Adults',
|
|
66
66
|
unitName: 'Adults',
|
|
67
67
|
pricing: [],
|
|
68
68
|
restrictions: {
|
|
69
|
-
paxCount: R.path(['OptGeneral',
|
|
70
|
-
minAge: R.path(['OptGeneral',
|
|
71
|
-
maxAge: R.path(['OptGeneral',
|
|
69
|
+
paxCount: R.path(['OptGeneral', 'MPFCU'], option),
|
|
70
|
+
minAge: R.path(['OptGeneral', 'Adult_From'], option),
|
|
71
|
+
maxAge: R.path(['OptGeneral', 'Adult_To'], option),
|
|
72
72
|
},
|
|
73
|
-
}, R.path(['OptGeneral',
|
|
73
|
+
}, R.path(['OptGeneral', 'ChildrenAllowed'], option) === 'Y' ? {
|
|
74
74
|
unitId: 'Children',
|
|
75
75
|
unitName: 'Children',
|
|
76
76
|
pricing: [],
|
|
77
77
|
restrictions: {
|
|
78
|
-
minAge: R.path(['OptGeneral',
|
|
79
|
-
maxAge: R.path(['OptGeneral',
|
|
78
|
+
minAge: R.path(['OptGeneral', 'Child_From'], option),
|
|
79
|
+
maxAge: R.path(['OptGeneral', 'Child_To'], option),
|
|
80
80
|
},
|
|
81
|
-
} : null, R.path(['OptGeneral',
|
|
81
|
+
} : null, R.path(['OptGeneral', 'InfantsAllowed'], option) === 'Y' ? {
|
|
82
82
|
unitId: 'Infants',
|
|
83
83
|
unitName: 'Infants',
|
|
84
84
|
pricing: [],
|
|
85
85
|
restrictions: {
|
|
86
|
-
minAge: R.path(['OptGeneral',
|
|
87
|
-
maxAge: R.path(['OptGeneral',
|
|
86
|
+
minAge: R.path(['OptGeneral', 'Infant_From'], option),
|
|
87
|
+
maxAge: R.path(['OptGeneral', 'Infant_To'], option),
|
|
88
88
|
},
|
|
89
89
|
} : null].filter(Boolean);
|
|
90
90
|
}
|
|
91
|
-
if (R.path(['OptGeneral',
|
|
91
|
+
if (R.path(['OptGeneral', 'SType'], option) === 'Y') {
|
|
92
92
|
return [['SG', 'Single'], ['TW', 'Twin'], ['DB', 'Double'], ['QD', 'Quad']]
|
|
93
|
-
.filter(([_, unitName]) => R.path(['OptGeneral',
|
|
93
|
+
.filter(([_, unitName]) => R.path(['OptGeneral', `${unitName}_Avail`], option) === 'Y')
|
|
94
94
|
.map(([unitId, unitName]) => ({
|
|
95
95
|
unitId,
|
|
96
96
|
unitName,
|
|
97
97
|
pricing: [],
|
|
98
98
|
restrictions: {
|
|
99
|
-
paxCount: R.path(['OptGeneral',
|
|
100
|
-
|| R.path(['OptGeneral',
|
|
99
|
+
paxCount: R.path(['OptGeneral', `${unitName}_Max`], option)
|
|
100
|
+
|| R.path(['OptGeneral', `${unitName}_Ad_Max`], option),
|
|
101
101
|
},
|
|
102
102
|
}));
|
|
103
103
|
}
|