diff --git a/handlers/admin/manualtriggers.go b/handlers/admin/manualtriggers.go index 8ad3836..1a8e3ec 100644 --- a/handlers/admin/manualtriggers.go +++ b/handlers/admin/manualtriggers.go @@ -2,9 +2,13 @@ package handlers import ( "database/sql" + "fmt" "html/template" + "log" "net/http" + "net/url" "strconv" + "synlotto-website/helpers" services "synlotto-website/services/tickets" ) @@ -13,27 +17,110 @@ func AdminTriggersHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { context := helpers.TemplateContext(w, r) + // Inject flash message if available + if flash := r.URL.Query().Get("flash"); flash != "" { + context["Flash"] = flash + } + if r.Method == http.MethodPost { - stats, err := services.RunTicketMatching(db, "manual") - if err != nil { - http.Error(w, "Matching failed: "+err.Error(), http.StatusInternalServerError) - return + action := r.FormValue("action") + flashMsg := "" + + switch action { + case "match": + stats, err := services.RunTicketMatching(db, "manual") + if err != nil { + http.Error(w, "Matching failed: "+err.Error(), http.StatusInternalServerError) + return + } + flashMsg = fmt.Sprintf("✅ Matched %d tickets, %d winners.", stats.TicketsMatched, stats.WinnersFound) + + case "prizes": + err := services.UpdateMissingPrizes(db) + if err != nil { + http.Error(w, "Prize update failed: "+err.Error(), http.StatusInternalServerError) + return + } + flashMsg = "✅ Missing prizes updated." + + case "run_all": + stats, err := services.RunTicketMatching(db, "manual") + if err != nil { + http.Error(w, "Matching failed: "+err.Error(), http.StatusInternalServerError) + return + } + err = services.UpdateMissingPrizes(db) + if err != nil { + http.Error(w, "Prize update failed: "+err.Error(), http.StatusInternalServerError) + return + } + flashMsg = fmt.Sprintf("✅ Matched %d tickets, %d winners. Prizes updated.", stats.TicketsMatched, stats.WinnersFound) + + default: + flashMsg = "⚠️ Unknown action." } - http.Redirect(w, r, "/admin/triggers?flash=Matched "+ - strconv.Itoa(stats.TicketsMatched)+" tickets, "+ - strconv.Itoa(stats.WinnersFound)+" winners.", http.StatusSeeOther) + // Redirect back with flash message + http.Redirect(w, r, "/admin/triggers?flash="+url.QueryEscape(flashMsg), http.StatusSeeOther) return } - // Render admin trigger page + // Render the admin trigger page tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( "templates/layout.html", "templates/admin/triggers.html", )) + err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { + log.Println("Template error:", err) http.Error(w, "Failed to load page", http.StatusInternalServerError) } } } + +func MatchTicketsHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + stats, err := services.RunTicketMatching(db, "manual") + if err != nil { + http.Error(w, "Matching failed: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/admin/triggers?flash=Matched "+ + strconv.Itoa(stats.TicketsMatched)+" tickets, "+ + strconv.Itoa(stats.WinnersFound)+" winners.", http.StatusSeeOther) + } +} + +func UpdateMissingPrizesHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := services.UpdateMissingPrizes(db) + if err != nil { + http.Error(w, "Prize update failed: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/admin/triggers?flash=Updated missing prize data.", http.StatusSeeOther) + } +} + +func RunAllHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + stats, err := services.RunTicketMatching(db, "manual") + if err != nil { + http.Error(w, "Matching failed: "+err.Error(), http.StatusInternalServerError) + return + } + + err = services.UpdateMissingPrizes(db) + if err != nil { + http.Error(w, "Prize update failed: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/admin/triggers?flash=Matched "+ + strconv.Itoa(stats.TicketsMatched)+" tickets, "+ + strconv.Itoa(stats.WinnersFound)+" winners. Prizes updated.", http.StatusSeeOther) + } +} diff --git a/matcher/engine.go b/matcher/engine.go index 145d671..a66a824 100644 --- a/matcher/engine.go +++ b/matcher/engine.go @@ -1,24 +1,46 @@ package matcher import ( + "database/sql" + "fmt" "synlotto-website/helpers" "synlotto-website/models" + thunderballRules "synlotto-website/rules" ) -func MatchTicketToDraw(ticket models.MatchTicket, draw models.DrawResult, rules []models.PrizeRule) models.MatchResult { +func MatchTicketToDraw(ticket models.MatchTicket, draw models.DrawResult, rules []models.PrizeRule, db *sql.DB) models.MatchResult { mainMatches := helpers.CountMatches(ticket.Balls, draw.Balls) bonusMatches := helpers.CountMatches(ticket.BonusBalls, draw.BonusBalls) prizeTier := getPrizeTier(ticket.GameType, mainMatches, bonusMatches, rules) isWinner := prizeTier != "" - return models.MatchResult{ + result := models.MatchResult{ MatchedDrawID: draw.DrawID, MatchedMain: mainMatches, MatchedBonus: bonusMatches, PrizeTier: prizeTier, IsWinner: isWinner, } + + if ticket.GameType == "Thunderball" && isWinner { + if idx, ok := thunderballRules.GetThunderballPrizeIndex(mainMatches, bonusMatches); ok { + query := fmt.Sprintf(`SELECT prize%d_per_winner FROM prizes_thunderball WHERE draw_date = ?`, idx) + + var amount int + err := db.QueryRow(query, draw.DrawDate).Scan(&amount) + if err == nil { + result.PrizeAmount = float64(amount) + if amount == 0 { + result.PrizeLabel = "Free Ticket" + } else { + result.PrizeLabel = fmt.Sprintf("£%.2f", float64(amount)) + } + } + } + } + + return result } func getPrizeTier(game string, main, bonus int, rules []models.PrizeRule) string { diff --git a/models/match.go b/models/match.go index 5a5290a..01e7f8a 100644 --- a/models/match.go +++ b/models/match.go @@ -22,6 +22,9 @@ type MatchResult struct { PrizeTier string IsWinner bool MatchedDrawID int + + PrizeAmount float64 + PrizeLabel string } type PrizeRule struct { diff --git a/models/ticket.go b/models/ticket.go index 9ea6346..0a4038f 100644 --- a/models/ticket.go +++ b/models/ticket.go @@ -25,4 +25,6 @@ type Ticket struct { Balls []int BonusBalls []int MatchedDraw DrawResult + PrizeAmount float64 `db:"prize_amount"` + PrizeLabel string `db:"prize_label"` } diff --git a/rules/thunderball.go b/rules/thunderball.go index 7479006..69fdcd5 100644 --- a/rules/thunderball.go +++ b/rules/thunderball.go @@ -2,8 +2,45 @@ package rules import "synlotto-website/models" -var ThunderballPrizeRules = []models.PrizeRule{ - {Game: "Thunderball", MainMatches: 5, BonusMatches: 1, Tier: "Jackpot"}, - {Game: "Thunderball", MainMatches: 5, BonusMatches: 0, Tier: "Second"}, - {Game: "Thunderball", MainMatches: 4, BonusMatches: 1, Tier: "Third"}, +type PrizeInfo struct { + Tier string + Amount float64 + Label string +} + +var ThunderballPrizeRules = []models.PrizeRule{ + {Game: "Thunderball", MainMatches: 0, BonusMatches: 1, Tier: "Tier 1"}, + {Game: "Thunderball", MainMatches: 1, BonusMatches: 1, Tier: "Tier 2"}, + {Game: "Thunderball", MainMatches: 2, BonusMatches: 1, Tier: "Tier 3"}, + {Game: "Thunderball", MainMatches: 3, BonusMatches: 0, Tier: "Tier 4"}, + {Game: "Thunderball", MainMatches: 3, BonusMatches: 1, Tier: "Tier 5"}, + {Game: "Thunderball", MainMatches: 4, BonusMatches: 0, Tier: "Tier 6"}, + {Game: "Thunderball", MainMatches: 4, BonusMatches: 1, Tier: "Tier 7"}, + {Game: "Thunderball", MainMatches: 5, BonusMatches: 0, Tier: "Second"}, + {Game: "Thunderball", MainMatches: 5, BonusMatches: 1, Tier: "Jackpot"}, +} + +func GetThunderballPrizeIndex(main, bonus int) (int, bool) { + switch { + case main == 5 && bonus == 1: + return 9, true + case main == 5 && bonus == 0: + return 8, true + case main == 4 && bonus == 1: + return 7, true + case main == 4 && bonus == 0: + return 6, true + case main == 3 && bonus == 1: + return 5, true + case main == 3 && bonus == 0: + return 4, true + case main == 2 && bonus == 1: + return 3, true + case main == 1 && bonus == 1: + return 2, true + case main == 0 && bonus == 1: + return 1, true + default: + return 0, false + } } diff --git a/services/tickets/ticketmatching.go b/services/tickets/ticketmatching.go index 4c8d119..01b3731 100644 --- a/services/tickets/ticketmatching.go +++ b/services/tickets/ticketmatching.go @@ -2,6 +2,7 @@ package services import ( "database/sql" + "fmt" "log" "synlotto-website/handlers" "synlotto-website/helpers" @@ -91,3 +92,74 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er return stats, nil } + +func UpdateMissingPrizes(db *sql.DB) error { + type TicketInfo struct { + ID int + GameType string + DrawDate string + Main int + Bonus int + } + + var tickets []TicketInfo + + // Step 1: Load all relevant tickets + rows, err := db.Query(` + SELECT id, game_type, draw_date, matched_main, matched_bonus + FROM my_tickets + WHERE is_winner = 1 AND (prize_label IS NULL OR prize_label = '') + `) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var t TicketInfo + if err := rows.Scan(&t.ID, &t.GameType, &t.DrawDate, &t.Main, &t.Bonus); err != nil { + log.Println("⚠️ Failed to scan row:", err) + continue + } + tickets = append(tickets, t) + } + + // Step 2: Now that the reader is closed, perform updates + for _, t := range tickets { + if t.GameType != "Thunderball" { + continue + } + + idx, ok := rules.GetThunderballPrizeIndex(t.Main, t.Bonus) + if !ok { + log.Printf("❌ No index for %d main, %d bonus", t.Main, t.Bonus) + continue + } + + query := fmt.Sprintf(`SELECT prize%d_per_winner FROM prizes_thunderball WHERE draw_date = ?`, idx) + + var amount int + err := db.QueryRow(query, t.DrawDate).Scan(&amount) + if err != nil { + log.Printf("❌ Prize lookup failed for ticket %d: %v", t.ID, err) + continue + } + + label := "Free Ticket" + if amount > 0 { + label = fmt.Sprintf("£%.2f", float64(amount)) + } + + _, err = db.Exec(` + UPDATE my_tickets SET prize_amount = ?, prize_label = ? WHERE id = ? + `, float64(amount), label, t.ID) + + if err != nil { + log.Printf("❌ Failed to update ticket %d: %v", t.ID, err) + } else { + log.Printf("✅ Updated ticket %d → %s", t.ID, label) + } + } + + return nil +} diff --git a/templates/account/tickets/my_tickets.html b/templates/account/tickets/my_tickets.html index 5b4c106..c68d957 100644 --- a/templates/account/tickets/my_tickets.html +++ b/templates/account/tickets/my_tickets.html @@ -15,6 +15,7 @@ Purchased Via Image + Prize @@ -70,6 +71,21 @@ View {{ else }}–{{ end }} + + {{ if $ticket.IsWinner }} + {{ if $ticket.PrizeLabel }} + {{ if eq $ticket.PrizeLabel "Free Ticket" }} + 🎟️ {{ $ticket.PrizeLabel }} + {{ else }} + 💷 {{ $ticket.PrizeLabel }} + {{ end }} + {{ else }} + pending + {{ end }} + {{ else }} + – + {{ end }} + {{ end }} diff --git a/templates/admin/triggers.html b/templates/admin/triggers.html index 1643780..6e1c5e9 100644 --- a/templates/admin/triggers.html +++ b/templates/admin/triggers.html @@ -1,16 +1,25 @@ {{ define "content" }}

Manual Admin Triggers

-

This page lets you manually run backend processes like result matching.

-
{{ .CSRFField }} - + + +
+ +
+ {{ .CSRFField }} + + +
+ +
+ {{ .CSRFField }} + +
{{ if .Flash }} -

{{ .Flash }}

-{{ end }} +

{{ .Flash }}

{{ end }} +{{ end }} \ No newline at end of file