Skip to content

E2-F2: Sales Phase Configuration

Epic: E2: Match & Stadium Management

Size: S (Small)

Problem / Outcome

Admins need to configure sales phases (Loyalty, General Public) for each match.

Scope

In-Scope:

  • API to set sales phases per match
  • Start/end dates configuration
  • Eligibility rules per phase
  • Ticket limits per phase (total tickets sellable in the phase)
  • Phased sector opening: a phase may optionally list the sectors it puts on sale (sector_scope); omitted = whole stadium. Lets the admin open only part of the stadium now and release more sectors in a later phase.

Out-of-Scope:

  • Automatic phase transitions (manual or scheduled)
  • Time-overlapping phases with disjoint sector scopes (phases never overlap in time, regardless of scope — at most one phase is active at any moment)

Acceptance Criteria

  • AC1: Given a match, when admin configures loyalty phase, then start/end dates and eligibility rules are saved
  • AC2: Given overlapping phases, when admin saves, then error "Phases cannot overlap" is returned
  • AC3: Given phase is active, when user accesses match, then phase-specific rules are enforced (cart rejects adds outside an active phase; sale_status reads NOT_ON_SALE between phases)
  • AC4: Sales closure triggers: all tickets sold (automatic) OR admin manually closes sales
  • AC5: Given a phase with a sector scope, only the listed sectors are sellable during the phase — the cart rejects other zones and the public zones endpoint flags them on_sale: false
  • AC6: Hard limit of 4 tickets per user per match applies cumulatively across all phases and channels (loyalty purchases count against the public-sale allowance — see E6-F2 AC4)

Data Model Impact

SalesPhase table:
- id (UUID, PK)
- match_id (UUID, FK)
- phase_type (ENUM: LOYALTY, GENERAL_PUBLIC)
- name (VARCHAR)
- start_at (TIMESTAMP, UTC)
- end_at (TIMESTAMP, UTC)
- eligibility_rules_json (JSONB, nullable)
- sector_scope (JSONB, nullable) — match_sector ids on sale in this phase; NULL = all sectors
- ticket_limit (INTEGER, nullable) — total tickets sellable in this phase; NULL/0 = unlimited
- is_active (BOOLEAN, default true)
- created_at (TIMESTAMP)

Per-user vs per-phase limits

ticket_limit caps the phase as a whole (e.g. release 5,000 tickets in presale). The per-user cap is the system-wide hard limit of 4 tickets per match, enforced cumulatively across phases; LOYALTY phases may lower a user's allocation further via eligibility_rules.max_tickets.

Permissions/Roles

  • Admin (Match Manager)

How to Verify

npm test -- --grep "sales phase"

Expected: Phases saved correctly, overlap validation works.

Dependencies

Implementation Tasks

See E2: Match ## Doc References Stadium Tasks

Doc References


Last Updated: July 2026