Files
MyClub/internal/models/ticket.go
T
Tomas Dvorak dfc079288f hot fix #1
2026-01-26 08:13:18 +01:00

200 lines
8.3 KiB
Go

package models
import (
"crypto/rand"
"fmt"
"math/big"
"time"
"gorm.io/gorm"
)
// TicketType represents a type of ticket with pricing and rules
type TicketType struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Name string `gorm:"not null" json:"name"`
Description string `gorm:"type:text" json:"description"`
PriceCents int64 `gorm:"not null" json:"price_cents"`
Currency string `gorm:"size:10;default:'CZK'" json:"currency"`
Color string `gorm:"size:20;default:'primary'" json:"color"` // primary, secondary, success, warning, danger
DisplayOrder int `gorm:"default:0" json:"display_order"`
Active bool `gorm:"default:true;index" json:"active"`
MaxTicketsPerOrder int `gorm:"default:10" json:"max_tickets_per_order"`
SaleStartTime *time.Time `json:"sale_start_time"`
SaleEndTime *time.Time `json:"sale_end_time"`
RequiresMembership bool `gorm:"default:false" json:"requires_membership"`
MinAge *int `json:"min_age"`
}
func (TicketType) TableName() string { return "ticket_types" }
// TicketCampaign represents a ticket sales campaign for a specific match or event
type TicketCampaign struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Title string `gorm:"not null" json:"title"`
Description string `gorm:"type:text" json:"description"`
// Match information
ExternalMatchID *string `gorm:"index" json:"external_match_id"` // FACR match ID
CompetitionCode *string `json:"competition_code"`
MatchDateTime *time.Time `json:"match_date_time"`
HomeTeam *string `json:"home_team"`
AwayTeam *string `json:"away_team"`
Venue *string `json:"venue"`
// Campaign settings
Active bool `gorm:"default:true;index" json:"active"`
SaleStartTime time.Time `gorm:"not null" json:"sale_start_time"`
SaleEndTime time.Time `gorm:"not null" json:"sale_end_time"`
MaxTotalTickets *int `json:"max_total_tickets"`
// Relationships
TicketTypes []TicketType `gorm:"many2many:campaign_ticket_types;" json:"ticket_types,omitempty"`
CampaignTicketTypes []CampaignTicketType `gorm:"foreignKey:CampaignID" json:"campaign_ticket_types,omitempty"`
Tickets []Ticket `gorm:"foreignKey:CampaignID" json:"tickets,omitempty"`
}
func (TicketCampaign) TableName() string { return "ticket_campaigns" }
// CampaignTicketType links ticket types to campaigns with campaign-specific overrides
type CampaignTicketType struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CampaignID uint `gorm:"not null;index" json:"campaign_id"`
Campaign TicketCampaign `gorm:"foreignKey:CampaignID" json:"-"`
TicketTypeID uint `gorm:"not null;index" json:"ticket_type_id"`
TicketType TicketType `gorm:"foreignKey:TicketTypeID" json:"-"`
// Campaign-specific overrides
PriceCents *int64 `json:"price_cents"` // Override default price if set
MaxQuantity *int `json:"max_quantity"` // Campaign-specific quantity limit
}
func (CampaignTicketType) TableName() string { return "campaign_ticket_types" }
// Ticket represents an actual sold/reserved ticket
type Ticket struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OrderID *uint `gorm:"index" json:"order_id,omitempty"`
Order *EshopOrder `gorm:"foreignKey:OrderID" json:"order,omitempty"`
CampaignID uint `gorm:"not null;index" json:"campaign_id"`
Campaign TicketCampaign `gorm:"foreignKey:CampaignID" json:"campaign,omitempty"`
TicketTypeID uint `gorm:"not null;index" json:"ticket_type_id"`
TicketType TicketType `gorm:"foreignKey:TicketTypeID" json:"ticket_type,omitempty"`
// Ticket holder information
HolderName string `gorm:"not null" json:"holder_name"`
HolderEmail string `gorm:"not null;index" json:"holder_email"`
HolderPhone string `gorm:"size:50" json:"holder_phone"`
// Ticket details
Quantity int `gorm:"not null;default:1" json:"quantity"`
UnitPriceCents int64 `gorm:"not null" json:"unit_price_cents"`
TotalPriceCents int64 `gorm:"not null" json:"total_price_cents"`
Currency string `gorm:"size:10;default:'CZK'" json:"currency"`
// Status tracking
Status string `gorm:"size:20;default:'reserved';index" json:"status"` // reserved, paid, cancelled, used
Barcode string `gorm:"unique" json:"barcode"`
QRCodeData string `gorm:"type:text" json:"qr_code_data"`
// Usage tracking
UsedAt *time.Time `json:"used_at"`
UsedBy *string `json:"used_by"`
}
func (Ticket) TableName() string { return "tickets" }
// TicketAvailability tracks available tickets per campaign/type
type TicketAvailability struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CampaignID uint `gorm:"not null;uniqueIndex:idx_campaign_type" json:"campaign_id"`
Campaign TicketCampaign `gorm:"foreignKey:CampaignID" json:"-"`
TicketTypeID uint `gorm:"not null;uniqueIndex:idx_campaign_type" json:"ticket_type_id"`
TicketType TicketType `gorm:"foreignKey:TicketTypeID" json:"-"`
TotalCapacity int `gorm:"not null;default:0" json:"total_capacity"`
SoldQuantity int `gorm:"not null;default:0" json:"sold_quantity"`
ReservedQuantity int `gorm:"not null;default:0" json:"reserved_quantity"`
AvailableQuantity int `gorm:"-" json:"available_quantity"` // Computed field
}
func (TicketAvailability) TableName() string { return "ticket_availability" }
// AvailableTicketView represents the database view for available tickets
type AvailableTicketView struct {
CampaignID uint `json:"campaign_id"`
CampaignTitle string `json:"campaign_title"`
CampaignDescription string `json:"campaign_description"`
ExternalMatchID *string `json:"external_match_id"`
CompetitionCode *string `json:"competition_code"`
MatchDateTime *time.Time `json:"match_date_time"`
HomeTeam *string `json:"home_team"`
AwayTeam *string `json:"away_team"`
Venue *string `json:"venue"`
SaleStartTime time.Time `json:"sale_start_time"`
SaleEndTime time.Time `json:"sale_end_time"`
TicketTypeID uint `json:"ticket_type_id"`
TicketTypeName string `json:"ticket_type_name"`
TicketTypeDesc string `json:"ticket_type_description"`
PriceCents int64 `json:"price_cents"`
MaxPerOrder int `json:"max_per_order"`
Color string `json:"color"`
AvailableQuantity int `json:"available_quantity"`
TotalCapacity int `json:"total_capacity"`
SaleStatus string `json:"sale_status"` // upcoming, available, sold_out, ended
}
func (AvailableTicketView) TableName() string { return "available_tickets_view" }
// Helper methods
// IsAvailable checks if tickets are currently available for purchase
func (atv AvailableTicketView) IsAvailable() bool {
return atv.SaleStatus == "available" && atv.AvailableQuantity > 0
}
// GetPriceInCZK returns price formatted for display
func (atv AvailableTicketView) GetPriceInCZK() float64 {
return float64(atv.PriceCents) / 100.0
}
// GetFormattedPrice returns price as string with CZK
func (atv AvailableTicketView) GetFormattedPrice() string {
return fmt.Sprintf("%.0f Kč", atv.GetPriceInCZK())
}
// BeforeCreate hook for Ticket to generate barcode
func (t *Ticket) BeforeCreate(tx *gorm.DB) error {
if t.Barcode == "" {
// Generate unique barcode
t.Barcode = generateTicketBarcode()
}
return nil
}
// generateTicketBarcode creates a unique barcode for tickets
func generateTicketBarcode() string {
// Generate random number for uniqueness
n, _ := rand.Int(rand.Reader, big.NewInt(10000))
// Simple implementation - in production, use proper barcode generation
return fmt.Sprintf("TKT-%d-%d", time.Now().Unix(), n.Int64())
}