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_statusreads 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