From 0779cd35feefd8406e9d8f8fba33880521bcb0a0 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Wed, 20 Aug 2025 15:56:44 +0300 Subject: [PATCH] report query fixes --- cmd/main.go | 2 +- db/query/report.sql | 102 +++--- gen/db/report.sql.go | 101 +++--- internal/domain/report.go | 13 + internal/services/report/service.go | 430 +++++++++++-------------- internal/web_server/cron.go | 46 ++- internal/web_server/handlers/report.go | 4 +- internal/web_server/routes.go | 2 +- 8 files changed, 342 insertions(+), 358 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 9f90784..354626e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -187,7 +187,7 @@ func main() { logger, ) - go httpserver.SetupReportCronJobs(context.Background(), reportSvc) + go httpserver.SetupReportCronJobs(context.Background(), reportSvc, "C:/Users/User/Desktop") go httpserver.ProcessBetCashback(context.TODO(), betSvc) bankRepository := repository.NewBankRepository(store) diff --git a/db/query/report.sql b/db/query/report.sql index 5f72931..b352554 100644 --- a/db/query/report.sql +++ b/db/query/report.sql @@ -1,59 +1,57 @@ -- name: GetCompanyWiseReport :many -SELECT b.company_id, - c.name AS company_name, - COUNT(*) AS total_bets, - COALESCE(SUM(b.amount), 0) AS total_cash_made, - COALESCE( - SUM( - CASE - WHEN b.cashed_out THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_out, - COALESCE( - SUM( - CASE - WHEN b.status = 5 THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_backs +SELECT + b.company_id, + c.name AS company_name, + COUNT(*) AS total_bets, + COALESCE(SUM(b.amount), 0) AS total_cash_made, + COALESCE( + SUM( + CASE + WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets + ELSE 0 + END + ), 0 + ) AS total_cash_out, + COALESCE( + SUM( + CASE + WHEN b.status = 5 THEN b.amount + ELSE 0 + END + ), 0 + ) AS total_cash_backs FROM shop_bet_detail b - JOIN companies c ON b.company_id = c.id +JOIN companies c ON b.company_id = c.id +JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to') -GROUP BY b.company_id, - c.name; +GROUP BY b.company_id, c.name; + -- name: GetBranchWiseReport :many -SELECT b.branch_id, - br.name AS branch_name, - br.company_id, - COUNT(*) AS total_bets, - COALESCE(SUM(b.amount), 0) AS total_cash_made, - COALESCE( - SUM( - CASE - WHEN b.cashed_out THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_out, - COALESCE( - SUM( - CASE - WHEN b.status = 5 THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_backs +SELECT + b.branch_id, + br.name AS branch_name, + br.company_id, + COUNT(*) AS total_bets, + COALESCE(SUM(b.amount), 0) AS total_cash_made, + COALESCE( + SUM( + CASE + WHEN sb.cashed_out THEN b.amount -- use cashed_out from shop_bets + ELSE 0 + END + ), 0 + ) AS total_cash_out, + COALESCE( + SUM( + CASE + WHEN b.status = 5 THEN b.amount + ELSE 0 + END + ), 0 + ) AS total_cash_backs FROM shop_bet_detail b - JOIN branches br ON b.branch_id = br.id +JOIN branches br ON b.branch_id = br.id +JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to') -GROUP BY b.branch_id, - br.name, - br.company_id; \ No newline at end of file +GROUP BY b.branch_id, br.name, br.company_id; diff --git a/gen/db/report.sql.go b/gen/db/report.sql.go index 63f097d..1a1ccde 100644 --- a/gen/db/report.sql.go +++ b/gen/db/report.sql.go @@ -12,35 +12,33 @@ import ( ) const GetBranchWiseReport = `-- name: GetBranchWiseReport :many -SELECT b.branch_id, - br.name AS branch_name, - br.company_id, - COUNT(*) AS total_bets, - COALESCE(SUM(b.amount), 0) AS total_cash_made, - COALESCE( - SUM( - CASE - WHEN b.cashed_out THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_out, - COALESCE( - SUM( - CASE - WHEN b.status = 5 THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_backs +SELECT + b.branch_id, + br.name AS branch_name, + br.company_id, + COUNT(*) AS total_bets, + COALESCE(SUM(b.amount), 0) AS total_cash_made, + COALESCE( + SUM( + CASE + WHEN sb.cashed_out THEN b.amount -- use cashed_out from shop_bets + ELSE 0 + END + ), 0 + ) AS total_cash_out, + COALESCE( + SUM( + CASE + WHEN b.status = 5 THEN b.amount + ELSE 0 + END + ), 0 + ) AS total_cash_backs FROM shop_bet_detail b - JOIN branches br ON b.branch_id = br.id +JOIN branches br ON b.branch_id = br.id +JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out WHERE b.created_at BETWEEN $1 AND $2 -GROUP BY b.branch_id, - br.name, - br.company_id +GROUP BY b.branch_id, br.name, br.company_id ` type GetBranchWiseReportParams struct { @@ -87,33 +85,32 @@ func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseRepo } const GetCompanyWiseReport = `-- name: GetCompanyWiseReport :many -SELECT b.company_id, - c.name AS company_name, - COUNT(*) AS total_bets, - COALESCE(SUM(b.amount), 0) AS total_cash_made, - COALESCE( - SUM( - CASE - WHEN b.cashed_out THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_out, - COALESCE( - SUM( - CASE - WHEN b.status = 5 THEN b.amount - ELSE 0 - END - ), - 0 - ) AS total_cash_backs +SELECT + b.company_id, + c.name AS company_name, + COUNT(*) AS total_bets, + COALESCE(SUM(b.amount), 0) AS total_cash_made, + COALESCE( + SUM( + CASE + WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets + ELSE 0 + END + ), 0 + ) AS total_cash_out, + COALESCE( + SUM( + CASE + WHEN b.status = 5 THEN b.amount + ELSE 0 + END + ), 0 + ) AS total_cash_backs FROM shop_bet_detail b - JOIN companies c ON b.company_id = c.id +JOIN companies c ON b.company_id = c.id +JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out WHERE b.created_at BETWEEN $1 AND $2 -GROUP BY b.company_id, - c.name +GROUP BY b.company_id, c.name ` type GetCompanyWiseReportParams struct { diff --git a/internal/domain/report.go b/internal/domain/report.go index 9c0c632..7e6ca55 100644 --- a/internal/domain/report.go +++ b/internal/domain/report.go @@ -488,6 +488,7 @@ type LiveWalletMetrics struct { BranchBalances []BranchWalletBalance `json:"branch_balances"` } +// Company-level aggregated report type CompanyReport struct { CompanyID int64 CompanyName string @@ -497,6 +498,7 @@ type CompanyReport struct { TotalCashBacks float64 } +// Branch-level aggregated report type BranchReport struct { BranchID int64 BranchName string @@ -506,3 +508,14 @@ type BranchReport struct { TotalCashOut float64 TotalCashBacks float64 } + + +// type CompanyReport struct { +// CompanyID int64 +// Branches []BranchReport +// } + +// type BranchReport struct { +// BranchID int64 +// Rows []interface{} // replace with the actual row type, e.g., dbgen.GetWalletTransactionsInRangeRow +// } diff --git a/internal/services/report/service.go b/internal/services/report/service.go index 17e0b57..1454bae 100644 --- a/internal/services/report/service.go +++ b/internal/services/report/service.go @@ -184,7 +184,7 @@ func (s *Service) GetBetAnalysis(ctx context.Context, filter domain.ReportFilter 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 { @@ -459,87 +459,76 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF return performances, nil } -func (s *Service) GenerateReport(ctx context.Context, period string) error { - data, err := s.fetchReportData(ctx, period) +func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error { + // Hardcoded output directory + outputDir := "C:/Users/User/Desktop/reports" + + // Ensure directory exists + if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create report directory: %w", err) + } + + companies, branchMap, err := s.fetchReportData(ctx, from, to) if err != nil { - return fmt.Errorf("fetch data: %w", err) + return err } - // Ensure the reports directory exists - if err := os.MkdirAll("reports", os.ModePerm); err != nil { - return fmt.Errorf("creating reports directory: %w", err) + // per-company reports + for _, company := range companies { + branches := branchMap[company.CompanyID] + if err := writeCompanyCSV(company, branches, from, to, outputDir); err != nil { + return fmt.Errorf("company %d CSV: %w", company.CompanyID, err) + } } - filePath := fmt.Sprintf("reports/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04")) + // summary report + if err := writeSummaryCSV(companies, from, to, outputDir); err != nil { + return fmt.Errorf("summary CSV: %w", err) + } + + return nil +} + +// writeCompanyCSV writes the company report to CSV in the hardcoded folder +func writeCompanyCSV(company domain.CompanyReport, branches []domain.BranchReport, from, to time.Time, outputDir string) error { + period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02")) + + filePath := fmt.Sprintf("%s/company_%d_%s_%s_%s.csv", + outputDir, + company.CompanyID, + from.Format("2006-01-02"), + to.Format("2006-01-02"), + time.Now().Format("2006-01-02_15-04"), + ) + file, err := os.Create(filePath) if err != nil { - return fmt.Errorf("create file: %w", err) + return fmt.Errorf("create company csv: %w", err) } defer file.Close() writer := csv.NewWriter(file) defer writer.Flush() - // Summary section - if err := writer.Write([]string{"Sports Betting Reports (Periodic)"}); err != nil { - return fmt.Errorf("write header: %w", err) - } - if err := writer.Write([]string{"Period", "Total Bets", "Total Cash Made", "Total Cash Out", "Total Cash Backs", "Total Deposits", "Total Withdrawals", "Total Tickets"}); err != nil { - return fmt.Errorf("write header row: %w", err) - } - if err := writer.Write([]string{ + // Company summary section + writer.Write([]string{"Company Betting Report"}) + writer.Write([]string{"Period", "Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) + 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), - }); err != nil { - return fmt.Errorf("write summary row: %w", err) - } - + fmt.Sprintf("%d", company.CompanyID), + company.CompanyName, + fmt.Sprintf("%d", company.TotalBets), + fmt.Sprintf("%.2f", company.TotalCashIn), + fmt.Sprintf("%.2f", company.TotalCashOut), + fmt.Sprintf("%.2f", company.TotalCashBacks), + }) writer.Write([]string{}) // Empty line - // 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 { - if err := writer.Write([]string{ - row.GameName, - fmt.Sprintf("%d", row.NumBets), - fmt.Sprintf("%.2f", row.TotalTransaction), - }); err != nil { - return fmt.Errorf("write virtual game row: %w", err) - } - } - - writer.Write([]string{}) // Empty line - - // Company Reports - 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 { - if err := 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), - }); err != nil { - return fmt.Errorf("write company row: %w", err) - } - } - - writer.Write([]string{}) // Empty line - - // Branch Reports - writer.Write([]string{"Branch Reports (Periodic)"}) + // Branch reports + writer.Write([]string{"Branch Reports"}) 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 { - if err := writer.Write([]string{ + for _, br := range branches { + writer.Write([]string{ fmt.Sprintf("%d", br.BranchID), br.BranchName, fmt.Sprintf("%d", br.CompanyID), @@ -547,211 +536,142 @@ func (s *Service) GenerateReport(ctx context.Context, period string) error { fmt.Sprintf("%.2f", br.TotalCashIn), fmt.Sprintf("%.2f", br.TotalCashOut), fmt.Sprintf("%.2f", br.TotalCashBacks), - }); err != nil { - return fmt.Errorf("write branch row: %w", err) - } + }) } - // Total Summary + if err := writer.Error(); err != nil { + return fmt.Errorf("flush error: %w", err) + } + + return nil +} + +// writeSummaryCSV writes the summary report to CSV in the hardcoded folder +func writeSummaryCSV(companies []domain.CompanyReport, from, to time.Time, outputDir string) error { + period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02")) + + filePath := fmt.Sprintf("%s/summary_%s_%s_%s.csv", + outputDir, + from.Format("2006-01-02"), + to.Format("2006-01-02"), + time.Now().Format("2006-01-02_15-04"), + ) + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("create summary csv: %w", err) + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + // Global summary + writer.Write([]string{"Global Betting Summary"}) + writer.Write([]string{"Period", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) + 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 + var totalIn, totalOut, totalBack float64 + for _, c := range companies { + totalBets += c.TotalBets + totalIn += c.TotalCashIn + totalOut += c.TotalCashOut + totalBack += c.TotalCashBacks } - writer.Write([]string{}) // Empty line - writer.Write([]string{"Total Summary"}) - writer.Write([]string{"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) - if err := writer.Write([]string{ + writer.Write([]string{ + period, fmt.Sprintf("%d", totalBets), - fmt.Sprintf("%.2f", totalCashIn), - fmt.Sprintf("%.2f", totalCashOut), - fmt.Sprintf("%.2f", totalCashBacks), - }); err != nil { - return fmt.Errorf("write total summary row: %w", err) + fmt.Sprintf("%.2f", totalIn), + fmt.Sprintf("%.2f", totalOut), + fmt.Sprintf("%.2f", totalBack), + }) + writer.Write([]string{}) // Empty line + + // Company breakdown + writer.Write([]string{"Company Reports"}) + writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"}) + for _, cr := range companies { + 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), + }) + } + + if err := writer.Error(); err != nil { + return fmt.Errorf("flush error: %w", err) } 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) - } +func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) ( + []domain.CompanyReport, map[int64][]domain.BranchReport, error, +) { + // --- company level --- + companyRows, err := s.repo.GetCompanyWiseReport(ctx, from, to) + if err != nil { + return nil, nil, fmt.Errorf("company-wise report: %w", err) } - // 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 + companies := make([]domain.CompanyReport, 0, len(companyRows)) 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{ + companies = append(companies, domain.CompanyReport{ CompanyID: row.CompanyID, CompanyName: row.CompanyName, TotalBets: row.TotalBets, - TotalCashIn: totalCashIn, - TotalCashOut: totalCashOut, - TotalCashBacks: totalCashBacks, + TotalCashIn: toFloat(row.TotalCashMade), + TotalCashOut: toFloat(row.TotalCashOut), + TotalCashBacks: toFloat(row.TotalCashBacks), }) } - branchRows, _ := s.repo.GetBranchWiseReport(ctx, from, to) - var branchReports []domain.BranchReport + // --- branch level --- + branchRows, err := s.repo.GetBranchWiseReport(ctx, from, to) + if err != nil { + return nil, nil, fmt.Errorf("branch-wise report: %w", err) + } + + branchMap := make(map[int64][]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{ + branch := domain.BranchReport{ BranchID: row.BranchID, BranchName: row.BranchName, CompanyID: row.CompanyID, TotalBets: row.TotalBets, - TotalCashIn: totalCashIn, - TotalCashOut: totalCashOut, - TotalCashBacks: totalCashBacks, - }) + TotalCashIn: toFloat(row.TotalCashMade), + TotalCashOut: toFloat(row.TotalCashOut), + TotalCashBacks: toFloat(row.TotalCashBacks), + } + branchMap[row.CompanyID] = append(branchMap[row.CompanyID], branch) } - 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 + return companies, branchMap, nil } -func getTimeRange(period string) (time.Time, time.Time) { +// helper to unify float conversions +func toFloat(val interface{}) float64 { + switch v := val.(type) { + case string: + if f, err := strconv.ParseFloat(v, 64); err == nil { + return f + } + case float64: + return v + case int: + return float64(v) + case int64: + return float64(v) + } + return 0 +} + +func GetTimeRange(period string) (time.Time, time.Time) { now := time.Now() switch strings.ToLower(period) { case "daily": @@ -808,4 +728,32 @@ func calculatePerformanceScore(perf domain.BranchPerformance) float64 { winRateScore := perf.WinRate * 0.1 return profitScore + customerScore + betScore + winRateScore -} \ No newline at end of file +} + +// toCompanyReport converts grouped data into []CompanyReport +// func toCompanyReport(grouped map[int64]map[int64][]interface{}) []domain.CompanyReport { +// companyReports := []domain.CompanyReport{} +// for companyID, branches := range grouped { +// companyReport := domain.CompanyReport{ +// CompanyID: companyID, +// Branches: []domain.BranchReport{}, +// } +// for branchID, rows := range branches { +// branchReport := domain.BranchReport{ +// BranchID: branchID, +// Rows: rows, +// } +// companyReport.Branches = append(companyReport.Branches, branchReport) +// } +// companyReports = append(companyReports, companyReport) +// } +// return companyReports +// } + +// // toBranchReport converts []interface{} to []BranchReport +// func toBranchReport(rows []interface{}, branchID int64) domain.BranchReport { +// return domain.BranchReport{ +// BranchID: branchID, +// Rows: rows, +// } +// } diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index ba95002..d75fc7d 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -127,17 +127,18 @@ func StartTicketCrons(ticketService ticket.Service, mongoLogger *zap.Logger) { mongoLogger.Info("Cron jobs started for ticket service") } -func SetupReportCronJobs(ctx context.Context, reportService *report.Service) { - c := cron.New(cron.WithSeconds()) // use WithSeconds for tighter intervals during testing +// SetupReportCronJobs schedules periodic report generation +func SetupReportCronJobs(ctx context.Context, reportService *report.Service, outputDir string) { + c := cron.New(cron.WithSeconds()) // use WithSeconds for testing schedule := []struct { spec string period string }{ - // { - // spec: "*/300 * * * * *", // Every 5 minutes (300 seconds) - // period: "5min", - // }, + { + spec: "*/60 * * * * *", // Every 5 minutes + period: "5min", + }, { spec: "0 0 0 * * *", // Daily at midnight period: "daily", @@ -154,9 +155,36 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service) { for _, job := range schedule { period := job.period + if _, err := c.AddFunc(job.spec, func() { - log.Printf("Running %s report at %s", period, time.Now().Format(time.RFC3339)) - if err := reportService.GenerateReport(ctx, period); err != nil { + now := time.Now() + var from, to time.Time + + switch period { + case "5min": + from = now.Add(-5 * time.Minute) + to = now + case "daily": + from = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()) + to = time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location()) + case "weekly": + // last Sunday -> Saturday + weekday := int(now.Weekday()) + daysSinceSunday := (weekday + 7) % 7 + from = time.Date(now.Year(), now.Month(), now.Day()-daysSinceSunday-7, 0, 0, 0, 0, now.Location()) + to = from.AddDate(0, 0, 6).Add(time.Hour*23 + time.Minute*59 + time.Second*59) + case "monthly": + firstOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + from = firstOfMonth.AddDate(0, -1, 0) + to = firstOfMonth.Add(-time.Second) + default: + log.Printf("Unknown period: %s", period) + return + } + + log.Printf("Running %s report for period %s -> %s", period, from.Format(time.RFC3339), to.Format(time.RFC3339)) + + if err := reportService.GenerateReport(ctx, from, to); err != nil { log.Printf("Error generating %s report: %v", period, err) } else { log.Printf("Successfully generated %s report", period) @@ -167,7 +195,7 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service) { } c.Start() - log.Println("Cron jobs started for report generation service") + log.Printf("Cron jobs started. Reports will be saved to: %s", outputDir) } func ProcessBetCashback(ctx context.Context, betService *betSvc.Service) { diff --git a/internal/web_server/handlers/report.go b/internal/web_server/handlers/report.go index 63308da..7d31236 100644 --- a/internal/web_server/handlers/report.go +++ b/internal/web_server/handlers/report.go @@ -157,7 +157,7 @@ func (h *Handler) DownloadReportFile(c *fiber.Ctx) error { }) } - reportDir := "reports" + reportDir := "C:/Users/User/Desktop/reports" // Ensure reports directory exists if _, err := os.Stat(reportDir); os.IsNotExist(err) { @@ -207,7 +207,7 @@ func (h *Handler) DownloadReportFile(c *fiber.Ctx) error { // @Failure 500 {object} domain.ErrorResponse "Failed to read report directory" // @Router /api/v1/report-files/list [get] func (h *Handler) ListReportFiles(c *fiber.Ctx) error { - reportDir := "reports" + reportDir := "C:/Users/User/Desktop/reports" searchTerm := c.Query("search") page := c.QueryInt("page", 1) limit := c.QueryInt("limit", 20) diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index aa589b7..d4f00a6 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -281,7 +281,7 @@ func (a *App) initAppRoutes() { //Report Routes groupV1.Get("/reports/dashboard", a.authMiddleware, a.OnlyAdminAndAbove, h.GetDashboardReport) - groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile) + groupV1.Get("/report-files/download/:filename", h.DownloadReportFile) groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) //Alea Play Virtual Game Routes