Skip to content

Bookings

Purpose

A booking is a customer’s reservation for a single session. Small context (2 entities) but central: every booking crosses 5 other contexts — Catalog (session), Companies (customer), Passes (entitlement coverage), Wallet (balance debit, refund) and Payments (gateway when paid by card).

The Booking extra child rows snapshot each extra purchased alongside the booking.

Boundaries

  • In: the reservation record and its extras snapshot.
  • Out:
    • What is being booked (session, time slot) — Catalog.
    • WhoCompany customer.
    • Pass coverageCustomer entitlement; when a booking is paid by a pass, booking.customer_entitlement_id is set.
    • Money flowwallet_debited / bonus_debited flags here record that a debit happened; the actual ledger lives in Wallet and Payments.
    • RefundsRefund request — 0..1 per booking.

Entities

IDEntityRole
ENT-003BookingThe reservation — status, payment state, links to wallet/pass/refund
ENT-004Booking extraSnapshot of an extra purchased with the booking

Backend implementation

  • No dedicated feature library. Booking logic lives inside libs/features/activities/ — see bookings-client.service.ts and related controllers. The domain context (bookings/) and the file layout intentionally differ.
  • Surface routing:
    • Client: rich — /my-bookings, pay, cancel, list-by-session, list-by-company. Guest sub-surface (X2): POST /api/client/guest/companies/:companyId/sessions/:sessionId/bookings mounted on a sibling controller BookingsGuestController (no class-level guard, method-level @Public() + ThrottlerGuard). The guest controller and service (bookings-guest.service.ts) live in the same libs/features/activities/ lib and the same ActivitiesClientModule — the guest path is a thin adapter that reuses BookingsClientService.createBookingForCustomer and BookingsService.resolveCustomerId (widened to public for X2).
    • Business: embedded-only — bookings surface via SessionBookingDto inside session expansion (SessionResponseDto.bookings, activeBookingsCount). No top-level admin CRUD by design.
  • Module wiring: ActivitiesClientModule now exports BookingsClientService (X2 — so future cross-module consumers can inject the same orchestrator; intra-module DI already worked).
  • Drizzle schema: libs/shared/data-access-db/src/lib/schema/bookings.schema.ts (pgSchema('bookings')). No X2 migration — the guest flow stores rows with companyCustomers.userId = NULL, which the existing schema already supports.

Cross-context relationships

  • This context owns bookings.bookings and bookings.booking_extra.
  • This context references (all cross-schema FKs, allowed because relationships are central):
    • Catalog via booking.session_id (FK, ON DELETE CASCADE).
    • Companies via booking.customer_id (FK, ON DELETE CASCADE).
    • Passes via booking.customer_entitlement_id (FK, ON DELETE SET NULL — pass deletion does not orphan the booking).
  • This context is referenced by:
    • Wallet via refund_request.booking_id (UNIQUE — 0..1 refund per booking).
    • Catalog via contributor_review.booking_id (cross-schema soft ref, no FK — eligibility proof for reviews).

Open questions

  • OPEN: the PENDING_PAYMENT booking status is documented in the schema comment as the post-fact billing recovery state (when a SLOT_ATTENDEE booking finishes its session without wallet funds). Confirm the exact code path that transitions a booking into and out of it.
  • Bookings’ physical co-location in the activities feature lib is intentional today — extracting a dedicated libs/features/bookings/ is open work, not blocking.