tripstorekrabi-shared 1.25.7 → 1.25.9
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/availability.js +71 -66
- package/package.json +3 -3
- package/product.js +72 -28
package/availability.js
CHANGED
|
@@ -13,14 +13,15 @@ dayjs.extend(isSameOrAfter);
|
|
|
13
13
|
dayjs.extend(utc);
|
|
14
14
|
dayjs.extend(timezone);
|
|
15
15
|
|
|
16
|
-
const WEEKDAYS =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const WEEKDAYS = [
|
|
17
|
+
"Sunday",
|
|
18
|
+
"Monday",
|
|
19
|
+
"Tuesday",
|
|
20
|
+
"Wednesday",
|
|
21
|
+
"Thursday",
|
|
22
|
+
"Friday",
|
|
23
|
+
"Saturday"
|
|
24
|
+
];
|
|
24
25
|
|
|
25
26
|
export const INACTIVE = "inactive";
|
|
26
27
|
export const SINGLE_DATES = "singleDates";
|
|
@@ -28,9 +29,9 @@ export const BLOCKOUT = "blockout";
|
|
|
28
29
|
export const SCHEDULE_MODES = [INACTIVE, SINGLE_DATES, BLOCKOUT];
|
|
29
30
|
|
|
30
31
|
export function isAvailable(availability, date, schedule, options = {}) {
|
|
31
|
-
let available = availability
|
|
32
|
+
let available = Boolean(availability?.available);
|
|
32
33
|
|
|
33
|
-
if (available && options.viatorApi === true && (
|
|
34
|
+
if (available && options.viatorApi === true && (availability.viatorConnections ?? []).length === 0) {
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -40,7 +41,7 @@ export function isAvailable(availability, date, schedule, options = {}) {
|
|
|
40
41
|
|
|
41
42
|
if (available) {
|
|
42
43
|
if (schedule) {
|
|
43
|
-
|
|
44
|
+
const scheduleObject = findSchedule(availability, schedule);
|
|
44
45
|
if (scheduleObject) {
|
|
45
46
|
available = checkAvailability(date, scheduleObject);
|
|
46
47
|
} else {
|
|
@@ -48,33 +49,33 @@ export function isAvailable(availability, date, schedule, options = {}) {
|
|
|
48
49
|
}
|
|
49
50
|
} else if (availability.schedules) {
|
|
50
51
|
available = Object.values(availability.schedules).every(
|
|
51
|
-
(
|
|
52
|
+
(sched) => checkAvailability(date, sched)
|
|
52
53
|
);
|
|
53
54
|
} else if (availability.openingHours) {
|
|
54
55
|
available = Object.values(availability.openingHours).every(
|
|
55
|
-
(
|
|
56
|
+
(sched) => checkAvailability(date, sched)
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
if (available) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
available =
|
|
62
|
+
const checkDate = dayjs(date, "DD.MM.YYYY");
|
|
63
|
+
const today = dayjs().startOf('day');
|
|
64
|
+
available = checkDate.isSameOrAfter(today);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
return available;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function checkAvailability(date, schedule) {
|
|
70
|
-
|
|
71
|
+
const mode = schedule?.mode;
|
|
71
72
|
|
|
72
73
|
switch (mode) {
|
|
73
74
|
case INACTIVE: {
|
|
74
75
|
return false;
|
|
75
76
|
}
|
|
76
77
|
case SINGLE_DATES: {
|
|
77
|
-
return schedule.singleDates.includes(date);
|
|
78
|
+
return Array.isArray(schedule.singleDates) && schedule.singleDates.includes(date);
|
|
78
79
|
}
|
|
79
80
|
case BLOCKOUT: {
|
|
80
81
|
let available = true;
|
|
@@ -83,17 +84,17 @@ function checkAvailability(date, schedule) {
|
|
|
83
84
|
available = !schedule.blockoutDates.includes(date);
|
|
84
85
|
}
|
|
85
86
|
if (available && schedule.blockoutPeriods) {
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const day = dayjs(date, "DD.MM.YYYY");
|
|
88
|
+
const year = day.year();
|
|
88
89
|
available = !schedule.blockoutPeriods.some((period) => {
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const start = dayjs(`${period.start}.${year}`, "DD.MM.YYYY");
|
|
91
|
+
const end = dayjs(`${period.end}.${year}`, "DD.MM.YYYY");
|
|
91
92
|
return day.isBetween(start, end, 'day', '[]');
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
if (available && schedule.weekDays) {
|
|
96
|
-
|
|
97
|
+
const weekDay = WEEKDAYS[dayjs(date, "DD.MM.YYYY").day()];
|
|
97
98
|
available = schedule.weekDays.includes(weekDay);
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -106,9 +107,9 @@ function checkAvailability(date, schedule) {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
export function getSchedules(availability) {
|
|
109
|
-
if (availability
|
|
110
|
+
if (availability?.schedules) {
|
|
110
111
|
return Object.keys(availability.schedules).sort();
|
|
111
|
-
} else if (availability
|
|
112
|
+
} else if (availability?.openingHours) {
|
|
112
113
|
return Object.keys(availability.openingHours).sort();
|
|
113
114
|
} else {
|
|
114
115
|
return [];
|
|
@@ -116,64 +117,68 @@ export function getSchedules(availability) {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
export function findSchedule(availability, schedule) {
|
|
119
|
-
if (availability
|
|
120
|
-
return availability.schedules[schedule] ?? Object.entries(availability.schedules)[0];
|
|
121
|
-
} else if (availability
|
|
122
|
-
return availability.openingHours[schedule] ?? Object.entries(availability.openingHours)[0];
|
|
120
|
+
if (availability?.schedules) {
|
|
121
|
+
return availability.schedules[schedule] ?? Object.entries(availability.schedules)[0]?.[1];
|
|
122
|
+
} else if (availability?.openingHours) {
|
|
123
|
+
return availability.openingHours[schedule] ?? Object.entries(availability.openingHours)[0]?.[1];
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
export function getAvailabilityLabel(availability, schedule) {
|
|
127
|
-
if (!availability
|
|
128
|
+
if (!availability?.available) {
|
|
128
129
|
return "Not available";
|
|
129
|
-
}
|
|
130
|
-
let scheduleObject = findSchedule(availability, schedule);
|
|
131
|
-
|
|
132
|
-
if (scheduleObject.mode === INACTIVE) {
|
|
133
|
-
return "Not available";
|
|
134
|
-
} else if (scheduleObject.mode === SINGLE_DATES) {
|
|
135
|
-
return "Available on selected dates";
|
|
136
|
-
} else if (scheduleObject.mode === BLOCKOUT) {
|
|
137
|
-
if (scheduleObject.blockoutPeriods && scheduleObject.blockoutPeriods.length > 0) {
|
|
138
|
-
let starts = [];
|
|
139
|
-
let ends = [];
|
|
140
|
-
let startPeriod = scheduleObject.blockoutPeriods[0];
|
|
141
|
-
let year = dayjs().year();
|
|
142
|
-
scheduleObject.blockoutPeriods.forEach((period, i) => {
|
|
143
|
-
starts.push(dayjs(`${period.end}.${year}`, "DD.MM.YYYY").add(1, 'day'));
|
|
144
|
-
if (i + 1 < scheduleObject.blockoutPeriods.length) {
|
|
145
|
-
ends.push(dayjs(`${scheduleObject.blockoutPeriods[i + 1].start}.${year}`, "DD.MM.YYYY").subtract(1, 'day'));
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
ends.push(dayjs(`${startPeriod.start}.${year + 1}`, "DD.MM.YYYY").subtract(1, 'day'));
|
|
130
|
+
}
|
|
149
131
|
|
|
150
|
-
|
|
151
|
-
starts.forEach((start, i) => {
|
|
152
|
-
if (ends[i].diff(start, "month") > 1) {
|
|
153
|
-
availability.push(start.format("MMM") + " - " + ends[i].format("MMM"));
|
|
154
|
-
}
|
|
155
|
-
});
|
|
132
|
+
const scheduleObject = findSchedule(availability, schedule);
|
|
156
133
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
134
|
+
if (!scheduleObject || scheduleObject.mode === INACTIVE) {
|
|
135
|
+
return "Not available";
|
|
136
|
+
} else if (scheduleObject.mode === SINGLE_DATES) {
|
|
137
|
+
return "Available on selected dates";
|
|
138
|
+
} else if (scheduleObject.mode === BLOCKOUT) {
|
|
139
|
+
if (scheduleObject.blockoutPeriods && scheduleObject.blockoutPeriods.length > 0) {
|
|
140
|
+
const starts = [];
|
|
141
|
+
const ends = [];
|
|
142
|
+
const startPeriod = scheduleObject.blockoutPeriods[0];
|
|
143
|
+
const year = dayjs().year();
|
|
144
|
+
|
|
145
|
+
scheduleObject.blockoutPeriods.forEach((period, i) => {
|
|
146
|
+
starts.push(dayjs(`${period.end}.${year}`, "DD.MM.YYYY").add(1, 'day'));
|
|
147
|
+
if (i + 1 < scheduleObject.blockoutPeriods.length) {
|
|
148
|
+
ends.push(dayjs(`${scheduleObject.blockoutPeriods[i + 1].start}.${year}`, "DD.MM.YYYY").subtract(1, 'day'));
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
ends.push(dayjs(`${startPeriod.start}.${year + 1}`, "DD.MM.YYYY").subtract(1, 'day'));
|
|
152
|
+
|
|
153
|
+
const availabilityLabels = [];
|
|
154
|
+
starts.forEach((start, i) => {
|
|
155
|
+
if (ends[i].diff(start, "month") > 1) {
|
|
156
|
+
availabilityLabels.push(start.format("MMM") + " - " + ends[i].format("MMM"));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return availabilityLabels.join(", ");
|
|
161
|
+
} else if (scheduleObject.weekDays) {
|
|
162
|
+
return scheduleObject.weekDays.map((item) => item.substring(0, 3)).join(", ");
|
|
163
|
+
} else {
|
|
164
|
+
return "Daily";
|
|
163
165
|
}
|
|
164
166
|
}
|
|
167
|
+
|
|
168
|
+
return "Available";
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
export function isLastMinuteDate(dateValue, cutoffTime) {
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
const date = dayjs(dateValue, "DD.MM.YYYY").tz("Asia/Bangkok");
|
|
173
|
+
const now = dayjs().tz("Asia/Bangkok");
|
|
170
174
|
|
|
171
175
|
return now.add(cutoffTime, "hour").isSameOrAfter(date, "day");
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
export function isPastDate(dateValue, schedule) {
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
const startTime = (schedule || "00:00").split(" - ")[0].slice(0, 5);
|
|
180
|
+
const date = dayjs(`${dateValue} ${startTime}`, "DD.MM.YYYY HH:mm").tz("Asia/Bangkok");
|
|
181
|
+
const now = dayjs().tz("Asia/Bangkok");
|
|
177
182
|
|
|
178
183
|
return date.isSameOrBefore(now);
|
|
179
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tripstorekrabi-shared",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.9",
|
|
4
4
|
"description": "Shared library for Trip Store Krabi",
|
|
5
5
|
"author": "Stefan Sommer <tonprince@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"dayjs": "^1.11.23",
|
|
21
|
-
"libphonenumber-js": "^1.13.
|
|
21
|
+
"libphonenumber-js": "^1.13.14",
|
|
22
22
|
"nanoid": "^6.0.1",
|
|
23
|
-
"firebase-admin": "^14.
|
|
23
|
+
"firebase-admin": "^14.5.0"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"deploy": "npm publish"
|
package/product.js
CHANGED
|
@@ -2,55 +2,99 @@ import { calculateProductSellingPrice, countMinPeople } from "./price.js";
|
|
|
2
2
|
|
|
3
3
|
const MATCH_HTML_REGEXP = /(<([^>]+)>)/ig;
|
|
4
4
|
|
|
5
|
+
function toIsoDateString(val) {
|
|
6
|
+
if (!val) return null;
|
|
7
|
+
if (typeof val.toDate === 'function') return val.toDate().toISOString();
|
|
8
|
+
if (val instanceof Date) return val.toISOString();
|
|
9
|
+
if (typeof val === 'string') return val;
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stripHTML(html) {
|
|
14
|
+
if (!html) return '';
|
|
15
|
+
return String(html).replace(MATCH_HTML_REGEXP, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
export function buildIndex(id, product) {
|
|
6
|
-
|
|
19
|
+
if (!product) return null;
|
|
20
|
+
|
|
21
|
+
const lightweightRevisions = (product.pricing?.revisions || []).map((rev) => ({
|
|
7
22
|
id: rev.id,
|
|
8
|
-
startDate: rev.startDate
|
|
9
|
-
promotion: rev.promotion
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
startDate: toIsoDateString(rev.startDate),
|
|
24
|
+
promotion: rev.promotion
|
|
25
|
+
? {
|
|
26
|
+
startDate: toIsoDateString(rev.promotion.startDate),
|
|
27
|
+
endDate: toIsoDateString(rev.promotion.endDate)
|
|
28
|
+
}
|
|
29
|
+
: null
|
|
13
30
|
}));
|
|
14
31
|
|
|
32
|
+
const minPeople = countMinPeople(product.pricing);
|
|
33
|
+
const rawPrice =
|
|
34
|
+
minPeople > 0
|
|
35
|
+
? calculateProductSellingPrice(
|
|
36
|
+
{ adults: minPeople, children: 0, infants: 0 },
|
|
37
|
+
product.pricing
|
|
38
|
+
) / minPeople
|
|
39
|
+
: 0;
|
|
40
|
+
|
|
41
|
+
const safeIntPrice = isNaN(rawPrice) || !isFinite(rawPrice) ? 0 : Math.round(rawPrice);
|
|
42
|
+
|
|
15
43
|
let document = {
|
|
16
44
|
id,
|
|
17
|
-
title: product.title,
|
|
18
|
-
subtitle: product.subtitle,
|
|
19
|
-
summary: product.summary,
|
|
20
|
-
description: product.description
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
title: String(product.title || '').trim(),
|
|
46
|
+
subtitle: String(product.subtitle || '').trim(),
|
|
47
|
+
summary: String(product.summary || '').trim(),
|
|
48
|
+
description: Array.isArray(product.description)
|
|
49
|
+
? product.description.map((item) => stripHTML(item)).join(' ')
|
|
50
|
+
: stripHTML(product.description || ''),
|
|
51
|
+
heroPhoto: product.heroPhoto || '',
|
|
52
|
+
operator: String(
|
|
53
|
+
product.operator?.default?.name || product.operator?.name || product.operator || 'General'
|
|
54
|
+
).trim(),
|
|
55
|
+
url: product.url || '',
|
|
56
|
+
price: safeIntPrice,
|
|
57
|
+
priceType: product.pricing?.type || 'per_person',
|
|
58
|
+
availability: product.availability || 'available',
|
|
59
|
+
live: Boolean(product.live),
|
|
60
|
+
totalPassengers: Math.max(0, Math.round(Number(product.totalPassengers || 0))),
|
|
28
61
|
revisions: JSON.stringify(lightweightRevisions)
|
|
29
62
|
};
|
|
30
63
|
|
|
31
64
|
if (product.shortName) {
|
|
32
|
-
document.shortName = product.shortName;
|
|
65
|
+
document.shortName = String(product.shortName).trim();
|
|
33
66
|
}
|
|
34
67
|
|
|
35
|
-
if (product.destinations) {
|
|
68
|
+
if (Array.isArray(product.destinations) && product.destinations.length > 0) {
|
|
36
69
|
document.destinations = product.destinations;
|
|
70
|
+
} else if (product.destination) {
|
|
71
|
+
document.destinations = [product.destination];
|
|
37
72
|
}
|
|
38
|
-
|
|
73
|
+
|
|
74
|
+
if (Array.isArray(product.tags)) {
|
|
39
75
|
document.tags = product.tags;
|
|
76
|
+
} else {
|
|
77
|
+
document.tags = [];
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
if (product.from && product.to) {
|
|
43
81
|
document.from = product.from;
|
|
44
|
-
document.to = product.to
|
|
45
|
-
|
|
46
|
-
|
|
82
|
+
document.to = product.to;
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(product.waypoints) && product.waypoints.length > 0) {
|
|
85
|
+
const startWp = product.waypoints[0]?.latLng;
|
|
86
|
+
const endWp = product.waypoints[product.waypoints.length - 1]?.latLng;
|
|
87
|
+
|
|
88
|
+
if (startWp && typeof startWp.lat === 'number' && typeof startWp.lng === 'number') {
|
|
89
|
+
document.start = [startWp.lat, startWp.lng];
|
|
90
|
+
}
|
|
91
|
+
if (endWp && typeof endWp.lat === 'number' && typeof endWp.lng === 'number') {
|
|
92
|
+
document.end = [endWp.lat, endWp.lng];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
47
95
|
}
|
|
48
96
|
|
|
49
|
-
document.type = product.from ?
|
|
97
|
+
document.type = product.from ? 'transfer' : 'activity';
|
|
50
98
|
|
|
51
99
|
return document;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function stripHTML(html) {
|
|
55
|
-
return html.replace(MATCH_HTML_REGEXP, "");
|
|
56
100
|
}
|