@perdieminc/time-slots 0.0.1
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/LICENSE +21 -0
- package/README.md +100 -0
- package/package.json +63 -0
- package/src/constants.ts +45 -0
- package/src/index.ts +23 -0
- package/src/schedule/available-dates.ts +129 -0
- package/src/schedule/generate.ts +276 -0
- package/src/schedule/get-schedules.ts +264 -0
- package/src/schedule/location.ts +58 -0
- package/src/types/business-hours.d.ts +32 -0
- package/src/types/common.d.ts +5 -0
- package/src/types/get-schedules.d.ts +122 -0
- package/src/types/index.ts +34 -0
- package/src/types/location.d.ts +25 -0
- package/src/types/schedule-filter.d.ts +27 -0
- package/src/types/schedule.d.ts +83 -0
- package/src/types/timezone-support.d.ts +31 -0
- package/src/utils/business-hours.ts +120 -0
- package/src/utils/catering.ts +85 -0
- package/src/utils/date.ts +163 -0
- package/src/utils/schedule-filter.ts +140 -0
- package/src/utils/store-hours.ts +223 -0
- package/src/utils/time.ts +38 -0
|
@@ -0,0 +1,264 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { PrepTimeCadence } from "../constants";
|
|
2
|
+
|
|
3
|
+
export interface GetCateringPrepTimeParams {
|
|
4
|
+
items: CartItem[];
|
|
5
|
+
prepTimeCadence?: PrepTimeCadence;
|
|
6
|
+
prepTimeFrequency?: number;
|
|
7
|
+
timezone?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
BusinessHoursOverrideInput,
|
|
12
|
+
BusinessHoursOverrideOutput,
|
|
13
|
+
} from "./business-hours";
|
|
14
|
+
import type { BusyTimeItem } from "./common";
|
|
15
|
+
import type { FulfillmentPreference, LocationLike } from "./location";
|
|
16
|
+
import type { FulfillmentSchedule, Platform } from "./schedule";
|
|
17
|
+
|
|
18
|
+
// ── High-level input objects ────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export interface PreSaleConfig {
|
|
21
|
+
active: boolean;
|
|
22
|
+
due_start_date?: string | Date;
|
|
23
|
+
due_end_date?: string | Date;
|
|
24
|
+
use_store_hours_due?: boolean;
|
|
25
|
+
due_start_time?: string | null;
|
|
26
|
+
due_end_time?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WeeklyPreSaleConfig {
|
|
30
|
+
active: boolean;
|
|
31
|
+
pickup_days: number[];
|
|
32
|
+
ordering_days: number[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StoreConfig {
|
|
36
|
+
isAsapOrders: boolean;
|
|
37
|
+
isSameDayOrders: boolean;
|
|
38
|
+
max_future_order_days?: number;
|
|
39
|
+
businessHoursOverrides: BusinessHoursOverrideInput[];
|
|
40
|
+
preSaleConfig?: PreSaleConfig;
|
|
41
|
+
weeklyPreSaleConfig?: WeeklyPreSaleConfig;
|
|
42
|
+
}
|
|
43
|
+
export type CateringServiceType = {
|
|
44
|
+
min_quantity: number;
|
|
45
|
+
max_quantity: number;
|
|
46
|
+
serve_count: number;
|
|
47
|
+
prep_time: {
|
|
48
|
+
cadence: PrepTimeCadence;
|
|
49
|
+
frequency: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export interface CartItem {
|
|
53
|
+
preSale?: boolean;
|
|
54
|
+
weeklyPreSale?: boolean;
|
|
55
|
+
internalCategoryId?: string;
|
|
56
|
+
cateringService?: CateringServiceType;
|
|
57
|
+
}
|
|
58
|
+
export interface CateringPrepTimeResult {
|
|
59
|
+
prepTimeCadence: PrepTimeCadence;
|
|
60
|
+
prepTimeFrequency: number;
|
|
61
|
+
/** Only set when prepTimeCadence is not DAY (e.g. HOUR). */
|
|
62
|
+
weekDayPrepTimes?: Record<number, number>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PrepTimeSettings {
|
|
66
|
+
prepTimeInMinutes: number;
|
|
67
|
+
weekDayPrepTimes: Record<number, number>;
|
|
68
|
+
gapInMinutes: number;
|
|
69
|
+
busyTimes: Record<string, BusyTimeItem[]>;
|
|
70
|
+
prepTimeFrequency: number;
|
|
71
|
+
prepTimeCadence: PrepTimeCadence;
|
|
72
|
+
/** When fulfillment is DELIVERY, added to each weekday prep time so slots reflect when order is received. */
|
|
73
|
+
estimatedDeliveryMinutes?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── getSchedules params / result ────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export interface GetSchedulesParams {
|
|
79
|
+
store: StoreConfig;
|
|
80
|
+
locations: LocationLike[];
|
|
81
|
+
cartItems: CartItem[];
|
|
82
|
+
fulfillmentPreference: FulfillmentPreference;
|
|
83
|
+
prepTimeSettings: PrepTimeSettings;
|
|
84
|
+
currentLocation: LocationLike;
|
|
85
|
+
isCateringFlow?: boolean;
|
|
86
|
+
/** Platform for timezone handling in next-available-date logic. Default "web". */
|
|
87
|
+
platform?: Platform;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface GetSchedulesResult {
|
|
91
|
+
schedule: FulfillmentSchedule;
|
|
92
|
+
isWeeklyPreSaleAvailable: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Legacy flat params (kept for backward-compat if needed) ─────────────────
|
|
96
|
+
|
|
97
|
+
export interface InitScheduleParams {
|
|
98
|
+
isAsapOrders: boolean;
|
|
99
|
+
isSameDayOrders: boolean;
|
|
100
|
+
preSalePickupDays: number[];
|
|
101
|
+
isWeeklyPreSaleActive: boolean;
|
|
102
|
+
preSaleOrderingDays: number[];
|
|
103
|
+
locations: LocationLike[];
|
|
104
|
+
daysCount?: number;
|
|
105
|
+
fulfillmentPreference: FulfillmentPreference;
|
|
106
|
+
prepTimeInMinutes: number;
|
|
107
|
+
weekDayPrepTimes: Record<number, number>;
|
|
108
|
+
hasCartWeeklyPreSaleItem: boolean;
|
|
109
|
+
busyTimes: BusyTimeItem[];
|
|
110
|
+
cartCategoryIds: string[];
|
|
111
|
+
gapInMinutes: number;
|
|
112
|
+
businessHoursOverrides: Record<string, BusinessHoursOverrideOutput[]>;
|
|
113
|
+
currentLocation: LocationLike;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface InitScheduleResult {
|
|
117
|
+
businessHoursOverrides: Record<string, BusinessHoursOverrideOutput[]>;
|
|
118
|
+
locationId: string;
|
|
119
|
+
fulfillmentPreference: FulfillmentPreference;
|
|
120
|
+
schedule: FulfillmentSchedule;
|
|
121
|
+
isWeeklyPreSaleAvailable: boolean;
|
|
122
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
BusinessHour,
|
|
3
|
+
BusinessHourInput,
|
|
4
|
+
BusinessHoursOverrideInput,
|
|
5
|
+
BusinessHoursOverrideOutput,
|
|
6
|
+
} from "./business-hours";
|
|
7
|
+
export type { BusyTimeItem } from "./common";
|
|
8
|
+
export type {
|
|
9
|
+
CartItem,
|
|
10
|
+
CateringPrepTimeResult,
|
|
11
|
+
GetCateringPrepTimeParams,
|
|
12
|
+
GetSchedulesParams,
|
|
13
|
+
GetSchedulesResult,
|
|
14
|
+
InitScheduleParams,
|
|
15
|
+
InitScheduleResult,
|
|
16
|
+
PrepTimeSettings,
|
|
17
|
+
PreSaleConfig,
|
|
18
|
+
StoreConfig,
|
|
19
|
+
WeeklyPreSaleConfig,
|
|
20
|
+
} from "./get-schedules";
|
|
21
|
+
export type { FulfillmentPreference, LocationLike } from "./location";
|
|
22
|
+
export type {
|
|
23
|
+
DaySchedule,
|
|
24
|
+
FulfillmentSchedule,
|
|
25
|
+
GenerateLocationFulfillmentScheduleParams,
|
|
26
|
+
GenerateScheduleParams,
|
|
27
|
+
GetNextAvailableDatesParams,
|
|
28
|
+
GetOpeningClosingTimeOnDateParams,
|
|
29
|
+
Platform,
|
|
30
|
+
} from "./schedule";
|
|
31
|
+
export type {
|
|
32
|
+
FilterBusyTimesFromScheduleParams,
|
|
33
|
+
MenuType,
|
|
34
|
+
} from "./schedule-filter";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BusinessHourInput } from "./business-hours";
|
|
2
|
+
|
|
3
|
+
export type FulfillmentPreference = "PICKUP" | "DELIVERY" | "CURBSIDE";
|
|
4
|
+
export type BusinessHourType = {
|
|
5
|
+
day: number;
|
|
6
|
+
start_time: string;
|
|
7
|
+
end_time: string;
|
|
8
|
+
};
|
|
9
|
+
export interface LocationLike {
|
|
10
|
+
location_id: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
timezone: string;
|
|
13
|
+
pickup_hours?: BusinessHourInput[];
|
|
14
|
+
delivery_hours?: BusinessHourInput[];
|
|
15
|
+
curbside_hours?: {
|
|
16
|
+
use_pickup_hours?: boolean;
|
|
17
|
+
times?: BusinessHourInput[];
|
|
18
|
+
};
|
|
19
|
+
catering?: {
|
|
20
|
+
enabled: boolean | string;
|
|
21
|
+
pickup: Omit<BusinessHourType, "day">;
|
|
22
|
+
delivery: Omit<BusinessHourType, "day">;
|
|
23
|
+
};
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BusyTimeItem } from "./common";
|
|
2
|
+
import type { FulfillmentSchedule } from "./schedule";
|
|
3
|
+
export type MenuTimeSlot = {
|
|
4
|
+
all_day: boolean;
|
|
5
|
+
end_time: string | null;
|
|
6
|
+
start_time: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type MenuType = {
|
|
10
|
+
menu_id: string;
|
|
11
|
+
store_id: string;
|
|
12
|
+
location_id: string | null;
|
|
13
|
+
all_locations: boolean;
|
|
14
|
+
display_name: string;
|
|
15
|
+
description: string | null;
|
|
16
|
+
times: Record<string, MenuTimeSlot>;
|
|
17
|
+
category_ids: string[];
|
|
18
|
+
last_modified_by: string;
|
|
19
|
+
created_at: string;
|
|
20
|
+
updated_at: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface FilterBusyTimesFromScheduleParams {
|
|
24
|
+
schedule: FulfillmentSchedule;
|
|
25
|
+
busyTimes?: BusyTimeItem[];
|
|
26
|
+
cartCategoryIds?: string[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { PLATFORM } from "../constants";
|
|
2
|
+
import type {
|
|
3
|
+
BusinessHour,
|
|
4
|
+
BusinessHoursOverrideOutput,
|
|
5
|
+
} from "./business-hours";
|
|
6
|
+
import type { FulfillmentPreference, LocationLike } from "./location";
|
|
7
|
+
|
|
8
|
+
export interface DaySchedule {
|
|
9
|
+
date: Date;
|
|
10
|
+
originalStoreOpeningTime: Date | null;
|
|
11
|
+
originalStoreClosingTime: Date | null;
|
|
12
|
+
remainingShifts: number;
|
|
13
|
+
openingTime: Date;
|
|
14
|
+
closingTime: Date;
|
|
15
|
+
firstAvailableSlot: Date;
|
|
16
|
+
slots: Date[];
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type FulfillmentSchedule = DaySchedule[];
|
|
21
|
+
|
|
22
|
+
export interface GenerateScheduleParams {
|
|
23
|
+
currentDate?: Date;
|
|
24
|
+
prepTimeBehaviour?: number;
|
|
25
|
+
prepTimeInMinutes?: number;
|
|
26
|
+
weekDayPrepTimes?: Record<number, number>;
|
|
27
|
+
timeZone: string;
|
|
28
|
+
dates?: Date[];
|
|
29
|
+
businessHours?: BusinessHour[];
|
|
30
|
+
businessHoursOverrides?: BusinessHoursOverrideOutput[];
|
|
31
|
+
preSaleHoursOverride?: Array<{
|
|
32
|
+
startTime: string;
|
|
33
|
+
endTime: string;
|
|
34
|
+
month?: number;
|
|
35
|
+
day?: number;
|
|
36
|
+
}> | null;
|
|
37
|
+
gapInMinutes?: number;
|
|
38
|
+
prepTimeCadence?: PrepTimeCadence;
|
|
39
|
+
}
|
|
40
|
+
export type Platform = (typeof PLATFORM)[keyof typeof PLATFORM];
|
|
41
|
+
export interface GetNextAvailableDatesParams {
|
|
42
|
+
startDate: Date;
|
|
43
|
+
timeZone: string;
|
|
44
|
+
businessHours: BusinessHour[];
|
|
45
|
+
businessHoursOverrides?: BusinessHoursOverrideOutput[];
|
|
46
|
+
datesCount?: number;
|
|
47
|
+
preSaleDates?: number[];
|
|
48
|
+
presalePickupWeekDays?: number[];
|
|
49
|
+
endDate?: Date | null;
|
|
50
|
+
isDaysCadence?: boolean;
|
|
51
|
+
/** Platform for timezone handling. Web uses @date-fns/tz; ios/android use timezone-support. Default "web". */
|
|
52
|
+
platform?: Platform;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GenerateLocationFulfillmentScheduleParams {
|
|
56
|
+
startDate: Date;
|
|
57
|
+
prepTimeFrequency?: number;
|
|
58
|
+
prepTimeCadence?: PrepTimeCadence;
|
|
59
|
+
weekDayPrepTimes?: Record<number, number>;
|
|
60
|
+
location: LocationLike;
|
|
61
|
+
fulfillmentPreference: FulfillmentPreference;
|
|
62
|
+
/** Overrides for this location only (not keyed by location_id). */
|
|
63
|
+
businessHoursOverrides?: BusinessHoursOverrideOutput[];
|
|
64
|
+
preSaleHoursOverride?: Array<{
|
|
65
|
+
startTime: string;
|
|
66
|
+
endTime: string;
|
|
67
|
+
month?: number;
|
|
68
|
+
day?: number;
|
|
69
|
+
}> | null;
|
|
70
|
+
gapInMinutes?: number;
|
|
71
|
+
daysCount?: number;
|
|
72
|
+
preSaleDates?: number[];
|
|
73
|
+
presalePickupWeekDays?: number[];
|
|
74
|
+
endDate?: Date | null;
|
|
75
|
+
platform?: Platform;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface GetOpeningClosingTimeOnDateParams {
|
|
79
|
+
date?: Date;
|
|
80
|
+
businessHours?: BusinessHour[];
|
|
81
|
+
businessHoursOverrides?: BusinessHoursOverrideOutput[];
|
|
82
|
+
timeZone: string;
|
|
83
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare module "timezone-support" {
|
|
2
|
+
export function findTimeZone(timezone: string): string;
|
|
3
|
+
export function getUnixTime(time: {
|
|
4
|
+
zone: string;
|
|
5
|
+
year: number;
|
|
6
|
+
month: number;
|
|
7
|
+
day: number;
|
|
8
|
+
hours: number | string;
|
|
9
|
+
minutes: number | string;
|
|
10
|
+
seconds: number | string;
|
|
11
|
+
milliseconds: number | string;
|
|
12
|
+
}): number;
|
|
13
|
+
export function getZonedTime(
|
|
14
|
+
date: Date | number,
|
|
15
|
+
timezone: string,
|
|
16
|
+
): {
|
|
17
|
+
zone: string;
|
|
18
|
+
year: number;
|
|
19
|
+
month: number;
|
|
20
|
+
day: number;
|
|
21
|
+
hours: number | string;
|
|
22
|
+
minutes: number | string;
|
|
23
|
+
seconds: number | string;
|
|
24
|
+
milliseconds: number | string;
|
|
25
|
+
dayOfWeek: number;
|
|
26
|
+
};
|
|
27
|
+
export function toZonedTime(
|
|
28
|
+
date: Date | number | string,
|
|
29
|
+
timezone: string,
|
|
30
|
+
): Date;
|
|
31
|
+
}
|