This commit is contained in:
Tomas Dvorak
2026-01-26 08:13:18 +01:00
parent aa036b6550
commit dfc079288f
505 changed files with 95755 additions and 5712 deletions
+2 -2
View File
@@ -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
}
+231
View File
@@ -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" }
+259
View File
@@ -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"`
}
+260
View File
@@ -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"`
}
+137
View File
@@ -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)
}
+280
View File
@@ -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"`
}
+89
View File
@@ -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" }
+13
View File
@@ -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
+13
View File
@@ -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
+21
View File
@@ -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"
}
+199
View File
@@ -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())
}