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.
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:
- 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ésMaxGroupSizea TicketArticleConfig konfigurációban- A
GroupOrderIdösszeköti egy rendelés összes jegyét GroupIndex(1-től számozva) ésGroupTotala nyomon követéshez- A
CancelTicketGroupCommanda 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áskorEnterRuleId→ konkrét belépési szabályFreeArticles→ 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
TicketingEnabledbe 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ésselpostMessagesegítségével - Beágyazás csak
OnlineShopConfig.IframeAllowed=trueesetén és pontos HTTPS-originnel azIframeAllowedOriginslistá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 |