ti2 1.0.80 → 1.0.82
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/.cursorignore +1 -0
- package/README.md +27 -20
- package/controllers/app.js +1 -1
- package/controllers/bookings.js +17 -8
- package/controllers/graphql-schemas/itinerary-booking.js +94 -0
- package/controllers/graphql-schemas/itinerary-product.js +135 -0
- package/index.js +2 -1
- package/package.json +1 -1
- package/test/plugin.js +262 -3
- package/tutorials/index.json +3 -0
- package/tutorials/itinerary-assist-plugin-dev.md +403 -0
- package/tutorials/plugin-development.md +33 -3
package/.cursorignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
package/README.md
CHANGED
|
@@ -33,29 +33,36 @@ Plugins are the connectores to other systems and/or features you intent to use;
|
|
|
33
33
|
|
|
34
34
|
### Plugin Library
|
|
35
35
|
|
|
36
|
-
| Methods | [Ventrata](https://github.com/TourConnect/ti2-ventrata) | [TravelGateX](https://github.com/TourConnect/ti2-travelgate) | [Didgigo](https://github.com/TourConnect/ti2-didgigo) | [TourConnect](https://github.com/TourConnect/ti2-tourconnect)
|
|
37
|
-
| ---- | -------- | ---------- | ---- | ---- |
|
|
36
|
+
| Methods | FareHarbor | PeekPro| Zaui | Xola |[Ventrata](https://github.com/TourConnect/ti2-ventrata) | [TravelGateX](https://github.com/TourConnect/ti2-travelgate) | [Didgigo](https://github.com/TourConnect/ti2-didgigo) | [TourConnect](https://github.com/TourConnect/ti2-tourconnect) | TourPlan |
|
|
37
|
+
| ---- | -------- | ---------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
|
38
38
|
|**Base**|
|
|
39
|
-
|validateToken
|
|
40
|
-
|
|
|
39
|
+
|validateToken|✓|✓|✓|✓|✓|✓|✓|✓|✓|
|
|
40
|
+
|tokenTemplate|✓|✓|✓|✓|✓|✓|✓|✓|✓|
|
|
41
41
|
|**Content**|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
42
|
+
|copyMedia||||||||✓|
|
|
43
|
+
|getProfile|||||||✓|✓|
|
|
44
|
+
|updateProfile||||||||✓|
|
|
45
|
+
|getLocations|||||||✓|✓|
|
|
46
|
+
|getLocation|||||||✓|✓|
|
|
47
|
+
|createLocation|||||||✓|✓|
|
|
48
|
+
|updateLocation|||||||✓|✓|
|
|
49
|
+
|getProducts|||||||✓|✓|
|
|
50
|
+
|getProduct|||||||✓|✓|
|
|
51
|
+
|createProduct|||||||✓|✓|
|
|
52
|
+
|updateProduct|||||||✓|✓|
|
|
52
53
|
|**Bookings**|
|
|
53
|
-
|searchProducts|✓|✓|
|
|
54
|
-
|searchBooking|✓|✓|
|
|
55
|
-
|searchAvailability|✓|✓|
|
|
56
|
-
|searchQuote|✓|✓|
|
|
57
|
-
|createBooking|✓|✓|
|
|
58
|
-
|cancelBooking|✓|✓|
|
|
54
|
+
|searchProducts|✓|✓|✓|✓|✓|✓|
|
|
55
|
+
|searchBooking|✓|✓|✓|✓|✓|✓|
|
|
56
|
+
|searchAvailability|✓|✓|✓|✓|✓|✓|
|
|
57
|
+
|searchQuote|✓|✓|✓|✓|✓|✓|
|
|
58
|
+
|createBooking|✓|✓|✓|✓|✓|✓|
|
|
59
|
+
|cancelBooking|✓|✓|✓|✓|✓|✓|
|
|
60
|
+
|searchProductsForItinerary|||||||||✓|
|
|
61
|
+
|searchAvailabilityForItinerary|||||||||✓|
|
|
62
|
+
|addServiceToItinerary|||||||||✓|
|
|
63
|
+
|getCreateItineraryFields|||||||||✓|
|
|
64
|
+
|searchItineraries|||||||||✓|
|
|
65
|
+
|queryAllotment|||||||||✓|
|
|
59
66
|
|
|
60
67
|
## Contributing
|
|
61
68
|
|
package/controllers/app.js
CHANGED
|
@@ -466,7 +466,7 @@ const getAppToken = async (req, res, next) => {
|
|
|
466
466
|
},
|
|
467
467
|
});
|
|
468
468
|
if (!userAppKey) {
|
|
469
|
-
return next({ status: 404, message:
|
|
469
|
+
return next({ status: 404, message: `User integratio is not found for ${integrationId}:${userId}:${hint}` });
|
|
470
470
|
}
|
|
471
471
|
return res.json({ token: await userAppKey.token });
|
|
472
472
|
};
|
package/controllers/bookings.js
CHANGED
|
@@ -6,6 +6,8 @@ const { typeDefs: availTypeDefs, query: availQuery } = require('./graphql-schema
|
|
|
6
6
|
const { typeDefs: bookingTypeDefs, query: bookingQuery } = require('./graphql-schemas/booking');
|
|
7
7
|
const { typeDefs: rateTypeDefs, query: rateQuery } = require('./graphql-schemas/rate');
|
|
8
8
|
const { typeDefs: pickupTypeDefs, query: pickupQuery } = require('./graphql-schemas/pickup-point');
|
|
9
|
+
const { typeDefs: itineraryProductTypeDefs, query: itineraryProductQuery } = require('./graphql-schemas/itinerary-product');
|
|
10
|
+
const { typeDefs: itineraryBookingTypeDefs, query: itineraryBookingQuery } = require('./graphql-schemas/itinerary-booking');
|
|
9
11
|
|
|
10
12
|
const typeDefsAndQueries = {
|
|
11
13
|
productTypeDefs,
|
|
@@ -18,6 +20,10 @@ const typeDefsAndQueries = {
|
|
|
18
20
|
rateQuery,
|
|
19
21
|
pickupTypeDefs,
|
|
20
22
|
pickupQuery,
|
|
23
|
+
itineraryProductTypeDefs,
|
|
24
|
+
itineraryProductQuery,
|
|
25
|
+
itineraryBookingTypeDefs,
|
|
26
|
+
itineraryBookingQuery,
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
const bookingsSearch = plugins => async (req, res, next) => {
|
|
@@ -37,8 +43,8 @@ const bookingsSearch = plugins => async (req, res, next) => {
|
|
|
37
43
|
});
|
|
38
44
|
assert(userAppKeys, 'could not find the app key');
|
|
39
45
|
const token = await userAppKeys.token;
|
|
40
|
-
assert(app.searchHotelBooking || app.searchBooking, `searchHotelBooking or searchBooking is not available for ${appKey}`);
|
|
41
|
-
const search = (app.searchHotelBooking || app.searchBooking).bind(app);
|
|
46
|
+
assert(app.searchItineraries || app.searchHotelBooking || app.searchBooking, `searchItineraries or searchHotelBooking or searchBooking is not available for ${appKey}`);
|
|
47
|
+
const search = (app.searchHotelBooking || app.searchBooking || app.searchItineraries).bind(app);
|
|
42
48
|
const results = await search({
|
|
43
49
|
axios,
|
|
44
50
|
token,
|
|
@@ -102,7 +108,8 @@ const $bookingsProductSearch = plugins => async ({
|
|
|
102
108
|
}));
|
|
103
109
|
assert(userAppKeys, 'could not find the app key');
|
|
104
110
|
const token = await userAppKeys.token;
|
|
105
|
-
const
|
|
111
|
+
const func = (app.searchProducts || app.searchProductsForItinerary).bind(app);
|
|
112
|
+
const results = await func({
|
|
106
113
|
axios,
|
|
107
114
|
token,
|
|
108
115
|
payload,
|
|
@@ -182,7 +189,8 @@ const bookingsAvailabilitySearch = plugins => async (req, res, next) => {
|
|
|
182
189
|
}));
|
|
183
190
|
assert(userAppKeys, 'could not find the app key');
|
|
184
191
|
const token = await userAppKeys.token;
|
|
185
|
-
const
|
|
192
|
+
const func = (app.searchAvailability || app.searchAvailabilityForItinerary).bind(app);
|
|
193
|
+
const results = await func({
|
|
186
194
|
axios,
|
|
187
195
|
token,
|
|
188
196
|
payload,
|
|
@@ -292,8 +300,8 @@ const createBooking = plugins => async (req, res, next) => {
|
|
|
292
300
|
}));
|
|
293
301
|
assert(userAppKeys, 'could not find the app key');
|
|
294
302
|
const token = await userAppKeys.token;
|
|
295
|
-
|
|
296
|
-
const results = await
|
|
303
|
+
const func = (app.createBooking || app.addServiceToItinerary).bind(app);
|
|
304
|
+
const results = await func({
|
|
297
305
|
axios,
|
|
298
306
|
token,
|
|
299
307
|
payload,
|
|
@@ -418,8 +426,9 @@ const getCreateBookingFields = plugins => async (req, res, next) => {
|
|
|
418
426
|
}));
|
|
419
427
|
assert(userAppKeys, 'could not find the app key');
|
|
420
428
|
const token = await userAppKeys.token;
|
|
421
|
-
assert(app.getCreateBookingFields, `getCreateBookingFields is not available for ${appKey}`);
|
|
422
|
-
const
|
|
429
|
+
assert(app.getCreateBookingFields || app.getCreateItineraryFields, `getCreateBookingFields or getCreateItineraryFields is not available for ${appKey}`);
|
|
430
|
+
const func = (app.getCreateItineraryFields || app.getCreateBookingFields).bind(app);
|
|
431
|
+
const results = await func({
|
|
423
432
|
axios,
|
|
424
433
|
token,
|
|
425
434
|
payload,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const typeDefs = `
|
|
2
|
+
type Passenger {
|
|
3
|
+
firstName: String!
|
|
4
|
+
lastName: String!
|
|
5
|
+
passengerType: String!
|
|
6
|
+
age: Int
|
|
7
|
+
dob: String
|
|
8
|
+
personId: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type PaxConfig {
|
|
12
|
+
roomType: String
|
|
13
|
+
adults: Int
|
|
14
|
+
children: Int
|
|
15
|
+
infants: Int
|
|
16
|
+
passengers: [Passenger]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ServiceLine {
|
|
20
|
+
serviceLineId: ID!
|
|
21
|
+
optionId: String!
|
|
22
|
+
optionName: String
|
|
23
|
+
linePrice: String
|
|
24
|
+
quantity: Int
|
|
25
|
+
startDate: String!
|
|
26
|
+
supplierName: String
|
|
27
|
+
supplierId: String
|
|
28
|
+
paxList: [Passenger]
|
|
29
|
+
paxConfigs: [PaxConfig]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Query {
|
|
33
|
+
bookingId: ID!
|
|
34
|
+
name: String!
|
|
35
|
+
bookingStatus: String!
|
|
36
|
+
ref: String!
|
|
37
|
+
agentRef: String
|
|
38
|
+
totalPrice: String!
|
|
39
|
+
travelDate: String!
|
|
40
|
+
enteredDate: String!
|
|
41
|
+
canEdit: Boolean!
|
|
42
|
+
serviceLines: [ServiceLine]!
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const query = `{
|
|
47
|
+
name
|
|
48
|
+
bookingId
|
|
49
|
+
bookingStatus
|
|
50
|
+
ref
|
|
51
|
+
agentRef
|
|
52
|
+
totalPrice
|
|
53
|
+
travelDate
|
|
54
|
+
enteredDate
|
|
55
|
+
canEdit
|
|
56
|
+
serviceLines {
|
|
57
|
+
serviceLineId
|
|
58
|
+
linePrice
|
|
59
|
+
quantity
|
|
60
|
+
optionId
|
|
61
|
+
optionName
|
|
62
|
+
supplierId
|
|
63
|
+
supplierName
|
|
64
|
+
supplierId
|
|
65
|
+
startDate
|
|
66
|
+
paxConfigs {
|
|
67
|
+
roomType
|
|
68
|
+
adults
|
|
69
|
+
children
|
|
70
|
+
infants
|
|
71
|
+
passengers {
|
|
72
|
+
firstName
|
|
73
|
+
lastName
|
|
74
|
+
passengerType
|
|
75
|
+
age
|
|
76
|
+
dob
|
|
77
|
+
personId
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
paxList {
|
|
81
|
+
firstName
|
|
82
|
+
lastName
|
|
83
|
+
passengerType
|
|
84
|
+
age
|
|
85
|
+
dob
|
|
86
|
+
personId
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}`;
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
typeDefs,
|
|
93
|
+
query,
|
|
94
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const typeDefs = `
|
|
2
|
+
|
|
3
|
+
type Extra {
|
|
4
|
+
id: ID!
|
|
5
|
+
name: String!
|
|
6
|
+
chargeBasis: String
|
|
7
|
+
isCompulsory: Boolean
|
|
8
|
+
isPricePerPerson: Boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type UnitRestriction {
|
|
12
|
+
allowed: Boolean
|
|
13
|
+
minAge: Int
|
|
14
|
+
maxAge: Int
|
|
15
|
+
maxPax: Int
|
|
16
|
+
maxAdults: Int
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type OptionRestrictions {
|
|
20
|
+
roomTypeRequired: Boolean
|
|
21
|
+
Adult: UnitRestriction
|
|
22
|
+
Child: UnitRestriction
|
|
23
|
+
Infant: UnitRestriction
|
|
24
|
+
Single: UnitRestriction
|
|
25
|
+
Double: UnitRestriction
|
|
26
|
+
Twin: UnitRestriction
|
|
27
|
+
Triple: UnitRestriction
|
|
28
|
+
Quad: UnitRestriction
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ProductUnit {
|
|
32
|
+
unitId: ID!
|
|
33
|
+
unitName: String!
|
|
34
|
+
restrictions: UnitRestriction
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ProductOption {
|
|
38
|
+
optionId: ID!
|
|
39
|
+
optionName: String!
|
|
40
|
+
lastUpdateTimestamp: Int
|
|
41
|
+
serviceType: String
|
|
42
|
+
extras: [Extra]
|
|
43
|
+
units: [ProductUnit]
|
|
44
|
+
restrictions: OptionRestrictions
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Query {
|
|
48
|
+
productId: ID!
|
|
49
|
+
productName: String!
|
|
50
|
+
address: String
|
|
51
|
+
description: String
|
|
52
|
+
serviceTypes: [String]
|
|
53
|
+
options: [ProductOption]
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const query = `{
|
|
58
|
+
productId
|
|
59
|
+
productName
|
|
60
|
+
description
|
|
61
|
+
serviceTypes
|
|
62
|
+
address
|
|
63
|
+
options {
|
|
64
|
+
optionId
|
|
65
|
+
optionName
|
|
66
|
+
lastUpdateTimestamp
|
|
67
|
+
serviceType
|
|
68
|
+
extras {
|
|
69
|
+
id
|
|
70
|
+
name
|
|
71
|
+
chargeBasis
|
|
72
|
+
isCompulsory
|
|
73
|
+
isPricePerPerson
|
|
74
|
+
}
|
|
75
|
+
units {
|
|
76
|
+
unitId
|
|
77
|
+
unitName
|
|
78
|
+
restrictions {
|
|
79
|
+
allowed
|
|
80
|
+
minAge
|
|
81
|
+
maxAge
|
|
82
|
+
maxPax
|
|
83
|
+
maxAdults
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
restrictions {
|
|
87
|
+
roomTypeRequired
|
|
88
|
+
Adult {
|
|
89
|
+
allowed
|
|
90
|
+
minAge
|
|
91
|
+
maxAge
|
|
92
|
+
}
|
|
93
|
+
Child {
|
|
94
|
+
allowed
|
|
95
|
+
minAge
|
|
96
|
+
maxAge
|
|
97
|
+
}
|
|
98
|
+
Infant {
|
|
99
|
+
allowed
|
|
100
|
+
minAge
|
|
101
|
+
maxAge
|
|
102
|
+
}
|
|
103
|
+
Single {
|
|
104
|
+
allowed
|
|
105
|
+
maxPax
|
|
106
|
+
maxAdults
|
|
107
|
+
}
|
|
108
|
+
Double {
|
|
109
|
+
allowed
|
|
110
|
+
maxPax
|
|
111
|
+
maxAdults
|
|
112
|
+
}
|
|
113
|
+
Twin {
|
|
114
|
+
allowed
|
|
115
|
+
maxPax
|
|
116
|
+
maxAdults
|
|
117
|
+
}
|
|
118
|
+
Triple {
|
|
119
|
+
allowed
|
|
120
|
+
maxPax
|
|
121
|
+
maxAdults
|
|
122
|
+
}
|
|
123
|
+
Quad {
|
|
124
|
+
allowed
|
|
125
|
+
maxPax
|
|
126
|
+
maxAdults
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}`;
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
typeDefs,
|
|
134
|
+
query,
|
|
135
|
+
};
|
package/index.js
CHANGED
|
@@ -270,7 +270,8 @@ module.exports = async ({
|
|
|
270
270
|
];
|
|
271
271
|
const body = req.customBody;
|
|
272
272
|
if (cachingOperations.indexOf(body.operationId) > -1) {
|
|
273
|
-
const
|
|
273
|
+
const cacheBody = R.omit(['requestId', 'date'], body);
|
|
274
|
+
const cacheKey = hash(cacheBody);
|
|
274
275
|
req.cacheKey = cacheKey;
|
|
275
276
|
const foundCache = await cache.get({
|
|
276
277
|
pluginName: body.params.appKey,
|
package/package.json
CHANGED
package/test/plugin.js
CHANGED
|
@@ -54,7 +54,36 @@ class Plugin {
|
|
|
54
54
|
});
|
|
55
55
|
this.searchQuote = jestPlugin.fn(() => ({ quote: [{ id: chance.guid() }] }));
|
|
56
56
|
this.createBooking = jestPlugin.fn(() => {});
|
|
57
|
-
this.
|
|
57
|
+
this.searchItineraries = jestPlugin.fn(() => ({ bookings: [] }));
|
|
58
|
+
this.searchProductsForItinerary = jestPlugin.fn(() => ({ products: [] }));
|
|
59
|
+
this.searchAvailabilityForItinerary = jestPlugin.fn(({
|
|
60
|
+
token,
|
|
61
|
+
payload: {
|
|
62
|
+
optionId,
|
|
63
|
+
startDate,
|
|
64
|
+
paxConfigs,
|
|
65
|
+
},
|
|
66
|
+
}) => {
|
|
67
|
+
expect(token).toBeTruthy();
|
|
68
|
+
expect(optionId).toBeTruthy();
|
|
69
|
+
expect(startDate).toBeTruthy();
|
|
70
|
+
expect(paxConfigs).toBeTruthy();
|
|
71
|
+
return { bookable: true, rates: [{ rateId: 'Default' }] };
|
|
72
|
+
});
|
|
73
|
+
this.getCreateItineraryFields = jestPlugin.fn(() => ({
|
|
74
|
+
customFields: [],
|
|
75
|
+
}));
|
|
76
|
+
this.addServiceToItinerary = jestPlugin.fn(() => ({
|
|
77
|
+
message: '',
|
|
78
|
+
booking: {
|
|
79
|
+
id: chance.guid(),
|
|
80
|
+
reference: chance.guid(),
|
|
81
|
+
linePrice: chance.guid(),
|
|
82
|
+
lineId: chance.guid(),
|
|
83
|
+
},
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
this.queryAllotment = jestPlugin.fn(async args => {
|
|
58
87
|
const {
|
|
59
88
|
axios,
|
|
60
89
|
payload,
|
|
@@ -65,7 +94,7 @@ class Plugin {
|
|
|
65
94
|
if (payload.keyPath === 'errorGeneral') {
|
|
66
95
|
await axios.get('http://www.example.com');
|
|
67
96
|
}
|
|
68
|
-
return { allotments: [] }
|
|
97
|
+
return { allotments: [] };
|
|
69
98
|
});
|
|
70
99
|
this.tokenTemplate = jestPlugin.fn(() => ({
|
|
71
100
|
apiKey: {
|
|
@@ -443,7 +472,7 @@ class Plugin {
|
|
|
443
472
|
* @returns {object} retVal - the return object
|
|
444
473
|
* @returns {Availability[]} retVal.availability - Array of availability objects
|
|
445
474
|
*/
|
|
446
|
-
|
|
475
|
+
availabilityCalendar() {
|
|
447
476
|
// return (args);
|
|
448
477
|
}
|
|
449
478
|
|
|
@@ -479,6 +508,219 @@ class Plugin {
|
|
|
479
508
|
*/
|
|
480
509
|
createBooking() {}
|
|
481
510
|
|
|
511
|
+
/**
|
|
512
|
+
* @typedef {Object} Passenger
|
|
513
|
+
* @property {string} firstName - Passenger first name
|
|
514
|
+
* @property {string} lastName - Passenger last name
|
|
515
|
+
* @property {PassengerType} passengerType - Passenger type
|
|
516
|
+
* @property {number} [age] - Passenger age
|
|
517
|
+
* @property {string} [dob] - Date of birth (YYYY-MM-DD)
|
|
518
|
+
* @property {string} [personId] - Unique identifier for the passenger
|
|
519
|
+
*/
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @typedef {('Single'|'Double'|'Twin'|'Triple'|'Quad'|'Other')} RoomType
|
|
523
|
+
*/
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* @typedef {('Adult'|'Child'|'Infant')} PassengerType
|
|
527
|
+
*/
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @typedef {Object} PaxConfig - At least one of adults, children or infants must be provided
|
|
531
|
+
* @property {RoomType} [roomType] - Room type
|
|
532
|
+
* @property {number} [adults] - Number of adults
|
|
533
|
+
* @property {number} [children] - Number of children
|
|
534
|
+
* @property {number} [infants] - Number of infants
|
|
535
|
+
* @property {Array<Passenger>} [passengers] - List of passengers for this pax config
|
|
536
|
+
*/
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* @typedef {Object} PUDOInfo
|
|
540
|
+
* @property {string} [time] - Travel time
|
|
541
|
+
* @property {string} [location] - Location details
|
|
542
|
+
* @property {string} [flightDetails] - Flight information
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
// ItineraryProduct Related Types
|
|
547
|
+
/**
|
|
548
|
+
* @typedef {Object} ItineraryProductUnit
|
|
549
|
+
* @property {RoomType|PassengerType} unitId - Unit identifier for Accommodation products, allowed values: 'Single', 'Double', 'Twin', 'Triple', 'Quad'; for Activity products, allowed values: 'Adult', 'Child', 'Infant'
|
|
550
|
+
* @property {string} unitName - Unit name, can be the same as unitId
|
|
551
|
+
* @property {ItineraryUnitRestriction} restrictions - Unit restrictions
|
|
552
|
+
*/
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @typedef {Object} ItineraryUnitRestriction
|
|
556
|
+
* @property {boolean} allowed - Whether the unit is available
|
|
557
|
+
* @property {number} [minAge] - Minimum age requirement
|
|
558
|
+
* @property {number} [maxAge] - Maximum age requirement
|
|
559
|
+
*/
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* @typedef {Object} ItineraryProductOption
|
|
563
|
+
* @property {string} optionId - Option identifier, this identifier should be unique in a sense that it should be able to represent the option and the product. For example, if in your system the option identifier are 1, 2, 3, say the productId is '123', the optionId you provided to us should be something like '123-1' or '123-2' etc.
|
|
564
|
+
* @property {string} optionName - Option name
|
|
565
|
+
* @property {number} [lastUpdateTimestamp] - Last update time
|
|
566
|
+
* @property {string} serviceType - Type of service, one of 'Accommodation', 'Activity', 'Transfer'
|
|
567
|
+
* @property {Array<Extra>} extras - Extras allowed for this product option
|
|
568
|
+
* @property {Array<ItineraryProductUnit>} units - Available units
|
|
569
|
+
* @property {Object} restrictions - Aggregated restrictions of each unit for this product option
|
|
570
|
+
* @property {boolean} [restrictions.roomTypeRequired] - Whether room type is required, if it's required, our IA tool will check if the roomType in the paxConfigs matches the roomType allowed in the option
|
|
571
|
+
* @property {ItineraryUnitRestriction} [restrictions.Adult] - restrictions for adult
|
|
572
|
+
* @property {ItineraryUnitRestriction} [restrictions.Child] - restrictions for child
|
|
573
|
+
* @property {ItineraryUnitRestriction} [restrictions.Infant] - restrictions for infant
|
|
574
|
+
* @property {ItineraryUnitRestriction} [restrictions.Single] - restrictions for Single room type
|
|
575
|
+
* @property {ItineraryUnitRestriction} [restrictions.Double] - restrictions for Double room type
|
|
576
|
+
* @property {ItineraryUnitRestriction} [restrictions.Twin] - restrictions for Twin room type
|
|
577
|
+
* @property {ItineraryUnitRestriction} [restrictions.Triple] - restrictions for Triple room type
|
|
578
|
+
* @property {ItineraryUnitRestriction} [restrictions.Quad] - restrictions for Quad room type
|
|
579
|
+
*/
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @typedef {Object} ItineraryProduct - An itinerary product contains two levels: product, option. For accommodation products, the product is the hotel, the option is the room name (Deluxe Room, Ocean View Room, etc). For activity products, the product is the Tour Company, the option is the activity type (e.g. Full Day, Half Day, etc). Specific data examples will be provided in the tutorial page.
|
|
583
|
+
* @property {string} productId - ItineraryProduct identifier
|
|
584
|
+
* @property {string} productName - ItineraryProduct name
|
|
585
|
+
* @property {Array<string>} [serviceTypes] - Types of services offered: ['Accommodation', 'Activity', 'Transfer']
|
|
586
|
+
* @property {Array<ItineraryProductOption>} options - Available options
|
|
587
|
+
*/
|
|
588
|
+
|
|
589
|
+
// Booking Related Types
|
|
590
|
+
/**
|
|
591
|
+
* @typedef {Object} CustomFieldValue
|
|
592
|
+
* @property {string} id - Custom Field identifier
|
|
593
|
+
* @property {string} value - Field value
|
|
594
|
+
*/
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* @typedef {Object} Extra
|
|
598
|
+
* @property {string} id - Extra identifier
|
|
599
|
+
* @property {string} name - Extra name
|
|
600
|
+
*/
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* @typedef {Object} ItineraryServiceLine
|
|
604
|
+
* @property {string} serviceLineId - Service line identifier
|
|
605
|
+
* @property {string} optionId - Option identifier
|
|
606
|
+
* @property {string} optionName - Option name
|
|
607
|
+
* @property {string} [supplierName] - Supplier name
|
|
608
|
+
* @property {string} [supplierId] - Supplier id
|
|
609
|
+
* @property {string} [linePrice] - Line price
|
|
610
|
+
* @property {string} [quantity] - Quantity
|
|
611
|
+
* @property {string} startDate - Start date (YYYY-MM-DD)
|
|
612
|
+
* @property {Array<PaxConfig>} paxConfigs - List of pax configs for this service line
|
|
613
|
+
* @property {Array<Passenger>} paxList - List of passengers for this service line
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* @typedef {Object} ItineraryBooking
|
|
618
|
+
* @property {string} bookingId - Unique booking identifier
|
|
619
|
+
* @property {string} name - Booking name
|
|
620
|
+
* @property {string} bookingStatus - Booking status
|
|
621
|
+
* @property {string} ref - Booking reference
|
|
622
|
+
* @property {string} agentRef - external booking reference
|
|
623
|
+
* @property {string} totalPrice - Total price of the booking
|
|
624
|
+
* @property {string} travelDate - Start Date of travel (YYYY-MM-DD)
|
|
625
|
+
* @property {string} enteredDate - Date of booking (YYYY-MM-DD)
|
|
626
|
+
* @property {boolean} canEdit - Whether the booking can be edited
|
|
627
|
+
* @property {ItineraryServiceLine[]} serviceLines - Booked service lines
|
|
628
|
+
*/
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* @typedef {('yes-no'|'short'|'long'|'count'|'extended-option')} CustomFieldTypes
|
|
632
|
+
/**
|
|
633
|
+
* @typedef {Object} CreateItineraryCustomField
|
|
634
|
+
* @property {string} id - Custom Field identifier
|
|
635
|
+
* @property {string} label - Custom Field label
|
|
636
|
+
* @property {CustomFieldTypes} type - Custom Field type
|
|
637
|
+
* @property {boolean} [isPerService] - Whether the custom field is per service, or per entire booking/quote
|
|
638
|
+
* @property {Array<Object>} [options] - Options if the type is extended-option
|
|
639
|
+
* @property {Object} options.value - Option value
|
|
640
|
+
* @property {Object} options.label - Option label
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Search for bookable products
|
|
645
|
+
* @async
|
|
646
|
+
* @param {Object} args
|
|
647
|
+
* @param {Object} args.token - Authentication token
|
|
648
|
+
* @param {object} args.payload - Search criteria
|
|
649
|
+
* @param {string} [args.payload.productId] - Optional specific product to search for
|
|
650
|
+
* @param {string} [args.payload.optionId] - Optional specific product option to search for
|
|
651
|
+
* @param {string} [args.payload.searchInput] - Optional text search across all fields
|
|
652
|
+
* @param {boolean} [args.payload.forceRefresh] - Optional flag to bypass cache
|
|
653
|
+
* @returns {object} retVal - the return object.
|
|
654
|
+
* @returns {Array<ItineraryProduct>} retVal.products - An array of products spec objects.
|
|
655
|
+
*/
|
|
656
|
+
searchProductsForItinerary() {}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* @typedef {Object} SearchAvailabilityForItineraryResponse
|
|
660
|
+
* @property {boolean} bookable - Whether the product is bookable
|
|
661
|
+
* @property {Array} [rates] - If there are multiple rates, they will be returned in this array
|
|
662
|
+
* @property {string} rates[].rateId - It's ok if you don't use such a thing as rateId in your internal system, but you can use it to package important information so we can make a booking. For example, you can do jwt.encode({ foo: 'bar' }, secret) and send it to us here, and later when we make a booking, we send the rateId to you so you can decode it and get the foo: 'bar' information.
|
|
663
|
+
* @property {string} [rates[].externalRateText] - Combined rate description
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* Search for product availability
|
|
667
|
+
* @async
|
|
668
|
+
* @param {Object} args
|
|
669
|
+
* @param {Object} args.token - Authentication token
|
|
670
|
+
* @param {object} args.payload - Search criteria
|
|
671
|
+
* @param {string} args.payload.optionId - ItineraryProduct option ID
|
|
672
|
+
* @param {string} args.payload.startDate - YYYY-MM-DD format
|
|
673
|
+
* @param {Array<PaxConfig>} args.payload.paxConfigs - Required for room-based products
|
|
674
|
+
* @param {number} [args.payload.chargeUnitQuantity=1] - Number of units to book
|
|
675
|
+
* @returns {SearchAvailabilityForItineraryResponse}
|
|
676
|
+
*/
|
|
677
|
+
searchAvailabilityForItinerary() {}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* @typedef {Object} AddServiceToItineraryResponse
|
|
681
|
+
* @property {string} message - Error message
|
|
682
|
+
* @property {object} booking - Booking object
|
|
683
|
+
* @property {string} booking.id - Booking identifier from your system
|
|
684
|
+
* @property {string} booking.reference - Reference number from your system
|
|
685
|
+
* @property {string} booking.linePrice - Price of the booking line
|
|
686
|
+
* @property {string} booking.lineId - Line identifier from your system
|
|
687
|
+
*/
|
|
688
|
+
/**
|
|
689
|
+
* Create an itinerary
|
|
690
|
+
* @async
|
|
691
|
+
* @param {Object} args
|
|
692
|
+
* @param {Object} args.token - Authentication token
|
|
693
|
+
* @param {object} args.payload - Booking/Quote details
|
|
694
|
+
* @param {string} args.payload.QB - 'Q' for quote, 'B' for booking
|
|
695
|
+
* @param {string} args.payload.quoteName - Name of the booking/quote
|
|
696
|
+
* @param {string} [args.payload.quoteId] - identifier of the booking/quote, if one is provided, we are expecting the service line to be inserted to an existing booking/quote
|
|
697
|
+
* @param {string} [args.payload.lineId] - identifier of the service line in the booking/quote, if one is provided, we are expecting an existing service line to be updated
|
|
698
|
+
* @param {string} [args.payload.rateId] - Rate identifier, we are sending 'Default' if no rates are provided by the check availability call
|
|
699
|
+
* @param {string} args.payload.optionId - Option identifier of the service line
|
|
700
|
+
* @param {string} args.payload.startDate - Start date always in (YYYY-MM-DD) format
|
|
701
|
+
* @param {string} [args.payload.reference] - Reference number from external system
|
|
702
|
+
* @param {Array<PaxConfig>} args.payload.paxConfigs - Passenger configurations
|
|
703
|
+
* @param {Array<Object>} [args.payload.extras] - Additional extras
|
|
704
|
+
* @param {Extra} args.payload.extras.selectedExtra - Selected extra
|
|
705
|
+
* @param {number} args.payload.extras.quantity - quantity
|
|
706
|
+
* @param {PUDOInfo} [args.payload.puInfo] - Pickup information
|
|
707
|
+
* @param {PUDOInfo} [args.payload.doInfo] - Dropoff information
|
|
708
|
+
* @param {string} [args.payload.notes] - Additional notes
|
|
709
|
+
* @param {Array<CustomFieldValue>} [args.payload.customFieldValues] - Custom field values
|
|
710
|
+
* @returns {AddServiceToItineraryResponse}
|
|
711
|
+
*/
|
|
712
|
+
addServiceToItinerary() {}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Some additional fields available for the addServiceToItinerary call
|
|
716
|
+
* @async
|
|
717
|
+
* @param {Object} args
|
|
718
|
+
* @param {Object} args.token - Authentication token
|
|
719
|
+
* @returns {object} retVal - the return object.
|
|
720
|
+
* @returns {Array<CreateItineraryCustomField>} retVal.customFields - An array of custom fields spec objects.
|
|
721
|
+
*/
|
|
722
|
+
getCreateItineraryFields() {}
|
|
723
|
+
|
|
482
724
|
/**
|
|
483
725
|
* Allotment Object
|
|
484
726
|
/**
|
|
@@ -496,6 +738,23 @@ class Plugin {
|
|
|
496
738
|
* @property {string[]} keyPaths - An array of key paths generated by combining supplier code and product codes.
|
|
497
739
|
*/
|
|
498
740
|
|
|
741
|
+
/**
|
|
742
|
+
* Search for itineraries
|
|
743
|
+
* @async
|
|
744
|
+
* @param {Object} args
|
|
745
|
+
* @param {Object} args.token - Authentication token
|
|
746
|
+
* @param {object} args.payload - Search criteria
|
|
747
|
+
* @param {string} args.payload.purchaseDateStart - Start date for purchase search (YYYY-MM-DD)
|
|
748
|
+
* @param {string} args.payload.purchaseDateEnd - End date for purchase search (YYYY-MM-DD)
|
|
749
|
+
* @param {string} args.payload.travelDateStart - Start date for travel search (YYYY-MM-DD)
|
|
750
|
+
* @param {string} args.payload.travelDateEnd - End date for travel search (YYYY-MM-DD)
|
|
751
|
+
* @param {string} args.payload.name - Search by customer name
|
|
752
|
+
* @param {string} args.payload.bookingId - Search by booking ID
|
|
753
|
+
* @returns {object} retVal - the return object.
|
|
754
|
+
* @returns {Array<ItineraryBooking>} retVal.bookings - An array of itinerary bookings matching search criteria.
|
|
755
|
+
*/
|
|
756
|
+
searchItineraries() {}
|
|
757
|
+
|
|
499
758
|
/**
|
|
500
759
|
* Query Allotment
|
|
501
760
|
* @async
|
package/tutorials/index.json
CHANGED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
|
|
2
|
+
### Important notes:
|
|
3
|
+
- Required methods:
|
|
4
|
+
- [searchProductsForItinerary]{@link Plugin#searchProductsForItinerary}
|
|
5
|
+
- [searchAvailabilityForItinerary]{@link Plugin#searchAvailabilityForItinerary}
|
|
6
|
+
- [addServiceToItinerary]{@link Plugin#addServiceToItinerary}
|
|
7
|
+
- [searchItineraries]{@link Plugin#searchItineraries}
|
|
8
|
+
- Optional methods:
|
|
9
|
+
- [getCreateItineraryFields]{@link Plugin#getCreateItineraryFields}
|
|
10
|
+
|
|
11
|
+
- If a key in the payload is said to be optional, it may be undefined or null, please handle them accordingly
|
|
12
|
+
- expected return structure should be exactly as described in the documentation, it's ok to include extra fields in the response, but please note that our IA tool will only handle the fields that are described in the documentation
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Existing Itinerary Assist Plugin
|
|
16
|
+
[Tourplan](https://github.com/tourconnect/ti2-tourplan)
|
|
17
|
+
Feel free to use it as a reference for how tests are written and how it handles the authentication and data mapping.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### FAQ
|
|
21
|
+
|
|
22
|
+
1. For searchProductsForItinerary, it says "when we send empty payload, the expected response is the entire list of ACTIVE products". How do I handle pagination if my system has thousands of products? Is there a limit?
|
|
23
|
+
|
|
24
|
+
We have to get access to the entire list of products, so we can create a vector database for AI to match the extracted text with a service. We recommend using a cache to store the products on your end and refresh it periodically.
|
|
25
|
+
|
|
26
|
+
2. My booking system uses different terms for passenger types (e.g., "CHD" instead of "Child"). Should I handle this mapping in the plugin, or will Ti2 provide a mapping configuration?
|
|
27
|
+
|
|
28
|
+
You should handle this mapping in the plugin, as it's specific to your system.
|
|
29
|
+
|
|
30
|
+
3. How should I handle currency conversion? My system supports multiple currencies, but I don't see currency fields in the response formats.
|
|
31
|
+
|
|
32
|
+
It's not required to include currency fields in the response, as the Itinerary Assist tool currently doesn't handle pricing related operations.
|
|
33
|
+
|
|
34
|
+
4. For real-time availability checks in searchAvailabilityForItinerary, what's the expected response time limit? My booking system might need to query multiple suppliers.
|
|
35
|
+
|
|
36
|
+
The availbility check is always just for one service. And we expect the response time to be less than 30 seconds.
|
|
37
|
+
|
|
38
|
+
5. Will Ti2 provide a validation suite to verify my plugin's responses match the expected format?
|
|
39
|
+
|
|
40
|
+
No, we currently rely on you to validate the response format, but we are working on a validation suite that will be available in the future.
|
|
41
|
+
|
|
42
|
+
6. The plugin.js file shows Jest tests, but are there specific test scenarios I need to cover?
|
|
43
|
+
|
|
44
|
+
No, we don't have a specific test scenarios, but we recommend you to cover the following:
|
|
45
|
+
- Authentication
|
|
46
|
+
- Response format for all operations
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### [searchProductsForItinerary]{@link Plugin#searchProductsForItinerary}
|
|
52
|
+
|
|
53
|
+
**We need the entire list of ACTIVE products so that we can create vector database for AI to match the extracted text with a service**
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
#### Example Payload
|
|
57
|
+
**Note: when we send empty payload, the expected response is the entire list of ACTIVE products**
|
|
58
|
+
```typescript
|
|
59
|
+
{}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Example Response
|
|
63
|
+
**Note: We provided graphql types and queries for Itinerary Product, in hope for a easier way to map your data to our format. However, if you are not familiar with graphql, you don't have to use them, just make sure the response is in the correct format**
|
|
64
|
+
```typescript
|
|
65
|
+
{
|
|
66
|
+
"products": [
|
|
67
|
+
// Example of an Accommodation product
|
|
68
|
+
{
|
|
69
|
+
"productId": "1",
|
|
70
|
+
"productName": "DoubleTree by Hilton Angel Kings Cross",
|
|
71
|
+
"serviceTypes": [
|
|
72
|
+
"Accommodation"
|
|
73
|
+
],
|
|
74
|
+
"options": [
|
|
75
|
+
{
|
|
76
|
+
"extras": [],
|
|
77
|
+
"lastUpdateTimestamp": 1586595549,
|
|
78
|
+
"optionId": "106784",
|
|
79
|
+
"optionName": "Bed and English Breakfast-Executive Room",
|
|
80
|
+
"restrictions": {
|
|
81
|
+
"Adult": {
|
|
82
|
+
"allowed": true,
|
|
83
|
+
"maxAge": "999",
|
|
84
|
+
"minAge": "16"
|
|
85
|
+
},
|
|
86
|
+
"Child": {
|
|
87
|
+
"allowed": true,
|
|
88
|
+
"maxAge": "15",
|
|
89
|
+
"minAge": "2"
|
|
90
|
+
},
|
|
91
|
+
"Double": {
|
|
92
|
+
"allowed": true,
|
|
93
|
+
"maxAdults": "2",
|
|
94
|
+
"maxPax": "2"
|
|
95
|
+
},
|
|
96
|
+
"Infant": {
|
|
97
|
+
"allowed": true,
|
|
98
|
+
"maxAge": "1",
|
|
99
|
+
"minAge": "0"
|
|
100
|
+
},
|
|
101
|
+
"Quad": {
|
|
102
|
+
"allowed": false
|
|
103
|
+
},
|
|
104
|
+
"Single": {
|
|
105
|
+
"allowed": true,
|
|
106
|
+
"maxAdults": "1",
|
|
107
|
+
"maxPax": "1"
|
|
108
|
+
},
|
|
109
|
+
"Triple": {
|
|
110
|
+
"allowed": false
|
|
111
|
+
},
|
|
112
|
+
"Twin": {
|
|
113
|
+
"allowed": false
|
|
114
|
+
},
|
|
115
|
+
"roomTypeRequired": true
|
|
116
|
+
},
|
|
117
|
+
"serviceType": "Accommodation",
|
|
118
|
+
"units": [
|
|
119
|
+
{
|
|
120
|
+
"restrictions": {
|
|
121
|
+
"allowed": true,
|
|
122
|
+
"paxCount": "1"
|
|
123
|
+
},
|
|
124
|
+
"unitId": "Single",
|
|
125
|
+
"unitName": "Single"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"restrictions": {
|
|
129
|
+
"allowed": false
|
|
130
|
+
},
|
|
131
|
+
"unitId": "Twin",
|
|
132
|
+
"unitName": "Twin"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"restrictions": {
|
|
136
|
+
"allowed": true,
|
|
137
|
+
"paxCount": "2"
|
|
138
|
+
},
|
|
139
|
+
"unitId": "Double",
|
|
140
|
+
"unitName": "Double"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"restrictions": {
|
|
144
|
+
"allowed": false
|
|
145
|
+
},
|
|
146
|
+
"unitId": "Quad",
|
|
147
|
+
"unitName": "Quad"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
// Example of a Transfers product
|
|
154
|
+
{
|
|
155
|
+
"productId": "6489",
|
|
156
|
+
"productName": "Davids of London Ltd",
|
|
157
|
+
"serviceTypes": [
|
|
158
|
+
"Transfers"
|
|
159
|
+
],
|
|
160
|
+
"options": [
|
|
161
|
+
{
|
|
162
|
+
"extras": [],
|
|
163
|
+
"lastUpdateTimestamp": 1700750093,
|
|
164
|
+
"optionId": "LONTRDAVIDSHDWBVC",
|
|
165
|
+
"optionName": "Half-Day Warner Bros Studios (6-Hours)-FIT- V- Class (1-5 Pax)",
|
|
166
|
+
"restrictions": {
|
|
167
|
+
"Adult": {
|
|
168
|
+
"allowed": true,
|
|
169
|
+
"maxAge": "999",
|
|
170
|
+
"minAge": "16"
|
|
171
|
+
},
|
|
172
|
+
"Child": {
|
|
173
|
+
"allowed": true,
|
|
174
|
+
"maxAge": "15",
|
|
175
|
+
"minAge": "5"
|
|
176
|
+
},
|
|
177
|
+
"Double": {
|
|
178
|
+
"allowed": false
|
|
179
|
+
},
|
|
180
|
+
"Infant": {
|
|
181
|
+
"allowed": true,
|
|
182
|
+
"maxAge": "4",
|
|
183
|
+
"minAge": "0"
|
|
184
|
+
},
|
|
185
|
+
"Quad": {
|
|
186
|
+
"allowed": false
|
|
187
|
+
},
|
|
188
|
+
"Single": {
|
|
189
|
+
"allowed": false
|
|
190
|
+
},
|
|
191
|
+
"Triple": {
|
|
192
|
+
"allowed": false
|
|
193
|
+
},
|
|
194
|
+
"Twin": {
|
|
195
|
+
"allowed": false
|
|
196
|
+
},
|
|
197
|
+
"roomTypeRequired": false
|
|
198
|
+
},
|
|
199
|
+
"serviceType": "Transfers",
|
|
200
|
+
"units": [
|
|
201
|
+
{
|
|
202
|
+
"restrictions": {
|
|
203
|
+
"maxAge": "999",
|
|
204
|
+
"minAge": "16"
|
|
205
|
+
},
|
|
206
|
+
"unitId": "Adults",
|
|
207
|
+
"unitName": "Adults"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"restrictions": {
|
|
211
|
+
"maxAge": "15",
|
|
212
|
+
"minAge": "5"
|
|
213
|
+
},
|
|
214
|
+
"unitId": "Children",
|
|
215
|
+
"unitName": "Children"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"restrictions": {
|
|
219
|
+
"maxAge": "4",
|
|
220
|
+
"minAge": "0"
|
|
221
|
+
},
|
|
222
|
+
"unitId": "Infants",
|
|
223
|
+
"unitName": "Infants"
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
### [searchAvailabilityForItinerary]{@link Plugin#searchAvailabilityForItinerary}
|
|
235
|
+
|
|
236
|
+
#### Example Payload
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
{
|
|
240
|
+
optionId: 'LONTRDAVIDSHDWBVC',
|
|
241
|
+
startDate: '2025-04-01',
|
|
242
|
+
chargeUnitQuantity: 1,
|
|
243
|
+
paxConfigs: [{ roomType: 'DB', adults: 2 }],
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
#### ExampleResponse
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
{
|
|
250
|
+
bookable: true,
|
|
251
|
+
rates: [{
|
|
252
|
+
rateId: '123',
|
|
253
|
+
externalRateText: 'Example rate description'
|
|
254
|
+
}]
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
### [addServiceToItinerary]{@link Plugin#addServiceToItinerary}
|
|
261
|
+
|
|
262
|
+
#### Example Payload
|
|
263
|
+
```javascript
|
|
264
|
+
{
|
|
265
|
+
quoteName: String,
|
|
266
|
+
rateId: String, // Optional
|
|
267
|
+
quoteId: String, // Optional
|
|
268
|
+
optionId: String,
|
|
269
|
+
startDate: String, // YYYY-MM-DD
|
|
270
|
+
reference: String, // Optional
|
|
271
|
+
paxConfigs: [{
|
|
272
|
+
roomType: String, // Optional
|
|
273
|
+
passengers: [{ // Optional
|
|
274
|
+
firstName: String,
|
|
275
|
+
lastName: String,
|
|
276
|
+
passengerType: String, // 'Adult' | 'Child' | 'Infant'
|
|
277
|
+
age: Number, // Optional
|
|
278
|
+
dob: String, // Optional, YYYY-MM-DD
|
|
279
|
+
personId: String // Optional
|
|
280
|
+
}]
|
|
281
|
+
}],
|
|
282
|
+
extras: [{ // Optional
|
|
283
|
+
selectedExtra: {
|
|
284
|
+
id: String
|
|
285
|
+
},
|
|
286
|
+
quantity: Number
|
|
287
|
+
}],
|
|
288
|
+
puInfo: { // Optional, pickup information
|
|
289
|
+
time: String, // Optional
|
|
290
|
+
location: String, // Optional
|
|
291
|
+
flightDetails: String // Optional
|
|
292
|
+
},
|
|
293
|
+
doInfo: { // Optional, dropoff information
|
|
294
|
+
time: String, // Optional
|
|
295
|
+
location: String, // Optional
|
|
296
|
+
flightDetails: String // Optional
|
|
297
|
+
},
|
|
298
|
+
notes: String, // Optional
|
|
299
|
+
QB: 'Q', // Q for Quote, B for Booking
|
|
300
|
+
customFieldValues: [{ // Optional
|
|
301
|
+
id: String,
|
|
302
|
+
value: String
|
|
303
|
+
}]
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Example Response
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
{
|
|
311
|
+
message: 'Booking created successfully',
|
|
312
|
+
booking: {
|
|
313
|
+
id: '123',
|
|
314
|
+
reference: 'ref-123',
|
|
315
|
+
linePrice: '100',
|
|
316
|
+
lineId: 'lineId-123',
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### [searchItineraries]{@link Plugin#searchItineraries}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
#### Example Payload
|
|
325
|
+
|
|
326
|
+
**Note: The payload must include either:**
|
|
327
|
+
- purchaseDateStart AND purchaseDateEnd, OR
|
|
328
|
+
- travelDateStart AND travelDateEnd, OR
|
|
329
|
+
- name, OR
|
|
330
|
+
- bookingId
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
|
|
334
|
+
{
|
|
335
|
+
purchaseDateStart: String, // Optional, YYYY-MM-DD
|
|
336
|
+
purchaseDateEnd: String, // Optional, YYYY-MM-DD
|
|
337
|
+
travelDateStart: String, // Optional, YYYY-MM-DD
|
|
338
|
+
travelDateEnd: String, // Optional, YYYY-MM-DD
|
|
339
|
+
name: String, // Optional
|
|
340
|
+
bookingId: String // Optional
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
#### Example Response
|
|
346
|
+
**Note: We provided graphql types and queries for Itinerary Booking, in hope for a easier way to map your data to our format. However, if you are not familiar with graphql, you don't have to use them, just make sure the response is in the correct format**
|
|
347
|
+
```javascript
|
|
348
|
+
{
|
|
349
|
+
"bookings": [{
|
|
350
|
+
"agentRef": "2356674/1",
|
|
351
|
+
"bookingId": "316559",
|
|
352
|
+
"bookingStatus": "Quotation",
|
|
353
|
+
"enteredDate": "2024-09-12",
|
|
354
|
+
"ref": "ALFI393706",
|
|
355
|
+
"serviceLines": [{
|
|
356
|
+
"optionId": "LONHOSANLONBFBDLX",
|
|
357
|
+
"optionName": "Bed and Full Buffet Breakfast",
|
|
358
|
+
"paxConfigs": [{
|
|
359
|
+
"adults": 1,
|
|
360
|
+
"children": 0,
|
|
361
|
+
"infants": 0,
|
|
362
|
+
"passengers": [{
|
|
363
|
+
"age": null,
|
|
364
|
+
"dob": null,
|
|
365
|
+
"firstName": "Sean",
|
|
366
|
+
"lastName": "Conta",
|
|
367
|
+
"passengerType": "Adult",
|
|
368
|
+
"personId": "628199",
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
"roomType": "DB",
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
"paxList": [{
|
|
375
|
+
"age": null,
|
|
376
|
+
"dob": null,
|
|
377
|
+
"firstName": "Sean",
|
|
378
|
+
"lastName": "Conta",
|
|
379
|
+
"passengerType": "Adult",
|
|
380
|
+
"personId": "628199",
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
"serviceLineId": "745684",
|
|
384
|
+
"startDate": "2025-08-13",
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
"totalPrice": "187795",
|
|
388
|
+
"travelDate": "2025-08-13",
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### Example
|
|
395
|
+
```javascript
|
|
396
|
+
await searchItineraries({
|
|
397
|
+
payload: {
|
|
398
|
+
purchaseDateStart: '2024-01-01',
|
|
399
|
+
purchaseDateEnd: '2024-12-31',
|
|
400
|
+
name: 'John Doe'
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
```
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Plugins can extend the functionality of Ti2 and/or provide access to other platforms; you should use one of the following methods to provide a stadandarized way to access other systems; if you want to implement new functionality that is not encompassed on the available methods you are more than welcome to add new methods and contribute to the Ti2 codebase.
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
## Steps to develop a plugin
|
|
7
|
+
1. Contact us to get access to the ti2-<your plugin name> repository.
|
|
8
|
+
- We will create a skeleton repository for you and add you as a contributor.
|
|
9
|
+
2. Clone the repository and start developing your plugin.
|
|
10
|
+
3. Follow respective tutorials to develop your plugin.
|
|
11
|
+
- [A Plugin for Itinerary Assist AI]{@tutorial itinerary-assist-plugin-dev}
|
|
12
|
+
4. Make sure to add tests for your plugin. For more information, refer to the [Testing Suite](#testing-suite) section.
|
|
13
|
+
5. Contact us to deploy your plugin to our hosted staging instance.
|
|
14
|
+
- Please let us know any environment variables that are required for your plugin to work. For more information, refer to the [Environment variables](#environment-variables) section.
|
|
15
|
+
- We will provide you with tokens and endpoints to test your plugin.
|
|
16
|
+
6. Test your plugin against our staging instance.
|
|
17
|
+
7. Contact us once you have tested your plugin and it's working as expected.
|
|
18
|
+
- We will deploy your plugin to our production instance.
|
|
19
|
+
|
|
5
20
|
## Available Methods
|
|
6
21
|
|
|
7
22
|
We are using a set of standardized methods to maintain compatibility; the following methods are available, more can be supported but should be a added to the TI2 spec first to maximize it's compatibility.
|
|
@@ -24,10 +39,15 @@ We are using a set of standardized methods to maintain compatibility; the follow
|
|
|
24
39
|
* [searchAvailability]{@link Plugin#searchAvailability}
|
|
25
40
|
* [searchQuote]{@link Plugin#searchQuote}
|
|
26
41
|
* [createBooking]{@link Plugin#createBooking}
|
|
27
|
-
|
|
42
|
+
* [searchProductsForItinerary]{@link Plugin#searchProductsForItinerary}
|
|
43
|
+
* [searchAvailabilityForItinerary]{@link Plugin#searchAvailabilityForItinerary}
|
|
44
|
+
* [addServiceToItinerary]{@link Plugin#addServiceToItinerary}
|
|
45
|
+
* [getCreateItineraryFields]{@link Plugin#getCreateItineraryFields}
|
|
46
|
+
* [searchItineraries]{@link Plugin#searchItineraries}
|
|
47
|
+
* [queryAllotment]{@link Plugin#queryAllotment}
|
|
28
48
|
---
|
|
29
49
|
|
|
30
|
-
## Environment variables
|
|
50
|
+
## <a id="environment-variables"></a> Environment variables
|
|
31
51
|
|
|
32
52
|
Plugin environment keys are passed down from the hosting ti2 instance when they are generated, this is the preferred way to access environment keys from the plugins, such values are not design to hold client / user's API keys or specific data, they are to be stored in the database via the AppKey collection.
|
|
33
53
|
|
|
@@ -40,6 +60,8 @@ ti2_tourconnect_apiUrl=http://backend:8080
|
|
|
40
60
|
|
|
41
61
|
## Codebase setup
|
|
42
62
|
|
|
63
|
+
**##Skip this section if you are using the skeleton repository.##**
|
|
64
|
+
|
|
43
65
|
You can review some of the plugins previously developed and use them as a guide, plugin methods are encourage to include tests and to return results with the same format; however, the returned values can include additional information, currently Ti2 is expected to run on node version 12.22.8; we suggest you use the same for your codebase.
|
|
44
66
|
|
|
45
67
|
|
|
@@ -51,6 +73,8 @@ $ node init .
|
|
|
51
73
|
|
|
52
74
|
## Entry file / constructor
|
|
53
75
|
|
|
76
|
+
**##Skip this section if you are using the skeleton repository.##**
|
|
77
|
+
|
|
54
78
|
The plugin is expected to have an index.js file that exports a Plugin Class like so:
|
|
55
79
|
|
|
56
80
|
```javascript
|
|
@@ -79,6 +103,8 @@ The plugin named ti2-ventrata would receive acceptLanguage variable and it's val
|
|
|
79
103
|
|
|
80
104
|
## Method calling
|
|
81
105
|
|
|
106
|
+
**##Skip this section if you are using the skeleton repository.##**
|
|
107
|
+
|
|
82
108
|
On the previous example code we are declaring a validateToken method; we are normally expected to receive two parameters one is token and the second one payload.
|
|
83
109
|
|
|
84
110
|
```javascript
|
|
@@ -104,7 +130,7 @@ module.exports = Plugin;
|
|
|
104
130
|
|
|
105
131
|
The token parameter should store all the settings for the current configured end user; on these example we are defaulting this settings to the ones configured to the pluging when it was instanced; so if the user settings do not include an apiUrl it will fall back to the environment variable on the running server.
|
|
106
132
|
|
|
107
|
-
## Testing Suite
|
|
133
|
+
## <a id="testing-suite"></a> Testing Suite
|
|
108
134
|
|
|
109
135
|
The plugin should contain a test file, for the following example assumes we will be using jest as the testing platform.
|
|
110
136
|
|
|
@@ -163,6 +189,8 @@ After setting up a [Ti2 instance]{@tutorial setup-your-instance} you can add you
|
|
|
163
189
|
|
|
164
190
|
## Extending the base API
|
|
165
191
|
|
|
192
|
+
**##Typically not needed##**
|
|
193
|
+
|
|
166
194
|
Ti2 uses the [Swagger API specification](https://swagger.io/specification/) standard to define it's own methods, you can review the basic methods [here](https://ti2-staging.tourconnect.com/api-docs/); these methods can be extended using the same format; this allows any plugin to extend the base API endpoints that are linked to any plugin method (part of the basic methods or new ones).
|
|
167
195
|
|
|
168
196
|
All the extender methods would be availble under the /\[plugin name] namespace, i.e.: /ti2-greatPlugin/ping.
|
|
@@ -212,6 +240,8 @@ paths:
|
|
|
212
240
|
|
|
213
241
|
## Database Migrations (plugin's own database tables)
|
|
214
242
|
|
|
243
|
+
**##Typically not needed##**
|
|
244
|
+
|
|
215
245
|
Ti2 uses [Sequelize v6.13](https://sequelize.org/v6) which is the database ORM we ecourage you to use, you can add your own tables via migrations that should be placed under a migrations folder on the root of the plugin folder.
|
|
216
246
|
|
|
217
247
|
Migrations should be run after the fact, from the root of ti2 instance like so:
|