@perdieminc/time-slots 0.0.1 → 0.0.3
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/README.md +9 -8
- package/lib/constants.d.ts +37 -0
- package/lib/constants.d.ts.map +1 -0
- package/lib/constants.js +37 -0
- package/lib/constants.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/schedule/available-dates.d.ts +3 -0
- package/lib/schedule/available-dates.d.ts.map +1 -0
- package/lib/schedule/available-dates.js +84 -0
- package/lib/schedule/available-dates.js.map +1 -0
- package/lib/schedule/generate.d.ts +3 -0
- package/lib/schedule/generate.d.ts.map +1 -0
- package/lib/schedule/generate.js +155 -0
- package/lib/schedule/generate.js.map +1 -0
- package/lib/schedule/get-schedules.d.ts +3 -0
- package/lib/schedule/get-schedules.d.ts.map +1 -0
- package/lib/schedule/get-schedules.js +182 -0
- package/lib/schedule/get-schedules.js.map +1 -0
- package/lib/schedule/location.d.ts +3 -0
- package/lib/schedule/location.d.ts.map +1 -0
- package/lib/schedule/location.js +38 -0
- package/lib/schedule/location.js.map +1 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +3 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types.d.ts +24 -0
- package/lib/types.js +2 -0
- package/lib/utils/business-hours.d.ts +6 -0
- package/lib/utils/business-hours.d.ts.map +1 -0
- package/lib/utils/business-hours.js +82 -0
- package/lib/utils/business-hours.js.map +1 -0
- package/lib/utils/catering.d.ts +9 -0
- package/lib/utils/catering.d.ts.map +1 -0
- package/lib/utils/catering.js +56 -0
- package/lib/utils/catering.js.map +1 -0
- package/lib/utils/date.d.ts +15 -0
- package/lib/utils/date.d.ts.map +1 -0
- package/lib/utils/date.js +109 -0
- package/lib/utils/date.js.map +1 -0
- package/lib/utils/schedule-filter.d.ts +8 -0
- package/lib/utils/schedule-filter.d.ts.map +1 -0
- package/lib/utils/schedule-filter.js +95 -0
- package/lib/utils/schedule-filter.js.map +1 -0
- package/lib/utils/store-hours.d.ts +14 -0
- package/lib/utils/store-hours.d.ts.map +1 -0
- package/lib/utils/store-hours.js +120 -0
- package/lib/utils/store-hours.js.map +1 -0
- package/lib/utils/time.d.ts +12 -0
- package/lib/utils/time.d.ts.map +1 -0
- package/lib/utils/time.js +30 -0
- package/lib/utils/time.js.map +1 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +18 -0
- package/package.json +8 -7
- package/src/constants.ts +0 -45
- package/src/index.ts +0 -23
- package/src/schedule/available-dates.ts +0 -129
- package/src/schedule/generate.ts +0 -276
- package/src/schedule/get-schedules.ts +0 -264
- package/src/schedule/location.ts +0 -58
- package/src/types/business-hours.d.ts +0 -32
- package/src/types/common.d.ts +0 -5
- package/src/types/get-schedules.d.ts +0 -122
- package/src/types/index.ts +0 -34
- package/src/types/location.d.ts +0 -25
- package/src/types/schedule-filter.d.ts +0 -27
- package/src/types/schedule.d.ts +0 -83
- package/src/types/timezone-support.d.ts +0 -31
- package/src/utils/business-hours.ts +0 -120
- package/src/utils/catering.ts +0 -85
- package/src/utils/date.ts +0 -163
- package/src/utils/schedule-filter.ts +0 -140
- package/src/utils/store-hours.ts +0 -223
- package/src/utils/time.ts +0 -38
package/src/schedule/generate.ts
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addMinutes,
|
|
3
|
-
compareAsc,
|
|
4
|
-
eachMinuteOfInterval,
|
|
5
|
-
isAfter,
|
|
6
|
-
isBefore,
|
|
7
|
-
max,
|
|
8
|
-
} from "date-fns";
|
|
9
|
-
import { findTimeZone, getZonedTime } from "timezone-support";
|
|
10
|
-
import type { PrepTimeBehaviourType } from "../constants";
|
|
11
|
-
import {
|
|
12
|
-
DEFAULT_PREP_TIME_IN_MINUTES,
|
|
13
|
-
PREP_TIME_CADENCE,
|
|
14
|
-
PrepTimeBehaviour,
|
|
15
|
-
} from "../constants";
|
|
16
|
-
import type {
|
|
17
|
-
BusinessHour,
|
|
18
|
-
BusinessHoursOverrideOutput,
|
|
19
|
-
DaySchedule,
|
|
20
|
-
GenerateScheduleParams,
|
|
21
|
-
} from "../types";
|
|
22
|
-
import {
|
|
23
|
-
isTodayInTimeZone,
|
|
24
|
-
isZeroPrepTimeForMidnightShift,
|
|
25
|
-
setHmOnDate,
|
|
26
|
-
} from "../utils/date";
|
|
27
|
-
|
|
28
|
-
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
interface GetSelectedBusinessHoursParams {
|
|
31
|
-
businessHours?: BusinessHour[];
|
|
32
|
-
businessHoursOverrides?: BusinessHoursOverrideOutput[];
|
|
33
|
-
date?: Date;
|
|
34
|
-
timeZone?: string;
|
|
35
|
-
preSaleHoursOverride?: Array<{
|
|
36
|
-
startTime: string;
|
|
37
|
-
endTime: string;
|
|
38
|
-
month?: number;
|
|
39
|
-
day?: number;
|
|
40
|
-
}> | null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getSelectedBusinessHours({
|
|
44
|
-
businessHours = [],
|
|
45
|
-
businessHoursOverrides = [],
|
|
46
|
-
date,
|
|
47
|
-
timeZone,
|
|
48
|
-
preSaleHoursOverride,
|
|
49
|
-
}: GetSelectedBusinessHoursParams): [
|
|
50
|
-
BusinessHour[],
|
|
51
|
-
ReturnType<typeof getZonedTime>,
|
|
52
|
-
] {
|
|
53
|
-
if (!date || !timeZone) {
|
|
54
|
-
return [[], getZonedTime(new Date(), findTimeZone(timeZone ?? "UTC"))];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const zonedDate = getZonedTime(date, findTimeZone(timeZone));
|
|
58
|
-
const dayOfWeek = zonedDate.dayOfWeek ?? 0;
|
|
59
|
-
|
|
60
|
-
const dayBusinessHours = businessHours?.filter((bh) => bh.day === dayOfWeek);
|
|
61
|
-
|
|
62
|
-
const businessHoursOverride = businessHoursOverrides?.filter(
|
|
63
|
-
(override) =>
|
|
64
|
-
zonedDate.month === override.month && zonedDate.day === override.day,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const selectedBusinessHours: BusinessHour[] = preSaleHoursOverride
|
|
68
|
-
? preSaleHoursOverride.map((o) => ({
|
|
69
|
-
day: dayOfWeek,
|
|
70
|
-
startTime: o.startTime,
|
|
71
|
-
endTime: o.endTime,
|
|
72
|
-
}))
|
|
73
|
-
: businessHoursOverride.length
|
|
74
|
-
? businessHoursOverride.map((o) => ({
|
|
75
|
-
day: dayOfWeek,
|
|
76
|
-
startTime: o.startTime ?? "00:00",
|
|
77
|
-
endTime: o.endTime ?? "23:59",
|
|
78
|
-
}))
|
|
79
|
-
: (dayBusinessHours ?? []);
|
|
80
|
-
|
|
81
|
-
return [selectedBusinessHours, zonedDate];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── Public API ──────────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
export function generateSchedule({
|
|
87
|
-
currentDate = new Date(),
|
|
88
|
-
prepTimeBehaviour = PrepTimeBehaviour.ROLL_FROM_FIRST_SHIFT,
|
|
89
|
-
weekDayPrepTimes = {},
|
|
90
|
-
timeZone,
|
|
91
|
-
dates = [],
|
|
92
|
-
businessHours = [],
|
|
93
|
-
businessHoursOverrides = [],
|
|
94
|
-
preSaleHoursOverride,
|
|
95
|
-
gapInMinutes = 15,
|
|
96
|
-
prepTimeCadence = null,
|
|
97
|
-
}: GenerateScheduleParams): DaySchedule[] {
|
|
98
|
-
const isMinutesCadence = prepTimeCadence === PREP_TIME_CADENCE.MINUTE;
|
|
99
|
-
let shiftStartDateWithPrepTime: Date | null = null;
|
|
100
|
-
return dates
|
|
101
|
-
.map((date, index) => {
|
|
102
|
-
const lastDate = dates?.[index - 1];
|
|
103
|
-
|
|
104
|
-
const [selectedBusinessHours, zonedDate] = getSelectedBusinessHours({
|
|
105
|
-
businessHours,
|
|
106
|
-
businessHoursOverrides,
|
|
107
|
-
date,
|
|
108
|
-
timeZone,
|
|
109
|
-
preSaleHoursOverride,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const [prevSelectedBusinessHours] = getSelectedBusinessHours({
|
|
113
|
-
businessHours,
|
|
114
|
-
businessHoursOverrides,
|
|
115
|
-
date: lastDate,
|
|
116
|
-
timeZone,
|
|
117
|
-
preSaleHoursOverride,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const weekDayPrepTime =
|
|
121
|
-
weekDayPrepTimes[zonedDate.dayOfWeek] ?? DEFAULT_PREP_TIME_IN_MINUTES;
|
|
122
|
-
|
|
123
|
-
const storeTimes = {
|
|
124
|
-
openingTime: null as Date | null,
|
|
125
|
-
closingTime: null as Date | null,
|
|
126
|
-
remainingShifts: 0,
|
|
127
|
-
totalShifts: 0,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
let isPrevDayMidnightTransition = false;
|
|
131
|
-
|
|
132
|
-
const slots = selectedBusinessHours
|
|
133
|
-
.flatMap((businessHour, i) => {
|
|
134
|
-
const startDate = setHmOnDate(date, businessHour.startTime, timeZone);
|
|
135
|
-
const shiftStartDate =
|
|
136
|
-
isMinutesCadence && shiftStartDateWithPrepTime
|
|
137
|
-
? max([shiftStartDateWithPrepTime, startDate])
|
|
138
|
-
: startDate;
|
|
139
|
-
const shiftEndDate = setHmOnDate(
|
|
140
|
-
date,
|
|
141
|
-
businessHour.endTime,
|
|
142
|
-
timeZone,
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (i === 0) {
|
|
146
|
-
storeTimes.openingTime = shiftStartDate;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (i === selectedBusinessHours.length - 1) {
|
|
150
|
-
storeTimes.closingTime = shiftEndDate;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (!isBefore(shiftStartDate, shiftEndDate)) {
|
|
154
|
-
if (isMinutesCadence) {
|
|
155
|
-
shiftStartDateWithPrepTime = null;
|
|
156
|
-
}
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
storeTimes.totalShifts += 1;
|
|
161
|
-
|
|
162
|
-
const fixedSlots = eachMinuteOfInterval(
|
|
163
|
-
{ start: shiftStartDate, end: shiftEndDate },
|
|
164
|
-
{ step: gapInMinutes },
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
if (isTodayInTimeZone(date, timeZone)) {
|
|
168
|
-
const openingTime = storeTimes.openingTime ?? new Date(0);
|
|
169
|
-
const baseDate =
|
|
170
|
-
currentDate instanceof Date ? currentDate : new Date(currentDate);
|
|
171
|
-
const currentDateWithPrepTime = addMinutes(
|
|
172
|
-
new Date(Math.max(baseDate.getTime(), openingTime.getTime())),
|
|
173
|
-
Math.max(DEFAULT_PREP_TIME_IN_MINUTES, weekDayPrepTime),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (isAfter(currentDateWithPrepTime, shiftEndDate)) {
|
|
177
|
-
// If the prep time cadence is minutes, we need to set the shift start date with the prep time
|
|
178
|
-
if (isMinutesCadence) {
|
|
179
|
-
shiftStartDateWithPrepTime = currentDateWithPrepTime;
|
|
180
|
-
}
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (isBefore(currentDateWithPrepTime, shiftStartDate)) {
|
|
185
|
-
storeTimes.remainingShifts += 1;
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
(prepTimeBehaviour as PrepTimeBehaviourType) ===
|
|
189
|
-
PrepTimeBehaviour.EVERY_SHIFT
|
|
190
|
-
) {
|
|
191
|
-
const shiftStartDateWithPrepTime = addMinutes(
|
|
192
|
-
shiftStartDate,
|
|
193
|
-
weekDayPrepTime,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const slotDates = fixedSlots.filter((d) =>
|
|
197
|
-
isAfter(d, shiftStartDateWithPrepTime),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
slotDates.unshift(shiftStartDateWithPrepTime);
|
|
201
|
-
return slotDates;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return fixedSlots;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const slotDates = fixedSlots.filter((d) =>
|
|
208
|
-
isAfter(d, currentDateWithPrepTime),
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
slotDates.unshift(currentDateWithPrepTime);
|
|
212
|
-
storeTimes.remainingShifts += 1;
|
|
213
|
-
return slotDates;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (prepTimeBehaviour === PrepTimeBehaviour.FIRST_SHIFT && i !== 0) {
|
|
217
|
-
storeTimes.remainingShifts += 1;
|
|
218
|
-
return fixedSlots;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const allowZeroPrepTimeForMidnightShift =
|
|
222
|
-
isZeroPrepTimeForMidnightShift({
|
|
223
|
-
prevDayBusinessHours: prevSelectedBusinessHours,
|
|
224
|
-
businessHour,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const prepTimeSlot = addMinutes(
|
|
228
|
-
prepTimeBehaviour === PrepTimeBehaviour.ROLL_FROM_FIRST_SHIFT &&
|
|
229
|
-
!isPrevDayMidnightTransition &&
|
|
230
|
-
storeTimes.openingTime
|
|
231
|
-
? storeTimes.openingTime
|
|
232
|
-
: shiftStartDate,
|
|
233
|
-
allowZeroPrepTimeForMidnightShift ? 0 : weekDayPrepTime,
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
isPrevDayMidnightTransition = allowZeroPrepTimeForMidnightShift;
|
|
237
|
-
|
|
238
|
-
if (prepTimeSlot > shiftEndDate) {
|
|
239
|
-
if (isMinutesCadence) {
|
|
240
|
-
shiftStartDateWithPrepTime = prepTimeSlot;
|
|
241
|
-
}
|
|
242
|
-
shiftStartDateWithPrepTime = prepTimeSlot;
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (prepTimeSlot < shiftStartDate) {
|
|
247
|
-
storeTimes.remainingShifts += 1;
|
|
248
|
-
return fixedSlots;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const slotDates = fixedSlots.filter((d) => isAfter(d, prepTimeSlot));
|
|
252
|
-
|
|
253
|
-
slotDates.unshift(prepTimeSlot);
|
|
254
|
-
storeTimes.remainingShifts += 1;
|
|
255
|
-
shiftStartDateWithPrepTime = null; //reset the shift start date with prep time
|
|
256
|
-
return slotDates;
|
|
257
|
-
})
|
|
258
|
-
.sort(compareAsc);
|
|
259
|
-
|
|
260
|
-
const currentDateMs =
|
|
261
|
-
currentDate instanceof Date ? currentDate.getTime() : currentDate;
|
|
262
|
-
const availableSlots = slots.filter((d) => d.getTime() >= currentDateMs);
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
date,
|
|
266
|
-
originalStoreOpeningTime: storeTimes.openingTime,
|
|
267
|
-
originalStoreClosingTime: storeTimes.closingTime,
|
|
268
|
-
remainingShifts: storeTimes.remainingShifts,
|
|
269
|
-
openingTime: slots[0],
|
|
270
|
-
closingTime: slots[slots.length - 1],
|
|
271
|
-
firstAvailableSlot: availableSlots[0],
|
|
272
|
-
slots: availableSlots,
|
|
273
|
-
};
|
|
274
|
-
})
|
|
275
|
-
.filter((a) => a.slots.length);
|
|
276
|
-
}
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { differenceInDays } from "date-fns";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_TIMEZONE,
|
|
4
|
-
FULFILLMENT_TYPES,
|
|
5
|
-
PLATFORM,
|
|
6
|
-
PREP_TIME_CADENCE,
|
|
7
|
-
} from "../constants";
|
|
8
|
-
import type {
|
|
9
|
-
CartItem,
|
|
10
|
-
FulfillmentSchedule,
|
|
11
|
-
GetSchedulesParams,
|
|
12
|
-
GetSchedulesResult,
|
|
13
|
-
PrepTimeSettings,
|
|
14
|
-
PreSaleConfig,
|
|
15
|
-
} from "../types";
|
|
16
|
-
import { getLocationsBusinessHoursOverrides } from "../utils/business-hours";
|
|
17
|
-
import { getCateringPrepTimeConfig } from "../utils/catering";
|
|
18
|
-
import { getPreSalePickupDates, overrideTimeZoneOnUTC } from "../utils/date";
|
|
19
|
-
import { filterBusyTimesFromSchedule } from "../utils/schedule-filter";
|
|
20
|
-
import { generateLocationFulfillmentSchedule } from "./location";
|
|
21
|
-
|
|
22
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
function deriveCartInfo(cartItems: CartItem[]) {
|
|
25
|
-
return {
|
|
26
|
-
hasPreSaleItem: cartItems.some((item) => item.preSale),
|
|
27
|
-
hasWeeklyPreSaleItem: cartItems.some((item) => item.weeklyPreSale),
|
|
28
|
-
categoryIds: Array.from(
|
|
29
|
-
new Set(
|
|
30
|
-
cartItems
|
|
31
|
-
.map((item) => item.internalCategoryId)
|
|
32
|
-
.filter((id): id is string => Boolean(id)),
|
|
33
|
-
),
|
|
34
|
-
),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function resolvePreSaleDates(
|
|
39
|
-
preSaleConfig: PreSaleConfig | undefined,
|
|
40
|
-
timezone: string,
|
|
41
|
-
) {
|
|
42
|
-
return {
|
|
43
|
-
startDate: preSaleConfig?.due_start_date
|
|
44
|
-
? overrideTimeZoneOnUTC(preSaleConfig.due_start_date, timezone)
|
|
45
|
-
: new Date(),
|
|
46
|
-
endDate: preSaleConfig?.due_end_date
|
|
47
|
-
? overrideTimeZoneOnUTC(preSaleConfig.due_end_date, timezone)
|
|
48
|
-
: new Date(),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function getPreSaleHoursOverride(
|
|
53
|
-
preSaleConfig: PreSaleConfig | undefined,
|
|
54
|
-
hasPreSaleItem: boolean,
|
|
55
|
-
) {
|
|
56
|
-
if (
|
|
57
|
-
preSaleConfig &&
|
|
58
|
-
!preSaleConfig.use_store_hours_due &&
|
|
59
|
-
hasPreSaleItem &&
|
|
60
|
-
preSaleConfig.due_start_time &&
|
|
61
|
-
preSaleConfig.due_end_time
|
|
62
|
-
) {
|
|
63
|
-
return [
|
|
64
|
-
{
|
|
65
|
-
startTime: preSaleConfig.due_start_time,
|
|
66
|
-
endTime: preSaleConfig.due_end_time,
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function resolveStartDate(
|
|
74
|
-
zonedDueStartDate: Date,
|
|
75
|
-
hasPreSaleItem: boolean,
|
|
76
|
-
): Date {
|
|
77
|
-
if (hasPreSaleItem) {
|
|
78
|
-
return new Date(Math.max(zonedDueStartDate.getTime(), Date.now()));
|
|
79
|
-
}
|
|
80
|
-
return new Date();
|
|
81
|
-
}
|
|
82
|
-
const WEEKDAY_KEYS = [0, 1, 2, 3, 4, 5, 6] as const;
|
|
83
|
-
|
|
84
|
-
function addEstimatedDeliveryToWeekDays(
|
|
85
|
-
weekDayPrepTimes: Record<number, number>,
|
|
86
|
-
estimatedDeliveryMinutes: number,
|
|
87
|
-
): Record<number, number> {
|
|
88
|
-
const result: Record<number, number> = {};
|
|
89
|
-
for (const day of WEEKDAY_KEYS) {
|
|
90
|
-
result[day] = (weekDayPrepTimes[day] ?? 0) + estimatedDeliveryMinutes;
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Resolves prep time config: for catering flow uses cart-derived cadence/frequency;
|
|
97
|
-
* when fulfillment is DELIVERY, adds estimatedDeliveryMinutes to all weekday prep times.
|
|
98
|
-
*/
|
|
99
|
-
function resolvePrepTimeConfig(
|
|
100
|
-
prepTimeSettings: PrepTimeSettings,
|
|
101
|
-
cartItems: CartItem[],
|
|
102
|
-
isCateringFlow: boolean,
|
|
103
|
-
fulfillmentPreference: "PICKUP" | "DELIVERY" | "CURBSIDE",
|
|
104
|
-
timezone: string = DEFAULT_TIMEZONE,
|
|
105
|
-
): PrepTimeSettings {
|
|
106
|
-
let resolved: PrepTimeSettings;
|
|
107
|
-
|
|
108
|
-
if (!isCateringFlow) {
|
|
109
|
-
const isDayCadence =
|
|
110
|
-
prepTimeSettings.prepTimeCadence === PREP_TIME_CADENCE.DAY;
|
|
111
|
-
resolved = {
|
|
112
|
-
...prepTimeSettings,
|
|
113
|
-
...(isDayCadence && { weekDayPrepTimes: {} }),
|
|
114
|
-
};
|
|
115
|
-
} else {
|
|
116
|
-
const cateringPrepTimeConfig = getCateringPrepTimeConfig({
|
|
117
|
-
items: cartItems,
|
|
118
|
-
prepTimeCadence: prepTimeSettings.prepTimeCadence,
|
|
119
|
-
prepTimeFrequency: prepTimeSettings.prepTimeFrequency,
|
|
120
|
-
timezone,
|
|
121
|
-
});
|
|
122
|
-
resolved = {
|
|
123
|
-
...prepTimeSettings,
|
|
124
|
-
...cateringPrepTimeConfig,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
const { estimatedDeliveryMinutes = 0 } = prepTimeSettings;
|
|
128
|
-
if (
|
|
129
|
-
fulfillmentPreference === FULFILLMENT_TYPES.DELIVERY &&
|
|
130
|
-
estimatedDeliveryMinutes > 0
|
|
131
|
-
) {
|
|
132
|
-
const baseWeekDays = resolved.weekDayPrepTimes ?? {};
|
|
133
|
-
resolved = {
|
|
134
|
-
...resolved,
|
|
135
|
-
weekDayPrepTimes: addEstimatedDeliveryToWeekDays(
|
|
136
|
-
baseWeekDays,
|
|
137
|
-
prepTimeSettings.estimatedDeliveryMinutes ?? 0,
|
|
138
|
-
),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return resolved;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ── Main ────────────────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
export function getSchedules({
|
|
148
|
-
store,
|
|
149
|
-
locations,
|
|
150
|
-
cartItems,
|
|
151
|
-
fulfillmentPreference,
|
|
152
|
-
prepTimeSettings,
|
|
153
|
-
currentLocation,
|
|
154
|
-
isCateringFlow = false,
|
|
155
|
-
platform = PLATFORM.WEB,
|
|
156
|
-
}: GetSchedulesParams): GetSchedulesResult {
|
|
157
|
-
const {
|
|
158
|
-
isAsapOrders,
|
|
159
|
-
isSameDayOrders,
|
|
160
|
-
max_future_order_days: daysCount = 7,
|
|
161
|
-
weeklyPreSaleConfig,
|
|
162
|
-
preSaleConfig,
|
|
163
|
-
} = store;
|
|
164
|
-
|
|
165
|
-
const cart = deriveCartInfo(cartItems);
|
|
166
|
-
const resolvedPrepTime = resolvePrepTimeConfig(
|
|
167
|
-
prepTimeSettings,
|
|
168
|
-
cartItems,
|
|
169
|
-
isCateringFlow,
|
|
170
|
-
fulfillmentPreference,
|
|
171
|
-
currentLocation?.timezone,
|
|
172
|
-
);
|
|
173
|
-
const {
|
|
174
|
-
gapInMinutes,
|
|
175
|
-
busyTimes: busyTimesByLocationId,
|
|
176
|
-
prepTimeFrequency,
|
|
177
|
-
prepTimeCadence,
|
|
178
|
-
weekDayPrepTimes,
|
|
179
|
-
} = resolvedPrepTime;
|
|
180
|
-
|
|
181
|
-
const busyTimes = busyTimesByLocationId?.[currentLocation.location_id] ?? [];
|
|
182
|
-
|
|
183
|
-
const businessHoursOverrides =
|
|
184
|
-
getLocationsBusinessHoursOverrides(store.businessHoursOverrides, locations)[
|
|
185
|
-
currentLocation.location_id
|
|
186
|
-
] ?? [];
|
|
187
|
-
|
|
188
|
-
const filterSchedule = (schedule: FulfillmentSchedule) =>
|
|
189
|
-
filterBusyTimesFromSchedule({
|
|
190
|
-
schedule,
|
|
191
|
-
busyTimes,
|
|
192
|
-
cartCategoryIds: cart.categoryIds,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// ── Weekly pre-sale path (early return) ─────────────────────────────────
|
|
196
|
-
if (
|
|
197
|
-
cart.hasWeeklyPreSaleItem &&
|
|
198
|
-
weeklyPreSaleConfig?.active &&
|
|
199
|
-
!isCateringFlow
|
|
200
|
-
) {
|
|
201
|
-
const weeklyPickupDates = getPreSalePickupDates(
|
|
202
|
-
weeklyPreSaleConfig?.pickup_days,
|
|
203
|
-
weeklyPreSaleConfig?.ordering_days,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
if (weeklyPickupDates.length > 0) {
|
|
207
|
-
const schedule = generateLocationFulfillmentSchedule({
|
|
208
|
-
startDate: weeklyPickupDates[0],
|
|
209
|
-
location: currentLocation,
|
|
210
|
-
fulfillmentPreference,
|
|
211
|
-
businessHoursOverrides,
|
|
212
|
-
gapInMinutes,
|
|
213
|
-
daysCount: 7,
|
|
214
|
-
preSaleDates: weeklyPickupDates.map((d) => d.getDate()),
|
|
215
|
-
presalePickupWeekDays: weeklyPreSaleConfig.pickup_days,
|
|
216
|
-
platform,
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
schedule: filterSchedule(schedule),
|
|
221
|
-
isWeeklyPreSaleAvailable: true,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ── Main schedule path ──────────────────────────────────────────────────
|
|
227
|
-
const isPreSaleEnabled =
|
|
228
|
-
(preSaleConfig?.active ?? false) && cart.hasPreSaleItem && !isCateringFlow;
|
|
229
|
-
const preSaleDates = resolvePreSaleDates(
|
|
230
|
-
preSaleConfig,
|
|
231
|
-
currentLocation.timezone,
|
|
232
|
-
);
|
|
233
|
-
const preSaleHoursOverride = getPreSaleHoursOverride(
|
|
234
|
-
preSaleConfig,
|
|
235
|
-
cart.hasPreSaleItem,
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
const schedule = generateLocationFulfillmentSchedule({
|
|
239
|
-
startDate: resolveStartDate(preSaleDates.startDate, cart.hasPreSaleItem),
|
|
240
|
-
prepTimeFrequency,
|
|
241
|
-
prepTimeCadence,
|
|
242
|
-
location: currentLocation,
|
|
243
|
-
fulfillmentPreference,
|
|
244
|
-
businessHoursOverrides,
|
|
245
|
-
gapInMinutes,
|
|
246
|
-
daysCount: isPreSaleEnabled
|
|
247
|
-
? differenceInDays(preSaleDates.endDate, preSaleDates.startDate) + 1
|
|
248
|
-
: !isAsapOrders && !isSameDayOrders
|
|
249
|
-
? daysCount
|
|
250
|
-
: 1,
|
|
251
|
-
platform,
|
|
252
|
-
|
|
253
|
-
...(!isPreSaleEnabled && {
|
|
254
|
-
weekDayPrepTimes,
|
|
255
|
-
}),
|
|
256
|
-
...(preSaleHoursOverride && { preSaleHoursOverride }),
|
|
257
|
-
...(isPreSaleEnabled && { endDate: preSaleDates.endDate }),
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
schedule: filterSchedule(schedule),
|
|
262
|
-
isWeeklyPreSaleAvailable: false,
|
|
263
|
-
};
|
|
264
|
-
}
|
package/src/schedule/location.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { roundToNearestMinutes } from "date-fns";
|
|
2
|
-
import { PLATFORM, PREP_TIME_CADENCE } from "../constants";
|
|
3
|
-
import type {
|
|
4
|
-
FulfillmentSchedule,
|
|
5
|
-
GenerateLocationFulfillmentScheduleParams,
|
|
6
|
-
} from "../types";
|
|
7
|
-
import { getLocationBusinessHoursForFulfillment } from "../utils/business-hours";
|
|
8
|
-
import { getNextAvailableDates } from "./available-dates";
|
|
9
|
-
import { generateSchedule } from "./generate";
|
|
10
|
-
|
|
11
|
-
export function generateLocationFulfillmentSchedule({
|
|
12
|
-
startDate,
|
|
13
|
-
prepTimeFrequency = 0,
|
|
14
|
-
prepTimeCadence = PREP_TIME_CADENCE.MINUTE,
|
|
15
|
-
weekDayPrepTimes,
|
|
16
|
-
location,
|
|
17
|
-
fulfillmentPreference,
|
|
18
|
-
businessHoursOverrides = [],
|
|
19
|
-
preSaleHoursOverride,
|
|
20
|
-
gapInMinutes,
|
|
21
|
-
daysCount = 1,
|
|
22
|
-
preSaleDates = [],
|
|
23
|
-
presalePickupWeekDays = [],
|
|
24
|
-
endDate = null,
|
|
25
|
-
platform = PLATFORM.WEB,
|
|
26
|
-
}: GenerateLocationFulfillmentScheduleParams): FulfillmentSchedule {
|
|
27
|
-
const isDaysCadence = prepTimeCadence === PREP_TIME_CADENCE.DAY;
|
|
28
|
-
const businessHours = getLocationBusinessHoursForFulfillment(
|
|
29
|
-
location,
|
|
30
|
-
fulfillmentPreference,
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const dates = getNextAvailableDates({
|
|
34
|
-
startDate,
|
|
35
|
-
businessHours,
|
|
36
|
-
businessHoursOverrides,
|
|
37
|
-
timeZone: location.timezone,
|
|
38
|
-
datesCount: daysCount,
|
|
39
|
-
preSaleDates,
|
|
40
|
-
endDate,
|
|
41
|
-
presalePickupWeekDays,
|
|
42
|
-
isDaysCadence,
|
|
43
|
-
platform,
|
|
44
|
-
});
|
|
45
|
-
// If prepTimeCadence is days, we need to skip opening days by prepTimeFrequency
|
|
46
|
-
const availableDates = isDaysCadence ? dates.slice(prepTimeFrequency) : dates;
|
|
47
|
-
return generateSchedule({
|
|
48
|
-
currentDate: roundToNearestMinutes(startDate),
|
|
49
|
-
weekDayPrepTimes,
|
|
50
|
-
timeZone: location.timezone,
|
|
51
|
-
dates: availableDates,
|
|
52
|
-
businessHours,
|
|
53
|
-
businessHoursOverrides,
|
|
54
|
-
preSaleHoursOverride,
|
|
55
|
-
gapInMinutes,
|
|
56
|
-
prepTimeCadence,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/** Raw business-hour record from the API (snake_case keys). */
|
|
2
|
-
export interface BusinessHourInput {
|
|
3
|
-
day: number;
|
|
4
|
-
start_time: string;
|
|
5
|
-
end_time: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Normalised business-hour used internally (camelCase keys). */
|
|
9
|
-
export interface BusinessHour {
|
|
10
|
-
day: number;
|
|
11
|
-
startTime: string;
|
|
12
|
-
endTime: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Raw business-hours override coming from the API. */
|
|
16
|
-
export interface BusinessHoursOverrideInput {
|
|
17
|
-
all_locations?: boolean;
|
|
18
|
-
location_ids?: string[];
|
|
19
|
-
month: number;
|
|
20
|
-
day: number;
|
|
21
|
-
start_time: string | null;
|
|
22
|
-
end_time: string | null;
|
|
23
|
-
is_open?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Normalised business-hours override. */
|
|
27
|
-
export interface BusinessHoursOverrideOutput {
|
|
28
|
-
month: number;
|
|
29
|
-
day: number;
|
|
30
|
-
startTime: string | null;
|
|
31
|
-
endTime: string | null;
|
|
32
|
-
}
|