Appearance
Scheduled Orders
Overview
Scheduled orders let customers place online orders for a future pickup or delivery time instead of for the current time (ASAP).
Key Purpose: Enable advance ordering for future times.
Purpose
Scheduling lets customers choose a future date and time for pickup or delivery. Available timeslots are calculated per day from the location's business hours (or a custom availability schedule) and, when order-capacity management is enabled, filtered against capacity limits.
Key Concepts
- Timeslots: Generated per day from business hours (or the custom availability schedule). Slot length is
order_capacity.time_slot_duration_min(default15minutes; if capacity is disabled the calculation falls back to 30-minute slots). When capacity is enabled, slots are filtered against capacity limits. - Per-channel scheduling: Takeout (pickup), delivery, and eat-in (online "For Here") each have independent
enabledtoggles and adays_in_advancelimit (validated asnumeric|max:365; the back-office field clamps the entry to 0–365). - Online eat-in vs. table-QR dine-in: "Eat in" is an opt-in online-ordering option (
eat_in.enabled) where a customer orders ahead on the storefront and eats in at a chosen future time — it behaves like online pickup (timeslot picker, future-days scheduling, shares online-ordering order capacity) but the order is recorded as dine-in (For Here) for POS/VAT purposes and follows the location's business hours (no cut-offs, no blocked periods). This is distinct from table-QR ordering (scanning a QR code at a table), which is immediate/ASAP only and never goes through scheduling or capacity checks. - Capacity checking: When order capacity is enabled, slot availability is checked at order submission for online pickup/delivery. Transaction updates that book a slot use a cache lock with retry (up to 3 attempts, 15-second lock TTL per attempt, 200 ms between retries).
- Blocked Times: A merchant-configurable list of recurring weekly windows (
blocked_periods) during which pickup/delivery slots are hidden from the picker, even during otherwise-open business hours (e.g. a daily kitchen rush). Applies to pickup and delivery only, not eat-in. - Send to POS timing:
send_to_poscontrols whether an order is sent to the POS/KDS when it is placed (order_placed) or at the scheduled pickup/delivery time (pickup_delivery_time). - Custom scheduled orders: An advanced toggle (
custom_scheduled_orders) that exposeshours_in_advanceandminutes_before_pickup_delivery_timethresholds for finer control over when scheduled orders are sent.
Actions
Enable Scheduled Pickup Orders
Toggle schedule_order_for_future_days.takeout.enabled and set schedule_order_for_future_days.takeout.days_in_advance to allow customers to schedule pickup orders.
Enable Scheduled Delivery Orders
Toggle schedule_order_for_future_days.delivery.enabled and set schedule_order_for_future_days.delivery.days_in_advance independently from pickup scheduling.
Enable Scheduled Eat-In Orders
Toggle schedule_order_for_future_days.eat_in.enabled and set schedule_order_for_future_days.eat_in.days_in_advance independently from pickup/delivery scheduling. Only shown/relevant when online eat-in ("For Here") is itself enabled (eat_in.enabled, configured on the main Online Ordering settings).
Configure Pickup Last Slot
Toggle Last pickup slot at closing time (takeout.last_slot_at_closing_time, default on) to control how close to closing the last pickup slot is. On: the last pickup slot is exactly at closing time. Off: the last pickup slot is one preparation time before closing. This setting only affects pickup — delivery always reserves preparation time before closing. It replaced the old two-option "Accept Orders Until" selector, which is no longer shown in the back office.
Configure Blocked Times
On the Blocked times card, click Add blocked time to add a recurring weekly window (blocked_periods) during which pickup/delivery slots are hidden, then pick the time slot and toggle the days of the week it's active on. Click the trash icon to remove a blocked period. Requires order_capacity.time_slot_duration_min to be configured first (the time-slot picker is disabled otherwise). Affects pickup and delivery only, not eat-in.
Enable Custom Availability
Toggle custom_availability to define a specific weekly schedule for when scheduled orders are accepted, instead of using the location's business hours.
Configure Send to POS Behavior
On the Receiving Orders form, set send_to_pos to pickup_delivery_time (send near the scheduled time) or order_placed (send immediately). Optionally toggle custom_scheduled_orders to set the hours_in_advance and minutes_before_pickup_delivery_time thresholds.
Location
- Back-office route:
/online/online-ordering(nav: Online → Online Ordering) - Scheduling fields (
custom_availability,takeout.last_slot_at_closing_time,blocked_periods,schedule_order_for_future_days.*):src/views/online/online-ordering/forms/FullfilmentAndScheduling.vue - Send-to-POS / custom-scheduled-orders fields (
send_to_pos,custom_scheduled_orders,hours_in_advance,minutes_before_pickup_delivery_time):src/views/online/online-ordering/forms/ReceivingOrders.vue
All settings are persisted via StoreOnlineOrderingRequest.
Fields
Takeout (Pickup) Scheduling Enabled
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.takeout.enabled |
| Type | Toggle (boolean) |
| Default | true |
| Validation | `required |
Description: Allow customers to schedule pickup orders.
Takeout Days in Advance
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.takeout.days_in_advance |
| Type | Number |
| Default | 30 |
| Min | 0 (back-office field clamp) |
| Max | 365 |
| Validation | `required |
Description: How many days ahead customers can schedule pickup.
Delivery Scheduling Enabled
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.delivery.enabled |
| Type | Toggle (boolean) |
| Default | false |
| Validation | `required |
Description: Allow customers to schedule delivery orders.
Delivery Days in Advance
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.delivery.days_in_advance |
| Type | Number |
| Default | 30 |
| Min | 0 (back-office field clamp) |
| Max | 365 |
| Validation | `required |
Description: How many days ahead customers can schedule delivery.
Eat-In Scheduling Enabled
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.eat_in.enabled |
| Type | Toggle (boolean) |
| Default | false |
| Validation | `nullable |
Description: Allow customers to schedule online eat-in ("For Here") orders for a future time. Only shown in the back office when eat-in itself is enabled (eat_in.enabled).
Eat-In Days in Advance
| Property | Value |
|---|---|
| Field ID | schedule_order_for_future_days.eat_in.days_in_advance |
| Type | Number |
| Default | 30 |
| Min | 0 (back-office field clamp) |
| Max | 365 |
| Validation | `required |
Description: How many days ahead customers can schedule an online eat-in order.
Pickup Last Slot at Closing Time
| Property | Value |
|---|---|
| Field ID | takeout.last_slot_at_closing_time |
| Type | Toggle (boolean) |
| Default | true |
| Validation | `sometimes |
| UI Label | "Last pickup slot at closing time" |
Description: When on (default), the last pickup slot is exactly the closing time. When off, the last pickup slot is one preparation time before closing. Pickup-only — delivery always reserves preparation time before closing. This replaced the old two-option accept_orders_until ("Accept Orders Until") selector, which is no longer shown in the back office. accept_orders_until is still present in the save payload/backend defaults for backward compatibility, but merchants cannot edit it — the "Accept Orders Until" UI has been fully retired.
Blocked Times
| Property | Value |
|---|---|
| Field ID | blocked_periods |
| Type | Array of { from, to, active_days } |
| Default | [] (empty) |
| Validation | `sometimes |
Description: A list of recurring weekly windows during which pickup/delivery slots are hidden from the storefront picker (e.g. a daily kitchen rush), even though the location is otherwise open. Each entry has a start time, an end time, and the weekdays it's active on. The back-office time-slot picker for a blocked period is generated from order_capacity.time_slot_duration_min, so slot duration must be configured before a blocked time can be added.
Business Logic:
- Applies to pickup and delivery only — eat-in follows business hours and is never gated by blocked periods.
- Matched against the customer-facing pickup/delivery time (kitchen prep time + delay), not the earlier kitchen-start time, using a half-open interval
[from, to). Location::isWithinBlockedPeriod()is the single source of truth, used consistently by order-placement validation, the post-capacity re-check, and the storefront slot filter — so a slot inside a blocked window is rejected everywhere, not just hidden from the picker.- When order splitting reschedules part of an order into a later slot, the reschedule search also skips blocked windows.
Custom Availability
| Property | Value |
|---|---|
| Field ID | custom_availability |
| Type | Toggle (boolean) |
| Default | false |
| Validation | `required |
Description: When on, a custom weekly schedule is used instead of the location's business hours.
Send to POS
| Property | Value |
|---|---|
| Field ID | send_to_pos |
| Type | Radio (enum SendToPosOptions) |
| Options | pickup_delivery_time, order_placed |
| Default | pickup_delivery_time |
| Validation | required + enum |
Description: Whether scheduled orders are sent to the POS/KDS at the scheduled pickup/delivery time or immediately when placed.
Custom Scheduled Orders
| Property | Value |
|---|---|
| Field ID | custom_scheduled_orders |
| Type | Toggle (boolean) |
| Default | false |
| Validation | `required |
Description: When on, exposes the hours_in_advance and minutes_before_pickup_delivery_time thresholds.
Hours in Advance
| Property | Value |
|---|---|
| Field ID | hours_in_advance |
| Type | Number |
| Validation | `required |
Description: Threshold (in hours) used when custom_scheduled_orders is enabled.
Minutes Before Pickup/Delivery Time
| Property | Value |
|---|---|
| Field ID | minutes_before_pickup_delivery_time |
| Type | Number |
| Validation | `required |
Description: Threshold (in minutes) used when custom_scheduled_orders is enabled.
Business Logic
Timeslot Generation
Customer selects a date for a dining option (pickup / delivery / eat-in)
│
▼
Eat-in (For Here)?
├── Yes → use the location's business hours (no cut-off checks, no blocked periods)
└── No (pickup/delivery) → custom_availability enabled?
├── Yes → use the custom weekly schedule
└── No → use the location's online-ordering business hours
then: not past same/next-day cutoff?
│
▼
Day has available hours (and, for pickup/delivery, not past cutoff)?
├── No → return no slots
└── Yes → generate slots every time_slot_duration_min minutes
│
▼
Capacity (order_capacity) enabled?
├── No → all generated slots returned as available
└── Yes → batch-check each slot against capacity limits
(eat-in shares the same online-ordering order-capacity limits as pickup/delivery)
│
▼
Pickup/delivery only: mark slots inside a blocked_periods window as unavailable
│
▼
Filter out past slots (for today) and full slots
│
▼
Return available timeslots (cached 60 s per location/channel/dining-option/day)Order Processing
Scheduled order placed (order_date holds the chosen time)
│
▼
send_to_pos setting?
├── pickup_delivery_time → sent near the scheduled time
└── order_placed → sent immediately on placementBusiness Rules
- Past timeslots are filtered out for today's date; when capacity is enabled, full slots are excluded.
- A day with no available business hours, or a day already past its same-day/next-day cutoff (pickup/delivery only), returns no slots.
- Order capacity applies to online-ordering pickup, delivery, and online eat-in (customer orders ahead on the storefront for a future dine-in time — dining option
For Here). Orders on other channels/flows (e.g. Kiosk, In-house, and table-QR ordering — a customer scanning a QR code at their table) are immediate/ASAP only and never go through scheduling or order-capacity checks. - Online eat-in has its own future-days scheduling window (
schedule_order_for_future_days.eat_in), independent from takeout/delivery, but always follows the location's business hours (not a custom availability schedule) and is exempt from same/next-day cut-offs and Blocked Times. - Blocked Times (
blocked_periods) hide pickup/delivery slots inside merchant-defined recurring weekly windows, even during otherwise-open hours. They apply to pickup and delivery only, are matched on the customer-facing pickup/delivery time (not the kitchen start time), and are enforced consistently at slot listing, order placement, and any capacity-driven reschedule/split. - Booking a slot uses a cache lock with retry (up to 3 attempts, 15-second lock TTL per attempt, 200 ms between retries) to avoid double-booking under concurrency.
- When capacity is enabled, the prep time used for slot checking is
max(location average prep time, largest individual item prep time) + the channel delay(delivery.delay,takeout.delayoreat_in.delay), clamped to 0-1440 minutes (24 hours). The item term is compared against the location average, not added to it, so it can only ever raise a quote above the location baseline, never lower it. The channel delay may be negative (a merchant can shorten the lead time with e.g.-10); it is added as-is and only the final result is clamped. The item term is also0today: per-item prep time is gated byONLINE_ORDERING_INCLUDE_ITEM_PREP_TIME, which defaults tofalseand is not set in production, staging or testing — so in practice the figure is the location average prep time plus the channel delay. The same figure is what the nearby-restaurants list advertises before the customer starts an order — one rule, one number. (Verified:app/RawModels/Location.php:2001-2018—getDiningOptionPrepTime(), the single definition, with the 1440 ceiling at:60;app/Services/OrderCapacity/OrderCapacityService.php:86-89delegates to it and:101-115supplies the item term;config/upvendo.phpline 26.) - The available-timeslots result is cached for 60 seconds, keyed per location, channel, dining option, day, and the prospective cart's prep time / value / quantity / category mix. It is busted immediately when a transaction books or changes a slot, and when the Online Settings page (order capacity) is saved. (Verified:
app/Services/OrderCapacity/OrderCapacityService.phplines 1789-1800 (60 s TTL and cache key), 267 and 489 (busted on booking);app/Services/BackOffice/OnlineSettingsService.phplines 106-113.) - Saving the scheduling fields on this page (Online Ordering,
PUT /back-office/online-ordering/{locationId}) does not bust the availability cache —custom_availability,blocked_periods,schedule_order_for_future_days.*, andtakeout.last_slot_at_closing_timecan take up to 60 seconds to show on the storefront. Only the Online Settings save clears it eagerly. (Verified:app/Services/BackOffice/OnlineOrderingService.php::update()lines 223-280 — saves and dispatchesReloadMenu, with noclearAvailableTimeslotsCachecall; contrastapp/Services/BackOffice/OnlineSettingsService.phplines 106-113.) - There is no
is_asap/asap_enabledfield. "ASAP" simply means no future time was chosen, so the order'sorder_dateis the current time. - The old two-option "Accept Orders Until" selector has been removed from the back office; pickup's closing-edge behavior is now controlled by the single "Last pickup slot at closing time" switch (
takeout.last_slot_at_closing_time).
FAQs
- "Can scheduling be enabled for delivery but not pickup?" Yes.
schedule_order_for_future_days.takeout,.delivery, and.eat_ineach have independentenabledanddays_in_advancesettings. - "What happens if a slot fills up while a customer is selecting it?" Capacity is re-checked at order submission under a cache lock. When order splitting is enabled, an order that doesn't fit one slot can be split across consecutive slots within the same day.
- "How often are available timeslots refreshed?" The result is cached for 60 seconds. It is busted immediately whenever a slot is booked or changed, and whenever Online Settings (order capacity) is saved — so capacity limit changes appear on the storefront straight away. Changes saved on the Online Ordering page itself (blocked times, custom availability, days-in-advance, last pickup slot) do not bust the cache and can take up to 60 seconds to appear. (Verified:
app/Services/BackOffice/OnlineSettingsService.phplines 106-113;app/Services/BackOffice/OnlineOrderingService.php::update()lines 223-280 has no cache clear.) - "Where did the 'Accept Orders Until' setting go?" It was replaced by the "Last pickup slot at closing time" switch (
takeout.last_slot_at_closing_time). On (default): the last pickup slot is exactly at closing time. Off: the last pickup slot is one preparation time before closing. Delivery is unaffected. - "How do I block off a time window so customers can't order during our lunch rush?" On the Online Ordering page, go to the Blocked times card under Fulfillment & Scheduling and add a blocked time: pick a start slot and toggle which days of the week it applies to. It hides pickup/delivery slots inside that window every week it's active. It doesn't affect eat-in.
- "Can I schedule an eat-in (dine-in) order for later today or another day?" Only if online eat-in ("For Here") is enabled and
schedule_order_for_future_days.eat_in.enabledis on. Eat-in shares the same order-capacity slot filtering as pickup/delivery but always follows business hours (not cut-offs or Blocked Times). Note this is different from scanning a table QR code, which is always immediate/ASAP. - "When is a scheduled order sent to the POS/KDS?" It depends on
send_to_pos:pickup_delivery_timesends it near the scheduled time;order_placedsends it immediately.custom_scheduled_ordersaddshours_in_advance/minutes_before_pickup_delivery_timecontrol.
Troubleshooting
- No slots available for a day → Check the location has online-ordering hours (or a custom availability schedule) for that day, the day isn't past its same/next-day cutoff (pickup/delivery), and that order-capacity limits or Blocked Times aren't hiding every slot.
- A specific time window never shows slots, even though the location is open → Check the Blocked times card — a recurring
blocked_periodswindow active on that weekday hides pickup/delivery slots inside it. Remove or edit the blocked period. - Scheduled order not appearing on the POS/KDS → Check
send_to_pos. Withpickup_delivery_timethe order is sent near the scheduled time; withorder_placedit is sent immediately on placement. - Customer sees a "slot full" error → The slot reached its capacity limit. Increase the limit in Order Capacity settings, or pick another time.
- Days-in-advance not working → Verify scheduling is enabled for the correct channel (
takeout,delivery, oreat_in) and thatdays_in_advanceis greater than 0. - Can't add a Blocked Time / the time-slot dropdown is disabled →
order_capacity.time_slot_duration_minmust be configured first; the Blocked Times picker is derived from it. - A scheduling change (blocked time, custom availability, days-in-advance, last pickup slot) isn't showing on the storefront yet → Saving the Online Ordering page does not clear the availability cache, so wait up to 60 seconds and reload. If it still doesn't show after that, the change was not saved or another rule (business hours, cut-off, capacity limits) is hiding the slots. (Verified:
app/Services/BackOffice/OnlineOrderingService.php::update()lines 223-280; 60 s TTL atapp/Services/OrderCapacity/OrderCapacityService.phpline 1800.)
Customer Impact
Storefront and kiosk behaviour below is not verified against code here (source of truth for this doc is the back-office settings + backend validation/capacity logic).
Online Ordering (storefront — not verified here)
- Date picker for future dates
- Timeslot selection showing only available slots
- Confirmation reflects the chosen time
Kiosk (not verified here)
- ASAP vs scheduled selection where applicable
Assistant Guidance
When answering questions about this feature:
- "Accept Orders Until" no longer exists as a UI control — do not tell merchants to look for it. Point them to the Last pickup slot at closing time switch (
takeout.last_slot_at_closing_time) instead; it only affects pickup, not delivery. - Do not conflate online eat-in (customer orders ahead on the storefront for a future dine-in time, dining option
For Here) with table-QR ordering (scanning a QR code at a table). Table-QR orders are always immediate/ASAP and never use scheduling, days-in-advance, or order-capacity checks. Online eat-in does use scheduling and shares order capacity with pickup/delivery. - Blocked Times (
blocked_periods) only apply to pickup and delivery, never to eat-in. If a merchant asks why an eat-in time slot doesn't respect a blocked window, that's expected — eat-in follows business hours only. - When a merchant asks why "no slots are available," check in this order: (1) location has online-ordering hours or a custom availability schedule for that day, (2) not past the same/next-day cutoff (pickup/delivery only), (3) no Blocked Times window covers the whole open period, (4) order-capacity limits aren't exhausted.
- Mention that each of takeout, delivery, and eat-in has its own independent
enabled+days_in_advancesetting — changing one does not affect the others. - Be precise about propagation delay, because the two settings pages behave differently. Order capacity changes (Online Settings page) clear the availability cache on save and show up on the storefront immediately. Scheduling changes made on the Online Ordering page (blocked times, custom availability, days-in-advance, last pickup slot) do not clear it and can take up to 60 seconds. Do not tell a merchant that capacity changes take up to a minute — that has not been true since the settings-save cache invalidation landed.
Relations
Depends On
- Business Hours: Source of available times when custom availability is off, and always the source for eat-in
- Order Capacity: Slot duration (
time_slot_duration_min), capacity limits, and the timeslot picker used to configure Blocked Times - Restricted Dates: Blocked dates
Affects
- Transactions: Chosen time stored in
order_date; dining optionFor Hereon online eat-in orders - KDS / POS: Order send timing (
send_to_pos)
Related Features
Examples
Pickup Scheduling Enabled, Delivery Disabled
json
{
"schedule_order_for_future_days": {
"takeout": {
"enabled": true,
"days_in_advance": 30
},
"delivery": {
"enabled": false,
"days_in_advance": 30
}
}
}Both Channels Enabled, Different Limits
json
{
"schedule_order_for_future_days": {
"takeout": {
"enabled": true,
"days_in_advance": 7
},
"delivery": {
"enabled": true,
"days_in_advance": 3
}
}
}Eat-In Scheduling Enabled
json
{
"eat_in": {
"enabled": true,
"delay": 0
},
"schedule_order_for_future_days": {
"eat_in": {
"enabled": true,
"days_in_advance": 14
}
}
}Pickup Last Slot and Blocked Times
json
{
"takeout": {
"last_slot_at_closing_time": false
},
"blocked_periods": [
{
"from": "14:30",
"to": "16:00",
"active_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
]
}Send-to-POS and Custom Scheduled Orders
json
{
"send_to_pos": "pickup_delivery_time",
"custom_scheduled_orders": true,
"hours_in_advance": 2,
"minutes_before_pickup_delivery_time": 30
}Available Timeslots Response
The available-timeslots result is an object keyed by slot start time (H:i); each value describes that slot.
json
{
"12:00": {
"start": "12:00",
"end": "12:15",
"duration_minutes": 15,
"available": true
},
"12:15": {
"start": "12:15",
"end": "12:30",
"duration_minutes": 15,
"available": false
}
}When order capacity is disabled, every generated slot is returned with "available": true and an informational "capacity_disabled": true flag.