package report import ( "context" "encoding/csv" "errors" "fmt" "log/slog" "os" "sort" "strconv" "strings" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" // notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" // virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" ) var ( ErrInvalidTimeRange = errors.New("invalid time range - start time must be before end time") ErrInvalidReportCriteria = errors.New("invalid report criteria") ) type Service struct { betStore bet.BetStore walletStore wallet.WalletStore transactionStore transaction.TransactionStore branchStore branch.BranchStore userStore user.UserStore repo repository.ReportRepository companyStore company.CompanyStore virtulaGamesStore repository.VirtualGameRepository notificationStore repository.NotificationRepository logger *slog.Logger } func NewService( betStore bet.BetStore, walletStore wallet.WalletStore, transactionStore transaction.TransactionStore, branchStore branch.BranchStore, userStore user.UserStore, repo repository.ReportRepository, companyStore company.CompanyStore, virtulaGamesStore repository.VirtualGameRepository, notificationStore repository.NotificationRepository, logger *slog.Logger, ) *Service { return &Service{ betStore: betStore, walletStore: walletStore, transactionStore: transactionStore, branchStore: branchStore, userStore: userStore, repo: repo, companyStore: companyStore, virtulaGamesStore: virtulaGamesStore, notificationStore: notificationStore, logger: logger, } } // GetDashboardSummary returns comprehensive dashboard metrics func (s *Service) GetDashboardSummary(ctx context.Context, filter domain.ReportFilter) (domain.DashboardSummary, error) { if err := validateTimeRange(filter); err != nil { return domain.DashboardSummary{}, err } var summary domain.DashboardSummary var err error // Get bets summary summary.TotalStakes, summary.TotalBets, summary.ActiveBets, summary.TotalWins, summary.TotalLosses, summary.WinBalance, err = s.betStore.GetBetSummary(ctx, filter) if err != nil { s.logger.Error("failed to get bet summary", "error", err) return domain.DashboardSummary{}, err } // Get customer metrics summary.CustomerCount, summary.ActiveCustomers, summary.InactiveCustomers, err = s.userStore.GetCustomerCounts(ctx, filter) if err != nil { s.logger.Error("failed to get customer counts", "error", err) return domain.DashboardSummary{}, err } // Get branch metrics summary.BranchesCount, summary.ActiveBranches, summary.InactiveBranches, err = s.branchStore.GetBranchCounts(ctx, filter) if err != nil { s.logger.Error("failed to get branch counts", "error", err) return domain.DashboardSummary{}, err } // Get transaction metrics summary.TotalDeposits, summary.TotalWithdrawals, err = s.transactionStore.GetTransactionTotals(ctx, filter) if err != nil { s.logger.Error("failed to get transaction totals", "error", err) return domain.DashboardSummary{}, err } // Get user role metrics summary.TotalCashiers, summary.ActiveCashiers, summary.InactiveCashiers, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleCashier), filter) if err != nil { s.logger.Error("failed to get cashier counts", "error", err) return domain.DashboardSummary{}, err } summary.TotalManagers, summary.ActiveManagers, summary.InactiveManagers, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleBranchManager), filter) if err != nil { s.logger.Error("failed to get manager counts", "error", err) return domain.DashboardSummary{}, err } summary.TotalAdmins, summary.ActiveAdmins, summary.InactiveAdmins, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleAdmin), filter) if err != nil { s.logger.Error("failed to get admin counts", "error", err) return domain.DashboardSummary{}, err } // Get wallet metrics summary.TotalWallets, err = s.walletStore.GetTotalWallets(ctx, filter) if err != nil { s.logger.Error("failed to get wallet counts", "error", err) return domain.DashboardSummary{}, err } // Get sport/game metrics summary.TotalGames, summary.ActiveGames, summary.InactiveGames, err = s.virtulaGamesStore.GetGameCounts(ctx, filter) if err != nil { s.logger.Error("failed to get game counts", "error", err) return domain.DashboardSummary{}, err } // Get company metrics summary.TotalCompanies, summary.ActiveCompanies, summary.InactiveCompanies, err = s.companyStore.GetCompanyCounts(ctx, filter) if err != nil { s.logger.Error("failed to get company counts", "error", err) return domain.DashboardSummary{}, err } // Get notification metrics summary.TotalNotifications, summary.ReadNotifications, summary.UnreadNotifications, err = s.notificationStore.GetNotificationCounts(ctx, filter) if err != nil { s.logger.Error("failed to get notification counts", "error", err) return domain.DashboardSummary{}, err } // Calculate derived metrics if summary.TotalBets > 0 { summary.AverageStake = summary.TotalStakes / domain.Currency(summary.TotalBets) summary.WinRate = float64(summary.TotalWins) / float64(summary.TotalBets) * 100 summary.Profit = summary.TotalStakes - summary.WinBalance } return summary, nil } // Getdomain.BetAnalysis returns detailed bet analysis func (s *Service) GetBetAnalysis(ctx context.Context, filter domain.ReportFilter) ([]domain.BetAnalysis, error) { if err := validateTimeRange(filter); err != nil { return nil, err } // Get basic bet stats betStats, err := s.betStore.GetBetStats(ctx, filter) if err != nil { s.logger.Error("failed to get bet stats", "error", err) return nil, err } // Get sport popularity sportPopularity, err := s.betStore.GetSportPopularity(ctx, filter) if err != nil { s.logger.Error("failed to get sport popularity", "error", err) return nil, err } // Get market popularity marketPopularity, err := s.betStore.GetMarketPopularity(ctx, filter) if err != nil { s.logger.Error("failed to get market popularity", "error", err) return nil, err } // Get extreme values extremeValues, err := s.betStore.GetExtremeValues(ctx, filter) if err != nil { s.logger.Error("failed to get extreme values", "error", err) return nil, err } // Combine data into analysis var analysis []domain.BetAnalysis for _, stat := range betStats { a := domain.BetAnalysis{ Date: stat.Date, TotalBets: stat.TotalBets, TotalStakes: stat.TotalStakes, TotalWins: stat.TotalWins, TotalPayouts: stat.TotalPayouts, Profit: stat.TotalStakes - stat.TotalPayouts, AverageOdds: stat.AverageOdds, } // Add sport popularity if sport, ok := sportPopularity[stat.Date]; ok { a.MostPopularSport = sport } // Add market popularity if market, ok := marketPopularity[stat.Date]; ok { a.MostPopularMarket = market } // Add extreme values if extremes, ok := extremeValues[stat.Date]; ok { a.HighestStake = extremes.HighestStake a.HighestPayout = extremes.HighestPayout } analysis = append(analysis, a) } // Sort by date sort.Slice(analysis, func(i, j int) bool { return analysis[i].Date.Before(analysis[j].Date) }) return analysis, nil } // Getdomain.CustomerActivity returns customer activity report func (s *Service) GetCustomerActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.CustomerActivity, error) { if err := validateTimeRange(filter); err != nil { return nil, err } // Get customer bet activity customerBets, err := s.betStore.GetCustomerBetActivity(ctx, filter) if err != nil { s.logger.Error("failed to get customer bet activity", "error", err) return nil, err } // Get customer details customerDetails, err := s.userStore.GetCustomerDetails(ctx, filter) if err != nil { s.logger.Error("failed to get customer details", "error", err) return nil, err } // Get customer preferences customerPrefs, err := s.betStore.GetCustomerPreferences(ctx, filter) if err != nil { s.logger.Error("failed to get customer preferences", "error", err) return nil, err } // Combine data into activity report var activities []domain.CustomerActivity for _, bet := range customerBets { activity := domain.CustomerActivity{ CustomerID: bet.CustomerID, TotalBets: bet.TotalBets, TotalStakes: bet.TotalStakes, TotalWins: bet.TotalWins, TotalPayouts: bet.TotalPayouts, Profit: bet.TotalStakes - bet.TotalPayouts, FirstBetDate: bet.FirstBetDate, LastBetDate: bet.LastBetDate, AverageStake: bet.TotalStakes / domain.Currency(bet.TotalBets), AverageOdds: bet.AverageOdds, } // Add customer details if details, ok := customerDetails[bet.CustomerID]; ok { activity.CustomerName = details.Name } // Add preferences if prefs, ok := customerPrefs[bet.CustomerID]; ok { activity.FavoriteSport = prefs.FavoriteSport activity.FavoriteMarket = prefs.FavoriteMarket } // Calculate win rate if bet.TotalBets > 0 { activity.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 } // Determine activity level activity.ActivityLevel = calculateActivityLevel(bet.TotalBets, bet.TotalStakes) activities = append(activities, activity) } // Sort by total stakes (descending) sort.Slice(activities, func(i, j int) bool { return activities[i].TotalStakes > activities[j].TotalStakes }) return activities, nil } // Getdomain.BranchPerformance returns branch performance report func (s *Service) GetBranchPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.BranchPerformance, error) { // Get branch bet activity branchBets, err := s.betStore.GetBranchBetActivity(ctx, filter) if err != nil { s.logger.Error("failed to get branch bet activity", "error", err) return nil, err } // Get branch details branchDetails, err := s.branchStore.GetBranchDetails(ctx, filter) if err != nil { s.logger.Error("failed to get branch details", "error", err) return nil, err } // Get branch transactions branchTransactions, err := s.transactionStore.GetBranchTransactionTotals(ctx, filter) if err != nil { s.logger.Error("failed to get branch transactions", "error", err) return nil, err } // Get branch customer counts branchCustomers, err := s.userStore.GetBranchCustomerCounts(ctx, filter) if err != nil { s.logger.Error("failed to get branch customer counts", "error", err) return nil, err } // Combine data into performance report var performances []domain.BranchPerformance for _, bet := range branchBets { performance := domain.BranchPerformance{ BranchID: bet.BranchID, TotalBets: bet.TotalBets, TotalStakes: bet.TotalStakes, TotalWins: bet.TotalWins, TotalPayouts: bet.TotalPayouts, Profit: bet.TotalStakes - bet.TotalPayouts, } // Add branch details if details, ok := branchDetails[bet.BranchID]; ok { performance.BranchName = details.Name performance.Location = details.Location performance.ManagerName = details.ManagerName } // Add transactions if transactions, ok := branchTransactions[bet.BranchID]; ok { performance.Deposits = transactions.Deposits performance.Withdrawals = transactions.Withdrawals } // Add customer counts if customers, ok := branchCustomers[bet.BranchID]; ok { performance.CustomerCount = customers } // Calculate metrics if bet.TotalBets > 0 { performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) } // Calculate performance score performance.PerformanceScore = calculatePerformanceScore(performance) performances = append(performances, performance) } // Sort by performance score (descending) sort.Slice(performances, func(i, j int) bool { return performances[i].PerformanceScore > performances[j].PerformanceScore }) return performances, nil } // Getdomain.SportPerformance returns sport performance report func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.SportPerformance, error) { // Get sport bet activity sportBets, err := s.betStore.GetSportBetActivity(ctx, filter) if err != nil { s.logger.Error("failed to get sport bet activity", "error", err) return nil, err } // Get sport details (names) sportDetails, err := s.betStore.GetSportDetails(ctx, filter) if err != nil { s.logger.Error("failed to get sport details", "error", err) return nil, err } // Get sport market popularity sportMarkets, err := s.betStore.GetSportMarketPopularity(ctx, filter) if err != nil { s.logger.Error("failed to get sport market popularity", "error", err) return nil, err } // Combine data into performance report var performances []domain.SportPerformance for _, bet := range sportBets { performance := domain.SportPerformance{ SportID: bet.SportID, TotalBets: bet.TotalBets, TotalStakes: bet.TotalStakes, TotalWins: bet.TotalWins, TotalPayouts: bet.TotalPayouts, Profit: bet.TotalStakes - bet.TotalPayouts, AverageOdds: bet.AverageOdds, } // Add sport details if details, ok := sportDetails[bet.SportID]; ok { performance.SportName = details } // Add market popularity if market, ok := sportMarkets[bet.SportID]; ok { performance.MostPopularMarket = market } // Calculate metrics if bet.TotalBets > 0 { performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) } performances = append(performances, performance) } // Sort by total stakes (descending) and assign popularity rank sort.Slice(performances, func(i, j int) bool { return performances[i].TotalStakes > performances[j].TotalStakes }) for i := range performances { performances[i].PopularityRank = i + 1 } return performances, nil } func (s *Service) GenerateReport(ctx context.Context, period string) error { data, err := s.fetchReportData(ctx, period) if err != nil { return fmt.Errorf("fetch data: %w", err) } filePath := fmt.Sprintf("/host-desktop/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04")) file, err := os.Create(filePath) if err != nil { return fmt.Errorf("create file: %w", err) } defer file.Close() writer := csv.NewWriter(file) defer writer.Flush() // Summary section writer.Write([]string{"Sports Betting Reports (Periodic)"}) writer.Write([]string{"Period", "Total Bets", "Total Cash Made", "Total Cash Out", "Total Cash Backs", "Total Deposits", "Total Withdrawals", "Total Tickets"}) writer.Write([]string{ period, fmt.Sprintf("%d", data.TotalBets), fmt.Sprintf("%.2f", data.TotalCashIn), fmt.Sprintf("%.2f", data.TotalCashOut), fmt.Sprintf("%.2f", data.CashBacks), fmt.Sprintf("%.2f", data.Deposits), fmt.Sprintf("%.2f", data.Withdrawals), fmt.Sprintf("%d", data.TotalTickets), }) writer.Write([]string{}) // Empty line for spacing // Virtual Game Summary section writer.Write([]string{"Virtual Game Reports (Periodic)"}) writer.Write([]string{"Game Name", "Number of Bets", "Total Transaction Sum"}) for _, row := range data.VirtualGameStats { writer.Write([]string{ row.GameName, fmt.Sprintf("%d", row.NumBets), fmt.Sprintf("%.2f", row.TotalTransaction), }) } writer.Write([]string{}) // Empty line writer.Write([]string{"Company Reports (Periodic)"}) writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) for _, cr := range data.CompanyReports { writer.Write([]string{ fmt.Sprintf("%d", cr.CompanyID), cr.CompanyName, fmt.Sprintf("%d", cr.TotalBets), fmt.Sprintf("%.2f", cr.TotalCashIn), fmt.Sprintf("%.2f", cr.TotalCashOut), fmt.Sprintf("%.2f", cr.TotalCashBacks), }) } writer.Write([]string{}) // Empty line writer.Write([]string{"Branch Reports (Periodic)"}) writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) for _, br := range data.BranchReports { writer.Write([]string{ fmt.Sprintf("%d", br.BranchID), br.BranchName, fmt.Sprintf("%d", br.CompanyID), fmt.Sprintf("%d", br.TotalBets), fmt.Sprintf("%.2f", br.TotalCashIn), fmt.Sprintf("%.2f", br.TotalCashOut), fmt.Sprintf("%.2f", br.TotalCashBacks), }) } var totalBets int64 var totalCashIn, totalCashOut, totalCashBacks float64 for _, cr := range data.CompanyReports { totalBets += cr.TotalBets totalCashIn += cr.TotalCashIn totalCashOut += cr.TotalCashOut totalCashBacks += cr.TotalCashBacks } writer.Write([]string{}) writer.Write([]string{"Total Summary"}) writer.Write([]string{"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) writer.Write([]string{ fmt.Sprintf("%d", totalBets), fmt.Sprintf("%.2f", totalCashIn), fmt.Sprintf("%.2f", totalCashOut), fmt.Sprintf("%.2f", totalCashBacks), }) return nil } func (s *Service) fetchReportData(ctx context.Context, period string) (domain.ReportData, error) { from, to := getTimeRange(period) // companyID := int64(0) // Basic metrics totalBets, _ := s.repo.GetTotalBetsMadeInRange(ctx, from, to) cashIn, _ := s.repo.GetTotalCashMadeInRange(ctx, from, to) cashOut, _ := s.repo.GetTotalCashOutInRange(ctx, from, to) cashBacks, _ := s.repo.GetTotalCashBacksInRange(ctx, from, to) // Wallet Transactions transactions, _ := s.repo.GetWalletTransactionsInRange(ctx, from, to) var totalDeposits, totalWithdrawals float64 for _, tx := range transactions { switch strings.ToLower(tx.Type.String) { case "deposit": totalDeposits += float64(tx.TotalAmount) case "withdraw": totalWithdrawals += float64(tx.TotalAmount) } } // Ticket Count totalTickets, _ := s.repo.GetAllTicketsInRange(ctx, from, to) // Virtual Game Summary virtualGameStats, _ := s.repo.GetVirtualGameSummaryInRange(ctx, from, to) // Convert []dbgen.GetVirtualGameSummaryInRangeRow to []domain.VirtualGameStat var virtualGameStatsDomain []domain.VirtualGameStat for _, row := range virtualGameStats { var totalTransaction float64 switch v := row.TotalTransactionSum.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalTransaction = val } case float64: totalTransaction = v case int: totalTransaction = float64(v) default: totalTransaction = 0 } virtualGameStatsDomain = append(virtualGameStatsDomain, domain.VirtualGameStat{ GameName: row.GameName, NumBets: row.NumberOfBets, TotalTransaction: totalTransaction, }) } companyRows, _ := s.repo.GetCompanyWiseReport(ctx, from, to) var companyReports []domain.CompanyReport for _, row := range companyRows { var totalCashIn, totalCashOut, totalCashBacks float64 switch v := row.TotalCashMade.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashIn = val } case float64: totalCashIn = v case int: totalCashIn = float64(v) default: totalCashIn = 0 } switch v := row.TotalCashOut.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashOut = val } case float64: totalCashOut = v case int: totalCashOut = float64(v) default: totalCashOut = 0 } switch v := row.TotalCashBacks.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashBacks = val } case float64: totalCashBacks = v case int: totalCashBacks = float64(v) default: totalCashBacks = 0 } companyReports = append(companyReports, domain.CompanyReport{ CompanyID: row.CompanyID.Int64, CompanyName: row.CompanyName, TotalBets: row.TotalBets, TotalCashIn: totalCashIn, TotalCashOut: totalCashOut, TotalCashBacks: totalCashBacks, }) } branchRows, _ := s.repo.GetBranchWiseReport(ctx, from, to) var branchReports []domain.BranchReport for _, row := range branchRows { var totalCashIn, totalCashOut, totalCashBacks float64 switch v := row.TotalCashMade.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashIn = val } case float64: totalCashIn = v case int: totalCashIn = float64(v) default: totalCashIn = 0 } switch v := row.TotalCashOut.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashOut = val } case float64: totalCashOut = v case int: totalCashOut = float64(v) default: totalCashOut = 0 } switch v := row.TotalCashBacks.(type) { case string: val, err := strconv.ParseFloat(v, 64) if err == nil { totalCashBacks = val } case float64: totalCashBacks = v case int: totalCashBacks = float64(v) default: totalCashBacks = 0 } branchReports = append(branchReports, domain.BranchReport{ BranchID: row.BranchID.Int64, BranchName: row.BranchName, CompanyID: row.CompanyID, TotalBets: row.TotalBets, TotalCashIn: totalCashIn, TotalCashOut: totalCashOut, TotalCashBacks: totalCashBacks, }) } return domain.ReportData{ TotalBets: totalBets, TotalCashIn: cashIn, TotalCashOut: cashOut, CashBacks: cashBacks, Deposits: totalDeposits, Withdrawals: totalWithdrawals, TotalTickets: totalTickets.TotalTickets, VirtualGameStats: virtualGameStatsDomain, CompanyReports: companyReports, BranchReports: branchReports, }, nil } func getTimeRange(period string) (time.Time, time.Time) { now := time.Now() switch strings.ToLower(period) { case "daily": start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) end := start.Add(5 * time.Minute) return start, end case "weekly": weekday := int(now.Weekday()) if weekday == 0 { weekday = 7 } start := now.AddDate(0, 0, -weekday+1) start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, now.Location()) end := start.AddDate(0, 0, 7) return start, end case "monthly": start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) end := start.AddDate(0, 1, 0) return start, end default: // Default to daily start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) end := start.Add(24 * time.Hour) return start, end } } // func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) { // // Get company bet activity // companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter) // if err != nil { // s.logger.Error("failed to get company bet activity", "error", err) // return nil, err // } // // Get company details // companyDetails, err := s.branchStore.GetCompanyDetails(ctx, filter) // if err != nil { // s.logger.Error("failed to get company details", "error", err) // return nil, err // } // // Get company branches // companyBranches, err := s.branchStore.GetCompanyBranchCounts(ctx, filter) // if err != nil { // s.logger.Error("failed to get company branch counts", "error", err) // return nil, err // } // // Combine data into performance report // var performances []domain.CompanyPerformance // for _, bet := range companyBets { // performance := domain.CompanyPerformance{ // CompanyID: bet.CompanyID, // TotalBets: bet.TotalBets, // TotalStakes: bet.TotalStakes, // TotalWins: bet.TotalWins, // TotalPayouts: bet.TotalPayouts, // Profit: bet.TotalStakes - bet.TotalPayouts, // } // // Add company details // if details, ok := companyDetails[bet.CompanyID]; ok { // performance.CompanyName = details.Name // performance.ContactEmail = details.ContactEmail // } // // Add branch counts // if branches, ok := companyBranches[bet.CompanyID]; ok { // performance.TotalBranches = branches.Total // performance.ActiveBranches = branches.Active // } // // Calculate metrics // if bet.TotalBets > 0 { // performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 // performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) // } // performances = append(performances, performance) // } // // Sort by profit (descending) // sort.Slice(performances, func(i, j int) bool { // return performances[i].Profit > performances[j].Profit // }) // return performances, nil // } // GetCashierPerformance returns cashier performance report // func (s *Service) GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error) { // // Get cashier bet activity // cashierBets, err := s.betStore.GetCashierBetActivity(ctx, filter) // if err != nil { // s.logger.Error("failed to get cashier bet activity", "error", err) // return nil, err // } // // Get cashier details // cashierDetails, err := s.userStore.GetCashierDetails(ctx, filter) // if err != nil { // s.logger.Error("failed to get cashier details", "error", err) // return nil, err // } // // Get cashier transactions // cashierTransactions, err := s.transactionStore.GetCashierTransactionTotals(ctx, filter) // if err != nil { // s.logger.Error("failed to get cashier transactions", "error", err) // return nil, err // } // // Combine data into performance report // var performances []domain.CashierPerformance // for _, bet := range cashierBets { // performance := domain.CashierPerformance{ // CashierID: bet.CashierID, // TotalBets: bet.TotalBets, // TotalStakes: bet.TotalStakes, // TotalWins: bet.TotalWins, // TotalPayouts: bet.TotalPayouts, // Profit: bet.TotalStakes - bet.TotalPayouts, // } // // Add cashier details // if details, ok := cashierDetails[bet.CashierID]; ok { // performance.CashierName = details.Name // performance.BranchID = details.BranchID // performance.BranchName = details.BranchName // } // // Add transactions // if transactions, ok := cashierTransactions[bet.CashierID]; ok { // performance.Deposits = transactions.Deposits // performance.Withdrawals = transactions.Withdrawals // } // // Calculate metrics // if bet.TotalBets > 0 { // performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 // performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) // } // performances = append(performances, performance) // } // // Sort by total stakes (descending) // sort.Slice(performances, func(i, j int) bool { // return performances[i].TotalStakes > performances[j].TotalStakes // }) // return performances, nil // } // GetNotificationReport returns notification statistics report // func (s *Service) GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error) { // // Get notification counts by type // countsByType, err := s.notificationStore.GetNotificationCountsByType(ctx, filter) // if err != nil { // s.logger.Error("failed to get notification counts by type", "error", err) // return domain.NotificationReport{}, err // } // // Get notification delivery stats // deliveryStats, err := s.notificationStore.GetNotificationDeliveryStats(ctx, filter) // if err != nil { // s.logger.Error("failed to get notification delivery stats", "error", err) // return domain.NotificationReport{}, err // } // // Get most active notification recipients // activeRecipients, err := s.notificationStore.GetMostActiveNotificationRecipients(ctx, filter) // if err != nil { // s.logger.Error("failed to get active notification recipients", "error", err) // return domain.NotificationReport{}, err // } // return domain.NotificationReport{ // CountsByType: countsByType, // DeliveryStats: deliveryStats, // ActiveRecipients: activeRecipients, // }, nil // } // Helper functions func validateTimeRange(filter domain.ReportFilter) error { if filter.StartTime.Valid && filter.EndTime.Valid { if filter.StartTime.Value.After(filter.EndTime.Value) { return ErrInvalidTimeRange } } return nil } func calculateActivityLevel(totalBets int64, totalStakes domain.Currency) string { switch { case totalBets > 100 || totalStakes > 10000: return "High" case totalBets > 50 || totalStakes > 5000: return "Medium" default: return "Low" } } func calculatePerformanceScore(perf domain.BranchPerformance) float64 { // Simple scoring algorithm - can be enhanced based on business rules profitScore := float64(perf.Profit) / 1000 customerScore := float64(perf.CustomerCount) * 0.1 betScore := float64(perf.TotalBets) * 0.01 winRateScore := perf.WinRate * 0.1 return profitScore + customerScore + betScore + winRateScore }