ti2-tourplan 1.0.133 → 1.0.135

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.
@@ -4155,6 +4155,7 @@ Object {
4155
4155
  "country": "United Kingdom",
4156
4156
  "currency": "GBP",
4157
4157
  "extras": Array [],
4158
+ "inclusions": Array [],
4158
4159
  "lastUpdateTimestamp": 1700750093,
4159
4160
  "optRates": Object {
4160
4161
  "CancelPolicies": Object {
package/index.test.js CHANGED
@@ -867,6 +867,7 @@ describe('search tests', () => {
867
867
  },
868
868
  });
869
869
  expect(R.path(['products', 0, 'options', 0, 'city'], retVal)).toBe('London');
870
+ expect(R.path(['products', 0, 'options', 0, 'inclusions'], retVal)).toEqual([]);
870
871
  expect(retVal).toMatchSnapshot();
871
872
  });
872
873
 
@@ -1850,6 +1851,50 @@ describe('search tests', () => {
1850
1851
  }
1851
1852
  });
1852
1853
 
1854
+ it('searchItineraries searches alphanumeric bookingId candidates as AgentRef', async () => {
1855
+ axios.mockImplementation(getFixture);
1856
+ mockCallTourplan.mockImplementation(async request => {
1857
+ const { model } = request;
1858
+ const listRequest = model && model.ListBookingsRequest;
1859
+ if (listRequest && listRequest.BookingId === 'SGMX269690') {
1860
+ return { ListBookingsReply: { BookingHeaders: { BookingHeader: [] } } };
1861
+ }
1862
+ if (listRequest && listRequest.Ref === 'SGMX269690') {
1863
+ return { ListBookingsReply: { BookingHeaders: { BookingHeader: [] } } };
1864
+ }
1865
+ if (listRequest && listRequest.AgentRef === 'SGMX269690') {
1866
+ return {
1867
+ ListBookingsReply: {
1868
+ BookingHeaders: {
1869
+ BookingHeader: [{ BookingId: '316559' }],
1870
+ },
1871
+ },
1872
+ };
1873
+ }
1874
+ return defaultCallTourplanImplementation(request);
1875
+ });
1876
+
1877
+ const retVal = await app.searchItineraries({
1878
+ axios,
1879
+ token,
1880
+ typeDefsAndQueries,
1881
+ payload: {
1882
+ bookingId: 'SGMX269690',
1883
+ },
1884
+ });
1885
+
1886
+ expect(retVal.bookings.length).toBe(1);
1887
+ expect(retVal.bookings[0].bookingId).toBe('316559');
1888
+ const listRequests = mockCallTourplan.mock.calls
1889
+ .map(([{ model }]) => model && model.ListBookingsRequest)
1890
+ .filter(Boolean);
1891
+ expect(listRequests).toEqual(expect.arrayContaining([
1892
+ expect.objectContaining({ BookingId: 'SGMX269690' }),
1893
+ expect.objectContaining({ Ref: 'SGMX269690' }),
1894
+ expect.objectContaining({ AgentRef: 'SGMX269690' }),
1895
+ ]));
1896
+ });
1897
+
1853
1898
  it('searchItineraries by name (NameContains)', async () => {
1854
1899
  axios.mockImplementation(getFixture);
1855
1900
  const retVal = await app.searchItineraries({
@@ -7,6 +7,7 @@ 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 { addStructuredOptionMetadata } = require('./option-metadata');
10
11
  const {
11
12
  isEnabled,
12
13
  normalizePickupPoints,
@@ -260,7 +261,7 @@ const searchProductsForItinerary = async ({
260
261
  || R.path(['PickupPoints'], rawOption),
261
262
  )
262
263
  : undefined;
263
- return {
264
+ return addStructuredOptionMetadata({
264
265
  ...R.omit(['city', 'country', 'rateContext'], currentOption),
265
266
  ...(city ? { city } : {}),
266
267
  ...(country ? { country } : {}),
@@ -269,7 +270,7 @@ const searchProductsForItinerary = async ({
269
270
  ...(R.path([currentOption.optionId], optionRatesByOptionId)
270
271
  ? { optRates: R.path([currentOption.optionId], optionRatesByOptionId) }
271
272
  : {}),
272
- };
273
+ });
273
274
  }),
274
275
  }));
275
276
  return {
@@ -0,0 +1,44 @@
1
+ const BREAKFAST = 'breakfast';
2
+
3
+ const normalizeText = value => String(value || '').toLowerCase();
4
+
5
+ const hasNegatedBreakfast = value => {
6
+ const text = normalizeText(value);
7
+ return /\b(?:no|without|excluding|exclude|excludes)\s+breakfast\b/.test(text)
8
+ || /\b(?:no|without)\s+bed\s+(?:and|&)\s+breakfast\b/.test(text)
9
+ || /\b(?:do|does)\s+not\s+(?:include|includes)\s+breakfast\b/.test(text)
10
+ || /\bnot\s+including\s+breakfast\b/.test(text)
11
+ || /\bbreakfast\b\s+(?:is\s+)?(?:not\s+included|not\s+inclusive|excluded)\b/.test(text);
12
+ };
13
+
14
+ const includesBreakfast = value => {
15
+ if (hasNegatedBreakfast(value)) return false;
16
+ const text = normalizeText(value);
17
+ return /\bbreakfast\b\s+(?:is\s+)?(?:included|inclusive)\b/.test(text)
18
+ || /\b(?:including|includes|include|with)\s+breakfast\b/.test(text)
19
+ || /\bbed\s+(?:and|&)\s+breakfast\b/.test(text);
20
+ };
21
+
22
+ const getOptionInclusions = optionName => (
23
+ includesBreakfast(optionName) ? [BREAKFAST] : []
24
+ );
25
+
26
+ const getExtraCategory = extraName => {
27
+ if (hasNegatedBreakfast(extraName)) return null;
28
+ return /\bbreakfast\b/.test(normalizeText(extraName)) ? BREAKFAST : null;
29
+ };
30
+
31
+ const addStructuredOptionMetadata = option => ({
32
+ ...option,
33
+ inclusions: getOptionInclusions(option && option.optionName),
34
+ extras: (option && option.extras || []).map(extra => {
35
+ const category = getExtraCategory(extra && extra.name);
36
+ return category ? { ...extra, category } : extra;
37
+ }),
38
+ });
39
+
40
+ module.exports = {
41
+ addStructuredOptionMetadata,
42
+ getExtraCategory,
43
+ getOptionInclusions,
44
+ };
@@ -0,0 +1,42 @@
1
+ const {
2
+ addStructuredOptionMetadata,
3
+ getExtraCategory,
4
+ getOptionInclusions,
5
+ } = require('./option-metadata');
6
+
7
+ describe('structured option metadata', () => {
8
+ test.each([
9
+ ['Witherview Super King, Including Breakfast', ['breakfast']],
10
+ ['Bed and Breakfast', ['breakfast']],
11
+ ['Bed & Breakfast', ['breakfast']],
12
+ ['Breakfast not included', []],
13
+ ['No breakfast included', []],
14
+ ['Does not include breakfast', []],
15
+ ['Not including breakfast', []],
16
+ ['No bed and breakfast', []],
17
+ ['Room only', []],
18
+ ])('normalizes option inclusion text %s', (optionName, expected) => {
19
+ expect(getOptionInclusions(optionName)).toEqual(expected);
20
+ });
21
+
22
+ it('classifies selectable breakfast extras without changing unrelated extras', () => {
23
+ expect(addStructuredOptionMetadata({
24
+ optionName: 'Superior Room',
25
+ extras: [
26
+ { id: '1', name: 'Breakfast' },
27
+ { id: '2', name: 'Airport transfer' },
28
+ ],
29
+ })).toEqual({
30
+ optionName: 'Superior Room',
31
+ inclusions: [],
32
+ extras: [
33
+ { id: '1', name: 'Breakfast', category: 'breakfast' },
34
+ { id: '2', name: 'Airport transfer' },
35
+ ],
36
+ });
37
+ });
38
+
39
+ it('does not classify negated breakfast text as a selectable category', () => {
40
+ expect(getExtraCategory('No breakfast')).toBeNull();
41
+ });
42
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-tourplan",
3
- "version": "1.0.133",
3
+ "version": "1.0.135",
4
4
  "description": "Tourplan's TI2 Plugin",
5
5
  "main": "index.js",
6
6
  "scripts": {