ti2-ventrata 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/.github/workflows/nodejs.yml +6 -0
- package/index.js +144 -319
- package/index.test.js +40 -38
- package/package.json +5 -2
- package/resolvers/availability.js +61 -0
- package/resolvers/booking.js +79 -0
- package/resolvers/product.js +48 -0
- package/resolvers/rate.js +34 -0
|
@@ -9,6 +9,9 @@ jobs:
|
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- uses: actions/checkout@v2
|
|
12
|
+
- run: sed -i 's/"file:.*"/"latest"/' package.json
|
|
13
|
+
- name: install dependencies
|
|
14
|
+
run: npm i
|
|
12
15
|
- uses: actions/setup-node@v1
|
|
13
16
|
with:
|
|
14
17
|
node-version: 12
|
|
@@ -38,6 +41,9 @@ jobs:
|
|
|
38
41
|
runs-on: ubuntu-latest
|
|
39
42
|
steps:
|
|
40
43
|
- uses: actions/checkout@v1
|
|
44
|
+
- run: sed -i 's/"file:.*"/"latest"/' package.json
|
|
45
|
+
- name: install dependencies
|
|
46
|
+
run: npm i
|
|
41
47
|
- uses: JS-DevTools/npm-publish@v1
|
|
42
48
|
with:
|
|
43
49
|
token: ${{ secrets.NPM_TOKEN }}
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const axiosRaw = require('axios');
|
|
2
2
|
const curlirize = require('axios-curlirize');
|
|
3
3
|
const R = require('ramda');
|
|
4
4
|
const Promise = require('bluebird');
|
|
@@ -6,189 +6,23 @@ const assert = require('assert');
|
|
|
6
6
|
const moment = require('moment');
|
|
7
7
|
const jwt = require('jsonwebtoken');
|
|
8
8
|
const wildcardMatch = require('./utils/wildcardMatch');
|
|
9
|
+
const { translateProduct } = require('./resolvers/product');
|
|
10
|
+
const { translateAvailability } = require('./resolvers/availability');
|
|
11
|
+
const { translateBooking } = require('./resolvers/booking');
|
|
9
12
|
|
|
10
13
|
const CONCURRENCY = 3; // is this ok ?
|
|
11
14
|
if (process.env.debug) {
|
|
12
|
-
curlirize(
|
|
15
|
+
curlirize(axiosRaw);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const doMap = (obj, map, omitParam = []) => {
|
|
22
|
-
const retVal = {};
|
|
23
|
-
const omit = [
|
|
24
|
-
...omitParam,
|
|
25
|
-
...Object.keys(map),
|
|
26
|
-
];
|
|
27
|
-
Object.entries(map).forEach(([attribute, fn]) => {
|
|
28
|
-
const newVal = fn(obj);
|
|
29
|
-
if (newVal !== undefined) {
|
|
30
|
-
retVal[attribute] = newVal;
|
|
31
|
-
}
|
|
18
|
+
const axios = async (...args) => axiosRaw(...args)
|
|
19
|
+
.catch(err => {
|
|
20
|
+
const errMsg = R.path(['response', 'data', 'errorMessage'], err);
|
|
21
|
+
console.log('error in ti2-ventrata', args[0], errMsg);
|
|
22
|
+
if (errMsg) throw new Error(errMsg);
|
|
23
|
+
throw err;
|
|
32
24
|
});
|
|
33
|
-
|
|
34
|
-
...retVal,
|
|
35
|
-
...R.omit(omit, obj),
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const doMapCurry = mapObj => item => doMap(item, mapObj);
|
|
40
|
-
|
|
41
|
-
const productMapIn = {
|
|
42
|
-
productId: R.path(['id']),
|
|
43
|
-
productName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
44
|
-
availableCurrencies: R.path(['availableCurrencies']),
|
|
45
|
-
defaultCurrency: R.path(['defaultCurrency']),
|
|
46
|
-
options: e => R.pathOr(undefined, ['options'], e).map(option => doMap(option, optionMapIn, ['id', 'title'])),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const optionMapIn = {
|
|
50
|
-
optionId: R.path(['id']),
|
|
51
|
-
optionName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
52
|
-
units: option => R.pathOr(undefined, ['units'], option).map(unit => doMap(unit, unitMap)),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const rateMap = {
|
|
56
|
-
rateId: R.path(['id']),
|
|
57
|
-
rateName: rateName => R.toLower(R.path(['internalName'], rateName)),
|
|
58
|
-
pricing: R.path(['pricingFrom']),
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const unitPricingMap = {
|
|
62
|
-
unitId: R.prop('unitId'),
|
|
63
|
-
original: R.prop('original'),
|
|
64
|
-
retail: R.prop('retail'),
|
|
65
|
-
net: R.prop('net'),
|
|
66
|
-
currencyPrecision: R.prop('currencyPrecision'),
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const availabilityMap = {
|
|
70
|
-
dateTimeStart: root => R.path(['localDateTimeStart'], root) || R.path(['localDate'], root),
|
|
71
|
-
dateTimeEnd: root => R.path(['localDateTimeEnd'], root) || R.path(['localDate'], root),
|
|
72
|
-
allDay: R.path(['allDay']),
|
|
73
|
-
pricing: root => R.path(['pricing'], root) || R.path(['pricingFrom'], root),
|
|
74
|
-
unitPricing: root => {
|
|
75
|
-
let before = [];
|
|
76
|
-
if (R.path(['unitPricing'], root)) before = R.path(['unitPricing'], root);
|
|
77
|
-
else before = R.path(['unitPricingFrom'], root) || [];
|
|
78
|
-
return before.map(item => doMap(item, unitPricingMap));
|
|
79
|
-
},
|
|
80
|
-
vacancies: R.prop('vacancies'),
|
|
81
|
-
offer: avail => (avail.offerCode ? doMap(avail, {
|
|
82
|
-
offerId: R.path(['offerCode']),
|
|
83
|
-
title: R.pathOr(undefined, ['offerTitle']),
|
|
84
|
-
description: R.pathOr(undefined, ['offer', 'description']),
|
|
85
|
-
}) : undefined),
|
|
86
|
-
meetingPoint: avail => (avail.meetingPoint ? doMap(avail, {
|
|
87
|
-
description: R.pathOr(undefined, ['meetingPoint']),
|
|
88
|
-
dateTime: R.pathOr(undefined, ['meetingLocalDateTime']),
|
|
89
|
-
coordinates: () => ((avail.meetingPointLatitude
|
|
90
|
-
&& avail.meetingPointLongitude) ? doMap(avail, {
|
|
91
|
-
lat: R.path(['meetingPointLatitude']),
|
|
92
|
-
long: R.path(['meetingPointLongitude']),
|
|
93
|
-
}) : undefined),
|
|
94
|
-
}) : undefined),
|
|
95
|
-
};
|
|
96
|
-
const capitalize = sParam => {
|
|
97
|
-
if (typeof sParam !== 'string') return '';
|
|
98
|
-
const s = sParam.replace(/_/g, ' ');
|
|
99
|
-
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const unitMap = {
|
|
103
|
-
unitId: R.path(['id']),
|
|
104
|
-
unitName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
105
|
-
type: R.path(['type']),
|
|
106
|
-
subtitle: R.path(['subtitle']),
|
|
107
|
-
restrictions: R.path(['restrictions']),
|
|
108
|
-
pricing: root => R.path(['pricing'], root) || R.path(['pricingFrom'], root),
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const itineraryMap = {
|
|
112
|
-
name: R.path(['name']),
|
|
113
|
-
type: R.path(['type']),
|
|
114
|
-
address: R.path(['address']),
|
|
115
|
-
coordinates: itinerary => ((itinerary.latitude
|
|
116
|
-
&& Boolean(itinerary.longitude)) ? doMap(itinerary, {
|
|
117
|
-
lat: R.path(['latitude']),
|
|
118
|
-
long: R.path(['longitude']),
|
|
119
|
-
}) : undefined),
|
|
120
|
-
duration: ({ duration }) => (isNilOrEmpty(duration) ? undefined : doMapCurry({
|
|
121
|
-
durationName: R.path(['duration']),
|
|
122
|
-
amount: R.path(['durationAmount']),
|
|
123
|
-
unit: R.path(['durationUnit']),
|
|
124
|
-
})),
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const unitItemMap = {
|
|
128
|
-
unitItemId: R.path(['uuid']),
|
|
129
|
-
unitId: R.path(['unitId']),
|
|
130
|
-
unitName: R.path(['unit', 'title']),
|
|
131
|
-
supplierBookingId: R.path(['supplierReference']),
|
|
132
|
-
status: e => capitalize(R.path(['status'], e)),
|
|
133
|
-
contact: R.path(['contact']),
|
|
134
|
-
pricing: R.path(['pricing']),
|
|
135
|
-
unit: root => doMap(root.unit, unitMap),
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const contactMapOut = {
|
|
139
|
-
country: R.path(['country']),
|
|
140
|
-
emailAddress: R.path(['emailAddress']),
|
|
141
|
-
name: R.path(['firstName']),
|
|
142
|
-
surname: R.path(['lastName']),
|
|
143
|
-
fullName: R.path(['fullName']),
|
|
144
|
-
locales: R.path(['locales']),
|
|
145
|
-
notes: R.path(['notes']),
|
|
146
|
-
phoneNumber: R.path(['phoneNumber']),
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const bookingMap = {
|
|
150
|
-
id: R.path(['id']),
|
|
151
|
-
orderId: R.path(['orderReference']),
|
|
152
|
-
bookingId: R.path(['supplierReference']),
|
|
153
|
-
supplierBookingId: R.path(['supplierReference']),
|
|
154
|
-
status: e => capitalize(R.path(['status'], e)),
|
|
155
|
-
productId: R.path(['product', 'id']),
|
|
156
|
-
productName: R.path(['product', 'title']),
|
|
157
|
-
optionId: R.path(['option', 'id']),
|
|
158
|
-
optionName: R.path(['option', 'title']),
|
|
159
|
-
itinerary: ({ option: { itinerary } = {} }) =>
|
|
160
|
-
(isNilOrEmptyArray(itinerary) ? undefined : itinerary.map(doMapCurry(itineraryMap))),
|
|
161
|
-
duration: booking => doMap(booking, {
|
|
162
|
-
durationName: R.path(['duration']),
|
|
163
|
-
amount: R.path(['durationAmount']),
|
|
164
|
-
unit: R.path(['durationUnit']),
|
|
165
|
-
}),
|
|
166
|
-
cancellable: R.path(['cancellable']),
|
|
167
|
-
unitItems: ({ unitItems }) =>
|
|
168
|
-
(isNilOrEmptyArray(unitItems) ? undefined : unitItems.map(doMapCurry(unitItemMap))),
|
|
169
|
-
start: R.path(['availability', 'localDateTimeStart']),
|
|
170
|
-
end: R.path(['availability', 'localDateTimeEnd']),
|
|
171
|
-
allDay: R.path(['availability', 'allDay']),
|
|
172
|
-
bookingDate: R.path(['utcCreatedAt']),
|
|
173
|
-
holder: booking => doMap(R.path(['contact'], booking), contactMapOut),
|
|
174
|
-
telephone: R.pathOr(undefined, ['contact', 'phoneNumber']),
|
|
175
|
-
notes: R.pathOr(undefined, ['notes']),
|
|
176
|
-
price: R.path(['pricing']),
|
|
177
|
-
offer: booking => (booking.offerCode ? doMap(booking, {
|
|
178
|
-
offerId: R.path(['offerCode']),
|
|
179
|
-
title: R.pathOr(undefined, ['offerTitle']),
|
|
180
|
-
description: R.pathOr(undefined, ['offer', 'description']),
|
|
181
|
-
}) : undefined),
|
|
182
|
-
cancelPolicy: R.pathOr(undefined, ['product', 'cancellationPolicy']), // TODO: Looks like cancellation text on appers on the shortDescription of the product entity .. an NLP extractor would be usefull ?
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const contactMap = {
|
|
186
|
-
fullName: holder => `${holder.name} ${holder.surname}`,
|
|
187
|
-
emailAddress: R.path(['emailAddress']),
|
|
188
|
-
phoneNumber: R.path(['phoneNumber']),
|
|
189
|
-
locales: R.path(['locales']),
|
|
190
|
-
country: R.path(['country']),
|
|
191
|
-
};
|
|
25
|
+
const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
|
|
192
26
|
|
|
193
27
|
const getHeaders = ({
|
|
194
28
|
apiKey,
|
|
@@ -201,6 +35,7 @@ const getHeaders = ({
|
|
|
201
35
|
...acceptLanguage ? { 'Accept-Language': acceptLanguage } : {},
|
|
202
36
|
'Content-Type': 'application/json',
|
|
203
37
|
...resellerId ? { Referer: resellerId } : {},
|
|
38
|
+
// 'Octo-Capabilities': 'octo/pricing,octo/pickups',
|
|
204
39
|
'Octo-Capabilities': 'octo/pricing',
|
|
205
40
|
});
|
|
206
41
|
|
|
@@ -280,6 +115,10 @@ class Plugin {
|
|
|
280
115
|
resellerId,
|
|
281
116
|
},
|
|
282
117
|
payload,
|
|
118
|
+
typeDefsAndQueries: {
|
|
119
|
+
productTypeDefs,
|
|
120
|
+
productQuery,
|
|
121
|
+
},
|
|
283
122
|
}) {
|
|
284
123
|
let url = `${endpoint || this.endpoint}/products`;
|
|
285
124
|
if (!isNilOrEmpty(payload)) {
|
|
@@ -300,7 +139,14 @@ class Plugin {
|
|
|
300
139
|
headers,
|
|
301
140
|
}));
|
|
302
141
|
if (!Array.isArray(results)) results = [results];
|
|
303
|
-
let products =
|
|
142
|
+
let products = await Promise.map(
|
|
143
|
+
results,
|
|
144
|
+
product => translateProduct({
|
|
145
|
+
rootValue: product,
|
|
146
|
+
typeDefs: productTypeDefs,
|
|
147
|
+
query: productQuery,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
304
150
|
// dynamic extra filtering
|
|
305
151
|
if (!isNilOrEmpty(payload)) {
|
|
306
152
|
const extraFilters = R.omit(['productId'], payload);
|
|
@@ -319,50 +165,20 @@ class Plugin {
|
|
|
319
165
|
}
|
|
320
166
|
|
|
321
167
|
async searchQuote({
|
|
322
|
-
token: {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
},
|
|
329
|
-
payload: {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
},
|
|
168
|
+
// token: {
|
|
169
|
+
// apiKey,
|
|
170
|
+
// endpoint,
|
|
171
|
+
// octoEnv,
|
|
172
|
+
// acceptLanguage,
|
|
173
|
+
// resellerId,
|
|
174
|
+
// },
|
|
175
|
+
// payload: {
|
|
176
|
+
// productIds,
|
|
177
|
+
// optionIds,
|
|
178
|
+
// occupancies,
|
|
179
|
+
// },
|
|
334
180
|
}) {
|
|
335
|
-
|
|
336
|
-
assert(productIds.length === optionIds.length, 'mismatched product/option combinations');
|
|
337
|
-
assert(productIds.length === occupancies.length, 'mismatched product/occupancies combinations');
|
|
338
|
-
assert(productIds.every(Boolean), 'some invalid productId(s)');
|
|
339
|
-
assert(optionIds.every(Boolean), 'some invalid optionId(s)');
|
|
340
|
-
assert(occupancies.every(Boolean), 'some invalid occupacies(s)');
|
|
341
|
-
const quote = occupancies.map(() => productIds.map(productId => ({ productId })));
|
|
342
|
-
let productsDetail = await Promise.map(R.uniq(productIds), productId => {
|
|
343
|
-
const headers = getHeaders({
|
|
344
|
-
apiKey,
|
|
345
|
-
endpoint,
|
|
346
|
-
octoEnv,
|
|
347
|
-
acceptLanguage,
|
|
348
|
-
resellerId,
|
|
349
|
-
});
|
|
350
|
-
const url = `${endpoint || this.endpoint}/products/${productId}`;
|
|
351
|
-
return axios({
|
|
352
|
-
method: 'get',
|
|
353
|
-
url,
|
|
354
|
-
headers,
|
|
355
|
-
});
|
|
356
|
-
}, { concurrency: CONCURRENCY }).map(({ data }) => data);
|
|
357
|
-
productsDetail = R.indexBy(R.prop('id'), productsDetail);
|
|
358
|
-
// console.log({ optionIds });
|
|
359
|
-
productIds.forEach((productId, productIdIx) => {
|
|
360
|
-
const optionDetail = productsDetail[productId]
|
|
361
|
-
.options.filter(({ id }) => id === optionIds[productIdIx])[0];
|
|
362
|
-
quote[productIdIx] =
|
|
363
|
-
pickUnit(optionDetail.units, occupancies[productIdIx]).map(e => doMap(e, rateMap));
|
|
364
|
-
});
|
|
365
|
-
return { quote };
|
|
181
|
+
return { quote: [] };
|
|
366
182
|
}
|
|
367
183
|
|
|
368
184
|
async searchAvailability({
|
|
@@ -373,42 +189,33 @@ class Plugin {
|
|
|
373
189
|
acceptLanguage,
|
|
374
190
|
resellerId,
|
|
375
191
|
},
|
|
376
|
-
token,
|
|
377
192
|
payload: {
|
|
378
193
|
productIds,
|
|
379
194
|
optionIds,
|
|
380
|
-
|
|
195
|
+
units,
|
|
381
196
|
startDate,
|
|
382
197
|
endDate,
|
|
383
198
|
dateFormat,
|
|
384
199
|
currency,
|
|
385
200
|
},
|
|
201
|
+
typeDefsAndQueries: {
|
|
202
|
+
availTypeDefs,
|
|
203
|
+
availQuery,
|
|
204
|
+
},
|
|
386
205
|
}) {
|
|
387
206
|
assert(this.jwtKey, 'JWT secret should be set');
|
|
388
|
-
assert(occupancies.length > 0, 'there should be at least one occupancy');
|
|
389
207
|
assert(
|
|
390
208
|
productIds.length === optionIds.length,
|
|
391
209
|
'mismatched productIds/options length',
|
|
392
210
|
);
|
|
393
211
|
assert(
|
|
394
|
-
optionIds.length ===
|
|
395
|
-
'mismatched options/
|
|
212
|
+
optionIds.length === units.length,
|
|
213
|
+
'mismatched options/units length',
|
|
396
214
|
);
|
|
397
215
|
assert(productIds.every(Boolean), 'some invalid productId(s)');
|
|
398
216
|
assert(optionIds.every(Boolean), 'some invalid optionId(s)');
|
|
399
|
-
assert(occupancies.every(Boolean), 'some invalid occupacies(s)');
|
|
400
217
|
const localDateStart = moment(startDate, dateFormat).format('YYYY-MM-DD');
|
|
401
218
|
const localDateEnd = moment(endDate, dateFormat).format('YYYY-MM-DD');
|
|
402
|
-
// obtain the rates
|
|
403
|
-
const { quote } = await this.searchQuote({
|
|
404
|
-
token,
|
|
405
|
-
payload: {
|
|
406
|
-
productIds,
|
|
407
|
-
optionIds,
|
|
408
|
-
occupancies,
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
const rates = quote.map(q => q.map(({ rateId }) => rateId));
|
|
412
219
|
const headers = getHeaders({
|
|
413
220
|
apiKey,
|
|
414
221
|
endpoint,
|
|
@@ -417,43 +224,35 @@ class Plugin {
|
|
|
417
224
|
resellerId,
|
|
418
225
|
});
|
|
419
226
|
const url = `${endpoint || this.endpoint}/availability`;
|
|
420
|
-
|
|
421
|
-
await Promise.map(
|
|
422
|
-
const qtys = R.countBy(x => x)(rate);
|
|
227
|
+
const availability = (
|
|
228
|
+
await Promise.map(productIds, async (productId, ix) => {
|
|
423
229
|
const data = {
|
|
424
|
-
productId
|
|
425
|
-
optionId: optionIds[
|
|
230
|
+
productId,
|
|
231
|
+
optionId: optionIds[ix],
|
|
426
232
|
localDateStart,
|
|
427
233
|
localDateEnd,
|
|
428
|
-
|
|
429
|
-
units: Object.entries(qtys).map(([id, quantity]) => ({
|
|
430
|
-
id, quantity,
|
|
431
|
-
})),
|
|
234
|
+
units: units[ix].map(u => ({ id: u.unitId, quantity: u.quantity })),
|
|
432
235
|
};
|
|
433
|
-
|
|
236
|
+
if (currency) data.currency = currency;
|
|
237
|
+
const result = R.path(['data'], await axios({
|
|
434
238
|
method: 'post',
|
|
435
239
|
url,
|
|
436
240
|
data,
|
|
437
241
|
headers,
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
optionId: optionIds[availsIx],
|
|
447
|
-
availabilityId: avail.id,
|
|
242
|
+
}));
|
|
243
|
+
return Promise.map(result, avail => translateAvailability({
|
|
244
|
+
typeDefs: availTypeDefs,
|
|
245
|
+
query: availQuery,
|
|
246
|
+
rootValue: avail,
|
|
247
|
+
variableValues: {
|
|
248
|
+
productId,
|
|
249
|
+
optionId: optionIds[ix],
|
|
448
250
|
currency,
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
available: false,
|
|
455
|
-
})),
|
|
456
|
-
)),
|
|
251
|
+
unitsWithQuantity: units[ix],
|
|
252
|
+
jwtKey: this.jwtKey,
|
|
253
|
+
},
|
|
254
|
+
}));
|
|
255
|
+
}, { concurrency: CONCURRENCY })
|
|
457
256
|
);
|
|
458
257
|
return { availability };
|
|
459
258
|
}
|
|
@@ -466,42 +265,33 @@ class Plugin {
|
|
|
466
265
|
acceptLanguage,
|
|
467
266
|
resellerId,
|
|
468
267
|
},
|
|
469
|
-
token,
|
|
470
268
|
payload: {
|
|
471
269
|
productIds,
|
|
472
270
|
optionIds,
|
|
473
|
-
|
|
271
|
+
units,
|
|
474
272
|
startDate,
|
|
475
273
|
endDate,
|
|
476
274
|
currency,
|
|
477
275
|
dateFormat,
|
|
478
276
|
},
|
|
277
|
+
typeDefsAndQueries: {
|
|
278
|
+
availTypeDefs,
|
|
279
|
+
availQuery,
|
|
280
|
+
},
|
|
479
281
|
}) {
|
|
480
282
|
assert(this.jwtKey, 'JWT secret should be set');
|
|
481
|
-
assert(occupancies.length > 0, 'there should be at least one occupancy');
|
|
482
283
|
assert(
|
|
483
284
|
productIds.length === optionIds.length,
|
|
484
285
|
'mismatched productIds/options length',
|
|
485
286
|
);
|
|
486
287
|
assert(
|
|
487
|
-
optionIds.length ===
|
|
488
|
-
'mismatched options/
|
|
288
|
+
optionIds.length === units.length,
|
|
289
|
+
'mismatched options/units length',
|
|
489
290
|
);
|
|
490
291
|
assert(productIds.every(Boolean), 'some invalid productId(s)');
|
|
491
292
|
assert(optionIds.every(Boolean), 'some invalid optionId(s)');
|
|
492
|
-
assert(occupancies.every(Boolean), 'some invalid occupacies(s)');
|
|
493
293
|
const localDateStart = moment(startDate, dateFormat).format('YYYY-MM-DD');
|
|
494
294
|
const localDateEnd = moment(endDate, dateFormat).format('YYYY-MM-DD');
|
|
495
|
-
// obtain the rates
|
|
496
|
-
const { quote } = await this.searchQuote({
|
|
497
|
-
token,
|
|
498
|
-
payload: {
|
|
499
|
-
productIds,
|
|
500
|
-
optionIds,
|
|
501
|
-
occupancies,
|
|
502
|
-
},
|
|
503
|
-
});
|
|
504
|
-
const rates = quote.map(q => q.map(({ rateId }) => rateId));
|
|
505
295
|
const headers = getHeaders({
|
|
506
296
|
apiKey,
|
|
507
297
|
endpoint,
|
|
@@ -510,31 +300,29 @@ class Plugin {
|
|
|
510
300
|
resellerId,
|
|
511
301
|
});
|
|
512
302
|
const url = `${endpoint || this.endpoint}/availability/calendar`;
|
|
513
|
-
|
|
514
|
-
await Promise.map(
|
|
515
|
-
const qtys = R.countBy(x => x)(rate);
|
|
303
|
+
const availability = (
|
|
304
|
+
await Promise.map(productIds, async (productId, ix) => {
|
|
516
305
|
const data = {
|
|
517
|
-
productId
|
|
518
|
-
optionId: optionIds[
|
|
306
|
+
productId,
|
|
307
|
+
optionId: optionIds[ix],
|
|
519
308
|
localDateStart,
|
|
520
309
|
localDateEnd,
|
|
521
|
-
|
|
522
|
-
units:
|
|
523
|
-
id, quantity,
|
|
524
|
-
})),
|
|
310
|
+
// units is required here to get the total pricing for the calendar
|
|
311
|
+
units: units[ix].map(u => ({ id: u.unitId, quantity: u.quantity })),
|
|
525
312
|
};
|
|
526
|
-
|
|
313
|
+
if (currency) data.currency = currency;
|
|
314
|
+
const result = await axios({
|
|
527
315
|
method: 'post',
|
|
528
316
|
url,
|
|
529
317
|
data,
|
|
530
318
|
headers,
|
|
531
319
|
});
|
|
320
|
+
return Promise.map(result.data, avail => translateAvailability({
|
|
321
|
+
rootValue: avail,
|
|
322
|
+
typeDefs: availTypeDefs,
|
|
323
|
+
query: availQuery,
|
|
324
|
+
}));
|
|
532
325
|
}, { concurrency: CONCURRENCY })
|
|
533
|
-
).map(({ data }) => data);
|
|
534
|
-
availability = availability.map(
|
|
535
|
-
(avails, availsIx) => (avails.map(
|
|
536
|
-
avail => doMap(avail, availabilityMap),
|
|
537
|
-
)),
|
|
538
326
|
);
|
|
539
327
|
return { availability };
|
|
540
328
|
}
|
|
@@ -548,12 +336,17 @@ class Plugin {
|
|
|
548
336
|
resellerId,
|
|
549
337
|
},
|
|
550
338
|
payload: {
|
|
339
|
+
rebookingId,
|
|
551
340
|
availabilityKey,
|
|
552
341
|
holder,
|
|
553
342
|
notes,
|
|
554
343
|
reference,
|
|
555
344
|
settlementMethod,
|
|
556
345
|
},
|
|
346
|
+
typeDefsAndQueries: {
|
|
347
|
+
bookingTypeDefs,
|
|
348
|
+
bookingQuery,
|
|
349
|
+
},
|
|
557
350
|
}) {
|
|
558
351
|
assert(availabilityKey, 'an availability code is required !');
|
|
559
352
|
assert(R.path(['name'], holder), 'first name is required');
|
|
@@ -565,33 +358,45 @@ class Plugin {
|
|
|
565
358
|
acceptLanguage,
|
|
566
359
|
resellerId,
|
|
567
360
|
});
|
|
568
|
-
|
|
569
|
-
let data = await jwt.verify(availabilityKey, this.jwtKey);
|
|
361
|
+
const dataForCreateBooking = await jwt.verify(availabilityKey, this.jwtKey);
|
|
570
362
|
let booking = R.path(['data'], await axios({
|
|
571
|
-
method: 'post',
|
|
572
|
-
url
|
|
363
|
+
method: rebookingId ? 'patch' : 'post',
|
|
364
|
+
url: `${endpoint || this.endpoint}/bookings${rebookingId ? `/${rebookingId}` : ''}`,
|
|
573
365
|
data: {
|
|
574
366
|
settlementMethod,
|
|
575
|
-
...
|
|
367
|
+
...dataForCreateBooking,
|
|
576
368
|
notes,
|
|
577
369
|
},
|
|
578
370
|
headers,
|
|
579
371
|
}));
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
372
|
+
// for booking update, we may not need to confirm again
|
|
373
|
+
if (!booking.utcConfirmedAt) {
|
|
374
|
+
const dataForConfirmBooking = {
|
|
375
|
+
contact: {
|
|
376
|
+
fullName: `${holder.name} ${holder.surname}`,
|
|
377
|
+
emailAddress: R.path(['emailAddress'], holder),
|
|
378
|
+
phoneNumber: R.path(['phoneNumber'], holder),
|
|
379
|
+
locales: R.path(['locales'], holder),
|
|
380
|
+
country: R.path(['country'], holder),
|
|
381
|
+
},
|
|
382
|
+
notes,
|
|
383
|
+
resellerReference: reference,
|
|
384
|
+
settlementMethod,
|
|
385
|
+
};
|
|
386
|
+
booking = R.path(['data'], await axios({
|
|
387
|
+
method: 'post',
|
|
388
|
+
url: `${endpoint || this.endpoint}/bookings/${booking.uuid}/confirm`,
|
|
389
|
+
data: dataForConfirmBooking,
|
|
390
|
+
headers,
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
return ({
|
|
394
|
+
booking: await translateBooking({
|
|
395
|
+
rootValue: booking,
|
|
396
|
+
typeDefs: bookingTypeDefs,
|
|
397
|
+
query: bookingQuery,
|
|
398
|
+
}),
|
|
399
|
+
});
|
|
595
400
|
}
|
|
596
401
|
|
|
597
402
|
async cancelBooking({
|
|
@@ -607,6 +412,10 @@ class Plugin {
|
|
|
607
412
|
id,
|
|
608
413
|
reason,
|
|
609
414
|
},
|
|
415
|
+
typeDefsAndQueries: {
|
|
416
|
+
bookingTypeDefs,
|
|
417
|
+
bookingQuery,
|
|
418
|
+
},
|
|
610
419
|
}) {
|
|
611
420
|
assert(!isNilOrEmpty(bookingId) || !isNilOrEmpty(id), 'Invalid booking id');
|
|
612
421
|
const headers = getHeaders({
|
|
@@ -623,7 +432,13 @@ class Plugin {
|
|
|
623
432
|
data: { reason },
|
|
624
433
|
headers,
|
|
625
434
|
}));
|
|
626
|
-
return ({
|
|
435
|
+
return ({
|
|
436
|
+
cancellation: await translateBooking({
|
|
437
|
+
rootValue: booking,
|
|
438
|
+
typeDefs: bookingTypeDefs,
|
|
439
|
+
query: bookingQuery,
|
|
440
|
+
}),
|
|
441
|
+
});
|
|
627
442
|
}
|
|
628
443
|
|
|
629
444
|
async searchBooking({
|
|
@@ -642,6 +457,10 @@ class Plugin {
|
|
|
642
457
|
travelDateEnd,
|
|
643
458
|
dateFormat,
|
|
644
459
|
},
|
|
460
|
+
typeDefsAndQueries: {
|
|
461
|
+
bookingTypeDefs,
|
|
462
|
+
bookingQuery,
|
|
463
|
+
},
|
|
645
464
|
}) {
|
|
646
465
|
assert(
|
|
647
466
|
!isNilOrEmpty(bookingId)
|
|
@@ -707,7 +526,13 @@ class Plugin {
|
|
|
707
526
|
}
|
|
708
527
|
return [];
|
|
709
528
|
})();
|
|
710
|
-
return ({
|
|
529
|
+
return ({
|
|
530
|
+
bookings: await Promise.map(R.unnest(bookings), booking => translateBooking({
|
|
531
|
+
rootValue: booking,
|
|
532
|
+
typeDefs: bookingTypeDefs,
|
|
533
|
+
query: bookingQuery,
|
|
534
|
+
})),
|
|
535
|
+
});
|
|
711
536
|
}
|
|
712
537
|
}
|
|
713
538
|
|
package/index.test.js
CHANGED
|
@@ -6,12 +6,27 @@ const faker = require('faker');
|
|
|
6
6
|
const Plugin = require('./index');
|
|
7
7
|
const fixtureUnits = require('./__fixtures__/units.js');
|
|
8
8
|
|
|
9
|
+
const { typeDefs: productTypeDefs, query: productQuery } = require('./node_modules/ti2/controllers/graphql-schemas/product');
|
|
10
|
+
const { typeDefs: availTypeDefs, query: availQuery } = require('./node_modules/ti2/controllers/graphql-schemas/availability');
|
|
11
|
+
const { typeDefs: bookingTypeDefs, query: bookingQuery } = require('./node_modules/ti2/controllers/graphql-schemas/booking');
|
|
12
|
+
const { typeDefs: rateTypeDefs, query: rateQuery } = require('./node_modules/ti2/controllers/graphql-schemas/rate');
|
|
13
|
+
|
|
14
|
+
const typeDefsAndQueries = {
|
|
15
|
+
productTypeDefs,
|
|
16
|
+
productQuery,
|
|
17
|
+
availTypeDefs,
|
|
18
|
+
availQuery,
|
|
19
|
+
bookingTypeDefs,
|
|
20
|
+
bookingQuery,
|
|
21
|
+
rateTypeDefs,
|
|
22
|
+
rateQuery,
|
|
23
|
+
};
|
|
24
|
+
|
|
9
25
|
const app = new Plugin({
|
|
10
26
|
jwtKey: process.env.ti2_ventrata_jwtKey,
|
|
27
|
+
endpoint: process.env.ti2_ventrata_endpoint,
|
|
11
28
|
});
|
|
12
29
|
|
|
13
|
-
const rnd = arr => arr[Math.floor(Math.random() * arr.length)];
|
|
14
|
-
|
|
15
30
|
describe('search tests', () => {
|
|
16
31
|
let products;
|
|
17
32
|
let testProduct = {
|
|
@@ -103,6 +118,7 @@ describe('search tests', () => {
|
|
|
103
118
|
it('get for all products, a test product should exist', async () => {
|
|
104
119
|
const retVal = await app.searchProducts({
|
|
105
120
|
token,
|
|
121
|
+
typeDefsAndQueries,
|
|
106
122
|
});
|
|
107
123
|
expect(Array.isArray(retVal.products)).toBeTruthy();
|
|
108
124
|
// console.log(retVal.products.filter(({ productName }) => productName === testProduct.productName));
|
|
@@ -121,6 +137,7 @@ describe('search tests', () => {
|
|
|
121
137
|
payload: {
|
|
122
138
|
productId: testProduct.productId,
|
|
123
139
|
},
|
|
140
|
+
typeDefsAndQueries,
|
|
124
141
|
});
|
|
125
142
|
expect(Array.isArray(retVal.products)).toBeTruthy();
|
|
126
143
|
expect(retVal.products).toHaveLength(1);
|
|
@@ -132,6 +149,7 @@ describe('search tests', () => {
|
|
|
132
149
|
payload: {
|
|
133
150
|
productName: '*bus*',
|
|
134
151
|
},
|
|
152
|
+
typeDefsAndQueries,
|
|
135
153
|
});
|
|
136
154
|
expect(Array.isArray(retVal.products)).toBeTruthy();
|
|
137
155
|
expect(retVal.products.length).toBeGreaterThan(0);
|
|
@@ -146,62 +164,39 @@ describe('search tests', () => {
|
|
|
146
164
|
dateFormat,
|
|
147
165
|
productIds: [
|
|
148
166
|
'28ca088b-bc7b-4746-ab06-5971f1ed5a5e',
|
|
149
|
-
'
|
|
167
|
+
'3465143f-4902-447a-9c1e-8e5598666663',
|
|
150
168
|
],
|
|
151
|
-
optionIds: ['DEFAULT', '
|
|
152
|
-
|
|
153
|
-
[{
|
|
154
|
-
[{
|
|
169
|
+
optionIds: ['DEFAULT', 'dbe73645-2dd9-4cde-ade0-4faa95668d01'],
|
|
170
|
+
units: [
|
|
171
|
+
[{ unitId: 'unit_c1709f42-297e-4f7e-bd6b-3e77d4622d8a', quantity: 2 }],
|
|
172
|
+
[{ unitId: 'unit_d49f8d25-5b37-4365-b67d-daa0594d021e', quantity: 2 }],
|
|
155
173
|
],
|
|
156
174
|
},
|
|
175
|
+
typeDefsAndQueries,
|
|
157
176
|
});
|
|
158
177
|
expect(retVal).toBeTruthy();
|
|
159
178
|
const { availability } = retVal;
|
|
160
179
|
expect(availability).toHaveLength(2);
|
|
161
180
|
expect(availability[0].length).toBeGreaterThan(0);
|
|
162
181
|
});
|
|
163
|
-
|
|
164
|
-
const retVal = await app.searchQuote({
|
|
165
|
-
token,
|
|
166
|
-
payload: {
|
|
167
|
-
startDate: moment().add(6, 'M').format(dateFormat),
|
|
168
|
-
endDate: moment().add(6, 'M').add(2, 'd').format(dateFormat),
|
|
169
|
-
dateFormat,
|
|
170
|
-
productIds: busProducts.map(({ productId }) => productId),
|
|
171
|
-
optionIds: busProducts.map(({ options }) =>
|
|
172
|
-
faker.random.arrayElement(options).optionId),
|
|
173
|
-
occupancies: [
|
|
174
|
-
[{ age: 30 }, { age: 40 }],
|
|
175
|
-
[{ age: 30 }, { age: 40 }],
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
expect(retVal).toBeTruthy();
|
|
180
|
-
const { quote } = retVal;
|
|
181
|
-
expect(quote.length).toBeGreaterThan(0);
|
|
182
|
-
expect(quote[0]).toContainObject([{
|
|
183
|
-
rateName: 'adult',
|
|
184
|
-
pricing: expect.toContainObject([{
|
|
185
|
-
currency: 'USD',
|
|
186
|
-
}]),
|
|
187
|
-
}]);
|
|
188
|
-
});
|
|
182
|
+
|
|
189
183
|
let availabilityKey;
|
|
190
184
|
it('should be able to get availability', async () => {
|
|
191
185
|
const retVal = await app.searchAvailability({
|
|
192
186
|
token,
|
|
187
|
+
typeDefsAndQueries,
|
|
193
188
|
payload: {
|
|
194
189
|
startDate: moment().add(6, 'M').format(dateFormat),
|
|
195
190
|
endDate: moment().add(6, 'M').add(2, 'd').format(dateFormat),
|
|
196
191
|
dateFormat,
|
|
197
192
|
productIds: [
|
|
198
193
|
'28ca088b-bc7b-4746-ab06-5971f1ed5a5e',
|
|
199
|
-
'
|
|
194
|
+
'3465143f-4902-447a-9c1e-8e5598666663',
|
|
200
195
|
],
|
|
201
|
-
optionIds: ['DEFAULT', '
|
|
202
|
-
|
|
203
|
-
[{
|
|
204
|
-
[{
|
|
196
|
+
optionIds: ['DEFAULT', 'dbe73645-2dd9-4cde-ade0-4faa95668d01'],
|
|
197
|
+
units: [
|
|
198
|
+
[{ unitId: 'unit_c1709f42-297e-4f7e-bd6b-3e77d4622d8a', quantity: 2 }],
|
|
199
|
+
[{ unitId: 'unit_d49f8d25-5b37-4365-b67d-daa0594d021e', quantity: 2 }],
|
|
205
200
|
],
|
|
206
201
|
},
|
|
207
202
|
});
|
|
@@ -218,6 +213,7 @@ describe('search tests', () => {
|
|
|
218
213
|
const fullName = faker.name.findName().split(' ');
|
|
219
214
|
const retVal = await app.createBooking({
|
|
220
215
|
token,
|
|
216
|
+
typeDefsAndQueries,
|
|
221
217
|
payload: {
|
|
222
218
|
availabilityKey,
|
|
223
219
|
notes: faker.lorem.paragraph(),
|
|
@@ -244,6 +240,7 @@ describe('search tests', () => {
|
|
|
244
240
|
it('should be able to cancel the booking', async () => {
|
|
245
241
|
const retVal = await app.cancelBooking({
|
|
246
242
|
token,
|
|
243
|
+
typeDefsAndQueries,
|
|
247
244
|
payload: {
|
|
248
245
|
bookingId: booking.id,
|
|
249
246
|
reason: faker.lorem.paragraph(),
|
|
@@ -259,6 +256,7 @@ describe('search tests', () => {
|
|
|
259
256
|
it('it should be able to search bookings by id', async () => {
|
|
260
257
|
const retVal = await app.searchBooking({
|
|
261
258
|
token,
|
|
259
|
+
typeDefsAndQueries,
|
|
262
260
|
payload: {
|
|
263
261
|
bookingId: booking.id,
|
|
264
262
|
},
|
|
@@ -270,6 +268,7 @@ describe('search tests', () => {
|
|
|
270
268
|
it('it should be able to search bookings by reference', async () => {
|
|
271
269
|
const retVal = await app.searchBooking({
|
|
272
270
|
token,
|
|
271
|
+
typeDefsAndQueries,
|
|
273
272
|
payload: {
|
|
274
273
|
bookingId: reference,
|
|
275
274
|
},
|
|
@@ -281,6 +280,7 @@ describe('search tests', () => {
|
|
|
281
280
|
it('it should be able to search bookings by supplierBookingId', async () => {
|
|
282
281
|
const retVal = await app.searchBooking({
|
|
283
282
|
token,
|
|
283
|
+
typeDefsAndQueries,
|
|
284
284
|
payload: {
|
|
285
285
|
bookingId: booking.supplierBookingId,
|
|
286
286
|
},
|
|
@@ -292,6 +292,7 @@ describe('search tests', () => {
|
|
|
292
292
|
it('it should be able to search bookings by travelDate', async () => {
|
|
293
293
|
const retVal = await app.searchBooking({
|
|
294
294
|
token,
|
|
295
|
+
typeDefsAndQueries,
|
|
295
296
|
payload: {
|
|
296
297
|
travelDateStart: moment().add(6, 'M').format(dateFormat),
|
|
297
298
|
travelDateEnd: moment().add(6, 'M').add(2, 'd').format(dateFormat),
|
|
@@ -306,6 +307,7 @@ describe('search tests', () => {
|
|
|
306
307
|
const fullName = faker.name.findName().split(' ');
|
|
307
308
|
const retVal = await app.createBooking({
|
|
308
309
|
token,
|
|
310
|
+
typeDefsAndQueries,
|
|
309
311
|
payload: {
|
|
310
312
|
availabilityKey,
|
|
311
313
|
notes: faker.lorem.paragraph(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ti2-ventrata",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Ventrata's TI2 Plugin",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
"testTimeout": 10000
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@graphql-tools/schema": "^9.0.12",
|
|
30
31
|
"axios": "^0.24.0",
|
|
31
32
|
"axios-curlirize": "^1.3.7",
|
|
32
33
|
"bluebird": "^3.7.2",
|
|
34
|
+
"graphql": "^16.6.0",
|
|
33
35
|
"jsonwebtoken": "^8.5.1",
|
|
34
36
|
"moment": "^2.29.4",
|
|
35
37
|
"ramda": "^0.27.1"
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
"jest-cli": "^27.4.7",
|
|
45
47
|
"jest-diff": "^27.4.2",
|
|
46
48
|
"jest-environment-node": "27.4",
|
|
47
|
-
"jest-util": "27.4"
|
|
49
|
+
"jest-util": "27.4",
|
|
50
|
+
"ti2": "latest"
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
2
|
+
const { graphql } = require('graphql');
|
|
3
|
+
const R = require('ramda');
|
|
4
|
+
const jwt = require('jsonwebtoken');
|
|
5
|
+
|
|
6
|
+
const resolvers = {
|
|
7
|
+
Query: {
|
|
8
|
+
key: (root, args) => {
|
|
9
|
+
const {
|
|
10
|
+
productId,
|
|
11
|
+
optionId,
|
|
12
|
+
currency,
|
|
13
|
+
unitsWithQuantity,
|
|
14
|
+
jwtKey,
|
|
15
|
+
} = args;
|
|
16
|
+
if (!jwtKey) return null;
|
|
17
|
+
return jwt.sign({
|
|
18
|
+
productId,
|
|
19
|
+
optionId,
|
|
20
|
+
availabilityId: root.id,
|
|
21
|
+
currency,
|
|
22
|
+
unitItems: R.chain(u => new Array(u.quantity).fill(1).map(() => ({
|
|
23
|
+
unitId: u.unitId,
|
|
24
|
+
})), unitsWithQuantity),
|
|
25
|
+
}, jwtKey);
|
|
26
|
+
},
|
|
27
|
+
dateTimeStart: root => R.path(['localDateTimeStart'], root) || R.path(['localDate'], root),
|
|
28
|
+
dateTimeEnd: root => R.path(['localDateTimeEnd'], root) || R.path(['localDate'], root),
|
|
29
|
+
allDay: R.path(['allDay']),
|
|
30
|
+
vacancies: R.prop('vacancies'),
|
|
31
|
+
available: root => root.status !== 'SOLD_OUT',
|
|
32
|
+
// get the starting price
|
|
33
|
+
pricing: R.prop('pricingFrom'),
|
|
34
|
+
unitPricing: R.prop('unitPricingFrom'),
|
|
35
|
+
pickupAvailable: R.prop('pickupAvailable'),
|
|
36
|
+
pickupRequired: R.prop('pickupRequired'),
|
|
37
|
+
pickupPoints: root => R.pathOr([], ['pickupPoints'], root)
|
|
38
|
+
.map(o => ({
|
|
39
|
+
...o,
|
|
40
|
+
postal: o.postal_code,
|
|
41
|
+
})),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const translateAvailability = async ({ rootValue, variableValues, typeDefs, query }) => {
|
|
46
|
+
const schema = makeExecutableSchema({
|
|
47
|
+
typeDefs,
|
|
48
|
+
resolvers,
|
|
49
|
+
});
|
|
50
|
+
const retVal = await graphql({
|
|
51
|
+
schema,
|
|
52
|
+
rootValue,
|
|
53
|
+
source: query,
|
|
54
|
+
variableValues,
|
|
55
|
+
});
|
|
56
|
+
if (retVal.errors) throw new Error(retVal.errors);
|
|
57
|
+
return retVal.data;
|
|
58
|
+
};
|
|
59
|
+
module.exports = {
|
|
60
|
+
translateAvailability,
|
|
61
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
2
|
+
const R = require('ramda');
|
|
3
|
+
const { graphql } = require('graphql');
|
|
4
|
+
|
|
5
|
+
const capitalize = sParam => {
|
|
6
|
+
if (typeof sParam !== 'string') return '';
|
|
7
|
+
const s = sParam.replace(/_/g, ' ');
|
|
8
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
9
|
+
};
|
|
10
|
+
const resolvers = {
|
|
11
|
+
Query: {
|
|
12
|
+
id: R.path(['id']),
|
|
13
|
+
orderId: R.path(['orderReference']),
|
|
14
|
+
bookingId: R.path(['supplierReference']),
|
|
15
|
+
supplierBookingId: R.path(['supplierReference']),
|
|
16
|
+
status: e => capitalize(R.path(['status'], e)),
|
|
17
|
+
productId: R.path(['product', 'id']),
|
|
18
|
+
productName: root => R.path(['product', 'title'], root) || R.path(['product', 'internalName'], root),
|
|
19
|
+
cancellable: R.path(['cancellable']),
|
|
20
|
+
editable: R.path(['cancellable']),
|
|
21
|
+
unitItems: ({ unitItems }) => unitItems.map(unitItem => ({
|
|
22
|
+
unitItemId: unitItem.uuid,
|
|
23
|
+
unitId: unitItem.unitId,
|
|
24
|
+
unitName: unitItem.title || unitItem.internalName,
|
|
25
|
+
})),
|
|
26
|
+
start: R.path(['availability', 'localDateTimeStart']),
|
|
27
|
+
end: R.path(['availability', 'localDateTimeEnd']),
|
|
28
|
+
allDay: R.path(['availability', 'allDay']),
|
|
29
|
+
bookingDate: R.path(['utcCreatedAt']),
|
|
30
|
+
holder: root => ({
|
|
31
|
+
name: R.path(['contact', 'fullName'], root).split(' ')[0],
|
|
32
|
+
surname: R.last(R.path(['contact', 'fullName'], root).split(' ')),
|
|
33
|
+
fullName: R.path(['contact', 'fullName'], root),
|
|
34
|
+
phoneNumber: R.path(['contact', 'phoneNumber'], root),
|
|
35
|
+
emailAddress: R.path(['contact', 'emailAddress'], root),
|
|
36
|
+
}),
|
|
37
|
+
notes: root => root.notes || '',
|
|
38
|
+
price: root => root.pricing,
|
|
39
|
+
cancelPolicy: ({ option }) => {
|
|
40
|
+
if (option.cancellationCutoff) {
|
|
41
|
+
return `Cancel up to ${option.cancellationCutoff} before activity starts`;
|
|
42
|
+
}
|
|
43
|
+
return '';
|
|
44
|
+
},
|
|
45
|
+
optionId: R.path(['option', 'id']),
|
|
46
|
+
optionName: root => R.path(['option', 'title'], root) || R.path(['option', 'internalName'], root),
|
|
47
|
+
resellerReference: R.propOr('', 'resellerReference'),
|
|
48
|
+
publicUrl: () => null,
|
|
49
|
+
privateUrl: () => null,
|
|
50
|
+
pickupRequested: R.prop('pickupRequested'),
|
|
51
|
+
pickupPointId: R.prop('pickupPointId'),
|
|
52
|
+
pickupPoint: root => {
|
|
53
|
+
const pickupPoint = R.path(['pickupPoint'], root);
|
|
54
|
+
if (!pickupPoint) return null;
|
|
55
|
+
return {
|
|
56
|
+
...pickupPoint,
|
|
57
|
+
postal: pickupPoint.postal_code,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const translateBooking = async ({ rootValue, typeDefs, query }) => {
|
|
64
|
+
const schema = makeExecutableSchema({
|
|
65
|
+
typeDefs,
|
|
66
|
+
resolvers,
|
|
67
|
+
});
|
|
68
|
+
const retVal = await graphql({
|
|
69
|
+
schema,
|
|
70
|
+
rootValue,
|
|
71
|
+
source: query,
|
|
72
|
+
});
|
|
73
|
+
if (retVal.errors) throw new Error(retVal.errors);
|
|
74
|
+
return retVal.data;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
translateBooking,
|
|
79
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
2
|
+
const R = require('ramda');
|
|
3
|
+
const { graphql } = require('graphql');
|
|
4
|
+
|
|
5
|
+
const resolvers = {
|
|
6
|
+
Query: {
|
|
7
|
+
productId: R.path(['id']),
|
|
8
|
+
productName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
9
|
+
availableCurrencies: R.path(['availableCurrencies']),
|
|
10
|
+
defaultCurrency: R.path(['defaultCurrency']),
|
|
11
|
+
options: R.propOr([], 'options'),
|
|
12
|
+
},
|
|
13
|
+
Option: {
|
|
14
|
+
optionId: R.prop('id'),
|
|
15
|
+
optionName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
16
|
+
units: R.propOr([], 'units'),
|
|
17
|
+
},
|
|
18
|
+
Unit: {
|
|
19
|
+
unitId: R.path(['id']),
|
|
20
|
+
unitName: o => R.path(['title'], o) || R.prop('internalName', o),
|
|
21
|
+
subtitle: R.path(['subtitle']),
|
|
22
|
+
type: R.prop('type'),
|
|
23
|
+
pricing: root => R.path(['pricing'], root) || R.path(['pricingFrom'], root),
|
|
24
|
+
restrictions: R.prop('restrictions'),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const translateProduct = async ({
|
|
29
|
+
rootValue,
|
|
30
|
+
typeDefs,
|
|
31
|
+
query,
|
|
32
|
+
}) => {
|
|
33
|
+
const schema = makeExecutableSchema({
|
|
34
|
+
typeDefs,
|
|
35
|
+
resolvers,
|
|
36
|
+
});
|
|
37
|
+
const retVal = await graphql({
|
|
38
|
+
schema,
|
|
39
|
+
rootValue,
|
|
40
|
+
source: query,
|
|
41
|
+
});
|
|
42
|
+
if (retVal.errors) throw new Error(retVal.errors);
|
|
43
|
+
return retVal.data;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
translateProduct,
|
|
48
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
2
|
+
const R = require('ramda');
|
|
3
|
+
const { graphql } = require('graphql');
|
|
4
|
+
|
|
5
|
+
const resolvers = {
|
|
6
|
+
Query: {
|
|
7
|
+
rateId: R.path(['unitId']),
|
|
8
|
+
rateName: root => R.toLower(R.path(['unitName'], root)),
|
|
9
|
+
pricing: root => [{
|
|
10
|
+
original: R.path(['total_including_tax'], root),
|
|
11
|
+
retail: R.path(['total_including_tax'], root),
|
|
12
|
+
currencyPrecision: 2,
|
|
13
|
+
currency: R.path(['company', 'currency'], root),
|
|
14
|
+
}],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const translateRate = async ({ rootValue, typeDefs, query }) => {
|
|
19
|
+
const schema = makeExecutableSchema({
|
|
20
|
+
typeDefs,
|
|
21
|
+
resolvers,
|
|
22
|
+
});
|
|
23
|
+
const retVal = await graphql({
|
|
24
|
+
schema,
|
|
25
|
+
rootValue,
|
|
26
|
+
source: query,
|
|
27
|
+
});
|
|
28
|
+
if (retVal.errors) throw new Error(retVal.errors);
|
|
29
|
+
return retVal.data;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
translateRate,
|
|
34
|
+
};
|