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, defaultgen_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, defaultnow()),validUntil(nullable timestamp; NULL = indefinite).isActive(boolean, defaulttrue).
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
activityIdON DELETE CASCADE (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).dayOfWeek,startTime,durationMinutes,validFrom,isActiveNOT NULL (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).- No DB-level CHECK that
dayOfWeeklies in[0, 6]— application-side responsibility.
Business invariants:
dayOfWeekmust be in[0, 6]— application-side only, no DB CHECK.startTimemust be validHH:MM— application-side only, no DB CHECK on the text format.- Sessions are only materialised when
isActive=trueANDvalidFrom <= now <= COALESCE(validUntil, +∞). - Cascade on activity delete — every slot of the activity is removed.
- Pure join tables
time_slot_coachesandtime_slot_attendeescascade on slot delete (PK includestimeSlotId).
Lifecycle
No status enum — isActive boolean is the soft-disable flag.
- Create: insert via
POST /api/business/time-slots, immediately followed byTimeSlotsService.generateSessionsmaterialising 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_coachesandtime_slot_attendees; existing Session rows havetimeSlotIdset toNULL(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) —
activityId→activities.activities.id, on-delete cascade. N:1. - Location (ENT-011) —
locationId→activities.locations.id. N:1, optional. - Session (ENT-012) — generated 1:N (sessions reference
timeSlotIdwith 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
| Surface | Exposed | Notes |
|---|---|---|
| client | yes — TimeSlotBriefDto referenced from session / booking DTOs | Swagger UI |
| business | yes — inline within session / activity admin DTOs | Swagger UI |
| super-admin | no | — |
Known gotchas / open questions
recurrenceGroupIdhas 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_coachesandtime_slot_attendeesare 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”. startTimeis plaintext— 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
generateSessionsimplementation, 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.