ti2-tourplan 1.0.114 → 1.0.115
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.
|
@@ -119,6 +119,7 @@ const searchAvailabilityForItinerary = async ({
|
|
|
119
119
|
Defaults to 1.
|
|
120
120
|
*/
|
|
121
121
|
chargeUnitQuantity,
|
|
122
|
+
roomTypeRequired = true,
|
|
122
123
|
},
|
|
123
124
|
callTourplan,
|
|
124
125
|
cache,
|
|
@@ -206,8 +207,20 @@ const searchAvailabilityForItinerary = async ({
|
|
|
206
207
|
message,
|
|
207
208
|
dateRanges,
|
|
208
209
|
maxPaxPerCharge,
|
|
210
|
+
duration,
|
|
209
211
|
} = availabilityConfig;
|
|
210
212
|
|
|
213
|
+
let isDateRangeValidationRequired = true;
|
|
214
|
+
// Skip date range validation for packages & non-accomodation products
|
|
215
|
+
// This is required because:
|
|
216
|
+
// 1. non-accomodation products that are not packages do not have date ranges
|
|
217
|
+
// and can be booked even on the last date of valid date range
|
|
218
|
+
// 2. packages (including multi-day packages) are allowed to be booked even on
|
|
219
|
+
// the last date of valid date range. (A package is when duration is > 1).
|
|
220
|
+
if ((duration && duration > 1) || !roomTypeRequired) {
|
|
221
|
+
isDateRangeValidationRequired = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
211
224
|
// Validate max pax per charge
|
|
212
225
|
const maxPaxPerChargeError = validateMaxPaxPerCharge({
|
|
213
226
|
roomConfigs,
|
|
@@ -218,9 +231,9 @@ const searchAvailabilityForItinerary = async ({
|
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
let noOfDaysRatesAvailable = 0;
|
|
221
|
-
let allDatesHaveRatesAvailable = false;
|
|
222
234
|
|
|
223
235
|
if (dateRanges.length > 0) {
|
|
236
|
+
let allDatesHaveRatesAvailable = true;
|
|
224
237
|
const startDateIsInvalid = validateStartDay({
|
|
225
238
|
dateRanges,
|
|
226
239
|
startDate,
|
|
@@ -237,20 +250,24 @@ const searchAvailabilityForItinerary = async ({
|
|
|
237
250
|
|
|
238
251
|
const lastDateRangeEndDate = moment(dateRanges[dateRanges.length - 1].endDate);
|
|
239
252
|
noOfDaysRatesAvailable = lastDateRangeEndDate.diff(moment(startDate), 'days') + 1;
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
if (isDateRangeValidationRequired) {
|
|
254
|
+
// eslint-disable-next-line max-len
|
|
255
|
+
allDatesHaveRatesAvailable = doAllDatesHaveRatesAvailable(lastDateRangeEndDate, startDate, chargeUnitQuantity);
|
|
256
|
+
}
|
|
242
257
|
|
|
243
258
|
if (allDatesHaveRatesAvailable) {
|
|
244
259
|
// all dates have rates available, get stay rates for the given dates
|
|
245
260
|
// Validate date ranges and room configurations
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
261
|
+
if (isDateRangeValidationRequired) {
|
|
262
|
+
const dateRangesError = validateDateRanges({
|
|
263
|
+
dateRanges,
|
|
264
|
+
startDate,
|
|
265
|
+
chargeUnitQuantity,
|
|
266
|
+
});
|
|
251
267
|
|
|
252
|
-
|
|
253
|
-
|
|
268
|
+
if (dateRangesError) {
|
|
269
|
+
return dateRangesError;
|
|
270
|
+
}
|
|
254
271
|
}
|
|
255
272
|
|
|
256
273
|
// get stay rates for the given dates
|
|
@@ -353,13 +370,16 @@ const searchAvailabilityForItinerary = async ({
|
|
|
353
370
|
const customPeriodInfoMsg = useLastYearRate ? CUSTOM_PERIOD_LAST_YEAR_INFO_MSG : CUSTOM_PERIOD_LAST_AVAILABLE_INFO_MSG;
|
|
354
371
|
|
|
355
372
|
let sWarningMsg = '';
|
|
373
|
+
let dateRangesError = null;
|
|
356
374
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
375
|
+
if (isDateRangeValidationRequired) {
|
|
376
|
+
// Validate date ranges
|
|
377
|
+
dateRangesError = validateDateRanges({
|
|
378
|
+
dateRanges: [dateRangeToUse],
|
|
379
|
+
startDate: dateRangeToUse.startDate,
|
|
380
|
+
chargeUnitQuantity,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
363
383
|
|
|
364
384
|
let minStayRequired = 0;
|
|
365
385
|
if (dateRangesError) {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/* globals describe, it, expect, jest, beforeEach */
|
|
2
|
+
|
|
3
|
+
jest.mock('./itinerary-availability-utils', () => ({
|
|
4
|
+
validateMaxPaxPerCharge: jest.fn(() => null),
|
|
5
|
+
validateDateRanges: jest.fn(() => null),
|
|
6
|
+
validateStartDay: jest.fn(() => null),
|
|
7
|
+
getMatchingRateSet: jest.fn(() => ({ matchingRateSet: null })),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock('./product-connect/itinerary-pc-api-validation-helper', () => ({
|
|
11
|
+
validateProductConnect: jest.fn(async () => false),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('./itinerary-availability-helper', () => ({
|
|
15
|
+
getAgentCurrencyCode: jest.fn(async () => 'USD'),
|
|
16
|
+
getConversionRate: jest.fn(async () => ({ conversionRate: 1 })),
|
|
17
|
+
findNextValidDate: jest.fn(() => null),
|
|
18
|
+
getAvailabilityConfig: jest.fn(async () => ({
|
|
19
|
+
roomConfigs: [{ Adults: 2 }],
|
|
20
|
+
endDate: null,
|
|
21
|
+
message: null,
|
|
22
|
+
dateRanges: [{ startDate: '2025-04-01', endDate: '2025-04-30', rateSets: [] }],
|
|
23
|
+
duration: null,
|
|
24
|
+
maxPaxPerCharge: null,
|
|
25
|
+
})),
|
|
26
|
+
getNoRatesAvailableError: jest.fn(async () => 'No rates available'),
|
|
27
|
+
getStayResults: jest.fn(async () => ([{
|
|
28
|
+
RateId: 'R1',
|
|
29
|
+
Currency: 'USD',
|
|
30
|
+
TotalPrice: '10000',
|
|
31
|
+
AgentPrice: '9000',
|
|
32
|
+
}])),
|
|
33
|
+
getCustomRateDateRange: jest.fn(async () => ({ dateRangeToUse: null, errorMsg: 'No custom rate' })),
|
|
34
|
+
getRatesObjectArray: jest.fn(() => ([{ rateId: 'R1', totalPrice: 10000, agentPrice: 9000 }])),
|
|
35
|
+
getEmptyRateObject: jest.fn(() => ([{ rateId: 'EMPTY' }])),
|
|
36
|
+
MIN_MARKUP_PERCENTAGE: 0,
|
|
37
|
+
MAX_MARKUP_PERCENTAGE: 100,
|
|
38
|
+
MIN_EXTENDED_BOOKING_YEARS: 1,
|
|
39
|
+
MAX_EXTENDED_BOOKING_YEARS: 10,
|
|
40
|
+
GENERIC_AVALABILITY_CHK_ERROR_MESSAGE: 'Generic availability error',
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
jest.mock('./product-connect/itinerary-pc-rates-helper', () => ({
|
|
44
|
+
getCostFromProductConnect: jest.fn(async () => ({
|
|
45
|
+
success: false,
|
|
46
|
+
costPriceIncludingTax: 0,
|
|
47
|
+
taxRate: 0,
|
|
48
|
+
})),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
jest.mock('./product-connect/itinerary-pc-option-helper', () => ({
|
|
52
|
+
getOptionFromProductConnect: jest.fn(async () => null),
|
|
53
|
+
CROSS_SEASON_NOT_ALLOWED: 'N',
|
|
54
|
+
CROSS_SEASON_CAL_SPLIT_RATE: 'S',
|
|
55
|
+
CROSS_SEASON_CAL_USING_RATE_OF_FIRST_RATE_PERIOD: 'F',
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const itineraryAvailabilityUtils = require('./itinerary-availability-utils');
|
|
59
|
+
const itineraryAvailabilityHelper = require('./itinerary-availability-helper');
|
|
60
|
+
const { searchAvailabilityForItinerary } = require('./itinerary-availability');
|
|
61
|
+
|
|
62
|
+
describe('searchAvailabilityForItinerary validation flags', () => {
|
|
63
|
+
const baseToken = {
|
|
64
|
+
hostConnectEndpoint: 'https://test-host-connect.com',
|
|
65
|
+
hostConnectAgentID: 'test-agent-id',
|
|
66
|
+
hostConnectAgentPassword: 'test-agent-password',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const basePayload = {
|
|
70
|
+
optionId: 'OPTION_1',
|
|
71
|
+
startDate: '2025-04-01',
|
|
72
|
+
chargeUnitQuantity: 2,
|
|
73
|
+
paxConfigs: [{ roomType: 'DB', adults: 2 }],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
jest.clearAllMocks();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('runs date range validation when duration is not fixed and room type is required', async () => {
|
|
81
|
+
itineraryAvailabilityHelper.getAvailabilityConfig.mockResolvedValueOnce({
|
|
82
|
+
roomConfigs: [{ Adults: 2 }],
|
|
83
|
+
endDate: null,
|
|
84
|
+
message: null,
|
|
85
|
+
dateRanges: [{ startDate: '2025-04-01', endDate: '2025-04-30', rateSets: [] }],
|
|
86
|
+
duration: null,
|
|
87
|
+
maxPaxPerCharge: null,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const result = await searchAvailabilityForItinerary({
|
|
91
|
+
axios: jest.fn(),
|
|
92
|
+
token: baseToken,
|
|
93
|
+
payload: basePayload,
|
|
94
|
+
callTourplan: jest.fn(),
|
|
95
|
+
cache: { getOrExec: async ({ fn, fnParams }) => fn(...fnParams) },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(itineraryAvailabilityUtils.validateDateRanges).toHaveBeenCalled();
|
|
99
|
+
expect(result.bookable).toBe(true);
|
|
100
|
+
expect(result.type).toBe('inventory');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('skips date range validation when roomTypeRequired is false', async () => {
|
|
104
|
+
itineraryAvailabilityHelper.getAvailabilityConfig.mockResolvedValueOnce({
|
|
105
|
+
roomConfigs: [{ Adults: 2 }],
|
|
106
|
+
endDate: null,
|
|
107
|
+
message: null,
|
|
108
|
+
dateRanges: [{ startDate: '2025-04-01', endDate: '2025-04-30', rateSets: [] }],
|
|
109
|
+
duration: null,
|
|
110
|
+
maxPaxPerCharge: null,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = await searchAvailabilityForItinerary({
|
|
114
|
+
axios: jest.fn(),
|
|
115
|
+
token: baseToken,
|
|
116
|
+
payload: { ...basePayload, roomTypeRequired: false },
|
|
117
|
+
callTourplan: jest.fn(),
|
|
118
|
+
cache: { getOrExec: async ({ fn, fnParams }) => fn(...fnParams) },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(itineraryAvailabilityUtils.validateDateRanges).not.toHaveBeenCalled();
|
|
122
|
+
expect(result.bookable).toBe(true);
|
|
123
|
+
expect(result.type).toBe('inventory');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('skips date range validation when duration is greater than 1', async () => {
|
|
127
|
+
itineraryAvailabilityHelper.getAvailabilityConfig.mockResolvedValueOnce({
|
|
128
|
+
roomConfigs: [{ Adults: 2 }],
|
|
129
|
+
endDate: '2025-04-02',
|
|
130
|
+
message: 'Duration is fixed',
|
|
131
|
+
dateRanges: [{ startDate: '2025-04-01', endDate: '2025-04-30', rateSets: [] }],
|
|
132
|
+
duration: 2,
|
|
133
|
+
maxPaxPerCharge: null,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = await searchAvailabilityForItinerary({
|
|
137
|
+
axios: jest.fn(),
|
|
138
|
+
token: baseToken,
|
|
139
|
+
payload: basePayload,
|
|
140
|
+
callTourplan: jest.fn(),
|
|
141
|
+
cache: { getOrExec: async ({ fn, fnParams }) => fn(...fnParams) },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(itineraryAvailabilityUtils.validateDateRanges).not.toHaveBeenCalled();
|
|
145
|
+
expect(result.bookable).toBe(true);
|
|
146
|
+
expect(result.type).toBe('inventory');
|
|
147
|
+
expect(result.endDate).toBe('2025-04-02');
|
|
148
|
+
});
|
|
149
|
+
});
|
package/index.test.js
CHANGED
|
@@ -442,6 +442,27 @@ describe('search tests', () => {
|
|
|
442
442
|
expect(retVal.type).toBe('inventory');
|
|
443
443
|
});
|
|
444
444
|
|
|
445
|
+
it('searchAvailabilityForItinerary - default roomTypeRequired matches explicit true', async () => {
|
|
446
|
+
axios.mockImplementation(getFixture);
|
|
447
|
+
const payload = {
|
|
448
|
+
optionId: 'AKLACAKLSOFDYNAMC',
|
|
449
|
+
startDate: '2025-04-01',
|
|
450
|
+
chargeUnitQuantity: 2,
|
|
451
|
+
paxConfigs: [{ roomType: 'DB', adults: 1 }],
|
|
452
|
+
};
|
|
453
|
+
const defaultBehaviourResponse = await app.searchAvailabilityForItinerary({
|
|
454
|
+
axios,
|
|
455
|
+
token,
|
|
456
|
+
payload,
|
|
457
|
+
});
|
|
458
|
+
const explicitTrueResponse = await app.searchAvailabilityForItinerary({
|
|
459
|
+
axios,
|
|
460
|
+
token,
|
|
461
|
+
payload: { ...payload, roomTypeRequired: true },
|
|
462
|
+
});
|
|
463
|
+
expect(defaultBehaviourResponse).toEqual(explicitTrueResponse);
|
|
464
|
+
});
|
|
465
|
+
|
|
445
466
|
// Skip this test because we aren't using A check anymore
|
|
446
467
|
it.skip('searchAvailabilityForItinerary - bookable - on request', async () => {
|
|
447
468
|
axios.mockImplementation(getFixture);
|
package/package.json
CHANGED
package/resolvers/product.js
CHANGED
|
@@ -25,7 +25,8 @@ const resolvers = {
|
|
|
25
25
|
const lastUpdateISO = R.path(['OptGeneral', 'LastUpdate'], option);
|
|
26
26
|
return lastUpdateISO ? new Date(lastUpdateISO).getTime() / 1000 : null;
|
|
27
27
|
},
|
|
28
|
-
// Guides, Accommodation, Transfers, Entrance Fees, Meals, Rail, Sightseeing
|
|
28
|
+
// Guides, Accommodation, Transfers, Entrance Fees, Meals, Rail, Sightseeing,
|
|
29
|
+
// Rental Cars, Apartments, Blank Web Services, Farm Stays, Packages
|
|
29
30
|
serviceType: option => {
|
|
30
31
|
const st = R.pathOr('', ['OptGeneral', 'ButtonName'], option);
|
|
31
32
|
return typeof st === 'string' ? st : '';
|