Skip to content

Recurrence

The core stores concrete, dated slots. Recurrence - repeating availability ("every Tuesday 10:00-11:00") - is an optional layer that materialises those concrete slots, shipped separately as booking_recur.

Why materialise rather than store a rule

Bookings attach to concrete slots, and the BookingManager is oblivious to a slot's origin. So recurrence adds cleanly with zero core/schema change beyond the single nullable Slot.rule field already provisioned.

flowchart LR
    RULE[RecurrenceRule<br/>booking_recur] -->|generator + cron| GEN[Materialise slots]
    GEN --> S1[Slot 2026-07-07]
    GEN --> S2[Slot 2026-07-14]
    GEN --> S3[Slot 2026-07-21]
    S1 --> C1[categories + bookings]
    S2 --> C2[categories + bookings]
    Note[Slot.rule = rule id] -.-> S1

Bonus of materialising versus storing a pure RRULE:

  • Per-occurrence overrides are trivial - skip a date or bump one day's capacity by editing that single slot.
  • Open-ended recurrence is handled by a rolling-horizon cron that keeps a window of future slots materialised (the Smart Date Recur approach). The engine only ever sees concrete slots.

What booking_recur will add

  • A RecurrenceRule entity (RRULE + a default category template + horizon).
  • A generator service + cron that creates/updates slots and stamps Slot.rule with the originating rule id.
  • A policy for already-booked generated slots when a rule changes - the one decision recurrence defers, and it lives entirely in the submodule.

Already provisioned

Slot.rule (nullable string) exists in the core today purely so this layer can attach later without a schema update.