Skip to content

Commit b1a6de4

Browse files
authored
Modified structure to support round trips (#12)
* Added support of multi-segment in the query * Adapted schema for hotel * Finalized price plan mapping for air * Added support for count property for passengers in air search * Fixed example for hotel search
1 parent 72edad3 commit b1a6de4

File tree

9 files changed

+229
-88
lines changed

9 files changed

+229
-88
lines changed

helpers/camaroTemplates/hotelAvail.js

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
const hotelAvailTransformTemplate = {
33
accommodations: ['/soap:Envelope/soap:Body/OTA_HotelAvailRS/HotelStays/HotelStay', {
4-
_provider_: '#ervmax',
4+
_provider_: '#erevmax',
55
_id_: 'BasicPropertyInfo/@HotelCode',
66
name: 'BasicPropertyInfo/@HotelName',
77
type: '#hotel',
@@ -31,7 +31,20 @@ const hotelAvailTransformTemplate = {
3131
height: '@height',
3232
url: '@url',
3333
}],
34-
roomTypes: ['/soap:Envelope/soap:Body/OTA_HotelAvailRS/RoomStays/RoomStay/RoomTypes/RoomType', {
34+
}],
35+
_roomStays_: ['/soap:Envelope/soap:Body/OTA_HotelAvailRS/RoomStays/RoomStay', {
36+
_provider_: '#erevmax',
37+
_hotelCode_: 'BasicPropertyInfo/@HotelCode',
38+
_roomRates_: ['RoomRates/RoomRate',{
39+
ratePlanReference: '@RatePlanCode',
40+
roomTypeReference: '@RoomTypeCode',
41+
price: {
42+
currency: 'Total/@CurrencyCode',
43+
_afterTax_: 'Total/@AmountAfterTax',
44+
_beforeTax_: 'Total/@AmountBeforeTax',
45+
}
46+
}],
47+
_roomTypes_: ['RoomTypes/RoomType', {
3548
_id_: '@RoomTypeCode',
3649
name: 'RoomDescription/@Name',
3750
description: 'RoomDescription/Text',
@@ -55,7 +68,7 @@ const hotelAvailTransformTemplate = {
5568
_value_: '@Text',
5669
}],
5770
}],
58-
ratePlans: ['/soap:Envelope/soap:Body/OTA_HotelAvailRS/RoomStays/RoomStay/RatePlans/RatePlan', {
71+
_ratePlans_: ['RatePlans/RatePlan', {
5972
_id_: '@RatePlanCode',
6073
name: '@RatePlanName',
6174
penalties: {
@@ -65,19 +78,6 @@ const hotelAvailTransformTemplate = {
6578
},
6679
}],
6780
}],
68-
_roomStays_: ['/soap:Envelope/soap:Body/OTA_HotelAvailRS/RoomStays/RoomStay', {
69-
_provider_: '#erevmax',
70-
_hotelCode_: 'BasicPropertyInfo/@HotelCode',
71-
_roomRates_: ['RoomRates/RoomRate',{
72-
ratePlanReference: '@RatePlanCode',
73-
roomTypeReference: '@RoomTypeCode',
74-
price: {
75-
currency: 'Total/@CurrencyCode',
76-
_afterTax_: 'Total/@AmountAfterTax',
77-
_beforeTax_: 'Total/@AmountBeforeTax',
78-
}
79-
}],
80-
}],
8181
};
8282

8383
const errorsTransformTemplate = {

helpers/camaroTemplates/provideAirShopping.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ const provideAirShoppingTransformTemplate = {
1616
passengerReferences: 'Service/PassengerRefs',
1717
},
1818
}],
19-
itineraryCombinationReference: 'FlightsOverview/FlightRef',
20-
serviceClassReference: 'FlightsOverview/FlightRef/@PriceClassRef',
19+
//itineraryCombinationReference: 'FlightsOverview/FlightRef',
20+
//serviceClassReference: 'FlightsOverview/FlightRef/@PriceClassRef',
21+
flightsReferences: ['FlightsOverview/FlightRef', {
22+
flightRef: '.',
23+
priceClassRef: '@PriceClassRef',
24+
}],
2125
}],
2226
itineraries: {
2327
combinations: ['/S:Envelope/S:Body/AirShoppingRS/DataLists/FlightList/Flight', {
@@ -44,7 +48,7 @@ const provideAirShoppingTransformTemplate = {
4448
splittedArrivalDate: 'Arrival/Date',
4549
}],
4650
},
47-
serviceClasses: ['/S:Envelope/S:Body/AirShoppingRS/DataLists/PriceClassList/PriceClass', {
51+
pricePlans: ['/S:Envelope/S:Body/AirShoppingRS/DataLists/PriceClassList/PriceClass', {
4852
_id_: '@PriceClassID',
4953
name: 'ClassOfService/MarketingName',
5054
amenities: [],

helpers/parsers/index.js

+15-8
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,26 @@ const reduceRoomStays = (_roomStays_ => {
8282
_roomStays_.forEach(roomStay => {
8383

8484
// Create the accommodation key
85-
var hotelkey = `${roomStay._provider_}.${roomStay._hotelCode_}`;
85+
var accommodationReference = `${roomStay._provider_}.${roomStay._hotelCode_}`;
8686

8787
// Build the offers by parsing the room rates
8888
roomStay._roomRates_.forEach(roomRate => {
8989

90-
// Build the key and offer
91-
var key = hotelkey + "." + roomRate.ratePlanReference + "." + roomRate.roomTypeReference;
90+
// Build the offer key
91+
var offerKey = `${accommodationReference}.${roomRate.ratePlanReference}.${roomRate.roomTypeReference}`;
92+
93+
// Build the PricePlanReference
94+
var pricePlanReference = {
95+
accommodation: accommodationReference,
96+
roomType: roomRate.roomTypeReference,
97+
};
98+
pricePlansReferences = {};
99+
pricePlansReferences[roomRate.ratePlanReference] = pricePlanReference;
100+
92101
var offer = {
93102
// Reference from other elements
94-
ratePlanReference: roomRate.ratePlanReference,
95-
roomTypeReference: roomRate.roomTypeReference,
96-
accommodationReference: hotelkey,
97-
103+
pricePlansReferences: pricePlansReferences,
104+
98105
// Build price
99106
price: {
100107
currency: roomRate.price.currency,
@@ -104,7 +111,7 @@ const reduceRoomStays = (_roomStays_ => {
104111
};
105112

106113
// Add the offer item to the offers dict
107-
offers[key] = offer;
114+
offers[offerKey] = offer;
108115
});
109116
});
110117
return offers;

helpers/resolvers/searchFlight.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,34 @@ const searchFlight = async (body) => {
4545
});
4646
searchResults.itineraries.combinations = combinations;
4747

48+
// Create the offers
4849
for (const offer of Object.values(searchResults.offers)) {
50+
// Add offer items
4951
offer.offerItems = reduceToObjectByKey(offer.offerItems);
5052
offer.offerItems = reduceObjectToProperty(offer.offerItems, '_value_');
53+
54+
// Add the price plan references
55+
var pricePlansReferences = {};
56+
for(var flightsReference of offer.flightsReferences) {
57+
if(!pricePlansReferences[flightsReference.priceClassRef]) {
58+
pricePlansReferences[flightsReference.priceClassRef] = {'flights': [flightsReference.flightRef]};
59+
}
60+
else {
61+
pricePlansReferences[flightsReference.priceClassRef]['flights'].push(flightsReference.flightRef);
62+
}
63+
}
64+
offer.pricePlansReferences = pricePlansReferences;
65+
delete(offer.flightsReferences);
5166
}
5267

5368
searchResults.offers = roundCommissionDecimals(searchResults.offers);
5469
searchResults.offers = reduceToObjectByKey(searchResults.offers);
5570
searchResults.passengers = reduceToObjectByKey(searchResults.passengers);
5671
searchResults.checkedBaggages = reduceToObjectByKey(searchResults.checkedBaggages);
57-
searchResults.serviceClasses = useDictionary(searchResults.serviceClasses, searchResults.checkedBaggages, 'checkedBaggages');
58-
searchResults.serviceClasses = reduceToObjectByKey(searchResults.serviceClasses);
72+
searchResults.pricePlans = useDictionary(searchResults.pricePlans, searchResults.checkedBaggages, 'checkedBaggages');
73+
searchResults.pricePlans = reduceToObjectByKey(searchResults.pricePlans);
74+
75+
5976

6077
delete searchResults.checkedBaggages;
6178
return searchResults;

helpers/resolvers/searchHotel.js

+71-11
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,82 @@ const searchHotel = async (body) => {
3131

3232
const searchResults = await transform(response.data, hotelAvailTransformTemplate);
3333

34-
for (const accommodation of searchResults.accommodations) {
35-
accommodation.otherPolicies = reduceToObjectByKey(accommodation.otherPolicies);
36-
accommodation.otherPolicies = reduceObjectToProperty(accommodation.otherPolicies, '_value_');
37-
38-
for (const roomType of accommodation.roomTypes) {
34+
35+
// Go through the Room Stays to build the offers and gather the room types
36+
var accomodationRoomTypes = {};
37+
var offers = {};
38+
searchResults._roomStays_.forEach(roomStay => {
39+
40+
// Create the accommodation key
41+
var accommodationReference = `${roomStay._provider_}.${roomStay._hotelCode_}`;
42+
43+
// Handle the room types
44+
for (const roomType of roomStay._roomTypes_) {
45+
// Reduce the policies
3946
roomType.policies = reduceToObjectByKey(roomType.policies);
4047
roomType.policies = reduceObjectToProperty(roomType.policies, '_value_');
48+
49+
// Add the room type to the dict that will be used when building accomodation
50+
if(!(accomodationRoomTypes[accommodationReference])) {
51+
accomodationRoomTypes[accommodationReference] = {};
52+
}
53+
accomodationRoomTypes[accommodationReference][roomType._id_] = roomType;
54+
delete(accomodationRoomTypes[accommodationReference][roomType._id_]._id_);
4155
}
42-
accommodation.roomTypes = reduceToObjectByKey(accommodation.roomTypes);
43-
accommodation.ratePlans = reduceToObjectByKey(accommodation.ratePlans);
44-
}
4556

46-
searchResults.accommodations = reduceAcomodation(searchResults.accommodations);
57+
// Handle the Rate Plans
58+
searchResults.pricePlans = reduceToObjectByKey(roomStay._ratePlans_);
59+
delete(roomStay._ratePlans_);
60+
61+
// Build the offers by parsing the room rates
62+
roomStay._roomRates_.forEach(roomRate => {
4763

48-
// Create the offers
49-
searchResults.offers = reduceRoomStays(searchResults._roomStays_);
64+
// Build the offer key
65+
var offerKey = `${accommodationReference}.${roomRate.ratePlanReference}.${roomRate.roomTypeReference}`;
66+
67+
// Build the PricePlanReference
68+
var pricePlanReference = {
69+
accommodation: accommodationReference,
70+
roomType: roomRate.roomTypeReference,
71+
};
72+
pricePlansReferences = {};
73+
pricePlansReferences[roomRate.ratePlanReference] = pricePlanReference;
74+
75+
// Build the offer
76+
var offer = {
77+
// Reference from other elements
78+
pricePlansReferences: pricePlansReferences,
79+
80+
// Build price
81+
price: {
82+
currency: roomRate.price.currency,
83+
public: roomRate.price._afterTax_,
84+
taxes: new Number(roomRate.price._afterTax_) - new Number(roomRate.price._beforeTax_),
85+
},
86+
};
87+
88+
// Add the offer to the dict
89+
offers[offerKey] = offer;
90+
});
91+
});
92+
93+
// Parse the accomodations
94+
for (var accommodation of searchResults.accommodations) {
95+
96+
// Build the accomodation reference key
97+
var accommodationReference = `${accommodation._provider_}.${accommodation._id_}`;
98+
99+
// Reduce the policies
100+
accommodation.otherPolicies = reduceToObjectByKey(accommodation.otherPolicies);
101+
accommodation.otherPolicies = reduceObjectToProperty(accommodation.otherPolicies, '_value_');
102+
103+
// Add the room types gathered from Room Rates
104+
accommodation.roomTypes = accomodationRoomTypes[accommodationReference];
105+
106+
}
107+
searchResults.accommodations = reduceAcomodation(searchResults.accommodations);
108+
109+
searchResults.offers = offers;
50110
delete(searchResults._roomStays_);
51111

52112
return searchResults;

helpers/soapTemplates/searchOffers.js

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
// For documentation, https://github.com/windingtree/simard-schemas/blob/master/ndc/data-mapping.mds
22

3+
// The Passenger object
34
const mapPassengers = (passengers) => passengers.reduce((list, {Passenger}) => {
45
return `${list}
56
<edis:Passenger>
67
<edis:PTC>${Passenger.PTC}</edis:PTC>
78
</edis:Passenger>
89
`}, '')
10+
11+
12+
// The OriginDestination object
13+
const mapOriginDestinations = (OriginDestinations) => OriginDestinations.reduce((list, {OriginDestination}) => {
14+
return `${list}
15+
<edis:OriginDestination>
16+
<edis:Departure>
17+
<edis:AirportCode>${OriginDestination.Departure.AirportCode}</edis:AirportCode>
18+
<edis:Date>${OriginDestination.Departure.Date}</edis:Date>
19+
<edis:Time>${OriginDestination.Departure.Time}</edis:Time>
20+
</edis:Departure>
21+
<edis:Arrival>
22+
<edis:AirportCode>${OriginDestination.Arrival.AirportCode}</edis:AirportCode>
23+
</edis:Arrival>
24+
</edis:OriginDestination>
25+
`}, '')
26+
27+
// The Main request
928
const provideAirShoppingRequestTemplate = (data) => `<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:edis="http://www.iata.org/IATA/EDIST/2017.1">
1029
<soapenv:Header>
1130
<trackingMessageHeader xmlns="http://www.af-klm.com/soa/xsd/MessageHeader-V1_0">
@@ -57,16 +76,7 @@ const provideAirShoppingRequestTemplate = (data) => `<soapenv:Envelope xmlns:soa
5776
</edis:Party>
5877
<edis:CoreQuery>
5978
<edis:OriginDestinations>
60-
<edis:OriginDestination>
61-
<edis:Departure>
62-
<edis:AirportCode>${data.CoreQuery.OriginDestinations.OriginDestination.Departure.AirportCode}</edis:AirportCode>
63-
<edis:Date>${data.CoreQuery.OriginDestinations.OriginDestination.Departure.Date}</edis:Date>
64-
<edis:Time>${data.CoreQuery.OriginDestinations.OriginDestination.Departure.Time}</edis:Time>
65-
</edis:Departure>
66-
<edis:Arrival>
67-
<edis:AirportCode>${data.CoreQuery.OriginDestinations.OriginDestination.Arrival.AirportCode}</edis:AirportCode>
68-
</edis:Arrival>
69-
</edis:OriginDestination>
79+
${mapOriginDestinations(data.CoreQuery.OriginDestinations)}
7080
</edis:OriginDestinations>
7181
</edis:CoreQuery>
7282
<edis:Preference>

helpers/transformInputData/hotelAvail.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const mapRequestData = (hotelCodes, { accommodation: { arrival, departure }, pas
1313
HotelRef: {
1414
HotelCode: hotelCode,
1515
StayDateRange: {
16-
Start: arrival,
16+
Start: format(new Date(arrival), 'yyyy-MM-dd'),
1717
Duration: duration,
1818
RoomStayCandidates: {
1919
RoomStayCandidate: {

helpers/transformInputData/searchOffers.js

+37-12
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,63 @@
11
const format = require('date-fns/format');
22
const { airFranceConfig } = require('../../config');
33

4+
function expandPassengers(passengers) {
5+
var res = [];
6+
for(p of passengers) {
7+
// Create a default value
8+
if(!p.count) {
9+
res.push(p)
10+
}
11+
12+
// Otherwise create one for each count
13+
else {
14+
for(let i=0; i<p.count; i++) {
15+
res.push(p);
16+
}
17+
}
18+
}
19+
return res;
20+
}
21+
422
const mapNdcRequestData = ({itinerary, passengers}) => {
5-
const departureTime = new Date(itinerary.segments[0].departureTime);
23+
//const departureTime = new Date(itinerary.segments[0].departureTime);
624
const ndcRequestData = {
725
...airFranceConfig,
826
PointOfSale: {
927
RequestTime: (new Date(Date.now())).toISOString(),
1028
},
1129
CoreQuery: {
12-
// Later this should be updated to support multi-city search
13-
OriginDestinations: {
30+
// Origin Destinations contains the list of segments
31+
OriginDestinations: itinerary.segments.map(segment => ({
1432
OriginDestination: {
33+
// Departure Information
1534
Departure: {
16-
AirportCode: itinerary.segments[0].origin.locationType === 'airport'
17-
? itinerary.segments[0].origin.iataCode : undefined,
18-
Date: format(departureTime, 'yyyy-MM-dd'),
19-
Time: format(departureTime, 'HH:mm'),
35+
AirportCode: (segment.origin.locationType === 'airport'
36+
? segment.origin.iataCode : undefined),
37+
Date: format(new Date(segment.departureTime), 'yyyy-MM-dd'),
38+
Time: format(new Date(segment.departureTime), 'HH:mm'),
2039
},
40+
41+
// Arrival Information
2142
Arrival: {
22-
AirportCode: itinerary.segments[0].destination.locationType === 'airport'
23-
? itinerary.segments[0].destination.iataCode : undefined,
43+
AirportCode: segment.destination.locationType === 'airport'
44+
? segment.destination.iataCode : undefined,
2445
},
2546
},
26-
},
47+
})),
48+
49+
// Preference
2750
Preference: {
2851
CabinPreferences: {
2952
CabinType: {
30-
Code: 5,
53+
Code: 5, // Economy
3154
},
3255
},
3356
},
57+
58+
// Lists
3459
DataLists: {
35-
PassengerList: passengers.map(p => ({
60+
PassengerList: expandPassengers(passengers).map(p => ({
3661
Passenger: {
3762
PTC: p.type,
3863
},

0 commit comments

Comments
 (0)