mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
hot fix #1
This commit is contained in:
@@ -2,8 +2,8 @@ package models
|
||||
|
||||
type CommentReaction struct {
|
||||
BaseModel
|
||||
CommentID uint `json:"comment_id" gorm:"index;not null"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
CommentID uint `json:"comment_id" gorm:"not null"`
|
||||
UserID uint `json:"user_id" gorm:"not null"`
|
||||
Type string `json:"type" gorm:"size:24;not null;index"` // like|heart|smile|laugh|thumbs_up|thumbs_down|sad|angry
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EshopProductCategory represents a product category in the e-shop (e.g. dresy, čepice)
|
||||
type EshopProductCategory 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:"-"`
|
||||
|
||||
Slug string `gorm:"size:190;uniqueIndex;not null" json:"slug"`
|
||||
Name string `gorm:"size:255;not null" json:"name"`
|
||||
ParentID *uint `gorm:"index" json:"parent_id,omitempty"`
|
||||
DisplayOrder int `gorm:"default:0" json:"display_order"`
|
||||
Active bool `gorm:"default:true" json:"active"`
|
||||
}
|
||||
|
||||
func (EshopProductCategory) TableName() string { return "eshop_product_categories" }
|
||||
|
||||
// EshopProduct represents a sellable product in the e-shop
|
||||
// Prices are stored in cents for currency-safe arithmetic.
|
||||
type EshopProduct 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:"-"`
|
||||
|
||||
Slug string `gorm:"size:190;uniqueIndex;not null" json:"slug"`
|
||||
Name string `gorm:"size:255;not null" json:"name"`
|
||||
ShortDescription string `gorm:"type:text" json:"short_description"`
|
||||
DescriptionHTML string `gorm:"type:text" json:"description_html"`
|
||||
PriceCents int64 `json:"price_cents"`
|
||||
Currency string `gorm:"size:10;default:'CZK'" json:"currency"`
|
||||
VATRate float64 `json:"vat_rate"`
|
||||
Active bool `gorm:"default:true;index" json:"active"`
|
||||
StockMode string `gorm:"size:20;default:'finite'" json:"stock_mode"` // finite | unlimited
|
||||
DefaultImageURL string `gorm:"size:500" json:"default_image_url"`
|
||||
GalleryJSON string `gorm:"type:text" json:"gallery_json"` // JSON array of image URLs
|
||||
Tags string `gorm:"type:text" json:"tags"` // comma-separated or JSON
|
||||
MetadataJSON string `gorm:"type:text" json:"metadata_json"` // flexible metadata
|
||||
CategoryID *uint `gorm:"index" json:"category_id,omitempty"`
|
||||
Category *EshopProductCategory `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
||||
Variants []EshopProductVariant `gorm:"foreignKey:ProductID" json:"variants,omitempty"`
|
||||
}
|
||||
|
||||
func (EshopProduct) TableName() string { return "eshop_products" }
|
||||
|
||||
// EshopProductVariant represents a concrete variant of a product (size/color)
|
||||
type EshopProductVariant 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:"-"`
|
||||
|
||||
ProductID uint `gorm:"index;not null" json:"product_id"`
|
||||
Product EshopProduct `gorm:"foreignKey:ProductID" json:"-"`
|
||||
SKU string `gorm:"size:64;index" json:"sku"`
|
||||
Name string `gorm:"size:255" json:"name"`
|
||||
AttributesJSON string `gorm:"type:text" json:"attributes_json"` // e.g. {"size":"M","color":"Modrá"}
|
||||
StockQty int `json:"stock_qty"`
|
||||
Barcode string `gorm:"size:128" json:"barcode"`
|
||||
ImageURL string `gorm:"size:500" json:"image_url"`
|
||||
}
|
||||
|
||||
func (EshopProductVariant) TableName() string { return "eshop_product_variants" }
|
||||
|
||||
// EshopCart represents an open shopping cart (either user-based or session-based)
|
||||
type EshopCart 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:"-"`
|
||||
|
||||
UserID *uint `gorm:"index" json:"user_id,omitempty"`
|
||||
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
SessionToken string `gorm:"size:64;index" json:"session_token,omitempty"`
|
||||
Currency string `gorm:"size:10" json:"currency"`
|
||||
Completed bool `gorm:"default:false;index" json:"completed"`
|
||||
|
||||
Items []EshopCartItem `gorm:"foreignKey:CartID" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (EshopCart) TableName() string { return "eshop_carts" }
|
||||
|
||||
// EshopCartItem represents a single item in the shopping cart
|
||||
type EshopCartItem 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:"-"`
|
||||
|
||||
CartID uint `gorm:"index;not null" json:"cart_id"`
|
||||
Cart EshopCart `gorm:"foreignKey:CartID" json:"-"`
|
||||
ProductID uint `gorm:"index;not null" json:"product_id"`
|
||||
Product EshopProduct `gorm:"foreignKey:ProductID" json:"product"`
|
||||
VariantID *uint `gorm:"index" json:"variant_id,omitempty"`
|
||||
Variant *EshopProductVariant `gorm:"foreignKey:VariantID" json:"variant,omitempty"`
|
||||
Quantity int `gorm:"not null;default:1" json:"quantity"`
|
||||
|
||||
UnitPriceCents int64 `json:"unit_price_cents"`
|
||||
Currency string `gorm:"size:10" json:"currency"`
|
||||
}
|
||||
|
||||
func (EshopCartItem) TableName() string { return "eshop_cart_items" }
|
||||
|
||||
// EshopOrder represents a placed order
|
||||
// It is intentionally denormalized a bit so changes to user profile do not affect historical orders.
|
||||
type EshopOrder 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:"-"`
|
||||
|
||||
OrderNumber string `gorm:"size:32;uniqueIndex" json:"order_number"`
|
||||
UserID *uint `gorm:"index" json:"user_id,omitempty"`
|
||||
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
SessionToken string `gorm:"size:64;index" json:"session_token,omitempty"`
|
||||
|
||||
Email string `gorm:"size:255" json:"email"`
|
||||
FirstName string `gorm:"size:100" json:"first_name"`
|
||||
LastName string `gorm:"size:100" json:"last_name"`
|
||||
|
||||
BillingAddressJSON string `gorm:"type:text" json:"billing_address_json"`
|
||||
ShippingAddressJSON string `gorm:"type:text" json:"shipping_address_json"`
|
||||
|
||||
Status string `gorm:"size:32;index" json:"status"` // new, awaiting_payment, paid, cancelled, refunded, ready_to_ship, shipped, delivered
|
||||
TotalAmountCents int64 `json:"total_amount_cents"`
|
||||
Currency string `gorm:"size:10" json:"currency"`
|
||||
ShippingMethod string `gorm:"size:32" json:"shipping_method"`
|
||||
ShippingPriceCents int64 `json:"shipping_price_cents"`
|
||||
ShippingDataJSON string `gorm:"type:text" json:"shipping_data_json"`
|
||||
|
||||
TicketOrder *uint `gorm:"index" json:"ticket_order,omitempty"` // Flag for ticket orders
|
||||
TicketCampaignID *uint `gorm:"index" json:"ticket_campaign_id,omitempty"` // Link to ticket campaign
|
||||
|
||||
MetadataJSON string `gorm:"type:text" json:"metadata_json"`
|
||||
Items []EshopOrderItem `gorm:"foreignKey:OrderID" json:"items,omitempty"`
|
||||
Payments []EshopPayment `gorm:"foreignKey:OrderID" json:"payments,omitempty"`
|
||||
Labels []EshopShippingLabel `gorm:"foreignKey:OrderID" json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func (EshopOrder) TableName() string { return "eshop_orders" }
|
||||
|
||||
// EshopOrderItem represents a line item within an order
|
||||
type EshopOrderItem 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:"-"`
|
||||
|
||||
OrderID uint `gorm:"index;not null" json:"order_id"`
|
||||
Order EshopOrder `gorm:"foreignKey:OrderID" json:"-"`
|
||||
ProductID uint `gorm:"index" json:"product_id"`
|
||||
VariantID *uint `gorm:"index" json:"variant_id,omitempty"`
|
||||
|
||||
Name string `gorm:"size:255" json:"name"`
|
||||
SKU string `gorm:"size:64" json:"sku"`
|
||||
Quantity int `gorm:"not null;default:1" json:"quantity"`
|
||||
|
||||
UnitPriceCents int64 `json:"unit_price_cents"`
|
||||
Currency string `gorm:"size:10" json:"currency"`
|
||||
VATRate float64 `json:"vat_rate"`
|
||||
|
||||
TicketID *uint `gorm:"index" json:"ticket_id,omitempty"` // Link to ticket if this is a ticket item
|
||||
}
|
||||
|
||||
func (EshopOrderItem) TableName() string { return "eshop_order_items" }
|
||||
|
||||
// EshopPayment tracks payments for orders (e.g. Stripe)
|
||||
type EshopPayment 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:"-"`
|
||||
|
||||
OrderID uint `gorm:"index;not null" json:"order_id"`
|
||||
Order EshopOrder `gorm:"foreignKey:OrderID" json:"-"`
|
||||
|
||||
Provider string `gorm:"size:32" json:"provider"` // stripe, bank_transfer
|
||||
ProviderPaymentID string `gorm:"size:128;index" json:"provider_payment_id"`
|
||||
Status string `gorm:"size:32;index" json:"status"` // pending, succeeded, failed, refunded
|
||||
AmountCents int64 `json:"amount_cents"`
|
||||
Currency string `gorm:"size:10" json:"currency"`
|
||||
RawPayloadJSON string `gorm:"type:text" json:"raw_payload_json"`
|
||||
}
|
||||
|
||||
func (EshopPayment) TableName() string { return "eshop_payments" }
|
||||
|
||||
// EshopShippingLabel tracks carrier labels / Packeta packets for orders
|
||||
type EshopShippingLabel 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:"-"`
|
||||
|
||||
OrderID uint `gorm:"index;not null" json:"order_id"`
|
||||
Order EshopOrder `gorm:"foreignKey:OrderID" json:"-"`
|
||||
|
||||
Carrier string `gorm:"size:32" json:"carrier"` // packeta, courier, pickup
|
||||
PacketaPacketID string `gorm:"size:64;index" json:"packeta_packet_id"`
|
||||
TrackingNumber string `gorm:"size:64" json:"tracking_number"`
|
||||
LabelURL string `gorm:"size:500" json:"label_url"`
|
||||
Status string `gorm:"size:64" json:"status"`
|
||||
HistoryJSON string `gorm:"type:text" json:"history_json"`
|
||||
}
|
||||
|
||||
func (EshopShippingLabel) TableName() string { return "eshop_shipping_labels" }
|
||||
|
||||
// EshopSettings stores configuration specific to the e-shop instance
|
||||
type EshopSettings 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:"-"`
|
||||
|
||||
DefaultCurrency string `gorm:"size:10;default:'CZK'" json:"default_currency"`
|
||||
SupportedCurrencies string `gorm:"type:text" json:"supported_currencies"`
|
||||
DefaultCountry string `gorm:"size:2;default:'CZ'" json:"default_country"`
|
||||
ShippingOptionsJSON string `gorm:"type:text" json:"shipping_options_json"`
|
||||
TermsURL string `gorm:"size:500" json:"terms_url"`
|
||||
ReturnsPolicyURL string `gorm:"size:500" json:"returns_policy_url"`
|
||||
SupportEmail string `gorm:"size:255" json:"support_email"`
|
||||
SupportPhone string `gorm:"size:64" json:"support_phone"`
|
||||
}
|
||||
|
||||
func (EshopSettings) TableName() string { return "eshop_settings" }
|
||||
@@ -0,0 +1,259 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// FacilityType represents different types of facilities
|
||||
type FacilityType string
|
||||
|
||||
const (
|
||||
FacilityTypeField FacilityType = "field" // Football field, training pitch
|
||||
FacilityTypeGym FacilityType = "gym" // Indoor gym, fitness area
|
||||
FacilityTypeLocker FacilityType = "locker" // Locker rooms
|
||||
FacilityTypeClassroom FacilityType = "classroom" // Meeting rooms, classrooms
|
||||
FacilityTypeStorage FacilityType = "storage" // Equipment storage
|
||||
FacilityTypeOther FacilityType = "other" // Other facilities
|
||||
)
|
||||
|
||||
// FacilityStatus represents the current status of a facility
|
||||
type FacilityStatus string
|
||||
|
||||
const (
|
||||
FacilityStatusActive FacilityStatus = "active" // Available for booking
|
||||
FacilityStatusInactive FacilityStatus = "inactive" // Temporarily unavailable
|
||||
FacilityStatusMaintenance FacilityStatus = "maintenance" // Under maintenance
|
||||
FacilityStatusClosed FacilityStatus = "closed" // Permanently closed
|
||||
)
|
||||
|
||||
// Facility represents a physical facility that can be booked
|
||||
type Facility struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Type FacilityType `json:"type" gorm:"type:varchar(20);not null"`
|
||||
Status FacilityStatus `json:"status" gorm:"type:varchar(20);not null;default:'active'"`
|
||||
Capacity int `json:"capacity"` // Maximum capacity (people)
|
||||
Area float64 `json:"area"` // Area in square meters
|
||||
Location string `json:"location"` // Building/room location
|
||||
IsIndoor bool `json:"is_indoor" gorm:"default:true"`
|
||||
IsOutdoor bool `json:"is_outdoor" gorm:"default:false"`
|
||||
ImageURL string `json:"image_url"`
|
||||
|
||||
// Booking settings
|
||||
RequiresApproval bool `json:"requires_approval" gorm:"default:false"`
|
||||
MinBookingDuration int `json:"min_booking_duration"` // Minimum booking duration in minutes
|
||||
MaxBookingDuration int `json:"max_booking_duration"` // Maximum booking duration in minutes
|
||||
BookingAdvanceDays int `json:"booking_advance_days"` // How many days in advance users can book
|
||||
|
||||
// Pricing
|
||||
PricePerHour float64 `json:"price_per_hour"` // Price per hour for bookings
|
||||
|
||||
// Availability
|
||||
AvailabilityRules []FacilityAvailabilityRule `json:"availability_rules" gorm:"constraint:OnDelete:CASCADE"`
|
||||
|
||||
// Relationships
|
||||
Bookings []FacilityBooking `json:"bookings,omitempty" gorm:"constraint:OnDelete:CASCADE"`
|
||||
Equipment []FacilityEquipment `json:"equipment,omitempty" gorm:"constraint:OnDelete:CASCADE"`
|
||||
Maintenance []FacilityMaintenance `json:"maintenance,omitempty" gorm:"constraint:OnDelete:CASCADE"`
|
||||
}
|
||||
|
||||
// FacilityAvailabilityRule defines when a facility is available for booking
|
||||
type FacilityAvailabilityRule struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
DayOfWeek int `json:"day_of_week"` // 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
StartTime string `json:"start_time"` // HH:MM format
|
||||
EndTime string `json:"end_time"` // HH:MM format
|
||||
IsAvailable bool `json:"is_available" gorm:"default:true"`
|
||||
|
||||
// Recurring exceptions
|
||||
StartDate *time.Time `json:"start_date"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
}
|
||||
|
||||
// BookingStatus represents the status of a facility booking
|
||||
type BookingStatus string
|
||||
|
||||
const (
|
||||
BookingStatusPending BookingStatus = "pending" // Awaiting approval
|
||||
BookingStatusConfirmed BookingStatus = "confirmed" // Approved and confirmed
|
||||
BookingStatusCancelled BookingStatus = "cancelled" // Cancelled
|
||||
BookingStatusCompleted BookingStatus = "completed" // Completed
|
||||
BookingStatusNoShow BookingStatus = "noshow" // No show
|
||||
)
|
||||
|
||||
// FacilityBooking represents a booking for a facility
|
||||
type FacilityBooking struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
|
||||
Title string `json:"title" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
StartTime time.Time `json:"start_time" gorm:"not null"`
|
||||
EndTime time.Time `json:"end_time" gorm:"not null"`
|
||||
Status BookingStatus `json:"status" gorm:"type:varchar(20);not null;default:'pending'"`
|
||||
|
||||
// Pricing
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
PaymentStatus string `json:"payment_status" gorm:"default:'pending'"`
|
||||
|
||||
// Attendance
|
||||
ActualStartTime *time.Time `json:"actual_start_time"`
|
||||
ActualEndTime *time.Time `json:"actual_end_time"`
|
||||
AttendeesCount int `json:"attendees_count"`
|
||||
|
||||
// Notes
|
||||
InternalNotes string `json:"internal_notes"` // Admin-only notes
|
||||
PublicNotes string `json:"public_notes"` // Visible to booker
|
||||
|
||||
// Cancellation
|
||||
CancelledAt *time.Time `json:"cancelled_at"`
|
||||
CancelledBy *uint `json:"cancelled_by"`
|
||||
CancelReason string `json:"cancel_reason"`
|
||||
}
|
||||
|
||||
// EquipmentStatus represents the status of equipment
|
||||
type EquipmentStatus string
|
||||
|
||||
const (
|
||||
EquipmentStatusAvailable EquipmentStatus = "available"
|
||||
EquipmentStatusInUse EquipmentStatus = "in_use"
|
||||
EquipmentStatusMaintenance EquipmentStatus = "maintenance"
|
||||
EquipmentStatusDamaged EquipmentStatus = "damaged"
|
||||
EquipmentStatusLost EquipmentStatus = "lost"
|
||||
EquipmentStatusRetired EquipmentStatus = "retired"
|
||||
)
|
||||
|
||||
// FacilityEquipment represents equipment associated with a facility
|
||||
type FacilityEquipment struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"` // e.g., "balls", "cones", "goals", "training aids"
|
||||
Status EquipmentStatus `json:"status" gorm:"type:varchar(20);not null;default:'available'"`
|
||||
Quantity int `json:"quantity"` // Total quantity
|
||||
Available int `json:"available"` // Currently available quantity
|
||||
|
||||
// Purchase info
|
||||
PurchaseDate *time.Time `json:"purchase_date"`
|
||||
PurchasePrice float64 `json:"purchase_price"`
|
||||
Supplier string `json:"supplier"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
WarrantyExpiry *time.Time `json:"warranty_expiry"`
|
||||
|
||||
// Maintenance
|
||||
LastMaintenanceDate *time.Time `json:"last_maintenance_date"`
|
||||
NextMaintenanceDate *time.Time `json:"next_maintenance_date"`
|
||||
|
||||
// Location tracking
|
||||
CurrentLocation string `json:"current_location"`
|
||||
|
||||
ImageURL string `json:"image_url"`
|
||||
|
||||
// Usage tracking
|
||||
UsageCount int `json:"usage_count"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
}
|
||||
|
||||
// MaintenanceType represents the type of maintenance
|
||||
type MaintenanceType string
|
||||
|
||||
const (
|
||||
MaintenanceTypeRoutine MaintenanceType = "routine" // Regular maintenance
|
||||
MaintenanceTypeRepair MaintenanceType = "repair" // Repair work
|
||||
MaintenanceTypeInspection MaintenanceType = "inspection" // Safety inspection
|
||||
MaintenanceTypeUpgrade MaintenanceType = "upgrade" // Upgrades/improvements
|
||||
)
|
||||
|
||||
// FacilityMaintenance represents maintenance work on facilities
|
||||
type FacilityMaintenance struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
Type MaintenanceType `json:"type" gorm:"type:varchar(20);not null"`
|
||||
Title string `json:"title" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// Scheduling
|
||||
ScheduledDate *time.Time `json:"scheduled_date"`
|
||||
EstimatedDuration int `json:"estimated_duration"` // Duration in minutes
|
||||
ActualDuration int `json:"actual_duration"`
|
||||
|
||||
// Status
|
||||
Status string `json:"status" gorm:"default:'scheduled'"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
CompletedAt *time.Time `json:"completed_at"`
|
||||
|
||||
// Cost
|
||||
EstimatedCost float64 `json:"estimated_cost"`
|
||||
ActualCost float64 `json:"actual_cost"`
|
||||
|
||||
// Personnel
|
||||
AssignedTo string `json:"assigned_to"`
|
||||
PerformedBy string `json:"performed_by"`
|
||||
|
||||
// Impact on availability
|
||||
IsFacilityUnavailable bool `json:"is_facility_unavailable" gorm:"default:true"`
|
||||
|
||||
// Notes
|
||||
InternalNotes string `json:"internal_notes"`
|
||||
PublicNotes string `json:"public_notes"`
|
||||
|
||||
// Related equipment
|
||||
EquipmentAffected []string `json:"equipment_affected" gorm:"type:text"` // JSON array of equipment names
|
||||
}
|
||||
|
||||
// WeatherCondition represents weather conditions for outdoor activities
|
||||
type WeatherCondition struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
DateTime time.Time `json:"date_time" gorm:"not null"`
|
||||
Temperature float64 `json:"temperature"` // Celsius
|
||||
Humidity float64 `json:"humidity"` // Percentage
|
||||
Precipitation float64 `json:"precipitation"` // mm
|
||||
WindSpeed float64 `json:"wind_speed"` // km/h
|
||||
WindDirection int `json:"wind_direction"` // Degrees
|
||||
WeatherCode string `json:"weather_code"` // OpenWeatherMap condition code
|
||||
Description string `json:"description"` // Weather description
|
||||
IsSuitable bool `json:"is_suitable"` // Suitable for outdoor activities
|
||||
Recommendations string `json:"recommendations"` // Activity recommendations
|
||||
}
|
||||
|
||||
// FacilityBookingTemplate represents reusable booking templates
|
||||
type FacilityBookingTemplate struct {
|
||||
BaseModel
|
||||
FacilityID uint `json:"facility_id" gorm:"index;not null"`
|
||||
Facility Facility `json:"facility" gorm:"foreignKey:FacilityID"`
|
||||
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// Default booking settings
|
||||
Duration int `json:"duration"` // Duration in minutes
|
||||
PricePerHour float64 `json:"price_per_hour"` // Override facility price if set
|
||||
RequiresApproval bool `json:"requires_approval"`
|
||||
|
||||
// Recurrence pattern for regular bookings
|
||||
IsRecurring bool `json:"is_recurring"`
|
||||
RecurrencePattern string `json:"recurrence_pattern"` // JSON: daily, weekly, monthly
|
||||
|
||||
// Default settings
|
||||
DefaultTitle string `json:"default_title"`
|
||||
DefaultDescription string `json:"default_description"`
|
||||
DefaultAttendees int `json:"default_attendees"`
|
||||
|
||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Budget represents a budget category with limits and tracking
|
||||
type Budget struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name" gorm:"not null;size:255"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Category string `json:"category" gorm:"size:100;index"` // např. "Týmové provoz", "Stadion", "Marketing", "Cestování"
|
||||
|
||||
// Budget limits and tracking
|
||||
YearlyLimit float64 `json:"yearly_limit" gorm:"type:decimal(12,2)"`
|
||||
MonthlyLimit float64 `json:"monthly_limit" gorm:"type:decimal(12,2)"`
|
||||
CurrentSpend float64 `json:"current_spend" gorm:"type:decimal(12,2);default:0"`
|
||||
|
||||
// Budget period
|
||||
FiscalYear int `json:"fiscal_year" gorm:"index"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
|
||||
// Status and management
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
AlertThreshold float64 `json:"alert_threshold" gorm:"type:decimal(5,2);default:80"` // % pro varování
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Expenses []Expense `json:"expenses,omitempty" gorm:"foreignKey:BudgetID"`
|
||||
}
|
||||
|
||||
// Sponsorship represents sponsorship contracts and tracking
|
||||
type Sponsorship struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
SponsorName string `json:"sponsor_name" gorm:"not null;size:255"`
|
||||
SponsorLogo string `json:"sponsor_logo" gorm:"size:500"`
|
||||
ContactPerson string `json:"contact_person" gorm:"size:255"`
|
||||
ContactEmail string `json:"contact_email" gorm:"size:255"`
|
||||
ContactPhone string `json:"contact_phone" gorm:"size:50"`
|
||||
|
||||
// Contract details
|
||||
ContractNumber string `json:"contract_number" gorm:"size:100;uniqueIndex"`
|
||||
ContractType string `json:"contract_type" gorm:"size:100"` // "Hlavní", "Technický", "Mediální", "Akce"
|
||||
|
||||
// Financial terms
|
||||
TotalValue float64 `json:"total_value" gorm:"type:decimal(12,2)"`
|
||||
PaymentSchedule string `json:"payment_schedule" gorm:"size:100"` // "Měsíčně", "Čtvrtletně", "Ročně", "Jednorázově"
|
||||
Currency string `json:"currency" gorm:"size:3;default:'CZK'"`
|
||||
|
||||
// Contract period
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
AutoRenewal bool `json:"auto_renewal" gorm:"default:false"`
|
||||
RenewalNotice int `json:"renewal_notice" gorm:"default:90"` // dní předem
|
||||
|
||||
// Benefits and obligations
|
||||
Benefits string `json:"benefits" gorm:"type:text"` // JSON s detaily benefitů
|
||||
Obligations string `json:"obligations" gorm:"type:text"`
|
||||
|
||||
// Status tracking
|
||||
Status string `json:"status" gorm:"size:50;default:'active'"` // "active", "expired", "terminated", "pending"
|
||||
LastPaymentDate time.Time `json:"last_payment_date"`
|
||||
NextPaymentDate time.Time `json:"next_payment_date"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Payments []SponsorshipPayment `json:"payments,omitempty" gorm:"foreignKey:SponsorshipID"`
|
||||
Documents []SponsorshipDocument `json:"documents,omitempty" gorm:"foreignKey:SponsorshipID"`
|
||||
}
|
||||
|
||||
// SponsorshipPayment tracks individual payments from sponsors
|
||||
type SponsorshipPayment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
SponsorshipID uint `json:"sponsorship_id" gorm:"not null;index"`
|
||||
|
||||
// Payment details
|
||||
Amount float64 `json:"amount" gorm:"type:decimal(12,2)"`
|
||||
Currency string `json:"currency" gorm:"size:3;default:'CZK'"`
|
||||
PaymentDate time.Time `json:"payment_date"`
|
||||
PaymentMethod string `json:"payment_method" gorm:"size:100"` // "Bankovní převod", "Hotovost", "Karta"
|
||||
|
||||
// Reference and status
|
||||
ReferenceNumber string `json:"reference_number" gorm:"size:255"`
|
||||
Status string `json:"status" gorm:"size:50;default:'received'"` // "expected", "received", "overdue", "cancelled"
|
||||
Notes string `json:"notes" gorm:"type:text"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Sponsorship Sponsorship `json:"sponsorship,omitempty" gorm:"foreignKey:SponsorshipID"`
|
||||
}
|
||||
|
||||
// SponsorshipDocument stores contract documents and related files
|
||||
type SponsorshipDocument struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
SponsorshipID uint `json:"sponsorship_id" gorm:"not null;index"`
|
||||
|
||||
// Document details
|
||||
Name string `json:"name" gorm:"not null;size:255"`
|
||||
Type string `json:"type" gorm:"size:100"` // "Smlouva", "Faktura", "Dodatek", "Jiný"
|
||||
FileName string `json:"file_name" gorm:"size:500"`
|
||||
FilePath string `json:"file_path" gorm:"size:500"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
MimeType string `json:"mime_type" gorm:"size:100"`
|
||||
|
||||
// Document metadata
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Version string `json:"version" gorm:"size:20;default:'1.0'"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Sponsorship Sponsorship `json:"sponsorship,omitempty" gorm:"foreignKey:SponsorshipID"`
|
||||
}
|
||||
|
||||
// Expense represents individual expenses with receipt tracking
|
||||
type Expense struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Basic expense info
|
||||
Title string `json:"title" gorm:"not null;size:255"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Category string `json:"category" gorm:"size:100;index"` // "Cestování", "Materiál", "Služby", "Strava", "Ubytování"
|
||||
Subcategory string `json:"subcategory" gorm:"size:100"`
|
||||
|
||||
// Financial details
|
||||
Amount float64 `json:"amount" gorm:"type:decimal(12,2)"`
|
||||
Currency string `json:"currency" gorm:"size:3;default:'CZK'"`
|
||||
VATRate float64 `json:"vat_rate" gorm:"type:decimal(5,2);default:21"` // DPH sazba v %
|
||||
VATAmount float64 `json:"vat_amount" gorm:"type:decimal(12,2)"`
|
||||
TotalAmount float64 `json:"total_amount" gorm:"type:decimal(12,2)"`
|
||||
|
||||
// Expense details
|
||||
ExpenseDate time.Time `json:"expense_date"`
|
||||
PaymentMethod string `json:"payment_method" gorm:"size:100"` // "Hotovost", "Karta", "Faktura", "Proforma"
|
||||
|
||||
// Receipt and documentation
|
||||
HasReceipt bool `json:"has_receipt" gorm:"default:false"`
|
||||
ReceiptData string `json:"receipt_data" gorm:"type:text"` // OCR data z paragonu
|
||||
ReceiptImage string `json:"receipt_image" gorm:"size:500"` // cesta k obrázku paragonu
|
||||
|
||||
// Approval workflow
|
||||
Status string `json:"status" gorm:"size:50;default:'pending'"` // "pending", "approved", "rejected", "reimbursed"
|
||||
ApprovedBy uint `json:"approved_by"`
|
||||
ApprovedAt *time.Time `json:"approved_at"`
|
||||
RejectionReason string `json:"rejection_reason" gorm:"type:text"`
|
||||
|
||||
// Budget tracking
|
||||
BudgetID *uint `json:"budget_id" gorm:"index"`
|
||||
TeamID *uint `json:"team_id" gorm:"index"` // přiřazení k týmu
|
||||
ProjectID *uint `json:"project_id" gorm:"index"` // přiřazení k projektu
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Budget *Budget `json:"budget,omitempty" gorm:"foreignKey:BudgetID"`
|
||||
Documents []ExpenseDocument `json:"documents,omitempty" gorm:"foreignKey:ExpenseID"`
|
||||
}
|
||||
|
||||
// ExpenseDocument stores expense-related documents
|
||||
type ExpenseDocument struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
ExpenseID uint `json:"expense_id" gorm:"not null;index"`
|
||||
|
||||
// Document details
|
||||
Name string `json:"name" gorm:"not null;size:255"`
|
||||
Type string `json:"type" gorm:"size:100"` // "Paragon", "Faktura", "Smlouva", "Jiný"
|
||||
FileName string `json:"file_name" gorm:"size:500"`
|
||||
FilePath string `json:"file_path" gorm:"size:500"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
MimeType string `json:"mime_type" gorm:"size:100"`
|
||||
|
||||
// OCR data for receipts
|
||||
OCRData string `json:"ocr_data" gorm:"type:text"`
|
||||
OCRAccuracy float64 `json:"ocr_accuracy" gorm:"type:decimal(5,2)"` // spolehlivost OCR v %
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Expense Expense `json:"expense,omitempty" gorm:"foreignKey:ExpenseID"`
|
||||
}
|
||||
|
||||
// FinancialReport represents generated financial reports
|
||||
type FinancialReport struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Report details
|
||||
Name string `json:"name" gorm:"not null;size:255"`
|
||||
Type string `json:"type" gorm:"size:100"` // "monthly", "quarterly", "yearly", "custom"
|
||||
Period string `json:"period" gorm:"size:50"` // "2024-01", "Q1-2024", "2024", "custom"
|
||||
|
||||
// Report data
|
||||
ReportData string `json:"report_data" gorm:"type:text"` // JSON s daty reportu
|
||||
Summary string `json:"summary" gorm:"type:text"`
|
||||
|
||||
// File generation
|
||||
FilePath string `json:"file_path" gorm:"size:500"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
}
|
||||
|
||||
// FinancialSettings stores club-wide financial configuration
|
||||
type FinancialSettings struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Budget settings
|
||||
DefaultCurrency string `json:"default_currency" gorm:"size:3;default:'CZK'"`
|
||||
DefaultVATRate float64 `json:"default_vat_rate" gorm:"type:decimal(5,2);default:21"`
|
||||
FiscalYearStart string `json:"fiscal_year_start" gorm:"size:10;default:'01-01'"` // MM-DD format
|
||||
|
||||
// Approval settings
|
||||
ExpenseApprovalRequired bool `json:"expense_approval_required" gorm:"default:true"`
|
||||
MaxExpenseAutoApprove float64 `json:"max_expense_auto_approve" gorm:"type:decimal(12,2);default:1000"`
|
||||
|
||||
// Notification settings
|
||||
BudgetAlertEnabled bool `json:"budget_alert_enabled" gorm:"default:true"`
|
||||
BudgetAlertThreshold float64 `json:"budget_alert_threshold" gorm:"type:decimal(5,2);default:80"`
|
||||
SponsorshipAlertEnabled bool `json:"sponsorship_alert_enabled" gorm:"default:true"`
|
||||
|
||||
// OCR settings
|
||||
OCRServiceEnabled bool `json:"ocr_service_enabled" gorm:"default:true"`
|
||||
OCRProvider string `json:"ocr_provider" gorm:"size:50;default:'tesseract'"` // "tesseract", "google", "azure"
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Language represents a supported language
|
||||
type Language struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(5)" json:"id"`
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"`
|
||||
NativeName string `gorm:"type:varchar(100);not null" json:"native_name"`
|
||||
Code string `gorm:"type:varchar(10);not null;uniqueIndex" json:"code"`
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
SortOrder int `gorm:"default:0" json:"sort_order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Translation represents a translatable string
|
||||
type Translation struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Key string `gorm:"type:varchar(200);not null;index" json:"key"`
|
||||
LanguageCode string `gorm:"type:varchar(10);not null;index" json:"language_code"`
|
||||
Value string `gorm:"type:text;not null" json:"value"`
|
||||
Context string `gorm:"type:varchar(100)" json:"context"` // e.g., "navbar", "admin", "public"
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Relationships
|
||||
Language Language `gorm:"foreignKey:LanguageCode;references:Code" json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// ContentTranslation represents translations for content like articles, activities, etc.
|
||||
type ContentTranslation struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ContentType string `gorm:"type:varchar(50);not null;index" json:"content_type"` // "article", "activity", "page", etc.
|
||||
ContentID uint `gorm:"not null;index" json:"content_id"`
|
||||
LanguageCode string `gorm:"type:varchar(10);not null;index" json:"language_code"`
|
||||
Title string `gorm:"type:varchar(500)" json:"title"`
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
Excerpt string `gorm:"type:text" json:"excerpt"`
|
||||
MetaTitle string `gorm:"type:varchar(200)" json:"meta_title"`
|
||||
MetaDescription string `gorm:"type:varchar(500)" json:"meta_description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Relationships
|
||||
Language Language `gorm:"foreignKey:LanguageCode;references:Code" json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// UserLanguagePreference tracks user's preferred language
|
||||
type UserLanguagePreference struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null;uniqueIndex" json:"user_id"`
|
||||
LanguageCode string `gorm:"type:varchar(10);not null" json:"language_code"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Relationships
|
||||
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
Language Language `gorm:"foreignKey:LanguageCode;references:Code" json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for Language model
|
||||
func (Language) TableName() string {
|
||||
return "languages"
|
||||
}
|
||||
|
||||
// TableName returns the table name for Translation model
|
||||
func (Translation) TableName() string {
|
||||
return "translations"
|
||||
}
|
||||
|
||||
// TableName returns the table name for ContentTranslation model
|
||||
func (ContentTranslation) TableName() string {
|
||||
return "content_translations"
|
||||
}
|
||||
|
||||
// TableName returns the table name for UserLanguagePreference model
|
||||
func (UserLanguagePreference) TableName() string {
|
||||
return "user_language_preferences"
|
||||
}
|
||||
|
||||
// BeforeCreate sets default values
|
||||
func (l *Language) BeforeCreate(tx *gorm.DB) error {
|
||||
if l.SortOrder == 0 {
|
||||
l.SortOrder = 100
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefaultLanguage returns the default language
|
||||
func GetDefaultLanguage(db *gorm.DB) (*Language, error) {
|
||||
var lang Language
|
||||
err := db.Where("is_default = ? AND is_active = ?", true, true).First(&lang).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lang, nil
|
||||
}
|
||||
|
||||
// GetActiveLanguages returns all active languages ordered by sort order
|
||||
func GetActiveLanguages(db *gorm.DB) ([]Language, error) {
|
||||
var languages []Language
|
||||
err := db.Where("is_active = ?", true).Order("sort_order ASC, name ASC").Find(&languages).Error
|
||||
return languages, err
|
||||
}
|
||||
|
||||
// GetTranslation returns a translation for a specific key and language
|
||||
func GetTranslation(db *gorm.DB, key, languageCode string) (*Translation, error) {
|
||||
var translation Translation
|
||||
err := db.Where("key = ? AND language_code = ?", key, languageCode).First(&translation).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &translation, nil
|
||||
}
|
||||
|
||||
// GetTranslationWithFallback returns a translation, falling back to default language if needed
|
||||
func GetTranslationWithFallback(db *gorm.DB, key, languageCode string) (*Translation, error) {
|
||||
// Try requested language first
|
||||
translation, err := GetTranslation(db, key, languageCode)
|
||||
if err == nil {
|
||||
return translation, nil
|
||||
}
|
||||
|
||||
// Fall back to default language
|
||||
defaultLang, err := GetDefaultLanguage(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetTranslation(db, key, defaultLang.Code)
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Invoice represents a complete invoice with auto-fill capabilities
|
||||
type Invoice struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Invoice identification
|
||||
InvoiceNumber string `json:"invoice_number" gorm:"size:50;uniqueIndex;not null"`
|
||||
InvoiceType string `json:"invoice_type" gorm:"size:20;default:'faktura'"` // "faktura", "zalohova_faktura", "proforma_faktura", "dobropis"
|
||||
VariableSymbol string `json:"variable_symbol" gorm:"size:20"`
|
||||
ConstantSymbol string `json:"constant_symbol" gorm:"size:20"`
|
||||
SpecificSymbol string `json:"specific_symbol" gorm:"size:20"`
|
||||
|
||||
// Invoice dates
|
||||
IssueDate time.Time `json:"issue_date" gorm:"not null"`
|
||||
DueDate time.Time `json:"due_date" gorm:"not null"`
|
||||
TaxableSupplyDate time.Time `json:"taxable_supply_date"`
|
||||
|
||||
// Supplier information (auto-filled from club settings)
|
||||
SupplierName string `json:"supplier_name" gorm:"size:255;not null"`
|
||||
SupplierICO string `json:"supplier_ico" gorm:"size:20"`
|
||||
SupplierDIC string `json:"supplier_dic" gorm:"size:20"`
|
||||
SupplierAddress string `json:"supplier_address" gorm:"type:text"`
|
||||
SupplierCity string `json:"supplier_city" gorm:"size:100"`
|
||||
SupplierZIP string `json:"supplier_zip" gorm:"size:10"`
|
||||
SupplierCountry string `json:"supplier_country" gorm:"size:100;default:'Česká republika'"`
|
||||
|
||||
// Supplier bank information (auto-filled)
|
||||
BankName string `json:"bank_name" gorm:"size:255"`
|
||||
BankAccount string `json:"bank_account" gorm:"size:50"`
|
||||
BankIBAN string `json:"bank_iban" gorm:"size:50"`
|
||||
BankSWIFT string `json:"bank_swift" gorm:"size:20"`
|
||||
|
||||
// Customer information (manual or auto-fill from database)
|
||||
CustomerID *uint `json:"customer_id" gorm:"index"`
|
||||
CustomerName string `json:"customer_name" gorm:"size:255;not null"`
|
||||
CustomerICO string `json:"customer_ico" gorm:"size:20"`
|
||||
CustomerDIC string `json:"customer_dic" gorm:"size:20"`
|
||||
CustomerAddress string `json:"customer_address" gorm:"type:text"`
|
||||
CustomerCity string `json:"customer_city" gorm:"size:100"`
|
||||
CustomerZIP string `json:"customer_zip" gorm:"size:10"`
|
||||
CustomerCountry string `json:"customer_country" gorm:"size:100;default:'Česká republika'"`
|
||||
CustomerEmail string `json:"customer_email" gorm:"size:255"`
|
||||
CustomerPhone string `json:"customer_phone" gorm:"size:50"`
|
||||
|
||||
// Financial summary
|
||||
TotalAmount float64 `json:"total_amount" gorm:"type:decimal(15,2);not null"`
|
||||
TotalVAT float64 `json:"total_vat" gorm:"type:decimal(15,2);not null"`
|
||||
TotalAmountVAT float64 `json:"total_amount_vat" gorm:"type:decimal(15,2);not null"`
|
||||
TotalAmountWithoutVAT float64 `json:"total_amount_without_vat" gorm:"type:decimal(15,2);not null"`
|
||||
Currency string `json:"currency" gorm:"size:3;default:'CZK'"`
|
||||
|
||||
// Invoice status and workflow
|
||||
Status string `json:"status" gorm:"size:20;default:'draft'"` // "draft", "sent", "paid", "overdue", "cancelled"
|
||||
PaymentStatus string `json:"payment_status" gorm:"size:20;default:'unpaid'"` // "unpaid", "partially_paid", "paid", "overdue"
|
||||
PaymentDate *time.Time `json:"payment_date"`
|
||||
PaidAmount float64 `json:"paid_amount" gorm:"type:decimal(15,2);default:0"`
|
||||
|
||||
// Additional information
|
||||
Note string `json:"note" gorm:"type:text"`
|
||||
PaymentNote string `json:"payment_note" gorm:"type:text"`
|
||||
InternalNote string `json:"internal_note" gorm:"type:text"`
|
||||
|
||||
// PDF and sending
|
||||
PDFPath string `json:"pdf_path" gorm:"size:500"`
|
||||
PDFGeneratedAt *time.Time `json:"pdf_generated_at"`
|
||||
SentAt *time.Time `json:"sent_at"`
|
||||
SentTo string `json:"sent_to" gorm:"type:text"` // JSON array of emails
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Items []InvoiceItem `json:"items,omitempty" gorm:"foreignKey:InvoiceID;constraint:OnDelete:CASCADE"`
|
||||
Payments []InvoicePayment `json:"payments,omitempty" gorm:"foreignKey:InvoiceID;constraint:OnDelete:CASCADE"`
|
||||
Customer *InvoiceCustomer `json:"customer,omitempty" gorm:"foreignKey:CustomerID"`
|
||||
}
|
||||
|
||||
// InvoiceItem represents individual line items in an invoice
|
||||
type InvoiceItem struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
InvoiceID uint `json:"invoice_id" gorm:"not null;index"`
|
||||
|
||||
// Item details
|
||||
Description string `json:"description" gorm:"type:text;not null"`
|
||||
Quantity float64 `json:"quantity" gorm:"type:decimal(12,3);not null"`
|
||||
Unit string `json:"unit" gorm:"size:20;default:'ks'"` // "ks", "hod", "m", "kg", etc.
|
||||
UnitPrice float64 `json:"unit_price" gorm:"type:decimal(15,2);not null"`
|
||||
TotalPrice float64 `json:"total_price" gorm:"type:decimal(15,2);not null"`
|
||||
|
||||
// VAT information
|
||||
VATRate float64 `json:"vat_rate" gorm:"type:decimal(5,2);not null"` // 0, 10, 21
|
||||
VATAmount float64 `json:"vat_amount" gorm:"type:decimal(15,2);not null"`
|
||||
TotalWithVAT float64 `json:"total_with_vat" gorm:"type:decimal(15,2);not null"`
|
||||
|
||||
// Additional fields
|
||||
Code string `json:"code" gorm:"size:100"` // Product code or service code
|
||||
Note string `json:"note" gorm:"type:text"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Relations
|
||||
Invoice Invoice `json:"invoice,omitempty" gorm:"foreignKey:InvoiceID"`
|
||||
}
|
||||
|
||||
// InvoicePayment represents payments received for invoices
|
||||
type InvoicePayment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
InvoiceID uint `json:"invoice_id" gorm:"not null;index"`
|
||||
|
||||
// Payment details
|
||||
Amount float64 `json:"amount" gorm:"type:decimal(15,2);not null"`
|
||||
Currency string `json:"currency" gorm:"size:3;default:'CZK'"`
|
||||
PaymentDate time.Time `json:"payment_date" gorm:"not null"`
|
||||
PaymentMethod string `json:"payment_method" gorm:"size:50"` // "bank_transfer", "cash", "card", "other"
|
||||
|
||||
// Bank transfer details
|
||||
VariableSymbol string `json:"variable_symbol" gorm:"size:20"`
|
||||
ConstantSymbol string `json:"constant_symbol" gorm:"size:20"`
|
||||
SpecificSymbol string `json:"specific_symbol" gorm:"size:20"`
|
||||
BankAccount string `json:"bank_account" gorm:"size:50"`
|
||||
|
||||
// Additional information
|
||||
Note string `json:"note" gorm:"type:text"`
|
||||
ReferenceNumber string `json:"reference_number" gorm:"size:255"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
|
||||
// Relations
|
||||
Invoice Invoice `json:"invoice,omitempty" gorm:"foreignKey:InvoiceID"`
|
||||
}
|
||||
|
||||
// InvoiceCustomer represents customer database for auto-fill
|
||||
type InvoiceCustomer struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Basic information
|
||||
Name string `json:"name" gorm:"size:255;not null"`
|
||||
ICO string `json:"ico" gorm:"size:20;uniqueIndex"`
|
||||
DIC string `json:"dic" gorm:"size:20"`
|
||||
|
||||
// Address
|
||||
Address string `json:"address" gorm:"type:text"`
|
||||
City string `json:"city" gorm:"size:100"`
|
||||
ZIP string `json:"zip" gorm:"column:zip;size:10"`
|
||||
Country string `json:"country" gorm:"size:100;default:'Česká republika'"`
|
||||
|
||||
// Contact information
|
||||
Email string `json:"email" gorm:"size:255"`
|
||||
Phone string `json:"phone" gorm:"size:50"`
|
||||
Website string `json:"website" gorm:"size:255"`
|
||||
|
||||
// Business information
|
||||
BusinessType string `json:"business_type" gorm:"size:100"` // "individual", "company", "non_profit"
|
||||
VATPayer bool `json:"vat_payer" gorm:"default:true"`
|
||||
|
||||
// Notes and metadata
|
||||
Notes string `json:"notes" gorm:"type:text"`
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
|
||||
// Relations
|
||||
Invoices []Invoice `json:"invoices,omitempty" gorm:"foreignKey:CustomerID"`
|
||||
}
|
||||
|
||||
// InvoiceTemplate represents invoice templates for different types
|
||||
type InvoiceTemplate struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Template identification
|
||||
Name string `json:"name" gorm:"size:255;not null"`
|
||||
Type string `json:"type" gorm:"size:50;not null"` // "standard", "proforma", "credit_note"
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
|
||||
// Template content (HTML with placeholders)
|
||||
HeaderHTML string `json:"header_html" gorm:"type:text"`
|
||||
BodyHTML string `json:"body_html" gorm:"type:text"`
|
||||
FooterHTML string `json:"footer_html" gorm:"type:text"`
|
||||
|
||||
// CSS styling
|
||||
CSS string `json:"css" gorm:"type:text"`
|
||||
|
||||
// Default settings
|
||||
DefaultVATRate float64 `json:"default_vat_rate" gorm:"type:decimal(5,2);default:21"`
|
||||
DefaultPaymentTerm int `json:"default_payment_term" gorm:"default:14"` // days
|
||||
|
||||
// Status
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
Default bool `json:"default" gorm:"default:false"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
}
|
||||
|
||||
// InvoiceSettings represents global invoice settings
|
||||
type InvoiceSettings struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Company information (auto-fill source)
|
||||
CompanyName string `json:"company_name" gorm:"size:255;not null"`
|
||||
CompanyICO string `json:"company_ico" gorm:"size:20;not null"`
|
||||
CompanyDIC string `json:"company_dic" gorm:"size:20"`
|
||||
CompanyAddress string `json:"company_address" gorm:"type:text"`
|
||||
CompanyCity string `json:"company_city" gorm:"size:100"`
|
||||
CompanyZIP string `json:"company_zip" gorm:"size:10"`
|
||||
CompanyCountry string `json:"company_country" gorm:"size:100;default:'Česká republika'"`
|
||||
|
||||
// Bank information
|
||||
BankName string `json:"bank_name" gorm:"size:255"`
|
||||
BankAccount string `json:"bank_account" gorm:"size:50"`
|
||||
BankIBAN string `json:"bank_iban" gorm:"size:50"`
|
||||
BankSWIFT string `json:"bank_swift" gorm:"size:20"`
|
||||
|
||||
// Invoice numbering
|
||||
InvoiceNumberFormat string `json:"invoice_number_format" gorm:"size:100;default:'F{year}{seq:6}'"` // Format with placeholders
|
||||
NextInvoiceNumber int `json:"next_invoice_number" gorm:"default:1"`
|
||||
CurrentYear int `json:"current_year"`
|
||||
|
||||
// Default settings
|
||||
DefaultPaymentTerm int `json:"default_payment_term" gorm:"default:14"` // days
|
||||
DefaultVATRate float64 `json:"default_vat_rate" gorm:"type:decimal(5,2);default:21"`
|
||||
DefaultCurrency string `json:"default_currency" gorm:"size:3;default:'CZK'"`
|
||||
|
||||
// Email settings
|
||||
EmailFrom string `json:"email_from" gorm:"size:255"`
|
||||
EmailSubject string `json:"email_subject" gorm:"size:255;default:'Faktura č. {invoice_number}'"`
|
||||
EmailBody string `json:"email_body" gorm:"type:text"`
|
||||
|
||||
// PDF settings
|
||||
PDFLogoPath string `json:"pdf_logo_path" gorm:"size:500"`
|
||||
PDFFooter string `json:"pdf_footer" gorm:"type:text"`
|
||||
|
||||
// Legal information
|
||||
RegistrationNumber string `json:"registration_number" gorm:"size:50"`
|
||||
TaxRegistrationNumber string `json:"tax_registration_number" gorm:"size:50"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy uint `json:"updated_by"`
|
||||
}
|
||||
|
||||
// InvoiceSequence represents invoice number sequences
|
||||
type InvoiceSequence struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
|
||||
// Sequence identification
|
||||
Type string `json:"type" gorm:"size:50;not null"` // "faktura", "zalohova_faktura", "proforma_faktura", "dobropis"
|
||||
Year int `json:"year" gorm:"not null"`
|
||||
|
||||
// Sequence numbers
|
||||
CurrentNumber int `json:"current_number" gorm:"default:1"`
|
||||
Prefix string `json:"prefix" gorm:"size:20"`
|
||||
Suffix string `json:"suffix" gorm:"size:20"`
|
||||
Padding int `json:"padding" gorm:"default:6"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// ManualCompetition stores manually maintained competition metadata for manual club data mode.
|
||||
// It is scoped to the primary club (club_id/club_type from Settings) and mirrors key FACR fields.
|
||||
type ManualCompetition struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
// Linkage to main club
|
||||
ClubID string `gorm:"index;not null" json:"club_id"` // UUID from fotbal.cz club link
|
||||
ClubType string `gorm:"index;not null" json:"club_type"` // football|futsal
|
||||
|
||||
// FACR-like identifiers and links entered manually
|
||||
Code string `gorm:"index;not null" json:"code"` // Competition code, e.g. A1A
|
||||
Name string `gorm:"not null" json:"name"` // Display name, e.g. SATUM 5. liga mužů
|
||||
ExternalID string `gorm:"index;not null" json:"external_id"` // UUID from soutez/table link
|
||||
|
||||
MatchesLink string `json:"matches_link"` // e.g. https://www.fotbal.cz/souteze/turnaje/hlavni/<uuid>
|
||||
TableLink string `json:"table_link"` // e.g. https://www.fotbal.cz/souteze/turnaje/table/<uuid>
|
||||
|
||||
TeamCount string `json:"team_count"` // Optional; free-form number as string
|
||||
}
|
||||
|
||||
func (ManualCompetition) TableName() string { return "manual_competitions" }
|
||||
|
||||
// ManualMatch stores manually entered matches for a competition in manual mode.
|
||||
// It is designed so the backend can reconstruct the FACR Match JSON shape.
|
||||
type ManualMatch struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
CompetitionID uint `gorm:"index;not null" json:"competition_id"`
|
||||
Competition *ManualCompetition `gorm:"foreignKey:CompetitionID" json:"-"`
|
||||
|
||||
// Stable identifier parsed from the match link (UUID from fotbal.cz)
|
||||
ExternalMatchID string `gorm:"uniqueIndex;not null" json:"external_match_id"`
|
||||
|
||||
// Round label, e.g. "2. kolo"
|
||||
Round string `json:"round"`
|
||||
|
||||
// Whether the primary club plays at home. If false, the primary club is away.
|
||||
IsHome bool `json:"is_home"`
|
||||
|
||||
// Opponent information (name + fotbal.cz link & ID)
|
||||
OpponentName string `json:"opponent_name"`
|
||||
OpponentExternalID string `gorm:"index" json:"opponent_external_id"` // UUID from opponent club link
|
||||
OpponentURL string `json:"opponent_url"`
|
||||
|
||||
// Kickoff datetime in local time
|
||||
Kickoff time.Time `json:"kickoff"`
|
||||
|
||||
// Scores as free-form strings (e.g. "2:1", "2:1 (1:0)")
|
||||
Score string `json:"score"`
|
||||
HalftimeScore string `json:"halftime_score"`
|
||||
|
||||
// Match link (report URL) and location
|
||||
MatchURL string `json:"match_url"`
|
||||
Venue string `json:"venue"`
|
||||
|
||||
// Optional notes
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
func (ManualMatch) TableName() string { return "manual_matches" }
|
||||
|
||||
// ManualTableRow stores a single row in a competition table (standings) for manual mode.
|
||||
type ManualTableRow struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
CompetitionID uint `gorm:"index;not null" json:"competition_id"`
|
||||
Competition *ManualCompetition `gorm:"foreignKey:CompetitionID" json:"-"`
|
||||
|
||||
// Position, e.g. "1.", "2."
|
||||
Rank string `json:"rank"`
|
||||
|
||||
// Club identification: name and fotbal.cz UUID for logo matching
|
||||
TeamName string `json:"team_name"`
|
||||
ExternalTeamID string `gorm:"index" json:"external_team_id"`
|
||||
|
||||
// Basic stats; kept as strings to match FACR JSON and allow flexible input
|
||||
Played string `json:"played"` // Z
|
||||
Wins string `json:"wins"` // V
|
||||
Draws string `json:"draws"` // R
|
||||
Losses string `json:"losses"` // P
|
||||
Score string `json:"score"` // Skóre, e.g. "45:17"
|
||||
Points string `json:"points"` // B
|
||||
}
|
||||
|
||||
func (ManualTableRow) TableName() string { return "manual_table_rows" }
|
||||
@@ -274,6 +274,16 @@ type Settings struct {
|
||||
VideosSource string `json:"videos_source"` // auto | manual
|
||||
VideosLimit int `json:"videos_limit"` // number of items on homepage
|
||||
|
||||
// Transient decoded forms for admin/public JSON responses (not persisted)
|
||||
Videos []string `gorm:"-" json:"videos,omitempty"`
|
||||
VideosItems []struct {
|
||||
URL string `json:"url"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Length *string `json:"length,omitempty"`
|
||||
UploadedAt *string `json:"uploaded_at,omitempty"`
|
||||
ThumbnailURL *string `json:"thumbnail_url,omitempty"`
|
||||
} `gorm:"-" json:"videos_items,omitempty"`
|
||||
|
||||
// Manual videos storage (JSON strings)
|
||||
VideosJSON string `gorm:"type:text" json:"-"`
|
||||
VideosItemsJSON string `gorm:"type:text" json:"-"`
|
||||
@@ -340,6 +350,9 @@ type Settings struct {
|
||||
ErrorReviewAdminURL string `json:"error_review_admin_url"`
|
||||
ErrorReviewAdminToken string `json:"error_review_admin_token"`
|
||||
ErrorReviewUIURL string `json:"error_review_ui_url"`
|
||||
|
||||
// E-shop payment configuration
|
||||
RevolutEnabled bool `json:"revolut_enabled"`
|
||||
}
|
||||
|
||||
// TableName specifies table name for Settings model
|
||||
|
||||
@@ -104,6 +104,19 @@ func (n *NavigationItem) GetURL() string {
|
||||
"files": "/admin/soubory",
|
||||
"docs": "/admin/docs",
|
||||
"engagement": "/admin/engagement",
|
||||
"i18n": "/admin/jazyky",
|
||||
"financial_dashboard": "/admin/financial-dashboard",
|
||||
"expenses": "/admin/expenses",
|
||||
"invoices": "/admin/invoices",
|
||||
"invoice_settings": "/admin/invoice-settings",
|
||||
"customers": "/admin/customers",
|
||||
"eshop_products": "/admin/eshop-products",
|
||||
"tickets": "/admin/tickets",
|
||||
"facilities": "/admin/facilities",
|
||||
"equipment": "/admin/equipment",
|
||||
"maintenance": "/admin/maintenance",
|
||||
"manual_facr": "/admin/manual-facr",
|
||||
"qr_codes": "/admin/qr-codes",
|
||||
}
|
||||
if url, ok := adminURLMap[n.PageType]; ok {
|
||||
return url
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type QRCode struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name" gorm:"not null;size:255"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
TargetURL string `json:"target_url" gorm:"not null;size:500"`
|
||||
QRCodeURL string `json:"qr_code_url" gorm:"size:500"`
|
||||
ScanCount int `json:"scan_count" gorm:"default:0"`
|
||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (QRCode) TableName() string {
|
||||
return "qr_codes"
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user