Online Biletleme Sistemi¶
Biletleme sistemi, QR kod tabanlı giriş, kapasite yönetimi ve gün sonu raporuna entegrasyon ile etkinlik biletlerinin (Disco, etkinlikler) online satışını ve yönetimini sağlar.
Mimari¶
Biletleme, Disco modülüne benzer şekilde bir Feature-Pack (Dikas.Features.Ticketing) olarak uygulanmıştır. IFeatureModule arayüzünü kullanır ve DI aracılığıyla ana uygulamaya entegre edilir.
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
| Alan | Tür | Açıklama |
|---|---|---|
| TicketNumber | string | Okunabilir numara (TKT-YYYYMMDD-XXXXXX) |
| QrCode | string | Benzersiz QR kodu (GUID) |
| Name | string | Etkinlik/bilet adı |
| Status | int | 0=Purchased, 1=CheckedIn, 2=Cancelled, 3=Expired, 4=Refunded |
| ArticleId | string | Bilet ürününe referans |
| EventDate | DateTime | Etkinlik tarihi |
| TimeWindowStart/End | string? | Giriş zaman aralığı (HH:MM) |
| ExpiresAt | DateTime | Son kullanma tarihi (EventDate + 1 gün) |
| EnterGroupId | string? | Disco giriş grubu |
| EnterRuleId | string? | Disco giriş kuralı |
| AssignedCardId | string? | Check-in sırasında atanan NFC kartı |
| FreeDrinks | int | Dahil ücretsiz içecekler |
| FreeAccount | decimal | Dahil bakiye |
| FreeArticles | List\<FreeArticleItem> | Dahil ücretsiz ürünler |
| IncludesWardrobe | bool | Vestiyer dahil |
| GuestName | string? | Kişiselleştirilmiş misafir adı |
| GuestEmail | string? | Misafir e-postası |
| IsPersonalized | bool | Kişiselleştirme zorunluluğu |
| GroupOrderId | string? | Grup sipariş ID'si |
| GroupIndex/GroupTotal | int? | Gruptaki konum/toplam |
| Price | decimal | Bilet fiyatı |
| PaidAmount | decimal | Halihazırda ödenen tutar |
| RemainingAmount | decimal | Kalan açık tutar |
| RefundAmount | decimal? | İade tutarı |
| RefundMethod | string? | İade yöntemi |
| PaymentProvider | string | Ödeme sağlayıcısı (stripe, paypal, etc.) |
| PaymentTransactionId | string | Harici işlem ID'si |
| TaxClass | int | Vergi sınıfı |
| CheckedInAt/By/WorkplaceId | DateTime?/string? | Check-in verileri |
| CancelledAt/By/CancelReason | DateTime?/string? | İptal verileri |
| RefundedAt | DateTime? | İade zamanı |
| StatusHistory | List\<TicketStatusEntry> | Durum değişikliklerinin denetim izi |
| OrderId | string | Mağaza sipariş ID'si |
| CustomerId | string? | Müşteri referansı |
TicketArticleConfig¶
BaseDocument, DocumentType "TicketArticleConfig", ID-Prefix tktcfg_, cacheAll: true
Bir ürünü tüm varsayılanları ve kurallarıyla bilet türü olarak yapılandırır.
| Alan | Tür | Açıklama |
|---|---|---|
| ArticleId | string | İlişkilendirilmiş ürün |
| EnterGroupId | string? | Standart Disco giriş grubu |
| EnterRuleId | string? | Standart Disco giriş kuralı |
| FreeDrinks | int | Standart ücretsiz içecekler |
| FreeAccount | decimal | Standart ücretsiz bakiye |
| FreeArticles | List\<FreeArticleItem> | Standart ücretsiz ürünler |
| IncludesWardrobe | bool | Vestiyer dahil |
| MaxCapacityPerDate | int? | Tarih başına maks. bilet |
| OverbookingPercent | int? | % cinsinden aşırı rezervasyon (örn. 10 = +%10) |
| CancelDeadlineHours | int? | Etkinlikten önce saat cinsinden iptal süresi |
| AllowRefund | bool | İade izni |
| IsPersonalized | bool | Kişiselleştirme zorunluluğu |
| AllowGroupPurchase | bool | Grup satın alma izni |
| MaxGroupSize | int | Maks. grup boyutu (Standart: 10) |
| TimeWindowStart/End | string? | Standart giriş zaman aralığı |
| OnlineOnly | bool | Yalnızca online mağazada mevcut |
| AllowPartialPayment | bool | Kısmi ödeme/peşinat izni |
| MinPrepayment | decimal | Minimum peşinat |
| SoldPerDate | Dictionary\<string, int> | Tarih başına satılan biletler (yyyy-MM-dd → adet) |
API Uç Noktaları¶
TicketController — /api/v1/tickets [Authorize]¶
Biletler:
| Yöntem | Uç Nokta | Açıklama |
|---|---|---|
| GET | /{id} |
ID'ye göre bilet |
| GET | /by-qr/{qrCode} |
QR koduna göre bilet |
| GET | /by-date?date= |
Tarihe göre biletler |
| GET | /by-order/{orderId} |
Bir siparişin biletleri |
| GET | /by-customer/{customerId} |
Bir müşterinin biletleri |
| GET | /search?search=&status=&dateFrom=&dateTo=&limit= |
Bilet ara |
| GET | /capacity?date=&articleId= |
Kapasiteleri sorgula |
| GET | /stats?dateFrom=&dateTo= |
İstatistikler |
| POST | /validate |
QR kodunu doğrula (check-in olmadan) |
| POST | /check-in |
QR girişi gerçekleştir |
| POST | /{id}/cancel |
Bileti iptal et |
| POST | /{id}/pay |
Kalan tutarı öde |
| POST | /{id}/refund |
İade gerçekleştir |
| POST | /cancel-group/{groupOrderId} |
Grup siparişini iptal et |
Yapılandırmalar:
| Yöntem | Uç Nokta | Açıklama |
|---|---|---|
| GET | /configs |
Tüm bilet yapılandırmaları |
| GET | /configs/{id} |
ID'ye göre yapılandırma |
| POST | /configs |
Yapılandırma oluştur (201) |
| PUT | /configs/{id} |
Yapılandırmayı güncelle |
| DELETE | /configs/{id} |
Yapılandırmayı sil |
TicketPortalController — /api/v1/portal/tickets [Authorize(Policy="Portal")]¶
| Yöntem | Uç Nokta | Açıklama |
|---|---|---|
| GET | /my?customerId= |
Müşterinin kendi biletleri |
| POST | /{id}/cancel |
Müşteri kendi biletini iptal eder |
| GET | /{id}/qr |
PNG olarak QR kodu [AllowAnonymous] |
| GET | /{id}/pdf |
PDF olarak bilet [AllowAnonymous] |
TicketShopController — /api/v1/ticket-shop [AllowAnonymous]¶
| Yöntem | Uç Nokta | Açıklama |
|---|---|---|
| GET | /catalog |
Mevcut bilet türleri |
| GET | /capacity?date=&articleId= |
Müsaitliği kontrol et |
| POST | /validate |
Ödemeyi doğrula |
| POST | /purchase |
Bilet satın al |
Commands¶
| Command | Geri Dönüş | Açıklama |
|---|---|---|
| CreateTicketCommand | List\<TicketResponse> | 1+ bilet oluşturur, QR + numara üretir, PDF ile onay e-postası gönderir |
| CheckInTicketCommand | CheckInResult | QR'ı doğrular, durumu CheckedIn olarak ayarlar, zaman damgasını kaydeder |
| CancelTicketCommand | TicketResponse | Bileti iptal eder (yalnızca Status=Purchased), iptal süresini kontrol eder, iptal e-postası gönderir |
| ProcessTicketPaymentCommand | TicketResponse | Kalan ödemeyi kaydeder, PaidAmount/RemainingAmount günceller |
| RefundTicketCommand | TicketResponse | İade (yalnızca Status=Cancelled), AllowRefund kontrol eder |
| CancelTicketGroupCommand | List\<TicketResponse> | Bir grup siparişinin tüm biletlerini iptal eder |
| ValidateTicketCheckoutCommand | TicketCheckoutValidationResult | Mağaza için kapasite ve yapılandırma kontrolü |
| CreateTicketArticleConfigCommand | TicketArticleConfigResponse | Yeni bilet yapılandırması oluştur |
| UpdateTicketArticleConfigCommand | TicketArticleConfigResponse | Yapılandırmayı güncelle |
| DeleteTicketArticleConfigCommand | bool | Yapılandırmayı sil |
Queries¶
| Query | Geri Dönüş | Açıklama |
|---|---|---|
| GetTicketByIdQuery | TicketResponse | Tek bir bilet |
| GetTicketByQrCodeQuery | TicketResponse | QR koduyla bilet ara |
| GetTicketsByDateQuery | List\<TicketResponse> | Etkinlik tarihine göre biletler |
| GetTicketsByCustomerQuery | List\<TicketResponse> | Bir müşterinin biletleri |
| GetTicketsByOrderQuery | List\<TicketResponse> | Bir siparişin biletleri |
| SearchTicketsQuery | List\<TicketResponse> | Filtrelerle tam metin araması |
| GetTicketCapacityQuery | List\<TicketCapacityResponse> | Ürün/tarih başına kapasite |
| GetTicketStatsQuery | TicketStatsResponse | Toplu istatistikler |
| ValidateTicketQuery | CheckInResult | Yan etkisiz check-in doğrulaması |
| GetTicketArticleConfigsQuery | List\<TicketArticleConfigResponse> | Tüm yapılandırmalar |
| GetTicketArticleConfigQuery | TicketArticleConfigResponse | Tek bir yapılandırma |
| GetTicketCatalogQuery | List\<TicketShopItemResponse> | Online mağaza kataloğu |
Services¶
TicketQrService (Singleton)¶
QRCoder kütüphanesi ile QR kodları üretir.
interface ITicketQrService {
byte[] GeneratePng(string content, int pixelsPerModule = 10);
string GenerateSvg(string content);
}
TicketPdfService (Scoped)¶
QuestPDF ile A6 formatında bilet PDF'leri oluşturur. Gömülü QR kodu, bilet ayrıntıları ve isteğe bağlı mekan bilgileri içerir.
ExpireTicketsJob (HostedService)¶
Her 60 dakikada bir çalışan ve süresi dolmuş biletleri (Status=Purchased, ExpiresAt < şimdi) otomatik olarak Status=Expired olarak ayarlayan arka plan işi.
DayCloseTicketStatsProvider (Scoped)¶
Gün sonu raporu için bilet istatistikleri sunar:
interface IDayCloseTicketStatsProvider {
Task<DayCloseTicketStats> GetStatsForPeriodAsync(DateTime from, DateTime to, CancellationToken ct);
}
Geri dönüş: SoldOnlineCount/Amount, CheckedInCount, RefundedCount/Amount
İsteğe bağlı modüller arası bağımlılık: Varsayılan olarak NullDayCloseTicketStatsProvider kaydedilir, TicketingModule tarafından gerçek uygulamayla geçersiz kılınır.
Check-in akışı¶
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:
| Değer | Anlamı |
|---|---|
| 0 - Valid | Bilet geçerli, giriş izni var |
| 1 - Invalid | Bilet geçersiz/bulunamadı |
| 2 - Expired | Biletin süresi dolmuş |
| 3 - WrongDate | Yanlış etkinlik tarihi |
| 4 - OutsideTimeWindow | Giriş zaman aralığının dışında |
| 5 - AlreadyUsed | Zaten check-in yapılmış |
| 6 - Cancelled | Bilet iptal edilmiş |
| 7 - NameMismatch | İsim uyuşmazlığı (kişiselleştirilmiş bilet) |
| 8 - HasRemainingAmount | Açık kalan tutar (yine de giriş mümkün) |
Online Mağaza Ödeme Akışı¶
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
Kapasite kontrolü MaxCapacityPerDate ve OverbookingPercent değerlerini dikkate alır:
Effektive Kapazität = MaxCapacity + (MaxCapacity × OverbookingPercent / 100)
Verfügbar = Effektive Kapazität - SoldPerDate[datum]
Kapasite Yönetimi¶
Satılan biletler TicketArticleConfig.SoldPerDate içinde sözlük (Dictionary) olarak saklanır:
- MaxCapacityPerDate olmadan: Sınırsız müsaitlik
- MaxCapacityPerDate ile: Tarih başına kesin sınır
- OverbookingPercent ile: Kontrollü aşırı rezervasyona izin verir (örn. %10 = Maks 100'de 110)
Grup Satın Alma¶
Biletler grup olarak satın alınabilir:
- TicketArticleConfig üzerinde
AllowGroupPurchaseveMaxGroupSize GroupOrderIdbir siparişin tüm biletlerini birbirine bağlar- Takip için
GroupIndex(1 tabanlı) veGroupTotal CancelTicketGroupCommandtüm grubu iptal eder- Kişiselleştirilmiş grup biletleri için isteğe bağlı
GuestNames
Disco Entegrasyonu¶
Bilet yapılandırmaları Disco giriş gruplarıyla ilişkilendirilebilir:
EnterGroupId→ Bilet satın alımında otomatik atamaEnterRuleId→ Belirli giriş kuralıFreeArticles→ Disco modülünden ücretsiz ürünler- Check-in sırasında: Disco kartı (
AssignedCardId) atanabilir
Gün Sonu Raporu Entegrasyonu¶
IDayCloseTicketStatsProvider gün sonu raporu için şu göstergeleri sunar:
| Gösterge | Açıklama |
|---|---|
| SoldOnlineCount | Dönem içinde online satılan biletler |
| SoldOnlineAmount | Online satılan biletlerin toplam cirosu |
| CheckedInCount | Dönem içinde check-in yapılan biletler |
| RefundedCount | Dönem içinde iade edilen biletler |
| RefundedAmount | Toplam iade tutarı |
E-posta Entegrasyonu¶
İki e-posta şablonu:
- ticket_confirmation: Bilet satın alımından sonra gönderilir, ek olarak PDF bilet içerir
- ticket_cancellation: İptal sonrasında gönderilir
OperationalConfig Alanları¶
| Alan | Tür | Standart | Açıklama |
|---|---|---|---|
| TicketingEnabled | bool | false | Biletleme modülü etkin |
| TicketScanInDirectSale | bool | true | POS biletleri tarayabilir |
Frontend Bileşenleri¶
Yönetici — Biletler sayfası (/admin/tickets)¶
4 sekme: 1. Biletler: Arama, durum filtresi, tarih aralığı, TicketNumber/Name/EventDate/Status/Price içeren tablo, iptal işlemi, PDF bağlantısı 2. Kapasiteler: Tarih seçimi, ilerleme çubuğuyla kapasite kartları (Satılan/Maks/Mevcut) 3. İstatistikler: Gösterge kartları (Satılan, Check-in yapılan, Açık, İptal edilen, Süresi dolan, İade edilen, Ciro) 4. Yapılandırma: Tüm bilet ürün yapılandırmalarının tablosu
Yönetici — Ürün düzenleyici (Bilet sekmesi)¶
ExtraOption === 15 (bilet türü) olduğunda gösterilir:
- Disco bağlantısı (EnterGroup açılır menüsü)
- Ekstralar (Ücretsiz içecekler, ücretsiz bakiye, vestiyer)
- Kontenjan (MaxCapacity, Overbooking)
- İptal kuralları (Süre, iade)
- Seçenekler (Kişiselleştirme, grup satın alma, zaman aralığı, kısmi ödeme)
POS — Bilet taraması (/pos/ticket-scan)¶
- Otomatik odaklı QR kod giriş alanı
- Renkli durumla doğrulama sonucu (Yeşil/Turuncu/Kırmızı)
- Bilet ayrıntıları (Numara, ad, tarih, misafir, ekstralar)
- Geçerli biletlerde giriş düğmesi
- Açık ödemede kalan tutar uyarısı
Disco — Giriş (QR bilet bölümü)¶
- Kart taramasının altında QR kod girişi
- Kart atamasıyla doğrulama ve check-in
- Yalnızca
TicketingEnabledolduğunda görünür
Online Mağaza — Bilet kataloğu¶
- Fiyat, avantajlar (Ücretsiz içecekler, bakiye, vestiyer), tarih seçimi içeren bilet kartları
ticket:{date}notuyla sepet entegrasyonu
Extension kaydı¶
// 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 kaydı (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(...)
Veritabanı: CouchDB ve EF Core (SQLite/SQL Server) destekler. String-MaxLength kısıtlamaları ve Decimal-Precision(18,4) ile EF Core yapılandırması. SoldPerDate, JSON-TEXT sütunu olarak saklanır.
ID önekleri (IdGenerator): "Ticket" → "tkt", "TicketArticleConfig" → "tktcfg"
Bağımlılıklar¶
| Paket | Kullanım |
|---|---|
| QRCoder | QR kod üretimi (PNG, SVG) |
| QuestPDF | PDF bilet oluşturma (A6 formatı) |
| MediatR | CQRS deseni |
| FluentValidation | İstek doğrulama |