Kihagyás
v26.3

Online jegyértékesítési rendszer

A jegyértékesítési rendszer lehetővé teszi rendezvényjegyek (diszkó, rendezvények) online értékesítését és kezelését QR-kódos beléptetéssel, kapacitáskezeléssel és a napi zárásba való integrációval.

Nem tévesztendő össze az ügyfélportál hibajegyeivel. A beállítás, a beléptetés és az üzemi korlátok a Felhasználói kézikönyv: online jegyértékesítés oldalon találhatók.

Architektúra

A jegyértékesítés funkciócsomagként (Dikas.Features.Ticketing) valósul meg, a diszkó modulhoz hasonlóan. Az IFeatureModule interfészt használja, és DI segítségével kapcsolódik a fő alkalmazáshoz.

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

Entitások

Ticket

SoftDeletableDocument, DocumentType "Ticket", ID-előtag tkt_, cacheAll: false

Mező Típus Leírás
TicketNumber string Olvasható szám (TKT-YYYYMMDD-XXXXXX)
QrCode string Egyedi QR-kód (GUID)
Name string Rendezvény-/jegynév
Status int 0=Purchased, 1=CheckedIn, 2=Cancelled, 3=Expired, 4=Refunded
ArticleId string Hivatkozás a jegytermékre
EventDate DateTime A rendezvény dátuma
TimeWindowStart/End string? Beléptetési időablak (HH:MM)
ExpiresAt DateTime Lejárati dátum (EventDate + 1 nap)
EnterGroupId string? Diszkó belépőcsoport
EnterRuleId string? Diszkó belépési szabály
AssignedCardId string? A beléptetéskor hozzárendelt NFC-kártya
FreeDrinks int Benne foglalt ingyenes italok
FreeAccount decimal Benne foglalt egyenleg
FreeArticles List\<FreeArticleItem> Beállított kötelező/első termékek (történeti mezőnév)
IncludesWardrobe bool Ruhatárral együtt
GuestName string? Személyre szóló vendégnév
GuestEmail string? Vendég e-mail-címe
IsPersonalized bool Személyre szólás kötelező
GroupOrderId string? Csoportos rendelés azonosítója
GroupIndex/GroupTotal int? Pozíció/összes a csoportban
Price decimal Jegyár
PaidAmount decimal Már kifizetett összeg
RemainingAmount decimal Nyitott hátralék
RefundAmount decimal? Visszatérítési összeg
RefundMethod string? Visszatérítés módja
PaymentProvider string Fizetési szolgáltató (stripe, paypal stb.)
PaymentTransactionId string Külső tranzakcióazonosító
TaxClass int Adóosztály
CheckedInAt/By/WorkplaceId DateTime?/string? Beléptetési adatok
CancelledAt/By/CancelReason DateTime?/string? Sztornóadatok
RefundedAt DateTime? A visszatérítés időpontja
StatusHistory List\<TicketStatusEntry> Az állapotváltozások naplója (audit trail)
OrderId string Webáruházas rendelés azonosítója
CustomerId string? Ügyfélhivatkozás

TicketArticleConfig

BaseDocument, DocumentType "TicketArticleConfig", ID-előtag tktcfg_, cacheAll: true

Egy terméket jegytípusként állít be az összes alapértékkel és szabállyal.

Mező Típus Leírás
ArticleId string Kapcsolt termék
EnterGroupId string? Alapértelmezett diszkó belépőcsoport
EnterRuleId string? Alapértelmezett diszkó belépési szabály
FreeDrinks int Alapértelmezett ingyenes italok
FreeAccount decimal Alapértelmezett ingyenes egyenleg
FreeArticles List\<FreeArticleItem> Alapértelmezett kötelező/első termékek (történeti mezőnév)
IncludesWardrobe bool Ruhatárral együtt
MaxCapacityPerDate int? Max. jegyszám dátumonként
OverbookingPercent int? Túlfoglalás %-ban (pl. 10 = +10%)
CancelDeadlineHours int? Sztornóhatáridő órában a rendezvény előtt
AllowRefund bool Visszatérítés engedélyezett
IsPersonalized bool Személyre szólás kötelező
AllowGroupPurchase bool Csoportos vásárlás engedélyezett
MaxGroupSize int Max. csoportméret (alapértelmezés: 10)
TimeWindowStart/End string? Alapértelmezett beléptetési időablak
OnlineOnly bool Csak a webáruházban érhető el
AllowPartialPayment bool Részfizetés/előleg engedélyezett
MinPrepayment decimal Minimális előleg
SoldPerDate Dictionary\<string, int> Eladott jegyek dátumonként (yyyy-MM-dd → darabszám)

API-végpontok

TicketController — /api/v1/tickets [Authorize]

Jegyek:

Metódus Végpont Leírás
GET /{id} Jegy azonosító alapján
GET /by-qr/{qrCode} Jegy QR-kód alapján
GET /by-date?date= Jegyek dátum szerint
GET /by-order/{orderId} Egy rendelés jegyei
GET /by-customer/{customerId} Egy ügyfél jegyei
GET /search?search=&status=&dateFrom=&dateTo=&limit= Jegyek keresése
GET /capacity?date=&articleId= Kapacitások lekérdezése
GET /stats?dateFrom=&dateTo= Statisztikák
POST /validate QR-kód ellenőrzése (beléptetés nélkül)
POST /check-in QR-beléptetés végrehajtása
POST /{id}/cancel Jegy sztornózása
POST /{id}/pay Hátralék kifizetése
POST /{id}/refund Visszatérítés végrehajtása
POST /cancel-group/{groupOrderId} Csoportos rendelés sztornózása

Konfigurációk:

Metódus Végpont Leírás
GET /configs Összes jegykonfiguráció
GET /configs/{id} Konfiguráció azonosító alapján
POST /configs Konfiguráció létrehozása (201)
PUT /configs/{id} Konfiguráció frissítése
DELETE /configs/{id} Konfiguráció törlése

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

Metódus Végpont Leírás
GET /my A bejelentkezett portálügyfél saját jegyei; az ügyfél-azonosító kizárólag a tokenből származik
POST /{id}/cancel Az ügyfél sztornózza a saját jegyét
GET /{id}/qr QR-kód PNG formátumban [AllowAnonymous]
GET /{id}/pdf Jegy PDF formátumban [AllowAnonymous]

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

Metódus Végpont Leírás
GET /catalog Elérhető jegytípusok
GET /capacity?date=&articleId= Elérhetőség ellenőrzése
POST /validate Fizetési folyamat (checkout) ellenőrzése
POST /purchase Leállítva: mindig 410 Gone választ ad (TICKET_SHOP_PURCHASE_RETIRED)

Parancsok (Commands)

Command Visszatérési érték Leírás
CreateTicketCommand List\<TicketResponse> 1 vagy több jegyet hoz létre, QR-kódot és számot generál, visszaigazoló e-mailt küld PDF-fel
CheckInTicketCommand CheckInResult Ellenőrzi a QR-kódot, az állapotot CheckedIn értékre állítja, rögzíti az időbélyeget
CancelTicketCommand TicketResponse Sztornózza a jegyet (csak Status=Purchased esetén), ellenőrzi a sztornóhatáridőt, sztornóértesítő e-mailt küld
ProcessTicketPaymentCommand TicketResponse Lekönyveli a hátralék kifizetését, frissíti a PaidAmount/RemainingAmount mezőket
RefundTicketCommand TicketResponse Visszatérítés (csak Status=Cancelled esetén), ellenőrzi az AllowRefund értéket
CancelTicketGroupCommand List\<TicketResponse> Egy csoportos rendelés összes jegyét sztornózza
ValidateTicketCheckoutCommand TicketCheckoutValidationResult Kapacitás- és konfigurációellenőrzés a webáruház számára
CreateTicketArticleConfigCommand TicketArticleConfigResponse Új jegykonfiguráció létrehozása
UpdateTicketArticleConfigCommand TicketArticleConfigResponse Konfiguráció frissítése
DeleteTicketArticleConfigCommand bool Konfiguráció törlése

Lekérdezések (Queries)

Query Visszatérési érték Leírás
GetTicketByIdQuery TicketResponse Egyetlen jegy
GetTicketByQrCodeQuery TicketResponse Jegy keresése QR-kód alapján
GetTicketsByDateQuery List\<TicketResponse> Jegyek a rendezvény dátuma szerint
GetTicketsByCustomerQuery List\<TicketResponse> Egy ügyfél jegyei
GetTicketsByOrderQuery List\<TicketResponse> Egy rendelés jegyei
SearchTicketsQuery List\<TicketResponse> Teljes szöveges keresés szűrőkkel
GetTicketCapacityQuery List\<TicketCapacityResponse> Kapacitás termékenként/dátumonként
GetTicketStatsQuery TicketStatsResponse Összesített statisztikák
ValidateTicketQuery CheckInResult Beléptetési ellenőrzés mellékhatások nélkül
GetTicketArticleConfigsQuery List\<TicketArticleConfigResponse> Összes konfiguráció
GetTicketArticleConfigQuery TicketArticleConfigResponse Egyetlen konfiguráció
GetTicketCatalogQuery List\<TicketShopItemResponse> Webáruház-katalógus

Szolgáltatások (Services)

TicketQrService (Singleton)

QR-kódokat generál a QRCoder könyvtárral.

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

TicketPdfService (Scoped)

A6-os formátumú jegy-PDF-eket készít a QuestPDF segítségével. Tartalmazza a beágyazott QR-kódot, a jegy részleteit és opcionálisan a helyszín adatait.

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

ExpireTicketsJob (HostedService)

Háttérfeladat, amely 60 percenként fut, és a lejárt jegyeket (Status=Purchased, ExpiresAt < most) automatikusan Status=Expired állapotra állítja.

DayCloseTicketStatsProvider (Scoped)

Jegystatisztikákat szolgáltat a napi záráshoz:

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

Visszatérési érték: SoldOnlineCount/Amount, CheckedInCount, RefundedCount/Amount

Opcionális modulok közötti függőség: alapértelmezésként a NullDayCloseTicketStatsProvider van regisztrálva, ezt a TicketingModule a valódi megvalósítással írja felül.

Beléptetési folyamat

QR-kód beolvasása
    ↓
ValidateTicketQuery
    ├── Jegy nem található → Invalid
    ├── Status != Purchased → AlreadyUsed / Cancelled / Expired
    ├── EventDate != ma → WrongDate
    ├── TimeWindow-n kívül → OutsideTimeWindow
    ├── RemainingAmount > 0 → HasRemainingAmount (figyelmeztetés, beléptetés lehetséges)
    └── Minden rendben → Valid
    ↓
CheckInTicketCommand
    ├── Status → CheckedIn
    ├── CheckedInAt/By/WorkplaceId beállítása
    ├── AssignedCardId beállítása (opcionális, diszkókártyához)
    └── StatusHistory-bejegyzés létrehozása

CheckInStatus enum:

Érték Jelentés
0 - Valid A jegy érvényes, beléptetés engedélyezett
1 - Invalid A jegy érvénytelen/nem található
2 - Expired A jegy lejárt
3 - WrongDate Hibás rendezvénydátum
4 - OutsideTimeWindow A beléptetési időablakon kívül
5 - AlreadyUsed Már beléptetve
6 - Cancelled A jegy sztornózva
7 - NameMismatch Eltérő név (személyre szóló jegy)
8 - HasRemainingAmount Nyitott hátralék (a beléptetés ennek ellenére lehetséges)

Webáruházas fizetési folyamat (checkout)

1. GET /ticket-shop/catalog
   → Az elérhető jegytípusok listája

2. GET /ticket-shop/capacity?date=2026-03-15
   → Elérhetőség a kiválasztott dátumra

3. POST /ticket-shop/validate
   { articleId, eventDate, quantity }
   → Kapacitásellenőrzés, árkalkuláció

4. Jegytermékek megrendelése a szokásos webáruházas kosáron keresztül

5. Online fizetés megerősítése a `/api/v1/online-checkout` végponton
   → Nyugta feldolgozása
   → ezután idempotens jegykiállítás az `OnlineOrderTicketIssuer` által

A `POST /ticket-shop/purchase` szándékosan le van állítva, mert ez a régi út
bizonylat nélkül állított ki jegyeket. Vásárlási útként nem szabad újra használni.

A kapacitásellenőrzés figyelembe veszi a MaxCapacityPerDate és az OverbookingPercent értéket:

Tényleges kapacitás = MaxCapacity + (MaxCapacity × OverbookingPercent / 100)
Elérhető = Tényleges kapacitás - SoldPerDate[dátum]

Kapacitáskezelés

Az eladott jegyek a TicketArticleConfig.SoldPerDate mezőben, szótárként (dictionary) tárolódnak:

{
  "2026-03-15": 42,
  "2026-03-16": 85
}
  • MaxCapacityPerDate nélkül: korlátlan elérhetőség
  • MaxCapacityPerDate értékkel: kemény korlát dátumonként
  • OverbookingPercent értékkel: ellenőrzött túlfoglalást tesz lehetővé (pl. 10% = 110 max. 100 esetén)

Csoportos vásárlás

A jegyek csoportként is megvásárolhatók:

  • AllowGroupPurchase és MaxGroupSize a TicketArticleConfig konfigurációban
  • A GroupOrderId összeköti egy rendelés összes jegyét
  • GroupIndex (1-től számozva) és GroupTotal a nyomon követéshez
  • A CancelTicketGroupCommand a teljes csoportot sztornózza
  • A webáruházban vásárolt, személyre szóló csoportos jegyeken jelenleg minden jegyen a megrendelő neve áll; az egyes résztvevők nevét a fizetési folyamat nem rögzíti

Diszkó-integráció

A jegykonfigurációk diszkó belépőcsoportokhoz kapcsolhatók:

  • EnterGroupId → automatikus hozzárendelés jegyvásárláskor
  • EnterRuleId → konkrét belépési szabály
  • FreeArticles → kötelező/első termékek a diszkó modulból (történeti mezőnév; normál áron)
  • Beléptetéskor: diszkókártya (AssignedCardId) rendelhető hozzá

Integráció a napi zárásba

Az IDayCloseTicketStatsProvider a következő mutatókat szolgáltatja a napi záráshoz:

Mutató Leírás
SoldOnlineCount Az időszakban online eladott jegyek
SoldOnlineAmount Az online eladott jegyek teljes forgalma
CheckedInCount Az időszakban beléptetett jegyek
RefundedCount Az időszakban visszatérített jegyek
RefundedAmount A visszatérítések teljes összege

E-mail-integráció

Két e-mail-sablon:

  • ticket_confirmation: jegyvásárlás után kerül kiküldésre, mellékletként tartalmazza a PDF-jegyet
  • ticket_cancellation: sztornózás után kerül kiküldésre

OperationalConfig mezők

Mező Típus Alapértelmezés Leírás
TicketingEnabled bool false A jegyértékesítési modul aktiválva
TicketScanInDirectSale bool true A pénztár (POS) jegyeket olvashat be

Frontend-komponensek

Adminisztráció — Jegyek oldal (/admin/tickets)

4 fül: 1. Jegyek: keresés, állapotszűrő, dátumtartomány, táblázat TicketNumber/Name/EventDate/Status/Price oszlopokkal, sztornóművelet, PDF-hivatkozás 2. Kapacitások: dátumválasztás, kapacitáskártyák folyamatjelzővel (eladva/max./elérhető) 3. Statisztikák: mutatókártyák (eladva, beléptetve, nyitott, sztornózva, lejárt, visszatérítve, forgalom) 4. Konfiguráció: az összes jegytermék-konfiguráció táblázata

Adminisztráció — Termékszerkesztő (Jegy fül)

Akkor jelenik meg, ha ExtraOption === 15 (jegytípus): - Diszkókapcsolat (EnterGroup legördülő lista) - Extrák (ingyenes italok, ingyenes egyenleg, ruhatár) - Kontingens (MaxCapacity, Overbooking) - Sztornószabályok (határidő, visszatérítés) - Opciók (személyre szólás, csoportos vásárlás, időablak, részfizetés)

POS — Jegybeolvasás (/pos/ticket-scan)

  • QR-kód beviteli mező automatikus fókusszal
  • Ellenőrzési eredmény színes állapottal (zöld/narancs/piros)
  • Jegyadatok (szám, név, dátum, vendég, extrák)
  • Beléptetés gomb érvényes jegy esetén
  • Hátralék-figyelmeztetés nyitott fizetésnél

Diszkó — Beléptetés (QR-jegy szakasz)

  • QR-kód bevitel a kártyabeolvasás alatt
  • Ellenőrzés és beléptetés kártya-hozzárendeléssel
  • Csak akkor látható, ha a TicketingEnabled be van kapcsolva

Webáruház — Jegykatalógus

  • Jegykártyák árral, extrákkal (ingyenes italok, egyenleg, ruhatár) és dátumválasztással
  • Kosár-integráció ticket:{date} megjegyzéssel
  • Csak jegyeket tartalmazó beágyazás a /shop/tickets (illetve /order/tickets) címen dátummal, csoportlétszámmal, aktuális kapacitással és automatikus magasságjelzéssel postMessage segítségével
  • Beágyazás csak OnlineShopConfig.IframeAllowed=true esetén és pontos HTTPS-originnel az IframeAllowedOrigins listában; a CSP kizárólag ezt az engedélylistát tartalmazza

Bővítmény-regisztráció

// 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-regisztráció (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(...)

Adatbázis: támogatja a CouchDB-t és az EF Core-t (SQLite/SQL Server). Az EF Core-konfiguráció string-MaxLength-korlátozásokat és Decimal-Precision(18,4) pontosságot használ. A SoldPerDate JSON-TEXT oszlopként tárolódik.

ID-előtagok (IdGenerator): "Ticket" → "tkt", "TicketArticleConfig" → "tktcfg"

Függőségek

Csomag Felhasználás
QRCoder QR-kód-generálás (PNG, SVG)
QuestPDF PDF-jegy készítése (A6 formátum)
MediatR CQRS-minta
FluentValidation Kérés-ellenőrzés