Skip to content

Business Hours

Overview

Business Hours (labelled Operating hours in the back office) define which days and times a location is open. They are part of the Location configuration, not a standalone screen, and control when customers can place orders.

Key Purpose: Define the weekly operating schedule for each location.

Purpose

The Operating hours section of the location form lets you tick which days the location is open and set one or more time ranges per open day. These hours are stored on the location as business_hours and are used to decide whether a location is open at a given time.

Key Concepts

  • Per-day schedule: business_hours is keyed by full, capitalized weekday names (Monday through Sunday). Each day has an is_available flag and a times array.
  • Time ranges: Each day's times is an array of { "from": "HH:MM", "to": "HH:MM" } pairs. A day can have more than one range (split shifts, e.g. lunch and dinner).
  • 24-hour time: Times are stored and validated in 24-hour H:i format (e.g. 09:00, 17:30, 22:00).
  • Day toggle: Unticking a day sets is_available to false and clears that day's times.
  • Timezone awareness: Open/closed checks run in the location's stored timezone, falling back to Europe/Brussels when that field is empty. The stored value is derived at save time from the map pin (or, if that lookup fails, from the postal code) — it is not resolved again at check time.
  • Channel availability is separate: Online ordering and QR ordering each have their own optional "custom availability" schedule. When custom availability is off, they fall back to these business hours (see Channel Availability below).

Actions

Set the Weekly Schedule

Tick the days the location is open and add one or more time ranges per open day under Operating hours.

Apply Hours to All Days (Select All)

Use the Select All control to copy one schedule (availability + time ranges) to every day at once.

Add Split Shifts

Use Add Times on a day to add another time range within that day (e.g. 11:0014:30 and 17:0022:00) to represent a break between service periods.

Location

  • Backoffice Route: /settings/locations/:id — Operating hours is a section within the location form (use new as the id when adding a location). There is no dedicated business-hours URL.
  • Backend Model: app/RawModels/Location.php (getBusinessHours())
  • Validation: app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php
  • Vue Component: src/views/settings/Locations/forms/LocationBusinessHours.vue, which wraps the shared src/components/forms/OperatingHours.vue

Concepts

Regular Business Hours

A weekly schedule (Monday–Sunday). Each day is either open (is_available: true) with one or more time ranges, or closed (is_available: false).

Channel Availability

Online ordering and QR ordering can run on a different schedule from the main business hours, but this is configured on their own settings, not in the Operating hours section:

  • Online ordering (pickup and delivery): uses the online-ordering "custom availability" schedule when enabled, otherwise falls back to business hours.
  • QR ordering (for-here / table ordering): uses the QR-ordering "custom availability" schedule when enabled, otherwise falls back to business hours.

There are no separate per-day "pickup hours" or "delivery hours" fields — pickup and delivery both use the online-ordering schedule.

Restricted Dates

Restricted dates close the location on specific dates regardless of the weekly schedule. They are configured in their own section of the location form (see Locations / Restricted Dates).


Fields

The business_hours object is keyed by day name. Each day looks like:

json
{
  "Monday": {
    "is_available": true,
    "times": [
      { "from": "11:00", "to": "14:30" },
      { "from": "17:00", "to": "22:00" }
    ]
  }
}

Day Availability

PropertyValue
Field IDbusiness_hours.{Day}.is_available
LabelDay checkbox (Monday … Sunday)
TypeBoolean (checkbox)
RequiredYes (per day)

Description: Whether the location is open on this day. Days are the capitalized full names Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday.

Notes:

  • Unticking a day clears its times.
  • The form requires at least one open day with a valid time range before the location can be saved.

Time Ranges

PropertyValue
Field IDbusiness_hours.{Day}.times
LabelTime ranges (per day)
TypeArray of { from, to } objects
RequiredRequired when the day is is_available: true

Description: One or more opening periods for the day. Each entry has a from and a to. A day may have multiple ranges to represent split shifts.

Notes:

  • Both from and to are required when the day is open.
  • There is no configured limit on the number of ranges per day.

From / To

PropertyValue
Field IDbusiness_hours.{Day}.times[].from / business_hours.{Day}.times[].to
Label(from) … to … (to)
TypeTime
RequiredRequired when the day is open
Validationdate_format:H:i (24-hour, e.g. 08:00)

Description: Opening (from) and closing (to) time for a range.

Examples:

  • 09:00
  • 17:30
  • 22:00

Note: The field-level validation only checks that each value is a valid 24-hour H:i time, but the save path adds two further rules on top of it:

  • to must be strictly after from. Equal or reversed times are rejected with "There is an invalid time range".
  • Ranges within the same day must not overlap. An overlap is rejected with "There is a time overlap in the business hours".

Both run on create and update, for every day that is ticked open. There is still no limit on the number of ranges per day, and the open/closed check compares the times as plain strings, so a range whose to wraps past midnight is not handled specially.


Channel Availability

These are configured outside the Operating hours section and are documented here only to clarify how business hours interact with ordering channels. The exact UI for these schedules is part of the Online Ordering / QR ordering settings.

  • Online ordering schedule: If online-ordering custom availability is enabled, online ordering uses that schedule; otherwise it uses the location's business hours.
  • Pickup and delivery: Both follow the online-ordering schedule above. There are no independent pickup-only or delivery-only weekly schedules.
  • Eat-in (For Here, online): The open/close gate always uses the location's business hours — it never uses a QR or custom-availability schedule.
  • QR ordering schedule: A QR custom-availability schedule affects only which hours are displayed to customers; QR/table ordering itself is immediate and is not gated by this schedule (it never reaches the open/close check).
  • Show location hours only (show_location_hours_only, default off, in Online Ordering settings): a display-only toggle. When enabled, the storefront hides the separate online-ordering / QR-ordering hours column and shows only the location's business hours. It does not change actual ordering availability.

Business Logic

Open / Closed Check

When checking whether a location is open at a requested time (checkForBusinessHours):

Resolve the requested time in the location's timezone


Pick the schedule for the dining option:
  - Pickup / Delivery → online-ordering custom-availability schedule (or business hours if it's off)
  - Eat-in / For Here (online) → the location's business hours (never a QR schedule)
  - Otherwise → business hours
  (QR table ordering is immediate and never reaches this gate.)


Reject if the location is temporarily closed until a later date
Reject if the date is a restricted date
Reject if a same-day / next-day cutoff has passed (pickup / delivery only)
Reject if the date is beyond that channel's "schedule for future days" window
  (delivery / takeout / eat-in) — when that channel's future scheduling is off,
  the window collapses to 0 days and today is the only orderable date
Reject if the time falls within a merchant-configured blocked time period (pickup / delivery only)


Is that weekday is_available?
├── No → CLOSED
└── Yes → Is the requested time within any "from"–"to" range?
            ├── Yes → OPEN
            └── No  → CLOSED

The time comparison is time >= from && time <= to on the H:i strings.

Preparation Time

Preparation time is a separate Location setting (average_prep_time, default 20 minutes), not part of the business-hours schedule. The effective prep time per dining option is:

prep time = average_prep_time
          + delivery delay   (delivery orders)
          + takeout delay    (pickup orders)
          + eat_in delay     (online eat-in orders)

Those three dining options are the only ones that get a prep time, and only on the online-ordering channel. Every other channel or flow — kiosk, table-QR ordering — gets 0. Per-item prep times are not part of this either: that behaviour is gated off in every environment (see Menu Items). The delay may be negative, and only the final result is clamped, to 0-1440 minutes. (Verified: app/RawModels/Location.php:2001-2018getDiningOptionPrepTime(), the one place this is computed, with the 1440 ceiling at :60; the channel and table-QR exclusions at app/Services/OrderCapacity/OrderCapacityService.php:126-131.)

This affects the earliest time a customer can be served; it is not subtracted from the closing time as part of business hours.


Customer Impact

Online Ordering

ScenarioEffect
Day not availableThe location is treated as closed for that day
Time outside all rangesThe location is treated as closed at that time
Restricted dateThe location is closed for that date

Online Ordering — what the customer actually sees

StateConditionWhat the customer sees
No orderable date at allNeither pickup nor delivery has an orderable day inside its booking window (today + days-in-advance, honouring a temporary-closure reopening date) — typically today is closed and "schedule for future days" is offThe "Choose" date/time prompt is replaced by the closed banner "Not accepting orders"
Today unavailable, a later day is availableToday is closed (restricted date or a non-open weekday) but the future-days window reaches an open dayThe date auto-advances to the first orderable day, with the hint "Today is unavailable. The earliest order date is {date}." under the date field
Selected date past its cut-offThe same-day or next-day cut-off for the selected option has passedThe informational deadline hint is replaced by an amber alert "Same-day {option} ordering has closed (cut-off was {time}). Please choose another date." (or "Next-day …"), and Save is disabled
Table-QR past the accept-orders-until cut-offThe QR channel's Accept Orders Until is "Closing Time Minus Prep Time" and now falls inside (close − preparation time, close]QR checkout is blocked and the reason reads "QR ordering has closed for today". Past the actual closing time the ordinary closed handling takes over

Kiosk

Kiosk availability depends on kiosk/device configuration and is not driven solely by these business hours.


Relations

Depends On

  • Locations: Business hours are stored on the location (business_hours).
  • Timezone: Open/closed checks use the location's timezone.

Affects

  • Online Ordering: Falls back to business hours when no custom availability schedule is set.
  • QR Ordering: Falls back to business hours when no custom availability schedule is set.
  • Online-ordering readiness: A location counts as having business hours set when at least one day has a non-empty times array (part of the Ready / Partial / Incomplete badge on the Locations list).

Business Rules

  • Business hours are stored on the location as business_hours, keyed by capitalized day names MondaySunday.
  • Each day has a required is_available boolean. When a day is available, its times array (and each entry's from and to) is required.
  • from and to must be valid 24-hour H:i times, and to must be strictly after from — equal or reversed times are rejected on save with "There is an invalid time range". There is no limit on the number of ranges per day.
  • Time ranges within one day must not overlap; an overlap is rejected on save with "There is a time overlap in the business hours". The overlap test is strict, so back-to-back ranges are fine (11:0014:00 followed by 14:0022:00) while 11:0015:00 together with 14:0022:00 is rejected.
  • A requested date beyond the dining option's "schedule for future days" window (delivery / takeout / eat-in) is treated as closed. When that channel's future scheduling is off the window is 0 days, so only today is orderable.
  • Unticking a day clears that day's time ranges.
  • The location form requires at least one open day with a valid time range before saving.
  • Online ordering and QR ordering use their own custom-availability schedules when enabled, and otherwise fall back to these business hours. Pickup and delivery both follow the online-ordering schedule.
  • Open/closed checks are evaluated in the location's timezone and also respect restricted dates, the temporarily-closed-until date, and same-day / next-day cutoffs.

FAQs

  • "Where do I set my opening hours?" In Settings → Locations, open the location and go to the Operating hours section.
  • "Can I have a lunch break (two ranges in one day)?" Yes. Use Add Times to add a second range for that day, leaving a gap in between.
  • "Can I copy the same hours to every day?" Yes. Use Select All to apply one schedule to all days at once.
  • "What time format do I enter?" 24-hour HH:MM (e.g. 17:30), shown in the location's timezone.
  • "Do online ordering and delivery follow these hours?" By default yes. If you set a custom availability schedule for online ordering or QR ordering, that schedule is used instead. Pickup and delivery both follow the online-ordering schedule.
  • "How do I close for a holiday?" Use Restricted Dates on the location, not the weekly hours.

Troubleshooting

Problem: Location shows as closed during expected open hours

Causes:

  1. Wrong timezone on the location
  2. The day is not ticked (is_available false)
  3. The current time falls in a gap between time ranges
  4. A restricted date or temporarily-closed-until date applies
  5. A same-day / next-day cutoff has passed (pickup / delivery)
  6. A blocked time period is active for that time (pickup / delivery) — configured in Online Ordering settings, separate from the weekly business hours
  7. The date is beyond the dining option's "schedule for future days" window — with future scheduling off for that channel, only today is orderable

Solutions:

  1. Verify the location's timezone
  2. Tick the day in Operating hours
  3. Check the day's time ranges
  4. Check Restricted Dates and any temporary closure
  5. Check the online-ordering cutoff settings
  6. Check "Can customers schedule pickups & delivery for future days?" and its days-in-advance value for that channel

Problem: Can't save the location — operating hours error

Cause: No open day has a complete time range.

Solution: Tick at least one day and add a range with both a from and a to time.

Cause: Two ranges on the same day overlap, or a range's to is not later than its from (the error reads "There is a time overlap in the business hours" or "There is an invalid time range").

Solution: Make the ranges on that day disjoint, and make each to strictly later than its from.


Problem: Online ordering hours differ from what was set here

Cause: A custom availability schedule is enabled for online ordering or QR ordering, which overrides business hours for that channel.

Solution: Review the online-ordering / QR-ordering availability settings, or disable custom availability to fall back to business hours.


Examples

Standard Restaurant (Lunch & Dinner, closed Sunday)

json
{
  "Monday":    { "is_available": true,  "times": [{ "from": "11:30", "to": "14:30" }, { "from": "17:30", "to": "22:00" }] },
  "Tuesday":   { "is_available": true,  "times": [{ "from": "11:30", "to": "14:30" }, { "from": "17:30", "to": "22:00" }] },
  "Wednesday": { "is_available": true,  "times": [{ "from": "11:30", "to": "14:30" }, { "from": "17:30", "to": "22:00" }] },
  "Thursday":  { "is_available": true,  "times": [{ "from": "11:30", "to": "14:30" }, { "from": "17:30", "to": "22:00" }] },
  "Friday":    { "is_available": true,  "times": [{ "from": "11:30", "to": "14:30" }, { "from": "17:30", "to": "23:00" }] },
  "Saturday":  { "is_available": true,  "times": [{ "from": "17:00", "to": "23:00" }] },
  "Sunday":    { "is_available": false, "times": [] }
}

Continuous Hours

json
{
  "Monday":    { "is_available": true, "times": [{ "from": "10:00", "to": "22:00" }] },
  "Tuesday":   { "is_available": true, "times": [{ "from": "10:00", "to": "22:00" }] },
  "Wednesday": { "is_available": true, "times": [{ "from": "10:00", "to": "22:00" }] },
  "Thursday":  { "is_available": true, "times": [{ "from": "10:00", "to": "22:00" }] },
  "Friday":    { "is_available": true, "times": [{ "from": "10:00", "to": "23:00" }] },
  "Saturday":  { "is_available": true, "times": [{ "from": "10:00", "to": "23:00" }] },
  "Sunday":    { "is_available": true, "times": [{ "from": "11:00", "to": "21:00" }] }
}

Café (Breakfast & Lunch)

json
{
  "Monday":    { "is_available": true, "times": [{ "from": "07:00", "to": "16:00" }] },
  "Tuesday":   { "is_available": true, "times": [{ "from": "07:00", "to": "16:00" }] },
  "Wednesday": { "is_available": true, "times": [{ "from": "07:00", "to": "16:00" }] },
  "Thursday":  { "is_available": true, "times": [{ "from": "07:00", "to": "16:00" }] },
  "Friday":    { "is_available": true, "times": [{ "from": "07:00", "to": "16:00" }] },
  "Saturday":  { "is_available": true, "times": [{ "from": "08:00", "to": "15:00" }] },
  "Sunday":    { "is_available": true, "times": [{ "from": "09:00", "to": "14:00" }] }
}