Skip to content

Pass

Purpose

A pass template — a company’s reusable offer definition: name, validity in days, the activities it covers and how many sessions of each (Pass entitlement template), bundled extras (Pass entitlement covered extra), price tiers (Pass price), refund policy, and notification thresholds. When a customer buys, a Customer pass instance is created — a snapshot of this template.

Identity & key fields

  • Primary key: id (uuid, default gen_random_uuid()).
  • companyId (uuid, NOT NULL, FK → companies.company.id, on-delete restrict).
  • name, description (text).
  • validityDays (integer, NOT NULL) — schema comment lists typical values 30/60/90/180/365.
  • notifySessionsRemaining (nullable integer) — threshold to send “low sessions” notification.
  • expiryNotifyDays (nullable integer) — threshold to send “pass expiring soon” notification when no future booking exists.
  • currency (text, NOT NULL, default 'UAH').
  • cancelRefundPolicy (enum cancel_refund_policy: NONE, FULL, PROPORTIONAL, default NONE) — applied for WALLET-paid passes.
  • isActive (boolean, NOT NULL, default true).

isActive=false is a soft-disable: the pass cannot be sold to new customers but every already-issued Customer pass keeps working. cancelRefundPolicy describes what happens to the wallet balance when a WALLET-paid pass is cancelled; refunds for LIQPAY / MANUAL payments are handled out-of-band.

Invariants

  • companyId ON DELETE RESTRICT (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/passes.schema.ts).
  • validityDays, companyId, name, currency, cancelRefundPolicy, isActive NOT NULL (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/passes.schema.ts).

Business invariants:

  • companyId has a real cross-schema FK with ON DELETE RESTRICT to Company — a company cannot be hard-deleted while any pass template exists. This is one of the few cross-schema references that has an FK; see the note in ADR cross-schema-references-without-fk.
  • validityDays is the lifespan of an activated Customer pass — from activatedAt until validUntil = activatedAt + validityDays, minus any paused time.
  • Template edits are not retroactive — a Customer pass snapshots price, currency, priceName, paymentMethod, plus the entitlement structure at purchase time. Later edits to this template do not flow into already-purchased instances.
  • cancelRefundPolicy applies only to WALLET-paid passes — LIQPAY and MANUAL paths have their own refund handling outside this column.
  • ON DELETE RESTRICT propagates through the template-tier FK graph: deleting a pass with any active customer-pass or any entitlement-template is forbidden.

Lifecycle

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

  • Create: admin via POST /api/business/passes. Typically a transaction creates the pass row plus its entitlement-templates and price tiers in one shot.
  • Update: admin via PATCH /api/business/passes/{id} — name, validity, notification thresholds, refund policy, etc. Changes do not propagate to already-issued Customer pass instances.
  • Soft-disable: POST /api/business/passes/{id}/toggle flips isActive. New purchases blocked; existing customer-passes unaffected.
  • Delete: hard delete possible only when no active customer-passes and no entitlement-templates reference the pass (ON DELETE RESTRICT on both relationships).

Relationships

  • Company (ENT-016) — companyIdcompanies.company.id, on-delete restrict. N:1.
  • Pass entitlement template (ENT-031) — 1:N composition rows (which activities are included and how many sessions).
  • Pass price (ENT-032) — 1:N pricing tiers.
  • Customer pass (ENT-028) — 1:N issued / purchased instances.

API surfaces

SurfaceExposedNotes
clientyes — /companies/{companyId}/passes (PassClientDto, PassPriceClientDto, PassEntitlementClientDto)Swagger UI
businessyes — /passes, /passes/{id}, /passes/{id}/toggle (PassResponseDto, CreatePassDto, UpdatePassDto, PaginatedPassResponseDto, PassesIsActiveFilter)Swagger UI
super-adminno

Known gotchas / open questions

  • Cancellation refund policy applies only to WALLET-paid passes per schema comment. LIQPAY / MANUAL refund handling lives outside.
  • ON DELETE RESTRICT on companyId — a company cannot be hard-deleted while pass templates exist.
  • Template edits are NOT retroactive. This often surprises product folks: changing the price or pulling an activity out of a template does not affect customers who already bought the pass. Their customerPass row holds a snapshot at purchase time.
  • companyId here has a real cross-schema FK (RESTRICT) — different from activities.activities.companyId and friends, which have no FK at all. The ADR over-generalised “no cross-schema FK”; reality is more nuanced.

Recommendations

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

  • DB CHECK validity_days > 0 — promote the implicit rule into a constraint.
  • UNIQUE on (company_id, name) — typical UX expectation that a company doesn’t have two passes with the same display name.
  • Audit log for template changes — for compliance and disputes (“I bought it under these terms; you changed them later”). Today there is no record of when or who changed a template.
  • Document refund policies for every payment method, not just WALLET. LIQPAY/MANUAL refund handling is currently outside this column and outside the docs.
  • Revise ADR cross-schema-references-without-fk — add an explicit section explaining when cross-schema FKs are used (RESTRICT for template-tier ownership, CASCADE for tight booking-side coupling) vs when they are intentionally omitted.