@wix/headless-restaurants-olo 0.0.31 → 0.0.32

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.
Files changed (65) hide show
  1. package/cjs/dist/react/ClickableItem.d.ts +18 -3
  2. package/cjs/dist/react/ClickableItem.js +16 -9
  3. package/cjs/dist/react/FulfillmentDetails.d.ts +233 -0
  4. package/cjs/dist/react/FulfillmentDetails.js +255 -0
  5. package/cjs/dist/react/ItemDetails.d.ts +233 -36
  6. package/cjs/dist/react/ItemDetails.js +208 -0
  7. package/cjs/dist/react/OLO.d.ts +5 -160
  8. package/cjs/dist/react/OLO.js +6 -122
  9. package/cjs/dist/react/OLOMenus.d.ts +8 -2
  10. package/cjs/dist/react/OLOMenus.js +3 -4
  11. package/cjs/dist/react/Settings.d.ts +176 -48
  12. package/cjs/dist/react/Settings.js +276 -26
  13. package/cjs/dist/react/core/ClickableItem.d.ts +12 -5
  14. package/cjs/dist/react/core/ClickableItem.js +13 -14
  15. package/cjs/dist/react/core/FulfillmentDetails.d.ts +78 -0
  16. package/cjs/dist/react/core/FulfillmentDetails.js +177 -0
  17. package/cjs/dist/react/core/ItemDetails.js +2 -4
  18. package/cjs/dist/react/core/OLO.d.ts +6 -74
  19. package/cjs/dist/react/core/OLO.js +5 -44
  20. package/cjs/dist/react/core/OLOMenus.d.ts +1 -1
  21. package/cjs/dist/react/core/OLOMenus.js +2 -3
  22. package/cjs/dist/react/core/Settings.d.ts +138 -22
  23. package/cjs/dist/react/core/Settings.js +157 -34
  24. package/cjs/dist/react/core/index.d.ts +1 -0
  25. package/cjs/dist/react/core/index.js +1 -0
  26. package/cjs/dist/react/index.d.ts +2 -0
  27. package/cjs/dist/react/index.js +1 -0
  28. package/cjs/dist/services/fulfillments-service.js +80 -22
  29. package/cjs/dist/types/fulfillments-types.d.ts +49 -2
  30. package/cjs/dist/types/operation.d.ts +1 -1
  31. package/cjs/dist/utils/fulfillments-utils.d.ts +34 -1
  32. package/cjs/dist/utils/fulfillments-utils.js +209 -1
  33. package/dist/react/ClickableItem.d.ts +18 -3
  34. package/dist/react/ClickableItem.js +16 -9
  35. package/dist/react/FulfillmentDetails.d.ts +233 -0
  36. package/dist/react/FulfillmentDetails.js +255 -0
  37. package/dist/react/ItemDetails.d.ts +233 -36
  38. package/dist/react/ItemDetails.js +208 -0
  39. package/dist/react/OLO.d.ts +5 -160
  40. package/dist/react/OLO.js +6 -122
  41. package/dist/react/OLOMenus.d.ts +8 -2
  42. package/dist/react/OLOMenus.js +3 -4
  43. package/dist/react/Settings.d.ts +176 -48
  44. package/dist/react/Settings.js +276 -26
  45. package/dist/react/core/ClickableItem.d.ts +12 -5
  46. package/dist/react/core/ClickableItem.js +13 -14
  47. package/dist/react/core/FulfillmentDetails.d.ts +78 -0
  48. package/dist/react/core/FulfillmentDetails.js +177 -0
  49. package/dist/react/core/ItemDetails.js +2 -4
  50. package/dist/react/core/OLO.d.ts +6 -74
  51. package/dist/react/core/OLO.js +5 -44
  52. package/dist/react/core/OLOMenus.d.ts +1 -1
  53. package/dist/react/core/OLOMenus.js +2 -3
  54. package/dist/react/core/Settings.d.ts +138 -22
  55. package/dist/react/core/Settings.js +157 -34
  56. package/dist/react/core/index.d.ts +1 -0
  57. package/dist/react/core/index.js +1 -0
  58. package/dist/react/index.d.ts +2 -0
  59. package/dist/react/index.js +1 -0
  60. package/dist/services/fulfillments-service.js +80 -22
  61. package/dist/types/fulfillments-types.d.ts +49 -2
  62. package/dist/types/operation.d.ts +1 -1
  63. package/dist/utils/fulfillments-utils.d.ts +34 -1
  64. package/dist/utils/fulfillments-utils.js +209 -1
  65. package/package.json +2 -2
@@ -2,46 +2,108 @@ import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
3
3
  import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
4
4
  import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
5
- import { processFulfillmentTimeSlotByOperationList } from '../utils/fulfillments-utils.js';
5
+ import { processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
6
+ import { DispatchType, } from '../types/fulfillments-types.js';
6
7
  export const FulfillmentsServiceDefinition = defineService('fulfillments');
7
8
  export const FulfillmentsService = implementService.withConfig()(FulfillmentsServiceDefinition, ({ getService, config }) => {
8
- console.log('config', config);
9
9
  if (!config.operation) {
10
10
  throw new Error('Operation ID is required');
11
11
  }
12
12
  const signalsService = getService(SignalsServiceDefinition);
13
- const timeSlotsMap = new Map(config.timeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
13
+ const firstAvailableTimeSlotsByOperationList = new Map(config.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
14
14
  const fulfillments = signalsService.signal(config.fulfillments ?? []);
15
- const timeSlots = signalsService.signal(
15
+ const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(
16
16
  // @ts-expect-error - operation is not typed
17
- timeSlotsMap.get(config.operation?.id ?? '') ?? []);
17
+ config.operation?.id ?? '') ?? []);
18
18
  const isLoading = signalsService.signal(false);
19
19
  const error = signalsService.signal(null);
20
- const initialSelected = timeSlots.get()?.[0] ?? null;
20
+ const dispatchesInfoSignal = signalsService.signal({});
21
+ const selectedDispatchInfoSignal = signalsService.signal(null);
22
+ const asapTimeSignal = signalsService.signal(undefined);
23
+ const asapTimeRangeSignal = signalsService.signal(undefined);
24
+ processFulfillments(fulfillments.get() ?? [], firstAvailableTimeSlots.get() ?? [], config.operation ?? undefined).then((result) => {
25
+ const { dispatchesInfo } = result ?? {};
26
+ dispatchesInfoSignal.set(dispatchesInfo ?? {});
27
+ const selectedDispatchInfo = dispatchesInfoSignal.get()[selectedFulfillment.get()?.type];
28
+ selectedDispatchInfoSignal.set(selectedDispatchInfo ?? null);
29
+ const { maxTimeOptions, durationRangeOptions } = selectedDispatchInfo ?? {};
30
+ // const isDelivery = !!address;
31
+ const isAsap = !!selectedTimeSlot.get()?.startsNow;
32
+ // if (isDelivery && isAsap) {
33
+ const { fulfillmentDetails } = selectedTimeSlot.get() ?? {};
34
+ if (isAsap) {
35
+ asapTimeSignal.set(fulfillmentDetails?.maxTimeOptions ?? maxTimeOptions);
36
+ asapTimeRangeSignal.set(fulfillmentDetails?.durationRangeOptions ?? durationRangeOptions);
37
+ }
38
+ });
39
+ const pickupTimeSlot = firstAvailableTimeSlots
40
+ .get()
41
+ ?.find((t) => t.dispatchType === DispatchType.PICKUP) ?? null;
42
+ const initialSelected = pickupTimeSlot ?? firstAvailableTimeSlots.get()?.[0] ?? null;
21
43
  const selectedTimeSlot = signalsService.signal(initialSelected);
44
+ const selectedFulfillment = signalsService.signal(null);
45
+ const availableTypes = signalsService.signal([]);
22
46
  selectedTimeSlot.set(initialSelected);
47
+ const selectFulfillmentType = (type) => {
48
+ const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
49
+ null;
50
+ if (timeSlot) {
51
+ setSelectedTimeSlot(timeSlot);
52
+ }
53
+ };
54
+ /**
55
+ * Service Action: Set the selected time slot and update related fulfillment data
56
+ * This action updates:
57
+ * - selectedTimeSlot signal
58
+ * - selectedFulfillment signal (based on dispatch type)
59
+ * - minOrderPrice signal (based on fulfillment method)
60
+ */
23
61
  const setSelectedTimeSlot = (timeSlot) => {
62
+ // Update the selected time slot
24
63
  selectedTimeSlot.set(timeSlot);
64
+ // Update selected fulfillment based on dispatch type
65
+ const matchingFulfillment = fulfillments.get().find((f) => f.type === timeSlot.dispatchType) ??
66
+ null;
67
+ selectedFulfillment.set(matchingFulfillment);
68
+ selectedDispatchInfoSignal.set(dispatchesInfoSignal.get()[matchingFulfillment?.type] ?? null);
25
69
  };
26
- if (!config.timeSlots && config.operation) {
27
- loadFulfillmentsServiceConfig(config.operation).then((config) => {
28
- const timeSlotsMap = new Map(config.timeSlots?.map(processFulfillmentTimeSlotByOperationList) ??
29
- []);
30
- timeSlots.set(
70
+ // Initialize selected fulfillment for initial time slot
71
+ if (initialSelected && fulfillments.get()?.length > 0) {
72
+ const fulfillmentList = fulfillments.get();
73
+ const matchingFulfillment = fulfillmentList.find((f) => f.type === initialSelected.dispatchType) ?? null;
74
+ selectedFulfillment.set(matchingFulfillment);
75
+ }
76
+ // Load time slots and fulfillments if not provided
77
+ if (!config.firstAvailableTimeSlots && config.operation) {
78
+ loadFulfillmentsServiceConfig(config.operation).then((loadedConfig) => {
79
+ const timeSlotsMap = new Map(loadedConfig.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
80
+ const newTimeSlots =
31
81
  // @ts-expect-error - operation is not typed
32
- timeSlotsMap.get(config.operation?.id ?? '') ?? []);
33
- const initialSelected = timeSlots.get()?.[0] ?? null;
34
- selectedTimeSlot.set(initialSelected);
35
- fulfillments.set(config.fulfillments ?? []);
82
+ timeSlotsMap.get(loadedConfig.operation?.id ?? '') ?? [];
83
+ firstAvailableTimeSlots.set(newTimeSlots);
84
+ fulfillments.set(loadedConfig.fulfillments ?? []);
85
+ // Set initial time slot using the action to ensure all related signals are updated
86
+ const newInitialSelected = newTimeSlots[0];
87
+ if (newInitialSelected) {
88
+ setSelectedTimeSlot(newInitialSelected);
89
+ }
36
90
  });
37
91
  }
92
+ availableTypes.set(Array.from(new Set(firstAvailableTimeSlots.get()?.map((t) => t.dispatchType) ?? [])).sort((a, _) => (a === DispatchType.PICKUP ? -1 : 1)));
38
93
  return {
39
- timeSlots,
94
+ firstAvailableTimeSlots,
40
95
  fulfillments,
96
+ selectedFulfillment,
97
+ dispatchesInfoSignal,
98
+ selectedDispatchInfoSignal,
99
+ asapTimeSignal,
100
+ asapTimeRangeSignal,
41
101
  selectedTimeSlot,
102
+ availableTypes,
42
103
  isLoading,
43
104
  error,
44
105
  setSelectedTimeSlot,
106
+ selectFulfillmentType,
45
107
  };
46
108
  });
47
109
  export const loadFulfillmentsServiceConfig = async (operation) => {
@@ -55,13 +117,9 @@ export const loadFulfillmentsServiceConfig = async (operation) => {
55
117
  .in('_id', operation?.fulfillmentIds)
56
118
  .find(),
57
119
  ]);
58
- // const fulfillments = await operationsSDK.calculateFirstAvailableTimeSlotsPerOperation([
59
- // operationId,
60
- // ]);
61
- console.log('fulfillments', fulfillments.items);
62
- // const fulfillmentsMap = new Map(fulfillments.timeSlotsPerOperation?.map(processFulfillmentTimeSlotByOperationList));
120
+ console.log('operation', operation);
63
121
  return {
64
- timeSlots: timeSlots.timeSlotsPerOperation, //fulfillmentsMap.get(operationId) ?? [],
122
+ firstAvailableTimeSlots: timeSlots.timeSlotsPerOperation,
65
123
  operation: operation,
66
124
  fulfillments: fulfillments.items.filter((f) => f.enabled),
67
125
  };
@@ -1,6 +1,7 @@
1
1
  import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
2
2
  import { Signal } from '@wix/services-definitions/core-services/signals';
3
3
  import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
4
+ import { Address, DurationRange } from '@wix/auto_sdk_restaurants_operations';
4
5
  export declare enum DispatchType {
5
6
  /** Pickup fulfillment */
6
7
  PICKUP = "PICKUP",
@@ -21,16 +22,62 @@ export interface Fulfillment {
21
22
  type: string;
22
23
  isAvailable: boolean;
23
24
  }
25
+ export type DispatchInfo = {
26
+ address?: Address;
27
+ selectedTimeSlot?: TimeSlot;
28
+ maxTimeOptions?: number;
29
+ durationRangeOptions?: DurationRange;
30
+ minOrder?: string;
31
+ deliveryFee?: string;
32
+ freeFulfillmentPriceThreshold?: string;
33
+ minDate?: Date;
34
+ };
35
+ export type DispatchesInfo = Record<DispatchType, DispatchInfo>;
24
36
  export interface FulfillmentsServiceAPI {
25
- timeSlots: Signal<TimeSlot[]>;
37
+ /** Available time slots for the operation */
38
+ firstAvailableTimeSlots: Signal<TimeSlot[]>;
39
+ /** Available fulfillment methods (pickup, delivery, etc.) */
26
40
  fulfillments: Signal<fulfillemtMethodsSDK.FulfillmentMethod[]>;
41
+ /** Currently selected fulfillment method based on dispatch type */
42
+ selectedFulfillment: Signal<fulfillemtMethodsSDK.FulfillmentMethod | null>;
43
+ /** Currently selected time slot */
27
44
  selectedTimeSlot: Signal<TimeSlot | null>;
45
+ /** Currently selected ASAP time */
46
+ asapTimeSignal: Signal<number | undefined>;
47
+ /** Currently selected ASAP time range */
48
+ asapTimeRangeSignal: Signal<operationsSDK.DurationRange | undefined>;
49
+ /** Loading state */
28
50
  isLoading: Signal<boolean>;
51
+ /** Error state */
29
52
  error: Signal<string | null>;
53
+ /** Available dispatch types */
54
+ availableTypes: Signal<DispatchType[]>;
55
+ /**
56
+ * Dispatches info by dispatch type
57
+ */
58
+ dispatchesInfoSignal: Signal<DispatchesInfo>;
59
+ /**
60
+ * Currently selected dispatch info
61
+ */
62
+ selectedDispatchInfoSignal: Signal<DispatchInfo | null>;
63
+ /**
64
+ * Action: Set the selected time slot and update related fulfillment data
65
+ * This action automatically updates:
66
+ * - selectedTimeSlot signal
67
+ * - selectedFulfillment signal (based on dispatch type)
68
+ * - dispatchesInfo signal (based on dispatch type)
69
+ *
70
+ * @param timeSlot - The time slot to select
71
+ */
30
72
  setSelectedTimeSlot: (timeSlot: TimeSlot) => void;
73
+ /**
74
+ * Action: Select a fulfillment type and update related fulfillment data
75
+ * This action automatically updates the selected time slot and fulfillment data
76
+ */
77
+ selectFulfillmentType: (type: DispatchType) => void;
31
78
  }
32
79
  export interface FulfillmentsServiceConfig {
33
- timeSlots?: operationsSDK.TimeSlotForOperation[];
80
+ firstAvailableTimeSlots?: operationsSDK.TimeSlotForOperation[];
34
81
  operation?: operationsSDK.Operation;
35
82
  fulfillments?: fulfillemtMethodsSDK.FulfillmentMethod[];
36
83
  }
@@ -11,7 +11,7 @@ export type PreorderScheduling = {
11
11
  timeWindowDuration: number;
12
12
  timeInAdvance: MinMaxRange;
13
13
  };
14
- export type OperationType = 'PRE_ORDER' | 'ASAP';
14
+ export type OperationType = 'PRE_ORDER' | 'ASAP' | 'ASAP_AND_FUTURE';
15
15
  type BaseOperation = {
16
16
  id: string;
17
17
  name: string;
@@ -1,5 +1,21 @@
1
1
  import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
2
- import { TimeSlot } from '../types/fulfillments-types.js';
2
+ import { DispatchesInfo, TimeSlot } from '../types/fulfillments-types.js';
3
+ import { Operation } from '../types/operation.js';
4
+ import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
5
+ import { FulfillmentDetails } from '@wix/auto_sdk_restaurants_operations';
6
+ /**
7
+ * Format a string with placeholders like {0}, {1}, etc.
8
+ * Similar to C#'s String.Format
9
+ *
10
+ * @example
11
+ * stringFormat("Hello {0}, you have {1} messages", "John", 5)
12
+ * // Returns: "Hello John, you have 5 messages"
13
+ *
14
+ * @example
15
+ * stringFormat("{0} + {1} = {2}", 1, 2, 3)
16
+ * // Returns: "1 + 2 = 3"
17
+ */
18
+ export declare const stringFormat: (template: string, ...args: (string | number | boolean | null | undefined)[]) => string;
3
19
  export declare const createTimeSlotId: (startTime: Date, endTime: Date, maxTimeOptions?: number) => string;
4
20
  export declare function hasSameByField<T>(field: keyof T, arr: T[]): boolean;
5
21
  export declare function getMinValueObjects<T>(key: keyof T, arr: T[]): T[];
@@ -21,3 +37,20 @@ export declare const resolveDifferentMinOrderPriceOptionByFulfillmentInfo: (fulf
21
37
  export declare const resolveSameMinOrderPriceOptionByFulfillmentInfo: (fulfillmentInfo: operationsSDK.FulfillmentInfo[]) => operationsSDK.FulfillmentInfo;
22
38
  export declare const resolveFulfillmentInfo: (fulfillmentInfo: operationsSDK.FulfillmentInfo[]) => operationsSDK.FulfillmentInfo | undefined;
23
39
  export declare const processFulfillmentTimeSlotByOperationList: (operationTimeSlot: operationsSDK.TimeSlotForOperation) => [string, TimeSlot[] | undefined];
40
+ export declare function getFastestTimeOptions(arr: FulfillmentDetails[]): FulfillmentDetails[];
41
+ export declare function getSlowestTimeOption(arr: FulfillmentDetails[]): FulfillmentDetails;
42
+ export declare const createTimeRange: (arr: FulfillmentDetails[]) => {
43
+ maxTimeOptions: number | undefined;
44
+ durationRangeOptions?: undefined;
45
+ } | {
46
+ durationRangeOptions: {
47
+ minDuration: number | undefined;
48
+ maxDuration: number | undefined;
49
+ };
50
+ maxTimeOptions?: undefined;
51
+ };
52
+ export declare const processFulfillments: (fulfillments: fulfillemtMethodsSDK.FulfillmentMethod[], timeSlots: TimeSlot[], operation: Operation) => Promise<{
53
+ dispatchesInfo: DispatchesInfo;
54
+ isPickupConfigured: boolean;
55
+ isDeliveryConfigured: boolean;
56
+ } | undefined>;
@@ -1,5 +1,26 @@
1
1
  import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
2
- import { DispatchType } from '../types/fulfillments-types.js';
2
+ import { DispatchType, } from '../types/fulfillments-types.js';
3
+ import { EntitiesDayOfWeek as DayOfWeek, } from '@wix/auto_sdk_restaurants_fulfillment-methods';
4
+ import { FulfillmentTimeType, } from '@wix/auto_sdk_restaurants_operations';
5
+ /**
6
+ * Format a string with placeholders like {0}, {1}, etc.
7
+ * Similar to C#'s String.Format
8
+ *
9
+ * @example
10
+ * stringFormat("Hello {0}, you have {1} messages", "John", 5)
11
+ * // Returns: "Hello John, you have 5 messages"
12
+ *
13
+ * @example
14
+ * stringFormat("{0} + {1} = {2}", 1, 2, 3)
15
+ * // Returns: "1 + 2 = 3"
16
+ */
17
+ export const stringFormat = (template, ...args) => {
18
+ return template.replace(/{(\d+)}/g, (match, index) => {
19
+ const argIndex = parseInt(index, 10);
20
+ const arg = args[argIndex];
21
+ return arg !== undefined && arg !== null ? String(arg) : match;
22
+ });
23
+ };
3
24
  export const createTimeSlotId = (startTime, endTime, maxTimeOptions = 0) =>
4
25
  // maxTimeOptions is used to avoid collisions when order pacing is enabled
5
26
  `${startTime?.toUTCString()}-${endTime?.toUTCString()}-${maxTimeOptions}`;
@@ -142,3 +163,190 @@ export const processFulfillmentTimeSlotByOperationList = (operationTimeSlot) =>
142
163
  }),
143
164
  ];
144
165
  };
166
+ const DAY_OF_WEEK = {
167
+ [DayOfWeek.MON]: 1,
168
+ [DayOfWeek.TUE]: 2,
169
+ [DayOfWeek.WED]: 3,
170
+ [DayOfWeek.THU]: 4,
171
+ [DayOfWeek.FRI]: 5,
172
+ [DayOfWeek.SAT]: 6,
173
+ [DayOfWeek.SUN]: 7,
174
+ };
175
+ const convertTimeOfDayToDateTime = (time, timezone) => {
176
+ const now = new Date();
177
+ if (timezone) {
178
+ // Get today's date components in the specified timezone
179
+ const options = {
180
+ timeZone: timezone,
181
+ year: 'numeric',
182
+ month: '2-digit',
183
+ day: '2-digit',
184
+ };
185
+ const parts = new Intl.DateTimeFormat('en-US', options).formatToParts(now);
186
+ const year = parts.find((p) => p.type === 'year')?.value;
187
+ const month = parts.find((p) => p.type === 'month')?.value;
188
+ const day = parts.find((p) => p.type === 'day')?.value;
189
+ // Create an ISO string with the date in the timezone and the specified time
190
+ const hours = String(time.hours ?? 0).padStart(2, '0');
191
+ const minutes = String(time.minutes ?? 0).padStart(2, '0');
192
+ const isoString = `${year}-${month}-${day}T${hours}:${minutes}:00.000`;
193
+ // Convert to Date - this will be interpreted as local time
194
+ const localDate = new Date(isoString);
195
+ // Get what this time would be formatted as in the target timezone
196
+ const formattedInTz = new Date(localDate.toLocaleString('en-US', { timeZone: timezone }));
197
+ // Calculate the difference and adjust
198
+ const offset = localDate.getTime() - formattedInTz.getTime();
199
+ return new Date(localDate.getTime() + offset);
200
+ }
201
+ // If no timezone specified, use local time
202
+ const date = new Date();
203
+ date.setHours(time.hours ?? 0, time.minutes ?? 0, 0, 0);
204
+ return date;
205
+ };
206
+ const isInTimeRange = (time, range, timeZone) => {
207
+ const startTime = range.startTime
208
+ ? convertTimeOfDayToDateTime(range.startTime, timeZone)
209
+ : time;
210
+ const endTime = range.endTime
211
+ ? convertTimeOfDayToDateTime(range.endTime, timeZone)
212
+ : time;
213
+ return (time.getTime() >= startTime.getTime() && time.getTime() <= endTime.getTime());
214
+ };
215
+ const isFulfillmentAvailable = (fulfillment, operation) => {
216
+ const { availability: { timeZone, availableTimes } = {} } = fulfillment;
217
+ const { asapOptions: { maxInMinutes, rangeInMinutes } = {} } = operation;
218
+ const deliveryTime = fulfillment.deliveryOptions?.deliveryTimeInMinutes ?? 0;
219
+ const zone = timeZone;
220
+ const timeNow = new Date();
221
+ timeNow.setMinutes(timeNow.getMinutes() +
222
+ (maxInMinutes ?? rangeInMinutes?.min ?? 0) +
223
+ deliveryTime);
224
+ // getDay() returns 0 (Sunday) to 6 (Saturday), but we need 1 (Monday) to 7 (Sunday)
225
+ const currentDayOfWeek = timeNow.getDay() === 0 ? 7 : timeNow.getDay();
226
+ return !!availableTimes?.some((availableTime) => {
227
+ const { dayOfWeek, timeRanges } = availableTime;
228
+ const dayOfWeekNumber = DAY_OF_WEEK[dayOfWeek];
229
+ const isAvailable = dayOfWeekNumber === currentDayOfWeek &&
230
+ !!timeRanges?.some((range) => isInTimeRange(timeNow, range, zone));
231
+ return isAvailable;
232
+ });
233
+ };
234
+ const isAddressValid = (address) => {
235
+ return (address.country && (address.geocode?.latitude || address.geocode?.longitude));
236
+ };
237
+ const getConsistentValue = (values) => {
238
+ return values.length === 0 || values.some((value) => value !== values[0])
239
+ ? undefined
240
+ : (values[0] ?? undefined);
241
+ };
242
+ export function getFastestTimeOptions(arr) {
243
+ return arr.slice(1).reduce((minArr, item) => {
244
+ const { durationRangeOptions, maxTimeOptions, fulfillmentTimeType } = item;
245
+ const min = minArr[0];
246
+ const minTime = min?.fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE
247
+ ? Number(min?.durationRangeOptions?.minDuration)
248
+ : Number(min?.maxTimeOptions);
249
+ const currTime = fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE
250
+ ? Number(durationRangeOptions.minDuration)
251
+ : Number(maxTimeOptions);
252
+ if (currTime < minTime) {
253
+ return [item];
254
+ }
255
+ else if (currTime === minTime) {
256
+ return [...minArr, item];
257
+ }
258
+ return minArr;
259
+ }, (arr[0] ? [arr[0]] : []));
260
+ }
261
+ export function getSlowestTimeOption(arr) {
262
+ return arr.reduce((max, item) => {
263
+ const { durationRangeOptions, maxTimeOptions, fulfillmentTimeType } = item;
264
+ let maxTime;
265
+ if (max.fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE) {
266
+ maxTime = Number(max.durationRangeOptions?.maxDuration || 0);
267
+ }
268
+ else {
269
+ maxTime = Number(max.maxTimeOptions || 0);
270
+ }
271
+ if (durationRangeOptions &&
272
+ fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE) {
273
+ return Number(durationRangeOptions.maxDuration) > maxTime ? item : max;
274
+ }
275
+ else {
276
+ return Number(maxTimeOptions) > maxTime ? item : max;
277
+ }
278
+ }, {});
279
+ }
280
+ export const createTimeRange = (arr) => {
281
+ const fastestOption = getFastestTimeOptions(arr)[0];
282
+ const slowestOption = getSlowestTimeOption(arr);
283
+ const minDuration = fastestOption?.durationRangeOptions?.minDuration ??
284
+ fastestOption?.maxTimeOptions;
285
+ const maxDuration = slowestOption.durationRangeOptions?.maxDuration ??
286
+ slowestOption.maxTimeOptions;
287
+ if (minDuration === maxDuration) {
288
+ return { maxTimeOptions: minDuration };
289
+ }
290
+ else {
291
+ return { durationRangeOptions: { minDuration, maxDuration } };
292
+ }
293
+ };
294
+ export const processFulfillments = async (fulfillments, timeSlots, operation) => {
295
+ const shouldConsiderAvailability = timeSlots.length > 0;
296
+ try {
297
+ console.log('fulfillments :>>', fulfillments);
298
+ const pickupFulfillment = fulfillments.find((fulfillment) => fulfillment.type === 'PICKUP' && fulfillment.enabled);
299
+ let deliveryFulfillments = fulfillments.filter((fulfillment) => fulfillment.type === 'DELIVERY' && fulfillment.enabled);
300
+ const isPickupConfigured = !!pickupFulfillment;
301
+ const isDeliveryConfigured = deliveryFulfillments.length > 0;
302
+ if (shouldConsiderAvailability) {
303
+ deliveryFulfillments = deliveryFulfillments.filter((fulfillment) => isFulfillmentAvailable(fulfillment, operation));
304
+ }
305
+ const dispatchesInfo = {};
306
+ const locationAddress = operation.locationDetails?.address &&
307
+ isAddressValid(operation.locationDetails?.address)
308
+ ? operation.locationDetails.address
309
+ : undefined;
310
+ if (isPickupConfigured) {
311
+ const pickupAddress = (locationAddress ?? pickupFulfillment?.pickupOptions?.address)
312
+ ? pickupFulfillment.pickupOptions?.address
313
+ : undefined;
314
+ dispatchesInfo[DispatchType.PICKUP] = {
315
+ address: pickupAddress,
316
+ minOrder: pickupFulfillment?.minOrderPrice ?? undefined,
317
+ };
318
+ }
319
+ if (isDeliveryConfigured) {
320
+ const deliveryFee = getConsistentValue(deliveryFulfillments.map((f) => f.fee));
321
+ const freeFulfillmentPriceThreshold = getConsistentValue(deliveryFulfillments.map((f) => f.deliveryOptions?.freeDeliveryThreshold));
322
+ const minOrder = getConsistentValue(deliveryFulfillments.map((f) => f.minOrderPrice));
323
+ const { asapOptions: { maxInMinutes, rangeInMinutes } = {} } = operation;
324
+ const fulfillmentDetails = deliveryFulfillments.map((f) => {
325
+ const deliveryTime = f.deliveryOptions?.deliveryTimeInMinutes ?? 0;
326
+ const timeObject = typeof maxInMinutes === 'number'
327
+ ? {
328
+ maxTimeOptions: deliveryTime + maxInMinutes,
329
+ FulfillmentTimeType: FulfillmentTimeType.MAX_TIME,
330
+ }
331
+ : {
332
+ durationRangeOptions: {
333
+ minDuration: deliveryTime + (rangeInMinutes?.min ?? 0),
334
+ maxDuration: deliveryTime + (rangeInMinutes?.max ?? 0),
335
+ },
336
+ fulfillmentTimeType: FulfillmentTimeType.DURATION_RANGE,
337
+ };
338
+ return timeObject;
339
+ });
340
+ dispatchesInfo[DispatchType.DELIVERY] = {
341
+ minOrder,
342
+ deliveryFee,
343
+ freeFulfillmentPriceThreshold,
344
+ ...((typeof maxInMinutes === 'number' || !!rangeInMinutes) &&
345
+ fulfillmentDetails.length > 0 &&
346
+ createTimeRange(fulfillmentDetails)),
347
+ };
348
+ }
349
+ return { dispatchesInfo, isPickupConfigured, isDeliveryConfigured };
350
+ }
351
+ catch (error) { }
352
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-restaurants-olo",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -76,5 +76,5 @@
76
76
  "groupId": "com.wixpress.headless-components"
77
77
  }
78
78
  },
79
- "falconPackageHash": "e71f0f800480748c71d5007c1a290b5edc146c7d3f120e8321035555"
79
+ "falconPackageHash": "f46ff1fc3540537ffc9adfd80908d7c385237f41fc72716179e2d2e5"
80
80
  }