Skip to content
v26.3

Online Ticketing System

The ticketing system enables online sales and management of event tickets (clubs, events) with QR-code-based entry, capacity management, and integration into the end-of-day report.

Architecture

Ticketing is implemented as a feature pack (Dikas.Features.Ticketing), analogous to the club module. It uses the IFeatureModule interface and is integrated into the main application via DI.

Dikas.Features.Ticketing/
├── Entities/           # Ticket, TicketArticleConfig
├── Commands/           # 10 Commands (CQRS)
├── Queries/            # 12 Queries (CQRS)
├── Contracts/          # DTOs (Request/Response)
├── Controllers/        # 3 Controller (33 Endpoints)
├── Services/           # QR, PDF, ExpireJob, DayCloseStats
├── Validators/         # FluentValidation
└── TicketingModule.cs  # DI-Registrierung

Entities

Ticket

SoftDeletableDocument, DocumentType "Ticket", ID prefix tkt_, cacheAll: false

Field Type Description
TicketNumber string Readable number (TKT-YYYYMMDD-XXXXXX)
QrCode string Unique QR code (GUID)
Name string Event/ticket name
Status int 0=Purchased, 1=CheckedIn, 2=Cancelled, 3=Expired, 4=Refunded
ArticleId string Reference to the ticket article
EventDate DateTime Event date
TimeWindowStart/End string? Entry time window (HH:MM)
ExpiresAt DateTime Expiry date (EventDate + 1 day)
EnterGroupId string? Club entry group
EnterRuleId string? Club entry rule
AssignedCardId string? NFC card assigned at check-in
FreeDrinks int Included free drinks
FreeAccount decimal Included credit
FreeArticles List\<FreeArticleItem> Included free articles
IncludesWardrobe bool Cloakroom included
GuestName string? Personalized guest name
GuestEmail string? Guest email
IsPersonalized bool Personalization required
GroupOrderId string? Group order ID
GroupIndex/GroupTotal int? Position/total within group
Price decimal Ticket price
PaidAmount decimal Amount already paid
RemainingAmount decimal Outstanding balance
RefundAmount decimal? Refund amount
RefundMethod string? Refund method
PaymentProvider string Payment provider (stripe, paypal, etc.)
PaymentTransactionId string External transaction ID
TaxClass int Tax class
CheckedInAt/By/WorkplaceId DateTime?/string? Check-in data
CancelledAt/By/CancelReason DateTime?/string? Cancellation data
RefundedAt DateTime? Refund timestamp
StatusHistory List\<TicketStatusEntry> Audit trail of status changes
OrderId string Shop order ID
CustomerId string? Customer reference

TicketArticleConfig

BaseDocument, DocumentType "TicketArticleConfig", ID prefix tktcfg_, cacheAll: true

Configures an article as a ticket type with all defaults and rules.

Field Type Description
ArticleId string Linked article
EnterGroupId string? Default club entry group
EnterRuleId string? Default club entry rule
FreeDrinks int Default free drinks
FreeAccount decimal Default free credit
FreeArticles List\<FreeArticleItem> Default free articles
IncludesWardrobe bool Cloakroom included
MaxCapacityPerDate int? Max. tickets per date
OverbookingPercent int? Overbooking in % (e.g. 10 = +10%)
CancelDeadlineHours int? Cancellation deadline in hours before event
AllowRefund bool Refund allowed
IsPersonalized bool Personalization required
AllowGroupPurchase bool Group purchase allowed
MaxGroupSize int Max. group size (default: 10)
TimeWindowStart/End string? Default entry time window
OnlineOnly bool Only available in the online shop
AllowPartialPayment bool Partial payment/deposit allowed
MinPrepayment decimal Minimum deposit
SoldPerDate Dictionary\<string, int> Tickets sold per date (yyyy-MM-dd → count)

API Endpoints

TicketController — /api/v1/tickets [Authorize]

Tickets:

Method Endpoint Description
GET /{id} Ticket by ID
GET /by-qr/{qrCode} Ticket by QR code
GET /by-date?date= Tickets by date
GET /by-order/{orderId} Tickets of an order
GET /by-customer/{customerId} Tickets of a customer
GET /search?search=&status=&dateFrom=&dateTo=&limit= Search tickets
GET /capacity?date=&articleId= Query capacities
GET /stats?dateFrom=&dateTo= Statistics
POST /validate Validate QR code (without check-in)
POST /check-in Perform QR entry
POST /{id}/cancel Cancel ticket
POST /{id}/pay Pay outstanding balance
POST /{id}/refund Perform refund
POST /cancel-group/{groupOrderId} Cancel group order

Configurations:

Method Endpoint Description
GET /configs All ticket configurations
GET /configs/{id} Configuration by ID
POST /configs Create configuration (201)
PUT /configs/{id} Update configuration
DELETE /configs/{id} Delete configuration

TicketPortalController — /api/v1/portal/tickets [Authorize(Policy="Portal")]

Method Endpoint Description
GET /my?customerId= Customer's own tickets
POST /{id}/cancel Customer cancels their own ticket
GET /{id}/qr QR code as PNG [AllowAnonymous]
GET /{id}/pdf Ticket as PDF [AllowAnonymous]

TicketShopController — /api/v1/ticket-shop [AllowAnonymous]

Method Endpoint Description
GET /catalog Available ticket types
GET /capacity?date=&articleId= Check availability
POST /validate Validate checkout
POST /purchase Purchase tickets

Commands

Command Return Description
CreateTicketCommand List\<TicketResponse> Creates 1+ tickets, generates QR + number, sends confirmation email with PDF
CheckInTicketCommand CheckInResult Validates QR, sets status to CheckedIn, records timestamp
CancelTicketCommand TicketResponse Cancels ticket (only Status=Purchased), checks cancellation deadline, sends cancellation email
ProcessTicketPaymentCommand TicketResponse Books remaining payment, updates PaidAmount/RemainingAmount
RefundTicketCommand TicketResponse Refund (only Status=Cancelled), checks AllowRefund
CancelTicketGroupCommand List\<TicketResponse> Cancels all tickets of a group order
ValidateTicketCheckoutCommand TicketCheckoutValidationResult Capacity and configuration check for the shop
CreateTicketArticleConfigCommand TicketArticleConfigResponse Create new ticket configuration
UpdateTicketArticleConfigCommand TicketArticleConfigResponse Update configuration
DeleteTicketArticleConfigCommand bool Delete configuration

Queries

Query Return Description
GetTicketByIdQuery TicketResponse Single ticket
GetTicketByQrCodeQuery TicketResponse Find ticket by QR code
GetTicketsByDateQuery List\<TicketResponse> Tickets by event date
GetTicketsByCustomerQuery List\<TicketResponse> Tickets of a customer
GetTicketsByOrderQuery List\<TicketResponse> Tickets of an order
SearchTicketsQuery List\<TicketResponse> Full-text search with filters
GetTicketCapacityQuery List\<TicketCapacityResponse> Capacity per article/date
GetTicketStatsQuery TicketStatsResponse Aggregated statistics
ValidateTicketQuery CheckInResult Check-in validation without side effects
GetTicketArticleConfigsQuery List\<TicketArticleConfigResponse> All configurations
GetTicketArticleConfigQuery TicketArticleConfigResponse Single configuration
GetTicketCatalogQuery List\<TicketShopItemResponse> Online shop catalog

Services

TicketQrService (Singleton)

Generates QR codes with the QRCoder library.

interface ITicketQrService {
    byte[] GeneratePng(string content, int pixelsPerModule = 10);
    string GenerateSvg(string content);
}

TicketPdfService (Scoped)

Creates ticket PDFs in A6 format with QuestPDF. Contains an embedded QR code, ticket details, and optional venue information.

interface ITicketPdfService {
    byte[] GenerateTicketPdf(Ticket ticket, string? venueInfo = null);
}

ExpireTicketsJob (HostedService)

Background job that runs every 60 minutes and automatically sets expired tickets (Status=Purchased, ExpiresAt < now) to Status=Expired.

DayCloseTicketStatsProvider (Scoped)

Provides ticket statistics for the end-of-day report:

interface IDayCloseTicketStatsProvider {
    Task<DayCloseTicketStats> GetStatsForPeriodAsync(DateTime from, DateTime to, CancellationToken ct);
}

Return: SoldOnlineCount/Amount, CheckedInCount, RefundedCount/Amount

Optional cross-module dependency: NullDayCloseTicketStatsProvider is registered as the default and is overridden by the TicketingModule with the real implementation.

Check-in Flow

QR-Code scannen
ValidateTicketQuery
    ├── Ticket nicht gefunden → Invalid
    ├── Status != Purchased → AlreadyUsed / Cancelled / Expired
    ├── EventDate != heute → WrongDate
    ├── Außerhalb TimeWindow → OutsideTimeWindow
    ├── RemainingAmount > 0 → HasRemainingAmount (Warnung, Einlass möglich)
    └── Alles OK → Valid
CheckInTicketCommand
    ├── Status → CheckedIn
    ├── CheckedInAt/By/WorkplaceId setzen
    ├── AssignedCardId setzen (optional, für Disco-Karte)
    └── StatusHistory-Eintrag erstellen

CheckInStatus enum:

Value Meaning
0 - Valid Ticket valid, entry allowed
1 - Invalid Ticket invalid/not found
2 - Expired Ticket expired
3 - WrongDate Wrong event date
4 - OutsideTimeWindow Outside the entry time window
5 - AlreadyUsed Already checked in
6 - Cancelled Ticket cancelled
7 - NameMismatch Name discrepancy (personalized ticket)
8 - HasRemainingAmount Outstanding balance (entry still possible)

Online Shop Checkout Flow

1. GET /ticket-shop/catalog
   → Liste verfügbarer Ticket-Typen

2. GET /ticket-shop/capacity?date=2026-03-15
   → Verfügbarkeit für gewähltes Datum

3. POST /ticket-shop/validate
   { articleId, eventDate, quantity }
   → Kapazitätsprüfung, Preisberechnung

4. Zahlung extern (Stripe.js / PayPal SDK)

5. POST /ticket-shop/purchase
   { articleId, eventDate, quantity, customerEmail, customerName, paymentProvider }
   → Tickets erstellen, Bestätigungs-Email mit PDF-Tickets

The capacity check takes MaxCapacityPerDate and OverbookingPercent into account:

Effektive Kapazität = MaxCapacity + (MaxCapacity × OverbookingPercent / 100)
Verfügbar = Effektive Kapazität - SoldPerDate[datum]

Capacity Management

Sold tickets are stored in TicketArticleConfig.SoldPerDate as a dictionary:

{
  "2026-03-15": 42,
  "2026-03-16": 85
}
  • Without MaxCapacityPerDate: Unlimited availability
  • With MaxCapacityPerDate: Hard limit per date
  • With OverbookingPercent: Allows controlled overbooking (e.g. 10% = 110 at a max of 100)

Group Purchase

Tickets can be purchased as a group:

  • AllowGroupPurchase and MaxGroupSize on TicketArticleConfig
  • GroupOrderId links all tickets of an order
  • GroupIndex (1-based) and GroupTotal for tracking
  • CancelTicketGroupCommand cancels the entire group
  • Optional GuestNames for personalized group tickets

Club Integration

Ticket configurations can be linked to club entry groups:

  • EnterGroupId → Automatic assignment on ticket purchase
  • EnterRuleId → Specific entry rule
  • FreeArticles → Free articles from the club module
  • At check-in: club card (AssignedCardId) can be assigned

End-of-Day Report Integration

The IDayCloseTicketStatsProvider provides the following metrics for the end-of-day report:

Metric Description
SoldOnlineCount Tickets sold online in the period
SoldOnlineAmount Total revenue of tickets sold online
CheckedInCount Checked-in tickets in the period
RefundedCount Refunded tickets in the period
RefundedAmount Total refund amount

Email Integration

Two email templates:

  • ticket_confirmation: Sent after ticket purchase, contains the PDF ticket as an attachment
  • ticket_cancellation: Sent after cancellation

OperationalConfig Fields

Field Type Default Description
TicketingEnabled bool false Ticketing module enabled
TicketScanInDirectSale bool true POS can scan tickets

Frontend Components

Admin — Tickets page (/admin/tickets)

4 tabs: 1. Tickets: Search, status filter, date range, table with TicketNumber/Name/EventDate/Status/Price, void action, PDF link 2. Capacities: Date selection, capacity cards with progress bars (Sold/Max/Available) 3. Statistics: Metric cards (Sold, Checked in, Outstanding, Cancelled, Expired, Refunded, Revenue) 4. Configuration: Table of all ticket article configurations

Admin — Article editor (Ticket tab)

Shown when ExtraOption === 15 (ticket type): - Club link (EnterGroup dropdown) - Extras (free drinks, free credit, cloakroom) - Allotment (MaxCapacity, overbooking) - Cancellation rules (deadline, refund) - Options (personalization, group purchase, time window, partial payment)

POS — Ticket scan (/pos/ticket-scan)

  • QR code input field with autofocus
  • Validation result with colored status (Green/Orange/Red)
  • Ticket details (number, name, date, guest, extras)
  • Entry button for a valid ticket
  • Remaining balance warning for outstanding payment

Club — Entry (QR ticket section)

  • QR code input below the card scan
  • Validation and check-in with card assignment
  • Only visible when TicketingEnabled

Online Shop — Ticket catalog

  • Ticket cards with price, perks (free drinks, credit, cloakroom), date selection
  • Cart integration with ticket:{date} note

Extension Registration

// Admin-Sidebar
{ id: 'tickets', label: 'Tickets', icon: 'fa-solid fa-ticket', order: 115 }

// POS-Menü
{ id: 'ticket-scan', name: 'Ticket-Scan', icon: 'fa-solid fa-qrcode', order: 120 }

DI Registration (TicketingModule)

// Entities
Ticket:              cacheAll=false, BackupCategory="tickets"
TicketArticleConfig: cacheAll=true,  BackupCategory="tickets"

// Services
ITicketQrService             TicketQrService (Singleton)
ITicketPdfService            TicketPdfService (Scoped)
IDayCloseTicketStatsProvider  TicketDayCloseStatsProvider (Scoped)
ExpireTicketsJob             HostedService

// MediatR + FluentValidation
services.AddMediatR(...)
services.AddValidatorsFromAssembly(...)

Database: Supports CouchDB and EF Core (SQLite/SQL Server). EF Core configuration with String MaxLength constraints and Decimal Precision(18,4). SoldPerDate is stored as a JSON TEXT column.

ID prefixes (IdGenerator): "Ticket" → "tkt", "TicketArticleConfig" → "tktcfg"

Dependencies

Package Usage
QRCoder QR code generation (PNG, SVG)
QuestPDF PDF ticket creation (A6 format)
MediatR CQRS pattern
FluentValidation Request validation