ti2-tourplan 1.0.130 → 1.0.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.test.js +316 -0
- package/itinerary-products-search.js +44 -8
- package/package.json +1 -1
package/index.test.js
CHANGED
|
@@ -870,6 +870,240 @@ describe('search tests', () => {
|
|
|
870
870
|
expect(retVal).toMatchSnapshot();
|
|
871
871
|
});
|
|
872
872
|
|
|
873
|
+
describe('searchProductsForItinerary pickupPoints', () => {
|
|
874
|
+
const singlePickupPoint = {
|
|
875
|
+
Point_ID: '504',
|
|
876
|
+
PointDescription: 'Keio Plaza Hotel Tokyo',
|
|
877
|
+
CanPickup: 'Y',
|
|
878
|
+
CanDropoff: 'Y',
|
|
879
|
+
puTime: '0815',
|
|
880
|
+
doTime: '1800',
|
|
881
|
+
};
|
|
882
|
+
const defaultCallTourplan = mockCallTourplan.getMockImplementation();
|
|
883
|
+
const mockOptionIdProductSearch = ({ pickupPoints } = {}) => {
|
|
884
|
+
mockCallTourplan.mockImplementation(async ({ model }) => {
|
|
885
|
+
if (model.AgentInfoRequest) {
|
|
886
|
+
return { AgentInfoReply: { Currency: 'USD' } };
|
|
887
|
+
}
|
|
888
|
+
if (model.GetLocationsRequest) {
|
|
889
|
+
return { GetLocationsReply: { Locations: { Location: [] } } };
|
|
890
|
+
}
|
|
891
|
+
if (model.GetServicesRequest) {
|
|
892
|
+
return { GetServicesReply: { TPLServices: { TPLService: [] } } };
|
|
893
|
+
}
|
|
894
|
+
if (model.GetSystemSettingsRequest) {
|
|
895
|
+
return { GetSystemSettingsReply: { Countries: { Country: [] } } };
|
|
896
|
+
}
|
|
897
|
+
if (model.OptionInfoRequest) {
|
|
898
|
+
return {
|
|
899
|
+
OptionInfoReply: {
|
|
900
|
+
Option: {
|
|
901
|
+
Opt: 'LONSMPICKUP00001',
|
|
902
|
+
OptGeneral: {
|
|
903
|
+
SupplierId: '1001',
|
|
904
|
+
SupplierName: 'Pickup Supplier',
|
|
905
|
+
Description: 'Pickup Tour',
|
|
906
|
+
SType: 'N',
|
|
907
|
+
AdultsAllowed: 'Y',
|
|
908
|
+
ButtonName: 'Sightseeing',
|
|
909
|
+
...(pickupPoints !== undefined ? { PickupPoints: pickupPoints } : {}),
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
throw new Error(`Unexpected model: ${Object.keys(model)[0]}`);
|
|
916
|
+
});
|
|
917
|
+
};
|
|
918
|
+
const getOptionInfoRequest = () => mockCallTourplan.mock.calls
|
|
919
|
+
.map(([call]) => call.model.OptionInfoRequest)
|
|
920
|
+
.find(Boolean);
|
|
921
|
+
|
|
922
|
+
afterEach(() => {
|
|
923
|
+
mockCallTourplan.mockImplementation(defaultCallTourplan);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('uses GR OptionInfo by default for optionId search', async () => {
|
|
927
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
928
|
+
try {
|
|
929
|
+
mockOptionIdProductSearch();
|
|
930
|
+
|
|
931
|
+
const retVal = await app.searchProductsForItinerary({
|
|
932
|
+
axios,
|
|
933
|
+
token,
|
|
934
|
+
typeDefsAndQueries,
|
|
935
|
+
payload: {
|
|
936
|
+
optionId: 'LONSMPICKUP00001',
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
expect(getOptionInfoRequest().Info).toBe('GR');
|
|
941
|
+
expect(retVal.products[0].options[0].pickupPoints).toBeUndefined();
|
|
942
|
+
} finally {
|
|
943
|
+
warnSpy.mockRestore();
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
it.each([false, 'false'])(
|
|
948
|
+
'keeps GR OptionInfo when pickupPointsRequired is %p',
|
|
949
|
+
async pickupPointsRequired => {
|
|
950
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
951
|
+
try {
|
|
952
|
+
mockOptionIdProductSearch();
|
|
953
|
+
|
|
954
|
+
const retVal = await app.searchProductsForItinerary({
|
|
955
|
+
axios,
|
|
956
|
+
token,
|
|
957
|
+
typeDefsAndQueries,
|
|
958
|
+
payload: {
|
|
959
|
+
optionId: 'LONSMPICKUP00001',
|
|
960
|
+
pickupPointsRequired,
|
|
961
|
+
},
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
expect(getOptionInfoRequest().Info).toBe('GR');
|
|
965
|
+
expect(retVal.products[0].options[0].pickupPoints).toBeUndefined();
|
|
966
|
+
} finally {
|
|
967
|
+
warnSpy.mockRestore();
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
it('preserves Tourplan pickupPoints on options', async () => {
|
|
973
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
974
|
+
try {
|
|
975
|
+
mockOptionIdProductSearch({
|
|
976
|
+
pickupPoints: { PickupPoint: singlePickupPoint },
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const retVal = await app.searchProductsForItinerary({
|
|
980
|
+
axios,
|
|
981
|
+
token,
|
|
982
|
+
typeDefsAndQueries,
|
|
983
|
+
payload: {
|
|
984
|
+
optionId: 'LONSMPICKUP00001',
|
|
985
|
+
pickupPointsRequired: true,
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
expect(getOptionInfoRequest().Info).toBe('GRP');
|
|
990
|
+
expect(retVal.products[0].options[0].pickupPoints).toEqual({
|
|
991
|
+
PickupPoint: [singlePickupPoint],
|
|
992
|
+
});
|
|
993
|
+
} finally {
|
|
994
|
+
warnSpy.mockRestore();
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it('treats string true pickupPointsRequired as enabled', async () => {
|
|
999
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1000
|
+
try {
|
|
1001
|
+
mockOptionIdProductSearch({
|
|
1002
|
+
pickupPoints: { PickupPoint: singlePickupPoint },
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const retVal = await app.searchProductsForItinerary({
|
|
1006
|
+
axios,
|
|
1007
|
+
token,
|
|
1008
|
+
typeDefsAndQueries,
|
|
1009
|
+
payload: {
|
|
1010
|
+
optionId: 'LONSMPICKUP00001',
|
|
1011
|
+
pickupPointsRequired: 'true',
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
expect(getOptionInfoRequest().Info).toBe('GRP');
|
|
1016
|
+
expect(retVal.products[0].options[0].pickupPoints).toEqual({
|
|
1017
|
+
PickupPoint: [singlePickupPoint],
|
|
1018
|
+
});
|
|
1019
|
+
} finally {
|
|
1020
|
+
warnSpy.mockRestore();
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
it('keeps already-array PickupPoint values', async () => {
|
|
1025
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1026
|
+
try {
|
|
1027
|
+
const pickupPoints = {
|
|
1028
|
+
PickupPoint: [
|
|
1029
|
+
singlePickupPoint,
|
|
1030
|
+
{
|
|
1031
|
+
Point_ID: '505',
|
|
1032
|
+
PointDescription: 'Tokyo Station',
|
|
1033
|
+
CanPickup: 'N',
|
|
1034
|
+
CanDropoff: 'Y',
|
|
1035
|
+
doTime: '1800',
|
|
1036
|
+
},
|
|
1037
|
+
],
|
|
1038
|
+
};
|
|
1039
|
+
mockOptionIdProductSearch({ pickupPoints });
|
|
1040
|
+
|
|
1041
|
+
const retVal = await app.searchProductsForItinerary({
|
|
1042
|
+
axios,
|
|
1043
|
+
token,
|
|
1044
|
+
typeDefsAndQueries,
|
|
1045
|
+
payload: {
|
|
1046
|
+
optionId: 'LONSMPICKUP00001',
|
|
1047
|
+
pickupPointsRequired: 1,
|
|
1048
|
+
},
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
expect(getOptionInfoRequest().Info).toBe('GRP');
|
|
1052
|
+
expect(retVal.products[0].options[0].pickupPoints).toEqual(pickupPoints);
|
|
1053
|
+
} finally {
|
|
1054
|
+
warnSpy.mockRestore();
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
it('omits pickupPoints when Tourplan returns none', async () => {
|
|
1059
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1060
|
+
try {
|
|
1061
|
+
mockOptionIdProductSearch({
|
|
1062
|
+
pickupPoints: { PickupPoint: [] },
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
const retVal = await app.searchProductsForItinerary({
|
|
1066
|
+
axios,
|
|
1067
|
+
token,
|
|
1068
|
+
typeDefsAndQueries,
|
|
1069
|
+
payload: {
|
|
1070
|
+
optionId: 'LONSMPICKUP00001',
|
|
1071
|
+
pickupPointsRequired: true,
|
|
1072
|
+
},
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
expect(getOptionInfoRequest().Info).toBe('GRP');
|
|
1076
|
+
expect(retVal.products[0].options[0].pickupPoints).toBeUndefined();
|
|
1077
|
+
} finally {
|
|
1078
|
+
warnSpy.mockRestore();
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it('omits empty PickupPoints shells', async () => {
|
|
1083
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1084
|
+
try {
|
|
1085
|
+
mockOptionIdProductSearch({
|
|
1086
|
+
pickupPoints: {},
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
const retVal = await app.searchProductsForItinerary({
|
|
1090
|
+
axios,
|
|
1091
|
+
token,
|
|
1092
|
+
typeDefsAndQueries,
|
|
1093
|
+
payload: {
|
|
1094
|
+
optionId: 'LONSMPICKUP00001',
|
|
1095
|
+
pickupPointsRequired: 'yes',
|
|
1096
|
+
},
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
expect(getOptionInfoRequest().Info).toBe('GRP');
|
|
1100
|
+
expect(retVal.products[0].options[0]).not.toHaveProperty('pickupPoints');
|
|
1101
|
+
} finally {
|
|
1102
|
+
warnSpy.mockRestore();
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
|
|
873
1107
|
describe('searchProductsForItinerary omitServiceCodes', () => {
|
|
874
1108
|
const optionGeneral = {
|
|
875
1109
|
SupplierId: '1001',
|
|
@@ -940,6 +1174,11 @@ describe('search tests', () => {
|
|
|
940
1174
|
'???BD????????????',
|
|
941
1175
|
'???TR????????????',
|
|
942
1176
|
]);
|
|
1177
|
+
const infos = mockCallTourplan.mock.calls
|
|
1178
|
+
.map(([call]) => call.model.OptionInfoRequest)
|
|
1179
|
+
.filter(Boolean)
|
|
1180
|
+
.map(req => req.Info);
|
|
1181
|
+
expect(infos).toEqual(['G', 'G', 'G']);
|
|
943
1182
|
expect(debugSpy).not.toHaveBeenCalled();
|
|
944
1183
|
expect(retVal.products).toHaveLength(1);
|
|
945
1184
|
expect(retVal.products[0].options.map(o => o.optionId)).toEqual([
|
|
@@ -950,6 +1189,83 @@ describe('search tests', () => {
|
|
|
950
1189
|
debugSpy.mockRestore();
|
|
951
1190
|
});
|
|
952
1191
|
|
|
1192
|
+
it('requests pickup points in full-catalog OptionInfo when required', async () => {
|
|
1193
|
+
const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
|
|
1194
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1195
|
+
try {
|
|
1196
|
+
const tourplanPickupPoints = {
|
|
1197
|
+
PickupPoint: {
|
|
1198
|
+
Point_ID: '504',
|
|
1199
|
+
PointDescription: 'Keio Plaza Hotel Tokyo',
|
|
1200
|
+
CanPickup: 'Y',
|
|
1201
|
+
CanDropoff: 'Y',
|
|
1202
|
+
puTime: '0815',
|
|
1203
|
+
doTime: '1800',
|
|
1204
|
+
},
|
|
1205
|
+
};
|
|
1206
|
+
const normalizedPickupPoints = {
|
|
1207
|
+
PickupPoint: [tourplanPickupPoints.PickupPoint],
|
|
1208
|
+
};
|
|
1209
|
+
mockCallTourplan.mockImplementation(async ({ model }) => {
|
|
1210
|
+
if (model.AgentInfoRequest) {
|
|
1211
|
+
return { AgentInfoReply: { Currency: 'USD' } };
|
|
1212
|
+
}
|
|
1213
|
+
if (model.GetLocationsRequest) {
|
|
1214
|
+
return { GetLocationsReply: { Locations: { Location: [] } } };
|
|
1215
|
+
}
|
|
1216
|
+
if (model.GetSystemSettingsRequest) {
|
|
1217
|
+
return { GetSystemSettingsReply: { Countries: { Country: [] } } };
|
|
1218
|
+
}
|
|
1219
|
+
if (model.GetServicesRequest) return getServicesReply;
|
|
1220
|
+
const opt = model.OptionInfoRequest && model.OptionInfoRequest.Opt;
|
|
1221
|
+
if (optionsByOpt[opt]) {
|
|
1222
|
+
return {
|
|
1223
|
+
OptionInfoReply: {
|
|
1224
|
+
Option: {
|
|
1225
|
+
...optionsByOpt[opt],
|
|
1226
|
+
OptGeneral: {
|
|
1227
|
+
...optionsByOpt[opt].OptGeneral,
|
|
1228
|
+
PickupPoints: tourplanPickupPoints,
|
|
1229
|
+
},
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
throw new Error(`Unexpected OptionInfo Opt: ${opt}`);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
const retVal = await app.searchProductsForItinerary({
|
|
1238
|
+
axios,
|
|
1239
|
+
token,
|
|
1240
|
+
typeDefsAndQueries,
|
|
1241
|
+
payload: {
|
|
1242
|
+
searchInput: '',
|
|
1243
|
+
pickupPointsRequired: true,
|
|
1244
|
+
},
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
const infos = mockCallTourplan.mock.calls
|
|
1248
|
+
.map(([call]) => call.model.OptionInfoRequest)
|
|
1249
|
+
.filter(Boolean)
|
|
1250
|
+
.map(req => req.Info);
|
|
1251
|
+
expect(infos).toEqual(['GP', 'GP', 'GP']);
|
|
1252
|
+
expect(retVal.products[0].options.map(o => o.optionId)).toEqual([
|
|
1253
|
+
'TESTACOPTION00001',
|
|
1254
|
+
'TESTBDOPTION00001',
|
|
1255
|
+
'TESTTROPTION00001',
|
|
1256
|
+
]);
|
|
1257
|
+
expect(retVal.products[0].options.map(o => o.pickupPoints)).toEqual([
|
|
1258
|
+
normalizedPickupPoints,
|
|
1259
|
+
normalizedPickupPoints,
|
|
1260
|
+
normalizedPickupPoints,
|
|
1261
|
+
]);
|
|
1262
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1263
|
+
} finally {
|
|
1264
|
+
debugSpy.mockRestore();
|
|
1265
|
+
warnSpy.mockRestore();
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
|
|
953
1269
|
it('fetches all service codes when omitServiceCodes is empty', async () => {
|
|
954
1270
|
const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
|
|
955
1271
|
mockFullCatalog();
|
|
@@ -7,6 +7,30 @@ const { getCachedLocations } = require('./tp-helpers/locations');
|
|
|
7
7
|
const { getCachedServices } = require('./tp-helpers/services');
|
|
8
8
|
const { getCachedDestinationCountries } = require('./tp-helpers/system-settings');
|
|
9
9
|
const { enrichOptionWithCodeTables } = require('./tp-helpers/option-enrichment');
|
|
10
|
+
const { asArray } = require('./tp-helpers/values');
|
|
11
|
+
|
|
12
|
+
const isEnabled = value => {
|
|
13
|
+
if (value === true || value === 1) return true;
|
|
14
|
+
const normalized = String(value == null ? '' : value).trim().toLowerCase();
|
|
15
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Keep Tourplan PascalCase under camelCase pickupPoints (same pattern as optRates).
|
|
19
|
+
// Bokun channel-manager reads container.PickupPoint / point.Point_ID etc.
|
|
20
|
+
const normalizePickupPoints = rawPickupPoints => {
|
|
21
|
+
if (!rawPickupPoints || typeof rawPickupPoints !== 'object') return undefined;
|
|
22
|
+
const points = asArray(R.path(['PickupPoint'], rawPickupPoints))
|
|
23
|
+
.filter(point => point && typeof point === 'object' && !Array.isArray(point));
|
|
24
|
+
if (!points.length) return undefined;
|
|
25
|
+
return {
|
|
26
|
+
...rawPickupPoints,
|
|
27
|
+
PickupPoint: points,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getOptionInfoTag = ({ includeRates = false, pickupPointsRequired = false } = {}) => (
|
|
32
|
+
`${includeRates ? 'GR' : 'G'}${pickupPointsRequired ? 'P' : ''}`
|
|
33
|
+
);
|
|
10
34
|
|
|
11
35
|
const searchProductsForItinerary = async ({
|
|
12
36
|
axios,
|
|
@@ -30,6 +54,8 @@ const searchProductsForItinerary = async ({
|
|
|
30
54
|
// service codes to omit from full catalog search
|
|
31
55
|
// (e.g. ['AC'], 'AC', or 'AC,SM'); when empty/absent, get all
|
|
32
56
|
omitServiceCodes,
|
|
57
|
+
// Request Tourplan pickup/dropoff points in OptionInfo when product consumers need them.
|
|
58
|
+
pickupPointsRequired,
|
|
33
59
|
},
|
|
34
60
|
callTourplan,
|
|
35
61
|
cache,
|
|
@@ -38,6 +64,7 @@ const searchProductsForItinerary = async ({
|
|
|
38
64
|
const optionIds = optionId
|
|
39
65
|
? (Array.isArray(optionId) ? optionId : [optionId]).filter(Boolean)
|
|
40
66
|
: [];
|
|
67
|
+
const shouldRequestPickupPoints = isEnabled(pickupPointsRequired);
|
|
41
68
|
|
|
42
69
|
let options = [];
|
|
43
70
|
let servicesByCode;
|
|
@@ -62,7 +89,10 @@ const searchProductsForItinerary = async ({
|
|
|
62
89
|
const getOptionsModel = {
|
|
63
90
|
OptionInfoRequest: {
|
|
64
91
|
Opt: id,
|
|
65
|
-
Info:
|
|
92
|
+
Info: getOptionInfoTag({
|
|
93
|
+
includeRates: true,
|
|
94
|
+
pickupPointsRequired: shouldRequestPickupPoints,
|
|
95
|
+
}),
|
|
66
96
|
AgentID: hostConnectAgentID,
|
|
67
97
|
Password: hostConnectAgentPassword,
|
|
68
98
|
},
|
|
@@ -116,7 +146,7 @@ const searchProductsForItinerary = async ({
|
|
|
116
146
|
const getOptionsModel = {
|
|
117
147
|
OptionInfoRequest: {
|
|
118
148
|
Opt: `???${serviceCode}????????????`,
|
|
119
|
-
Info:
|
|
149
|
+
Info: getOptionInfoTag({ pickupPointsRequired: shouldRequestPickupPoints }),
|
|
120
150
|
AgentID: hostConnectAgentID,
|
|
121
151
|
Password: hostConnectAgentPassword,
|
|
122
152
|
...(lastUpdatedFrom ? {
|
|
@@ -217,10 +247,11 @@ const searchProductsForItinerary = async ({
|
|
|
217
247
|
concurrency: 10,
|
|
218
248
|
},
|
|
219
249
|
);
|
|
220
|
-
// Preserve raw OptRates from Tourplan in product search response
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
250
|
+
// Preserve raw OptRates / PickupPoints from Tourplan in product search response
|
|
251
|
+
// when available (camelCase keys: optRates, pickupPoints; Tourplan field casing
|
|
252
|
+
// inside). Re-attach after GraphQL because stock TI2 itinerary-product
|
|
253
|
+
// typeDefs/query omit these fields (same for city/country/currency).
|
|
254
|
+
// Also ensure city and currency are always on the option for product cache.
|
|
224
255
|
const enrichedByOptionId = R.indexBy(R.prop('Opt'), enrichedOptions);
|
|
225
256
|
const optionRatesByOptionId = options.reduce((acc, option) => {
|
|
226
257
|
const currentOptionId = R.path(['Opt'], option);
|
|
@@ -231,7 +262,7 @@ const searchProductsForItinerary = async ({
|
|
|
231
262
|
[currentOptionId]: currentOptionRates,
|
|
232
263
|
};
|
|
233
264
|
}, {});
|
|
234
|
-
const
|
|
265
|
+
const productsWithRawOptionMetadata = products.map(product => ({
|
|
235
266
|
...product,
|
|
236
267
|
options: R.pathOr([], ['options'], product).map(currentOption => {
|
|
237
268
|
const rawOption = R.path([currentOption.optionId], enrichedByOptionId) || {};
|
|
@@ -242,11 +273,16 @@ const searchProductsForItinerary = async ({
|
|
|
242
273
|
|| R.path(['__destination', 'city'], rawOption);
|
|
243
274
|
const country = currentOption.country
|
|
244
275
|
|| R.path(['__destination', 'country'], rawOption);
|
|
276
|
+
const pickupPoints = normalizePickupPoints(
|
|
277
|
+
R.path(['OptGeneral', 'PickupPoints'], rawOption)
|
|
278
|
+
|| R.path(['PickupPoints'], rawOption),
|
|
279
|
+
);
|
|
245
280
|
return {
|
|
246
281
|
...R.omit(['city', 'country', 'rateContext'], currentOption),
|
|
247
282
|
...(city ? { city } : {}),
|
|
248
283
|
...(country ? { country } : {}),
|
|
249
284
|
...(currency ? { currency } : {}),
|
|
285
|
+
...(pickupPoints ? { pickupPoints } : {}),
|
|
250
286
|
...(R.path([currentOption.optionId], optionRatesByOptionId)
|
|
251
287
|
? { optRates: R.path([currentOption.optionId], optionRatesByOptionId) }
|
|
252
288
|
: {}),
|
|
@@ -254,7 +290,7 @@ const searchProductsForItinerary = async ({
|
|
|
254
290
|
}),
|
|
255
291
|
}));
|
|
256
292
|
return {
|
|
257
|
-
products:
|
|
293
|
+
products: productsWithRawOptionMetadata,
|
|
258
294
|
productFields: [],
|
|
259
295
|
...(searchInput || optionId ? {} : configuration),
|
|
260
296
|
};
|