Vai al contenuto
v26.3

Sistema di ticketing online

Il sistema di ticketing consente la vendita online e la gestione di biglietti per eventi (Disco, manifestazioni) con ingresso basato su codice QR, gestione della capienza e integrazione nella chiusura giornaliera.

Architettura

Il ticketing è implementato come Feature-Pack (Dikas.Features.Ticketing), in modo analogo al modulo Disco. Utilizza l'interfaccia IFeatureModule e viene integrato nell'applicazione principale tramite 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", prefisso ID tkt_, cacheAll: false

Campo Tipo Descrizione
TicketNumber string Numero leggibile (TKT-YYYYMMDD-XXXXXX)
QrCode string Codice QR univoco (GUID)
Name string Nome evento/biglietto
Status int 0=Purchased, 1=CheckedIn, 2=Cancelled, 3=Expired, 4=Refunded
ArticleId string Riferimento all'articolo-biglietto
EventDate DateTime Data dell'evento
TimeWindowStart/End string? Fascia oraria d'ingresso (HH:MM)
ExpiresAt DateTime Data di scadenza (EventDate + 1 giorno)
EnterGroupId string? Gruppo d'ingresso Disco
EnterRuleId string? Regola d'ingresso Disco
AssignedCardId string? Carta NFC assegnata al check-in
FreeDrinks int Bevande gratuite incluse
FreeAccount decimal Credito incluso
FreeArticles List\<FreeArticleItem> Articoli gratuiti inclusi
IncludesWardrobe bool Guardaroba incluso
GuestName string? Nome ospite personalizzato
GuestEmail string? Email ospite
IsPersonalized bool Obbligo di personalizzazione
GroupOrderId string? ID ordine di gruppo
GroupIndex/GroupTotal int? Posizione/totale nel gruppo
Price decimal Prezzo del biglietto
PaidAmount decimal Importo già pagato
RemainingAmount decimal Importo residuo da pagare
RefundAmount decimal? Importo di rimborso
RefundMethod string? Metodo di rimborso
PaymentProvider string Provider di pagamento (stripe, paypal, ecc.)
PaymentTransactionId string ID transazione esterna
TaxClass int Classe fiscale
CheckedInAt/By/WorkplaceId DateTime?/string? Dati di check-in
CancelledAt/By/CancelReason DateTime?/string? Dati di storno
RefundedAt DateTime? Momento del rimborso
StatusHistory List\<TicketStatusEntry> Audit trail delle modifiche di stato
OrderId string ID ordine del negozio
CustomerId string? Riferimento al cliente

TicketArticleConfig

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

Configura un articolo come tipo di biglietto con tutti i valori predefiniti e le regole.

Campo Tipo Descrizione
ArticleId string Articolo collegato
EnterGroupId string? Gruppo d'ingresso Disco predefinito
EnterRuleId string? Regola d'ingresso Disco predefinita
FreeDrinks int Bevande gratuite predefinite
FreeAccount decimal Credito gratuito predefinito
FreeArticles List\<FreeArticleItem> Articoli gratuiti predefiniti
IncludesWardrobe bool Guardaroba incluso
MaxCapacityPerDate int? Max. biglietti per data
OverbookingPercent int? Overbooking in % (ad es. 10 = +10%)
CancelDeadlineHours int? Termine di storno in ore prima dell'evento
AllowRefund bool Rimborso consentito
IsPersonalized bool Obbligo di personalizzazione
AllowGroupPurchase bool Acquisto di gruppo consentito
MaxGroupSize int Dimensione massima del gruppo (predefinito: 10)
TimeWindowStart/End string? Fascia oraria d'ingresso predefinita
OnlineOnly bool Disponibile solo nel negozio online
AllowPartialPayment bool Pagamento parziale/acconto consentito
MinPrepayment decimal Acconto minimo
SoldPerDate Dictionary\<string, int> Biglietti venduti per data (yyyy-MM-dd → quantità)

Endpoint API

TicketController — /api/v1/tickets [Authorize]

Tickets:

Method Endpoint Descrizione
GET /{id} Biglietto per ID
GET /by-qr/{qrCode} Biglietto per codice QR
GET /by-date?date= Biglietti per data
GET /by-order/{orderId} Biglietti di un ordine
GET /by-customer/{customerId} Biglietti di un cliente
GET /search?search=&status=&dateFrom=&dateTo=&limit= Cercare biglietti
GET /capacity?date=&articleId= Interrogare le capienze
GET /stats?dateFrom=&dateTo= Statistiche
POST /validate Validare il codice QR (senza check-in)
POST /check-in Eseguire l'ingresso via QR
POST /{id}/cancel Stornare un biglietto
POST /{id}/pay Pagare l'importo residuo
POST /{id}/refund Eseguire il rimborso
POST /cancel-group/{groupOrderId} Stornare un ordine di gruppo

Configurazioni:

Method Endpoint Descrizione
GET /configs Tutte le configurazioni dei biglietti
GET /configs/{id} Configurazione per ID
POST /configs Creare una configurazione (201)
PUT /configs/{id} Aggiornare una configurazione
DELETE /configs/{id} Eliminare una configurazione

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

Method Endpoint Descrizione
GET /my?customerId= Biglietti propri del cliente
POST /{id}/cancel Il cliente storna il proprio biglietto
GET /{id}/qr Codice QR come PNG [AllowAnonymous]
GET /{id}/pdf Biglietto come PDF [AllowAnonymous]

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

Method Endpoint Descrizione
GET /catalog Tipi di biglietto disponibili
GET /capacity?date=&articleId= Verificare la disponibilità
POST /validate Validare il checkout
POST /purchase Acquistare biglietti

Commands

Command Restituisce Descrizione
CreateTicketCommand List\<TicketResponse> Crea 1+ biglietti, genera QR + numero, invia email di conferma con PDF
CheckInTicketCommand CheckInResult Valida il QR, imposta lo stato su CheckedIn, registra il timestamp
CancelTicketCommand TicketResponse Storna il biglietto (solo Status=Purchased), verifica il termine di storno, invia email di storno
ProcessTicketPaymentCommand TicketResponse Registra il pagamento residuo, aggiorna PaidAmount/RemainingAmount
RefundTicketCommand TicketResponse Rimborso (solo Status=Cancelled), verifica AllowRefund
CancelTicketGroupCommand List\<TicketResponse> Storna tutti i biglietti di un ordine di gruppo
ValidateTicketCheckoutCommand TicketCheckoutValidationResult Verifica di capienza e configurazione per il negozio
CreateTicketArticleConfigCommand TicketArticleConfigResponse Creare una nuova configurazione del biglietto
UpdateTicketArticleConfigCommand TicketArticleConfigResponse Aggiornare la configurazione
DeleteTicketArticleConfigCommand bool Eliminare la configurazione

Queries

Query Restituisce Descrizione
GetTicketByIdQuery TicketResponse Singolo biglietto
GetTicketByQrCodeQuery TicketResponse Cercare un biglietto per codice QR
GetTicketsByDateQuery List\<TicketResponse> Biglietti per data dell'evento
GetTicketsByCustomerQuery List\<TicketResponse> Biglietti di un cliente
GetTicketsByOrderQuery List\<TicketResponse> Biglietti di un ordine
SearchTicketsQuery List\<TicketResponse> Ricerca full-text con filtri
GetTicketCapacityQuery List\<TicketCapacityResponse> Capienza per articolo/data
GetTicketStatsQuery TicketStatsResponse Statistiche aggregate
ValidateTicketQuery CheckInResult Validazione del check-in senza effetti collaterali
GetTicketArticleConfigsQuery List\<TicketArticleConfigResponse> Tutte le configurazioni
GetTicketArticleConfigQuery TicketArticleConfigResponse Singola configurazione
GetTicketCatalogQuery List\<TicketShopItemResponse> Catalogo del negozio online

Services

TicketQrService (Singleton)

Genera codici QR con la libreria QRCoder.

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

TicketPdfService (Scoped)

Crea i PDF dei biglietti in formato A6 con QuestPDF. Contiene il codice QR incorporato, i dettagli del biglietto e le informazioni opzionali sulla sede.

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

ExpireTicketsJob (HostedService)

Job in background che viene eseguito ogni 60 minuti e imposta automaticamente i biglietti scaduti (Status=Purchased, ExpiresAt < adesso) sullo stato Status=Expired.

DayCloseTicketStatsProvider (Scoped)

Fornisce le statistiche dei biglietti per la chiusura giornaliera:

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

Restituisce: SoldOnlineCount/Amount, CheckedInCount, RefundedCount/Amount

Dipendenza cross-modulo opzionale: NullDayCloseTicketStatsProvider registrato come default, viene sovrascritto dal TicketingModule con l'implementazione reale.

Flusso di check-in

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

Enum CheckInStatus:

Valore Significato
0 - Valid Biglietto valido, ingresso consentito
1 - Invalid Biglietto non valido/non trovato
2 - Expired Biglietto scaduto
3 - WrongDate Data dell'evento errata
4 - OutsideTimeWindow Fuori dalla fascia oraria d'ingresso
5 - AlreadyUsed Già registrato all'ingresso
6 - Cancelled Biglietto stornato
7 - NameMismatch Discordanza di nome (biglietto personalizzato)
8 - HasRemainingAmount Importo residuo da pagare (ingresso comunque possibile)

Flusso di checkout del negozio online

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

La verifica della capienza tiene conto di MaxCapacityPerDate e OverbookingPercent:

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

Gestione della capienza

I biglietti venduti vengono memorizzati in TicketArticleConfig.SoldPerDate come dizionario:

{
  "2026-03-15": 42,
  "2026-03-16": 85
}
  • Senza MaxCapacityPerDate: disponibilità illimitata
  • Con MaxCapacityPerDate: limite rigido per data
  • Con OverbookingPercent: consente un overbooking controllato (ad es. 10% = 110 con Max 100)

Acquisto di gruppo

I biglietti possono essere acquistati come gruppo:

  • AllowGroupPurchase e MaxGroupSize su TicketArticleConfig
  • GroupOrderId collega tutti i biglietti di un ordine
  • GroupIndex (a base 1) e GroupTotal per il tracciamento
  • CancelTicketGroupCommand storna l'intero gruppo
  • GuestNames opzionali per biglietti di gruppo personalizzati

Integrazione Disco

Le configurazioni dei biglietti possono essere collegate ai gruppi d'ingresso Disco:

  • EnterGroupId → Assegnazione automatica all'acquisto del biglietto
  • EnterRuleId → Regola d'ingresso specifica
  • FreeArticles → Articoli gratuiti dal modulo Disco
  • Al check-in: è possibile assegnare la carta Disco (AssignedCardId)

Integrazione nella chiusura giornaliera

L'IDayCloseTicketStatsProvider fornisce i seguenti indicatori per la chiusura giornaliera:

Indicatore Descrizione
SoldOnlineCount Biglietti venduti online nel periodo
SoldOnlineAmount Fatturato totale dei biglietti venduti online
CheckedInCount Biglietti registrati all'ingresso nel periodo
RefundedCount Biglietti rimborsati nel periodo
RefundedAmount Importo totale dei rimborsi

Integrazione email

Due template email:

  • ticket_confirmation: viene inviato dopo l'acquisto del biglietto, contiene il PDF del biglietto in allegato
  • ticket_cancellation: viene inviato dopo lo storno

Campi OperationalConfig

Campo Tipo Predefinito Descrizione
TicketingEnabled bool false Modulo ticketing attivato
TicketScanInDirectSale bool true Il POS può scansionare i biglietti

Componenti frontend

Admin — Pagina biglietti (/admin/tickets)

4 schede: 1. Tickets: ricerca, filtro per stato, intervallo di date, tabella con TicketNumber/Name/EventDate/Status/Price, azione di storno, link PDF 2. Capienze: selezione della data, schede di capienza con barra di avanzamento (Venduti/Max/Disponibili) 3. Statistiche: schede con indicatori (Venduti, Registrati all'ingresso, In sospeso, Stornati, Scaduti, Rimborsati, Fatturato) 4. Configurazione: tabella di tutte le configurazioni articolo-biglietto

Admin — Editor articolo (scheda Ticket)

Viene visualizzato quando ExtraOption === 15 (tipo di biglietto): - Collegamento Disco (dropdown EnterGroup) - Extra (bevande gratuite, credito gratuito, guardaroba) - Contingente (MaxCapacity, Overbooking) - Regole di storno (termine, rimborso) - Opzioni (personalizzazione, acquisto di gruppo, fascia oraria, pagamento parziale)

POS — Scansione biglietto (/pos/ticket-scan)

  • Campo di inserimento del codice QR con autofocus
  • Risultato della validazione con stato colorato (verde/arancione/rosso)
  • Dettagli del biglietto (numero, nome, data, ospite, extra)
  • Pulsante d'ingresso per biglietto valido
  • Avviso di importo residuo in caso di pagamento in sospeso

Disco — Ingresso (sezione QR-Ticket)

  • Inserimento del codice QR sotto la scansione della carta
  • Validazione e check-in con assegnazione della carta
  • Visibile solo se TicketingEnabled

Negozio online — Catalogo biglietti

  • Schede biglietto con prezzo, perk (bevande gratuite, credito, guardaroba), selezione della data
  • Integrazione nel carrello con nota ticket:{date}

Registrazione dell'estensione

// 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 }

Registrazione DI (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: supporta CouchDB ed EF Core (SQLite/SQL Server). Configurazione EF Core con vincoli di String-MaxLength e Decimal-Precision(18,4). SoldPerDate viene memorizzato come colonna JSON-TEXT.

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

Dipendenze

Pacchetto Utilizzo
QRCoder Generazione codice QR (PNG, SVG)
QuestPDF Creazione PDF dei biglietti (formato A6)
MediatR Pattern CQRS
FluentValidation Validazione delle richieste