Skip to main content

Facility Hours

Configure operating hours for facility resources. Hours determine when resources are available for booking.

Overview

Facility hours define weekly operating schedules. Hours can be configured at:

  • Resource type level - Default hours for all resources of a type
  • Resource level - Override for a specific resource

The availability service integrates hours into its calculations, blocking reservations outside operating hours.

Hours Properties

FieldTypeDescription
tenantIdstringRequired - Tenant scope
clubIdstringOptional - Club scope
resourceIdstringOptional - Specific resource override
resourceTypeResourceTypeOptional - Hours for a resource type
dayOfWeeknumber0-6 (Sunday=0, Monday=1, ... Saturday=6)
opensAtMinutesnumberMinutes since midnight (e.g., 360 = 6:00 AM)
closesAtMinutesnumberMinutes since midnight (e.g., 1320 = 10:00 PM)
timeZonestringIANA time zone (e.g., "Africa/Johannesburg")

REST API

List Hours

GET /facility-hours?tenantId=1&clubId=10&resourceType=TENNIS_COURT

Query Parameters:

  • tenantId (required) - Tenant scope
  • clubId (optional) - Filter by club
  • resourceType (optional) - Filter by resource type
  • resourceId (optional) - Filter by specific resource
  • dayOfWeek (optional) - Filter by day (0-6)

Create or Update Hours (Upsert)

Hours are upserted by unique combination of (tenantId, clubId, resourceType/resourceId, dayOfWeek):

POST /facility-hours
Content-Type: application/json

{
"tenantId": "t-1",
"clubId": "c-1",
"resourceType": "TENNIS_COURT",
"dayOfWeek": 1,
"opensAtMinutes": 360,
"closesAtMinutes": 1320,
"timeZone": "Africa/Johannesburg"
}

Bulk Create Hours

Set hours for all days in one request:

POST /facility-hours/bulk
Content-Type: application/json

{
"tenantId": "t-1",
"clubId": "c-1",
"resourceType": "TENNIS_COURT",
"timeZone": "Africa/Johannesburg",
"hours": [
{ "dayOfWeek": 0, "opensAtMinutes": 420, "closesAtMinutes": 1200 },
{ "dayOfWeek": 1, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 2, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 3, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 4, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 5, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 6, "opensAtMinutes": 420, "closesAtMinutes": 1200 }
]
}

Delete Hours

DELETE /facility-hours/:id?tenantId=1

GraphQL API

Query Hours

query FacilityHours {
facilityHours(filters: {
tenantId: "t-1"
clubId: "c-1"
resourceType: TENNIS_COURT
}) {
id
dayOfWeek
opensAtMinutes
closesAtMinutes
timeZone
}
}

Create Hours

mutation CreateFacilityHours {
createFacilityHours(data: {
tenantId: "t-1"
clubId: "c-1"
resourceType: TENNIS_COURT
dayOfWeek: 1
opensAtMinutes: 360
closesAtMinutes: 1320
timeZone: "Africa/Johannesburg"
}) {
id
dayOfWeek
}
}

Time Conversion

Convert times to minutes since midnight:

TimeMinutes
5:00 AM300
6:00 AM360
7:00 AM420
8:00 AM480
9:00 PM1260
10:00 PM1320
11:00 PM1380

Formula: hours * 60 + minutes

Example Configurations

Tennis Courts (6 AM - 10 PM weekdays, 7 AM - 8 PM weekends)

[
{ "dayOfWeek": 0, "opensAtMinutes": 420, "closesAtMinutes": 1200 },
{ "dayOfWeek": 1, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 2, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 3, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 4, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 5, "opensAtMinutes": 360, "closesAtMinutes": 1320 },
{ "dayOfWeek": 6, "opensAtMinutes": 420, "closesAtMinutes": 1200 }
]

Gym (5 AM - 10 PM weekdays, 6 AM - 8 PM weekends)

[
{ "dayOfWeek": 0, "opensAtMinutes": 360, "closesAtMinutes": 1200 },
{ "dayOfWeek": 1, "opensAtMinutes": 300, "closesAtMinutes": 1320 },
{ "dayOfWeek": 2, "opensAtMinutes": 300, "closesAtMinutes": 1320 },
{ "dayOfWeek": 3, "opensAtMinutes": 300, "closesAtMinutes": 1320 },
{ "dayOfWeek": 4, "opensAtMinutes": 300, "closesAtMinutes": 1320 },
{ "dayOfWeek": 5, "opensAtMinutes": 300, "closesAtMinutes": 1320 },
{ "dayOfWeek": 6, "opensAtMinutes": 360, "closesAtMinutes": 1200 }
]

Blackout Periods

For temporary closures (maintenance, events, holidays), use blackout periods instead of modifying hours.

Create Blackout

POST /facility-blackouts
Content-Type: application/json

{
"tenantId": "t-1",
"clubId": "c-1",
"resourceId": "r-42",
"startTime": "2026-01-20T08:00:00Z",
"endTime": "2026-01-20T12:00:00Z",
"reason": "Scheduled maintenance - filter cleaning"
}

Blackouts can be scoped to:

  • A specific resource (resourceId)
  • All resources of a type (resourceType)
  • All resources at a club (neither resourceId nor resourceType)

List Blackouts

GET /facility-blackouts?tenantId=1&clubId=10&startFrom=2026-01-01T00:00:00Z

Delete Blackout

DELETE /facility-blackouts/:id?tenantId=1

Hours Precedence

When checking availability:

  1. Resource-specific hours (if defined for the resource)
  2. Resource type hours (if defined for the type at club level)
  3. Tenant-level type hours (if defined at tenant level)
  4. No restriction (if no hours are defined)

If hours are defined, reservations outside operating hours are rejected.