Skip to content

Time slot

Purpose

A weekly recurrence rule for an Activity — “every Monday at 10:00 for an hour”. From this rule the system materialises concrete Session rows on the calendar. Time slots make it possible for an admin to set up a steady schedule once instead of creating sessions one by one.

Identity & key fields

  • Primary key: id (uuid, default gen_random_uuid()).
  • activityId (uuid, FK → activities.activities.id, on-delete cascade).
  • locationId (nullable uuid, FK → activities.locations.id).
  • dayOfWeek (integer, NOT NULL) — 0 = Sunday, 1 = Monday, …, per schema comment.
  • startTime (text, NOT NULL) — "HH:MM" format.
  • durationMinutes (integer, default 60).
  • price (nullable decimal 10,2) — slot-level price override.
  • recurrenceGroupId (nullable uuid) — groups slots created together.
  • validFrom (timestamp, default now()), validUntil (nullable timestamp; NULL = indefinite).
  • isActive (boolean, default true).

isActive=false is a soft-disable that halts future session materialisation without disturbing already-materialised sessions. recurrenceGroupId is an app-side correlation id that groups slots created together as one bulk UI action (“Mon/Wed/Fri at 10:00”). validFrom / validUntil window controls when the rule is “live” for materialisation.

Invariants

  • activityId ON DELETE CASCADE (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • dayOfWeek, startTime, durationMinutes, validFrom, isActive NOT NULL (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • No DB-level CHECK that dayOfWeek lies in [0, 6] — application-side responsibility.

Business invariants:

  • dayOfWeek must be in [0, 6] — application-side only, no DB CHECK.
  • startTime must be valid HH:MM — application-side only, no DB CHECK on the text format.
  • Sessions are only materialised when isActive=true AND validFrom <= now <= COALESCE(validUntil, +∞).
  • Cascade on activity delete — every slot of the activity is removed.
  • Pure join tables time_slot_coaches and time_slot_attendees cascade on slot delete (PK includes timeSlotId).

Lifecycle

No status enum — isActive boolean is the soft-disable flag.

  • Create: insert via POST /api/business/time-slots, immediately followed by TimeSlotsService.generateSessions materialising N days of sessions ahead (time-slots.service.ts:122).
  • Update: changes to day/time/duration/price/location trigger re-materialisation via generateSessions (time-slots.service.ts:676).
  • Soft-disable: isActive=false — future materialisation stops, existing sessions are untouched.
  • Delete: hard delete cascades on time_slot_coaches and time_slot_attendees; existing Session rows have timeSlotId set to NULL (ON DELETE SET NULL) — they survive as orphans of the rule.

Steady-state materialisation runs through BullMQ via TimeSlotsService.generateSessionsForSlot, which returns nextRunInMs for self-re-queueing. The daily safety-net cron (ActivitiesScheduler.handleSessionGenerationSafetyNet at midnight) backfills any slots that the per-slot job missed.

Relationships

  • Activity (ENT-005) — activityIdactivities.activities.id, on-delete cascade. N:1.
  • Location (ENT-011) — locationIdactivities.locations.id. N:1, optional.
  • Session (ENT-012) — generated 1:N (sessions reference timeSlotId with on-delete set null).
  • Time slot attendee (ENT-014) — 1:N, recurring bookings against this rule.
  • Company member (coach) (ENT-019) — N:M via time_slot_coaches (pure join, PK = (timeSlotId, memberId), on-delete cascade both sides).

API surfaces

SurfaceExposedNotes
clientyes — TimeSlotBriefDto referenced from session / booking DTOsSwagger UI
businessyes — inline within session / activity admin DTOsSwagger UI
super-adminno

Known gotchas / open questions

  • recurrenceGroupId has no FK target — it is an application-side correlation id used to bulk-edit slots created from one UI action.
  • Pure join tables time_slot_coaches and time_slot_attendees are not documented as separate entities (see this entity’s Relationships block).
  • The recurrence model is weekly-only — fixed dayOfWeek + startTime. There is no support for “every N days” / “monthly” / “specific dates list”.
  • startTime is plain text — wrong formats (e.g. "25:00") fail only at session-materialisation time, not at slot insert.
  • Updating a slot that already has booked sessions is potentially destructive — depending on generateSessions implementation, future bookings may be moved or lost. Behaviour must be explicitly verified per change type (time-shift vs day-shift vs price-only).
  • OPEN: is (activityId, dayOfWeek, startTime) required to be unique? Not enforced at DB level.

Recommendations

Forward-looking improvements suggested while filling this doc — not currently in place.

  • DB CHECK on dayOfWeek BETWEEN 0 AND 6.
  • DB CHECK on start_time ~ '^([01]?\d|2[0-3]):[0-5]\d$' to catch malformed times at insert time.
  • Document UPDATE semantics for slots with existing booked sessions — when the day or time changes, what happens to the future sessions and their bookings? Move, recreate, cancel? Behaviour should be explicit and reversible.
  • Extend the recurrence model to support more than weekly. Adding a nullable rrule text (RFC 5545 RRULE) alongside the current weekly fields allows daily, monthly, every-N-days, exception dates, etc. — without rewriting the materialisation pipeline for the common weekly case.
  • Document BullMQ queue names, retry, and dedup config for the per-slot materialisation job in Catalog context — operationally critical and currently only in code.
  • Optional UNIQUE on (activityId, dayOfWeek, startTime, validFrom) if duplicate slots for the same time are considered an error.