mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
200 lines
8.3 KiB
Go
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())
|
|
}
|