ti2-tourplan 1.0.121 → 1.0.122
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.
|
@@ -140,6 +140,54 @@ const getDaysInYear = dateString => {
|
|
|
140
140
|
const year = moment(dateString).year();
|
|
141
141
|
return moment([year]).isLeapYear() ? 366 : 365;
|
|
142
142
|
};
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
Option Availability Integer List (OptAvail)
|
|
146
|
+
This is a list of integers that represent the availability of the option for
|
|
147
|
+
each day in the requested date range.
|
|
148
|
+
The integers are to be interpreted as follows:
|
|
149
|
+
Greater than 0 means that inventory is available, with the integer specifying
|
|
150
|
+
the number of units available.
|
|
151
|
+
For options with a service type of Y , the inventory is in units of rooms.
|
|
152
|
+
For other service types, the inventory is in units of pax.
|
|
153
|
+
-1 Not available.
|
|
154
|
+
-2 Available on free sell.
|
|
155
|
+
-3 Available on request.
|
|
156
|
+
Note: A return value of 0 or something less than -3 is impossible.
|
|
157
|
+
*/
|
|
158
|
+
const getAvailabilityOnly = async ({
|
|
159
|
+
optionId,
|
|
160
|
+
hostConnectEndpoint,
|
|
161
|
+
hostConnectAgentID,
|
|
162
|
+
hostConnectAgentPassword,
|
|
163
|
+
axios,
|
|
164
|
+
startDate,
|
|
165
|
+
requestedEndDate,
|
|
166
|
+
callTourplan,
|
|
167
|
+
}) => {
|
|
168
|
+
const isValidRequestedEndDate = moment(requestedEndDate, 'YYYY-MM-DD', true).isValid();
|
|
169
|
+
const dateTo = isValidRequestedEndDate
|
|
170
|
+
? requestedEndDate
|
|
171
|
+
: moment(startDate).add(1, 'year').subtract(1, 'day').format('YYYY-MM-DD');
|
|
172
|
+
const model = {
|
|
173
|
+
OptionInfoRequest: {
|
|
174
|
+
Opt: optionId,
|
|
175
|
+
Info: 'AD',
|
|
176
|
+
AgentID: hostConnectAgentID,
|
|
177
|
+
Password: hostConnectAgentPassword,
|
|
178
|
+
DateFrom: startDate,
|
|
179
|
+
DateTo: dateTo,
|
|
180
|
+
ACache: 'N',
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
const replyObj = await callTourplan({
|
|
184
|
+
model,
|
|
185
|
+
endpoint: hostConnectEndpoint,
|
|
186
|
+
axios,
|
|
187
|
+
xmlOptions: hostConnectXmlOptions,
|
|
188
|
+
});
|
|
189
|
+
return R.pathOr([], ['OptionInfoReply', 'Option', 'OptAvail'], replyObj);
|
|
190
|
+
};
|
|
143
191
|
/*
|
|
144
192
|
Get general option information from Tourplan API.
|
|
145
193
|
|
|
@@ -1327,6 +1375,7 @@ const findNextValidDate = (startDate, dateRanges) => {
|
|
|
1327
1375
|
|
|
1328
1376
|
module.exports = {
|
|
1329
1377
|
getAgentCurrencyCode,
|
|
1378
|
+
getAvailabilityOnly,
|
|
1330
1379
|
getAvailabilityConfig,
|
|
1331
1380
|
getNoRatesAvailableError,
|
|
1332
1381
|
getStayResults,
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
} = require('./itinerary-availability-helper');
|
|
19
19
|
|
|
20
20
|
const {
|
|
21
|
+
getAvailabilityOnly,
|
|
21
22
|
getAvailabilityConfig,
|
|
22
23
|
getNoRatesAvailableError,
|
|
23
24
|
getStayResults,
|
|
@@ -86,6 +87,28 @@ const returnEmptyRatesOrError = (allowSendingServicesWithoutARate, agentCurrency
|
|
|
86
87
|
};
|
|
87
88
|
};
|
|
88
89
|
|
|
90
|
+
const getOptAvailValues = optAvail => {
|
|
91
|
+
if (Array.isArray(optAvail)) {
|
|
92
|
+
return optAvail
|
|
93
|
+
.map(value => Number(value))
|
|
94
|
+
.filter(value => !Number.isNaN(value));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof optAvail === 'string') {
|
|
98
|
+
return optAvail
|
|
99
|
+
.trim()
|
|
100
|
+
.split(/\s+/)
|
|
101
|
+
.map(value => Number(value))
|
|
102
|
+
.filter(value => !Number.isNaN(value));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const isOptAvailBookable = optAvailValues => (
|
|
109
|
+
optAvailValues.some(value => value > 0 || value === -2 || value === -3)
|
|
110
|
+
);
|
|
111
|
+
|
|
89
112
|
const searchAvailabilityForItinerary = async ({
|
|
90
113
|
axios,
|
|
91
114
|
token: {
|
|
@@ -108,6 +131,7 @@ const searchAvailabilityForItinerary = async ({
|
|
|
108
131
|
payload: {
|
|
109
132
|
optionId,
|
|
110
133
|
startDate,
|
|
134
|
+
endDate: requestedEndDate,
|
|
111
135
|
/*
|
|
112
136
|
paxConfigs: [{ roomType: 'DB', adults: 2 }, { roomType: 'TW', children: 2 }]
|
|
113
137
|
*/
|
|
@@ -120,10 +144,38 @@ const searchAvailabilityForItinerary = async ({
|
|
|
120
144
|
*/
|
|
121
145
|
chargeUnitQuantity,
|
|
122
146
|
roomTypeRequired = true,
|
|
147
|
+
availabilityOnly = false,
|
|
123
148
|
},
|
|
124
149
|
callTourplan,
|
|
125
150
|
cache,
|
|
126
151
|
}) => {
|
|
152
|
+
const isAvailabilityOnly = availabilityOnly === true;
|
|
153
|
+
|
|
154
|
+
if (isAvailabilityOnly) {
|
|
155
|
+
const availabilityOnlyResponse = await getAvailabilityOnly({
|
|
156
|
+
optionId,
|
|
157
|
+
hostConnectEndpoint,
|
|
158
|
+
hostConnectAgentID,
|
|
159
|
+
hostConnectAgentPassword,
|
|
160
|
+
axios,
|
|
161
|
+
startDate,
|
|
162
|
+
requestedEndDate,
|
|
163
|
+
callTourplan,
|
|
164
|
+
});
|
|
165
|
+
const optAvailValues = getOptAvailValues(availabilityOnlyResponse);
|
|
166
|
+
const SCheckPass = isOptAvailBookable(optAvailValues);
|
|
167
|
+
return {
|
|
168
|
+
bookable: SCheckPass,
|
|
169
|
+
type: 'availability',
|
|
170
|
+
...(requestedEndDate && SCheckPass ? { endDate: requestedEndDate } : {}),
|
|
171
|
+
message: SCheckPass
|
|
172
|
+
? 'Availability checked successfully'
|
|
173
|
+
: 'No availability found for the requested range',
|
|
174
|
+
availability: optAvailValues,
|
|
175
|
+
rates: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
127
179
|
// Get agent currency code & cache it
|
|
128
180
|
const agentCurrencyCode = await getAgentCurrencyCode({
|
|
129
181
|
hostConnectEndpoint,
|
|
@@ -385,7 +437,11 @@ const searchAvailabilityForItinerary = async ({
|
|
|
385
437
|
if (dateRangesError) {
|
|
386
438
|
if (dateRangesError.message.includes('minimum stay')) {
|
|
387
439
|
// If minimum stay error, return availability with a warning message
|
|
388
|
-
const { matchingRateSet } = getMatchingRateSet(
|
|
440
|
+
const { matchingRateSet } = getMatchingRateSet(
|
|
441
|
+
dateRangeToUse.rateSets,
|
|
442
|
+
dateRangeToUse.startDate,
|
|
443
|
+
chargeUnitQuantity,
|
|
444
|
+
);
|
|
389
445
|
minStayRequired = matchingRateSet ? matchingRateSet.minSCU : 0;
|
|
390
446
|
sWarningMsg = MIN_STAY_WARNING_MESSAGE.replace('{minSCU}', minStayRequired);
|
|
391
447
|
} else if (dateRangesError.message.includes('rates are closed')) {
|
|
@@ -24,6 +24,7 @@ jest.mock('./itinerary-availability-helper', () => ({
|
|
|
24
24
|
maxPaxPerCharge: null,
|
|
25
25
|
})),
|
|
26
26
|
getNoRatesAvailableError: jest.fn(async () => 'No rates available'),
|
|
27
|
+
getAvailabilityOnly: jest.fn(async () => '-1 -1 -1'),
|
|
27
28
|
getStayResults: jest.fn(async () => ([{
|
|
28
29
|
RateId: 'R1',
|
|
29
30
|
Currency: 'USD',
|
|
@@ -146,4 +147,44 @@ describe('searchAvailabilityForItinerary validation flags', () => {
|
|
|
146
147
|
expect(result.type).toBe('inventory');
|
|
147
148
|
expect(result.endDate).toBe('2025-04-02');
|
|
148
149
|
});
|
|
150
|
+
|
|
151
|
+
it('uses availabilityOnly flow and keeps response shape consistent', async () => {
|
|
152
|
+
itineraryAvailabilityHelper.getAvailabilityOnly.mockResolvedValueOnce('-1 -2 -1');
|
|
153
|
+
|
|
154
|
+
const result = await searchAvailabilityForItinerary({
|
|
155
|
+
axios: jest.fn(),
|
|
156
|
+
token: baseToken,
|
|
157
|
+
payload: { ...basePayload, availabilityOnly: true, endDate: '2025-04-05' },
|
|
158
|
+
callTourplan: jest.fn(),
|
|
159
|
+
cache: { getOrExec: async ({ fn, fnParams }) => fn(...fnParams) },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(itineraryAvailabilityHelper.getAvailabilityOnly).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(itineraryAvailabilityHelper.getAgentCurrencyCode).not.toHaveBeenCalled();
|
|
164
|
+
expect(result).toEqual({
|
|
165
|
+
bookable: true,
|
|
166
|
+
type: 'availability',
|
|
167
|
+
endDate: '2025-04-05',
|
|
168
|
+
message: 'Availability checked successfully',
|
|
169
|
+
availability: [-1, -2, -1],
|
|
170
|
+
rates: [],
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('marks availabilityOnly response as not bookable when all days are unavailable', async () => {
|
|
175
|
+
itineraryAvailabilityHelper.getAvailabilityOnly.mockResolvedValueOnce('-1 -1 -1');
|
|
176
|
+
|
|
177
|
+
const result = await searchAvailabilityForItinerary({
|
|
178
|
+
axios: jest.fn(),
|
|
179
|
+
token: baseToken,
|
|
180
|
+
payload: { ...basePayload, availabilityOnly: true, endDate: '2025-04-05' },
|
|
181
|
+
callTourplan: jest.fn(),
|
|
182
|
+
cache: { getOrExec: async ({ fn, fnParams }) => fn(...fnParams) },
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(result.bookable).toBe(false);
|
|
186
|
+
expect(result.message).toBe('No availability found for the requested range');
|
|
187
|
+
expect(result.availability).toEqual([-1, -1, -1]);
|
|
188
|
+
expect(result.rates).toEqual([]);
|
|
189
|
+
});
|
|
149
190
|
});
|