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()) }