feat: Refactor report generation and management
- Removed detailed event routes from the API. - Added new report request routes for creating and fetching report requests. - Introduced new domain models for report requests, including metadata and status handling. - Implemented report request processing logic, including CSV generation for event interval reports. - Enhanced company statistics handling with new domain models and service methods. - Updated repository interfaces and implementations to support new report functionalities. - Added error handling and logging for report file operations and notifications.
This commit is contained in:
parent
5af3c5d978
commit
0ffba57ec5
|
|
@ -33,6 +33,15 @@ Clone the repository:
|
||||||
git clone https://github.com/your-org/fortunebet-backend.git
|
git clone https://github.com/your-org/fortunebet-backend.git
|
||||||
cd fortunebet-backend
|
cd fortunebet-backend
|
||||||
```
|
```
|
||||||
|
Then you will need to setup the database, which you can do using:
|
||||||
|
```bash
|
||||||
|
make db-up
|
||||||
|
```
|
||||||
|
|
||||||
|
You will also need to setup the necessary seed_data using:
|
||||||
|
```bash
|
||||||
|
make seed_data
|
||||||
|
```
|
||||||
|
|
||||||
## Environment Configuration
|
## Environment Configuration
|
||||||
Create a .env file in the root directory. This file is critical for running the application as it contains database credentials, secret keys, third-party integrations, and configuration flags.
|
Create a .env file in the root directory. This file is critical for running the application as it contains database credentials, secret keys, third-party integrations, and configuration flags.
|
||||||
|
|
|
||||||
12
cmd/main.go
12
cmd/main.go
|
|
@ -114,7 +114,7 @@ func main() {
|
||||||
|
|
||||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||||
userSvc := user.NewService(store, store, messengerSvc, cfg)
|
userSvc := user.NewService(store, store, messengerSvc, cfg)
|
||||||
eventSvc := event.New(cfg.Bet365Token, store, *settingSvc, domain.MongoDBLogger, cfg)
|
eventSvc := event.New(cfg.Bet365Token, store, settingSvc, domain.MongoDBLogger, cfg)
|
||||||
oddsSvc := odds.New(store, cfg, eventSvc, logger, domain.MongoDBLogger)
|
oddsSvc := odds.New(store, cfg, eventSvc, logger, domain.MongoDBLogger)
|
||||||
notificationRepo := repository.NewNotificationRepository(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||||
|
|
@ -139,7 +139,7 @@ func main() {
|
||||||
branchSvc := branch.NewService(store)
|
branchSvc := branch.NewService(store)
|
||||||
companySvc := company.NewService(store)
|
companySvc := company.NewService(store)
|
||||||
leagueSvc := league.New(store)
|
leagueSvc := league.New(store)
|
||||||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
|
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, settingSvc, notificationSvc)
|
||||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger)
|
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger)
|
||||||
resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, messengerSvc, *userSvc)
|
resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, messengerSvc, *userSvc)
|
||||||
bonusSvc := bonus.NewService(store, walletSvc, settingSvc, notificationSvc, domain.MongoDBLogger)
|
bonusSvc := bonus.NewService(store, walletSvc, settingSvc, notificationSvc, domain.MongoDBLogger)
|
||||||
|
|
@ -176,6 +176,7 @@ func main() {
|
||||||
transactionSvc := transaction.NewService(store, *branchSvc, *betSvc, *walletSvc, *userSvc)
|
transactionSvc := transaction.NewService(store, *branchSvc, *betSvc, *walletSvc, *userSvc)
|
||||||
|
|
||||||
reportSvc := report.NewService(
|
reportSvc := report.NewService(
|
||||||
|
store,
|
||||||
bet.BetStore(store),
|
bet.BetStore(store),
|
||||||
wallet.WalletStore(store),
|
wallet.WalletStore(store),
|
||||||
transaction.TransactionStore(store),
|
transaction.TransactionStore(store),
|
||||||
|
|
@ -185,7 +186,12 @@ func main() {
|
||||||
company.CompanyStore(store),
|
company.CompanyStore(store),
|
||||||
virtuaGamesRepo,
|
virtuaGamesRepo,
|
||||||
notificationRepo,
|
notificationRepo,
|
||||||
|
notificationSvc,
|
||||||
|
eventSvc,
|
||||||
|
companySvc,
|
||||||
logger,
|
logger,
|
||||||
|
domain.MongoDBLogger,
|
||||||
|
cfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
enePulseSvc := enetpulse.New(
|
enePulseSvc := enetpulse.New(
|
||||||
|
|
@ -238,6 +244,8 @@ func main() {
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
|
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
|
||||||
httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger)
|
httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger)
|
||||||
|
httpserver.StartStatCrons(*companySvc, eventSvc, domain.MongoDBLogger)
|
||||||
|
httpserver.StartReportCrons(reportSvc, domain.MongoDBLogger)
|
||||||
|
|
||||||
issueReportingRepo := repository.NewReportedIssueRepository(store)
|
issueReportingRepo := repository.NewReportedIssueRepository(store)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -442,12 +442,23 @@ CREATE TABLE companies (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
CREATE TABLE company_stats (
|
CREATE TABLE company_stats (
|
||||||
company_id BIGINT PRIMARY KEY,
|
company_id BIGINT NOT NULL,
|
||||||
total_bets BIGINT,
|
interval_start TIMESTAMP NOT NULL,
|
||||||
total_cash_made BIGINT,
|
total_bets BIGINT NOT NULL,
|
||||||
total_cash_out BIGINT,
|
total_stake BIGINT NOT NULL,
|
||||||
total_cash_backs BIGINT,
|
deducted_stake BIGINT NOT NULL,
|
||||||
updated_at TIMESTAMP DEFAULT now()
|
total_cash_out BIGINT NOT NULL,
|
||||||
|
total_cash_backs BIGINT NOT NULL,
|
||||||
|
number_of_unsettled BIGINT NOT NULL,
|
||||||
|
total_unsettled_amount BIGINT NOT NULL,
|
||||||
|
total_admins BIGINT NOT NULL,
|
||||||
|
total_managers BIGINT NOT NULL,
|
||||||
|
total_cashiers BIGINT NOT NULL,
|
||||||
|
total_customers BIGINT NOT NULL,
|
||||||
|
total_approvers BIGINT NOT NULL,
|
||||||
|
total_branches BIGINT NOT NULL,
|
||||||
|
updated_at TIMESTAMP DEFAULT now(),
|
||||||
|
UNIQUE(company_id, interval_start)
|
||||||
);
|
);
|
||||||
CREATE TABLE leagues (
|
CREATE TABLE leagues (
|
||||||
id BIGINT PRIMARY KEY,
|
id BIGINT PRIMARY KEY,
|
||||||
|
|
@ -575,23 +586,21 @@ CREATE TABLE IF NOT EXISTS raffle_game_filters (
|
||||||
game_id VARCHAR(150) NOT NULL,
|
game_id VARCHAR(150) NOT NULL,
|
||||||
CONSTRAINT unique_raffle_game UNIQUE (raffle_id, game_id)
|
CONSTRAINT unique_raffle_game UNIQUE (raffle_id, game_id)
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS accumulator (
|
|
||||||
outcome_count BIGINT PRIMARY KEY,
|
|
||||||
default_multiplier REAL NOT NULL
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS company_accumulator (
|
CREATE TABLE IF NOT EXISTS company_accumulator (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
company_id BIGINT NOT NULL,
|
company_id BIGINT NOT NULL,
|
||||||
outcome_count BIGINT NOT NULL,
|
outcome_count BIGINT NOT NULL,
|
||||||
multiplier REAL NOT NULL
|
multiplier REAL NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE reports (
|
CREATE TABLE report_requests (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
company_id BIGINT,
|
company_id BIGINT,
|
||||||
requested_by BIGINT,
|
requested_by BIGINT,
|
||||||
--For System Generated Reports
|
|
||||||
file_path TEXT,
|
file_path TEXT,
|
||||||
|
type TEXT NOT NULL,
|
||||||
status TEXT NOT NULL DEFAULT 'pending',
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
metadata JSONB NOT NULL,
|
||||||
|
reject_reason TEXT,
|
||||||
created_at TIMESTAMP DEFAULT now(),
|
created_at TIMESTAMP DEFAULT now(),
|
||||||
completed_at TIMESTAMP
|
completed_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
@ -602,20 +611,42 @@ SELECT companies.*,
|
||||||
wallets.is_active as wallet_is_active,
|
wallets.is_active as wallet_is_active,
|
||||||
users.first_name AS admin_first_name,
|
users.first_name AS admin_first_name,
|
||||||
users.last_name AS admin_last_name,
|
users.last_name AS admin_last_name,
|
||||||
users.phone_number AS admin_phone_number
|
users.phone_number AS admin_phone_number,
|
||||||
|
COALESCE(cs.total_bets, 0) AS total_bets,
|
||||||
|
COALESCE(cs.total_stake, 0) AS total_stake,
|
||||||
|
COALESCE(cs.deducted_stake, 0) AS deducted_stake,
|
||||||
|
COALESCE(cs.total_cash_out, 0) AS total_cash_out,
|
||||||
|
COALESCE(cs.total_cash_backs, 0) AS total_cash_backs,
|
||||||
|
COALESCE(cs.number_of_unsettled, 0) AS number_of_unsettled,
|
||||||
|
COALESCE(cs.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||||
|
COALESCE(cs.total_admins, 0) AS total_admins,
|
||||||
|
COALESCE(cs.total_managers, 0) AS total_managers,
|
||||||
|
COALESCE(cs.total_cashiers, 0) AS total_cashiers,
|
||||||
|
COALESCE(cs.total_customers, 0) AS total_customers,
|
||||||
|
COALESCE(cs.total_approvers, 0) AS total_approvers,
|
||||||
|
COALESCE(cs.total_branches, 0) AS total_branches,
|
||||||
|
cs.updated_at AS stats_updated_at
|
||||||
FROM companies
|
FROM companies
|
||||||
JOIN wallets ON wallets.id = companies.wallet_id
|
JOIN wallets ON wallets.id = companies.wallet_id
|
||||||
JOIN users ON users.id = companies.admin_id;
|
JOIN users ON users.id = companies.admin_id
|
||||||
;
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT *
|
||||||
|
FROM company_stats s
|
||||||
|
WHERE s.company_id = companies.id
|
||||||
|
ORDER BY s.interval_start DESC
|
||||||
|
LIMIT 1
|
||||||
|
) cs ON true;
|
||||||
CREATE VIEW branch_details AS
|
CREATE VIEW branch_details AS
|
||||||
SELECT branches.*,
|
SELECT branches.*,
|
||||||
CONCAT (users.first_name, ' ', users.last_name) AS manager_name,
|
CONCAT (users.first_name, ' ', users.last_name) AS manager_name,
|
||||||
users.phone_number AS manager_phone_number,
|
users.phone_number AS manager_phone_number,
|
||||||
wallets.balance,
|
wallets.balance,
|
||||||
wallets.is_active AS wallet_is_active
|
wallets.is_active AS wallet_is_active,
|
||||||
|
companies.name AS company_name
|
||||||
FROM branches
|
FROM branches
|
||||||
LEFT JOIN users ON branches.branch_manager_id = users.id
|
LEFT JOIN users ON branches.branch_manager_id = users.id
|
||||||
LEFT JOIN wallets ON wallets.id = branches.wallet_id;
|
LEFT JOIN wallets ON wallets.id = branches.wallet_id
|
||||||
|
JOIN companies ON companies.id = branches.company_id;
|
||||||
CREATE TABLE IF NOT EXISTS supported_operations (
|
CREATE TABLE IF NOT EXISTS supported_operations (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
|
|
@ -719,15 +750,15 @@ SELECT sd.*,
|
||||||
FROM shop_deposits AS sd
|
FROM shop_deposits AS sd
|
||||||
JOIN shop_transactions st ON st.id = sd.shop_transaction_id;
|
JOIN shop_transactions st ON st.id = sd.shop_transaction_id;
|
||||||
CREATE OR REPLACE VIEW event_detailed AS
|
CREATE OR REPLACE VIEW event_detailed AS
|
||||||
SELECT ewc.*,
|
SELECT events.*,
|
||||||
leagues.country_code as league_cc,
|
leagues.country_code as league_cc,
|
||||||
COALESCE(om.total_outcomes, 0) AS total_outcomes,
|
COALESCE(om.total_outcomes, 0) AS total_outcomes,
|
||||||
COALESCE(ebs.number_of_bets, 0) AS number_of_bets,
|
COALESCE(ebs.number_of_bets, 0) AS number_of_bets,
|
||||||
COALESCE(ebs.total_amount, 0) AS total_amount,
|
COALESCE(ebs.total_amount, 0) AS total_amount,
|
||||||
COALESCE(ebs.avg_bet_amount, 0) AS avg_bet_amount,
|
COALESCE(ebs.avg_bet_amount, 0) AS avg_bet_amount,
|
||||||
COALESCE(ebs.total_potential_winnings, 0) AS total_potential_winnings
|
COALESCE(ebs.total_potential_winnings, 0) AS total_potential_winnings
|
||||||
FROM events ewc
|
FROM events
|
||||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
|
LEFT JOIN event_bet_stats ebs ON ebs.event_id = events.id
|
||||||
LEFT JOIN leagues ON leagues.id = events.league_id
|
LEFT JOIN leagues ON leagues.id = events.league_id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT event_id,
|
SELECT event_id,
|
||||||
|
|
@ -735,9 +766,6 @@ FROM events ewc
|
||||||
FROM odds_market
|
FROM odds_market
|
||||||
GROUP BY event_id
|
GROUP BY event_id
|
||||||
) om ON om.event_id = events.id;
|
) om ON om.event_id = events.id;
|
||||||
CREATE MATERIALIZED VIEW event_detailed_mat AS
|
|
||||||
SELECT *
|
|
||||||
FROM event_detailed;
|
|
||||||
CREATE VIEW odds_market_with_event AS
|
CREATE VIEW odds_market_with_event AS
|
||||||
SELECT o.*,
|
SELECT o.*,
|
||||||
e.is_monitored,
|
e.is_monitored,
|
||||||
|
|
@ -774,7 +802,7 @@ SELECT e.*,
|
||||||
FROM events e
|
FROM events e
|
||||||
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
|
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
|
||||||
JOIN leagues l ON l.id = e.league_id
|
JOIN leagues l ON l.id = e.league_id
|
||||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
|
LEFT JOIN event_bet_stats ebs ON ebs.event_id = e.id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT event_id,
|
SELECT event_id,
|
||||||
SUM(number_of_outcomes) AS total_outcomes
|
SUM(number_of_outcomes) AS total_outcomes
|
||||||
|
|
@ -798,6 +826,15 @@ SELECT o.id,
|
||||||
cos.updated_at
|
cos.updated_at
|
||||||
FROM odds_market o
|
FROM odds_market o
|
||||||
LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id;
|
LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id;
|
||||||
|
CREATE VIEW report_request_detail AS
|
||||||
|
SELECT r.*,
|
||||||
|
c.name AS company_name,
|
||||||
|
c.slug AS company_slug,
|
||||||
|
u.first_name AS requester_first_name,
|
||||||
|
u.last_name AS requester_last_name
|
||||||
|
FROM report_requests r
|
||||||
|
LEFT JOIN companies c ON c.id = r.company_id
|
||||||
|
LEFT JOIN users u ON u.id = r.requested_by;
|
||||||
-- Foreign Keys
|
-- Foreign Keys
|
||||||
ALTER TABLE refresh_tokens
|
ALTER TABLE refresh_tokens
|
||||||
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id);
|
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS notifications (
|
||||||
'withdraw_success',
|
'withdraw_success',
|
||||||
'bet_placed',
|
'bet_placed',
|
||||||
'daily_report',
|
'daily_report',
|
||||||
|
'report_request',
|
||||||
'high_loss_on_bet',
|
'high_loss_on_bet',
|
||||||
'bet_overload',
|
'bet_overload',
|
||||||
'signup_welcome',
|
'signup_welcome',
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
-- Aggregate company stats
|
|
||||||
-- name: UpdateCompanyStats :exec
|
-- name: UpdateCompanyStats :exec
|
||||||
INSERT INTO company_stats (
|
WITH -- Aggregate bet data per company
|
||||||
company_id,
|
bet_stats AS (
|
||||||
total_bets,
|
SELECT company_id,
|
||||||
total_cash_made,
|
|
||||||
total_cash_backs,
|
|
||||||
updated_at
|
|
||||||
)
|
|
||||||
SELECT b.company_id,
|
|
||||||
COUNT(*) AS total_bets,
|
COUNT(*) AS total_bets,
|
||||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
COALESCE(SUM(amount), 0) AS total_stake,
|
||||||
|
COALESCE(
|
||||||
|
SUM(amount) * MAX(companies.deducted_percentage),
|
||||||
|
0
|
||||||
|
) AS deducted_stake,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets
|
WHEN cashed_out THEN amount
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
),
|
),
|
||||||
|
|
@ -22,21 +20,121 @@ SELECT b.company_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
WHEN b.status = 5 THEN b.amount
|
WHEN status = 3 THEN amount
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
),
|
),
|
||||||
0
|
0
|
||||||
) AS total_cash_backs,
|
) AS total_cash_backs,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE status = 5
|
||||||
|
) AS number_of_unsettled,
|
||||||
|
COALESCE(
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN status = 5 THEN amount
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0
|
||||||
|
) AS total_unsettled_amount
|
||||||
|
FROM shop_bet_detail
|
||||||
|
LEFT JOIN companies ON companies.id = shop_bet_detail.company_id
|
||||||
|
GROUP BY company_id
|
||||||
|
),
|
||||||
|
-- Aggregate user counts per company
|
||||||
|
user_stats AS (
|
||||||
|
SELECT company_id,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'admin'
|
||||||
|
) AS total_admins,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'branch_manager'
|
||||||
|
) AS total_managers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'cashier'
|
||||||
|
) AS total_cashiers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'customer'
|
||||||
|
) AS total_customers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'transaction_approver'
|
||||||
|
) AS total_approvers
|
||||||
|
FROM users
|
||||||
|
GROUP BY company_id
|
||||||
|
),
|
||||||
|
-- Aggregate branch counts per company
|
||||||
|
branch_stats AS (
|
||||||
|
SELECT company_id,
|
||||||
|
COUNT(*) AS total_branches
|
||||||
|
FROM branches
|
||||||
|
GROUP BY company_id
|
||||||
|
) -- Final combined aggregation
|
||||||
|
INSERT INTO company_stats (
|
||||||
|
company_id,
|
||||||
|
interval_start,
|
||||||
|
total_bets,
|
||||||
|
total_stake,
|
||||||
|
deducted_stake,
|
||||||
|
total_cash_out,
|
||||||
|
total_cash_backs,
|
||||||
|
number_of_unsettled,
|
||||||
|
total_unsettled_amount,
|
||||||
|
total_admins,
|
||||||
|
total_managers,
|
||||||
|
total_cashiers,
|
||||||
|
total_customers,
|
||||||
|
total_approvers,
|
||||||
|
total_branches,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
SELECT c.id AS company_id,
|
||||||
|
DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
||||||
|
COALESCE(b.total_bets, 0) AS total_bets,
|
||||||
|
COALESCE(b.total_stake, 0) AS total_stake,
|
||||||
|
COALESCE(b.deducted_stake, 0) AS deducted_stake,
|
||||||
|
COALESCE(b.total_cash_out, 0) AS total_cash_out,
|
||||||
|
COALESCE(b.total_cash_backs, 0) AS total_cash_backs,
|
||||||
|
COALESCE(b.number_of_unsettled, 0) AS number_of_unsettled,
|
||||||
|
COALESCE(b.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||||
|
COALESCE(u.total_admins, 0) AS total_admins,
|
||||||
|
COALESCE(u.total_managers, 0) AS total_managers,
|
||||||
|
COALESCE(u.total_cashiers, 0) AS total_cashiers,
|
||||||
|
COALESCE(u.total_customers, 0) AS total_customers,
|
||||||
|
COALESCE(u.total_approvers, 0) AS total_approvers,
|
||||||
|
COALESCE(br.total_branches, 0) AS total_branches,
|
||||||
NOW() AS updated_at
|
NOW() AS updated_at
|
||||||
FROM shop_bet_detail b
|
FROM companies c
|
||||||
JOIN companies c ON b.company_id = c.id
|
LEFT JOIN bet_stats b ON b.company_id = c.id
|
||||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
LEFT JOIN user_stats u ON u.company_id = c.id
|
||||||
GROUP BY b.company_id,
|
LEFT JOIN branch_stats br ON br.company_id = c.id ON CONFLICT (company_id, interval_start) DO
|
||||||
c.name ON CONFLICT (company_id) DO
|
|
||||||
UPDATE
|
UPDATE
|
||||||
SET total_bets = EXCLUDED.total_bets,
|
SET total_bets = EXCLUDED.total_bets,
|
||||||
total_cash_made = EXCLUDED.total_cash_made,
|
total_stake = EXCLUDED.total_stake,
|
||||||
|
deducted_stake = EXCLUDED.deducted_stake,
|
||||||
total_cash_out = EXCLUDED.total_cash_out,
|
total_cash_out = EXCLUDED.total_cash_out,
|
||||||
total_cash_back = EXCLUDED.total_cash_back,
|
total_cash_backs = EXCLUDED.total_cash_backs,
|
||||||
|
number_of_unsettled = EXCLUDED.number_of_unsettled,
|
||||||
|
total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
||||||
|
total_admins = EXCLUDED.total_admins,
|
||||||
|
total_managers = EXCLUDED.total_managers,
|
||||||
|
total_cashiers = EXCLUDED.total_cashiers,
|
||||||
|
total_customers = EXCLUDED.total_customers,
|
||||||
|
total_approvers = EXCLUDED.total_approvers,
|
||||||
|
total_branches = EXCLUDED.total_branches,
|
||||||
updated_at = EXCLUDED.updated_at;
|
updated_at = EXCLUDED.updated_at;
|
||||||
|
-- name: GetCompanyStatsByID :many
|
||||||
|
SELECT *
|
||||||
|
FROM company_stats
|
||||||
|
WHERE company_id = $1
|
||||||
|
ORDER BY interval_start DESC;
|
||||||
|
-- name: GetCompanyStats :many
|
||||||
|
SELECT DATE_TRUNC(sqlc.narg('interval'), interval_start)::timestamp AS interval_start,
|
||||||
|
company_stats.*
|
||||||
|
FROM company_stats
|
||||||
|
WHERE (
|
||||||
|
company_stats.company_id = sqlc.narg('company_id')
|
||||||
|
OR sqlc.narg('company_id') IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY interval_start
|
||||||
|
ORDER BY interval_start DESC;
|
||||||
|
|
@ -23,6 +23,3 @@ SET number_of_bets = EXCLUDED.number_of_bets,
|
||||||
avg_bet_amount = EXCLUDED.avg_bet_amount,
|
avg_bet_amount = EXCLUDED.avg_bet_amount,
|
||||||
total_potential_winnings = EXCLUDED.total_potential_winnings,
|
total_potential_winnings = EXCLUDED.total_potential_winnings,
|
||||||
updated_at = EXCLUDED.updated_at;
|
updated_at = EXCLUDED.updated_at;
|
||||||
|
|
||||||
-- name: UpdateEventDetailedViewMat :exec
|
|
||||||
REFRESH MATERIALIZED VIEW event_detailed_mat;
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
-- name: CreateReportRequest :one
|
||||||
|
INSERT INTO report_requests (
|
||||||
|
company_id,
|
||||||
|
requested_by,
|
||||||
|
type,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: GetAllReportRequests :many
|
||||||
|
SELECT *
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE (
|
||||||
|
company_id = sqlc.narg('company_id')
|
||||||
|
OR sqlc.narg('company_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
type = sqlc.narg('type')
|
||||||
|
OR sqlc.narg('type') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = sqlc.narg('status')
|
||||||
|
OR sqlc.narg('status') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
requested_by = sqlc.narg('requested_by')
|
||||||
|
OR sqlc.narg('requested_by') IS NULL
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
|
-- name: GetTotalReportRequests :one
|
||||||
|
SELECT COUNT(id)
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE (
|
||||||
|
company_id = sqlc.narg('company_id')
|
||||||
|
OR sqlc.narg('company_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
type = sqlc.narg('type')
|
||||||
|
OR sqlc.narg('type') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = sqlc.narg('status')
|
||||||
|
OR sqlc.narg('status') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
requested_by = sqlc.narg('requested_by')
|
||||||
|
OR sqlc.narg('requested_by') IS NULL
|
||||||
|
);
|
||||||
|
-- name: GetReportRequestByID :one
|
||||||
|
SELECT *
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE id = $1;
|
||||||
|
-- name: GetReportRequestByRequestedByID :many
|
||||||
|
SELECT *
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE requested_by = $1
|
||||||
|
AND (
|
||||||
|
type = sqlc.narg('type')
|
||||||
|
OR sqlc.narg('type') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = sqlc.narg('status')
|
||||||
|
OR sqlc.narg('status') IS NULL
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
|
-- name: UpdateReportRequest :exec
|
||||||
|
UPDATE report_requests
|
||||||
|
SET file_path = COALESCE(sqlc.narg(file_path), file_path),
|
||||||
|
reject_reason = COALESCE(sqlc.narg(reject_reason), reject_reason),
|
||||||
|
status = COALESCE(sqlc.narg(status), status),
|
||||||
|
completed_at = now()
|
||||||
|
WHERE id = $1;
|
||||||
|
|
@ -159,7 +159,7 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllBranches = `-- name: GetAllBranches :many
|
const GetAllBranches = `-- name: GetAllBranches :many
|
||||||
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active, company_name
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE (
|
WHERE (
|
||||||
company_id = $1
|
company_id = $1
|
||||||
|
|
@ -229,6 +229,7 @@ func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams)
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
&i.WalletIsActive,
|
&i.WalletIsActive,
|
||||||
|
&i.CompanyName,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -292,7 +293,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
|
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
|
||||||
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active, company_name
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE company_id = $1
|
WHERE company_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -322,6 +323,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
&i.WalletIsActive,
|
&i.WalletIsActive,
|
||||||
|
&i.CompanyName,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -334,7 +336,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByID = `-- name: GetBranchByID :one
|
const GetBranchByID = `-- name: GetBranchByID :one
|
||||||
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active, company_name
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -358,12 +360,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
&i.WalletIsActive,
|
&i.WalletIsActive,
|
||||||
|
&i.CompanyName,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
|
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
|
||||||
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active, company_name
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE branch_manager_id = $1
|
WHERE branch_manager_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -393,6 +396,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
&i.WalletIsActive,
|
&i.WalletIsActive,
|
||||||
|
&i.CompanyName,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -452,7 +456,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchBranchByName = `-- name: SearchBranchByName :many
|
const SearchBranchByName = `-- name: SearchBranchByName :many
|
||||||
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active, company_name
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE name ILIKE '%' || $1 || '%'
|
WHERE name ILIKE '%' || $1 || '%'
|
||||||
AND (
|
AND (
|
||||||
|
|
@ -491,6 +495,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, arg SearchBranchByName
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
&i.WalletIsActive,
|
&i.WalletIsActive,
|
||||||
|
&i.CompanyName,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllCompanies = `-- name: GetAllCompanies :many
|
const GetAllCompanies = `-- name: GetAllCompanies :many
|
||||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||||
FROM companies_details
|
FROM companies_details
|
||||||
WHERE (
|
WHERE (
|
||||||
name ILIKE '%' || $1 || '%'
|
name ILIKE '%' || $1 || '%'
|
||||||
|
|
@ -117,6 +117,20 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
|
||||||
&i.AdminFirstName,
|
&i.AdminFirstName,
|
||||||
&i.AdminLastName,
|
&i.AdminLastName,
|
||||||
&i.AdminPhoneNumber,
|
&i.AdminPhoneNumber,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalStake,
|
||||||
|
&i.DeductedStake,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
&i.NumberOfUnsettled,
|
||||||
|
&i.TotalUnsettledAmount,
|
||||||
|
&i.TotalAdmins,
|
||||||
|
&i.TotalManagers,
|
||||||
|
&i.TotalCashiers,
|
||||||
|
&i.TotalCustomers,
|
||||||
|
&i.TotalApprovers,
|
||||||
|
&i.TotalBranches,
|
||||||
|
&i.StatsUpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +143,7 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetCompanyByID = `-- name: GetCompanyByID :one
|
const GetCompanyByID = `-- name: GetCompanyByID :one
|
||||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||||
FROM companies_details
|
FROM companies_details
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -152,6 +166,20 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
|
||||||
&i.AdminFirstName,
|
&i.AdminFirstName,
|
||||||
&i.AdminLastName,
|
&i.AdminLastName,
|
||||||
&i.AdminPhoneNumber,
|
&i.AdminPhoneNumber,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalStake,
|
||||||
|
&i.DeductedStake,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
&i.NumberOfUnsettled,
|
||||||
|
&i.TotalUnsettledAmount,
|
||||||
|
&i.TotalAdmins,
|
||||||
|
&i.TotalManagers,
|
||||||
|
&i.TotalCashiers,
|
||||||
|
&i.TotalCustomers,
|
||||||
|
&i.TotalApprovers,
|
||||||
|
&i.TotalBranches,
|
||||||
|
&i.StatsUpdatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +208,7 @@ func (q *Queries) GetCompanyUsingSlug(ctx context.Context, slug string) (Company
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchCompanyByName = `-- name: SearchCompanyByName :many
|
const SearchCompanyByName = `-- name: SearchCompanyByName :many
|
||||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||||
FROM companies_details
|
FROM companies_details
|
||||||
WHERE name ILIKE '%' || $1 || '%'
|
WHERE name ILIKE '%' || $1 || '%'
|
||||||
`
|
`
|
||||||
|
|
@ -209,6 +237,20 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text)
|
||||||
&i.AdminFirstName,
|
&i.AdminFirstName,
|
||||||
&i.AdminLastName,
|
&i.AdminLastName,
|
||||||
&i.AdminPhoneNumber,
|
&i.AdminPhoneNumber,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalStake,
|
||||||
|
&i.DeductedStake,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
&i.NumberOfUnsettled,
|
||||||
|
&i.TotalUnsettledAmount,
|
||||||
|
&i.TotalAdmins,
|
||||||
|
&i.TotalManagers,
|
||||||
|
&i.TotalCashiers,
|
||||||
|
&i.TotalCustomers,
|
||||||
|
&i.TotalApprovers,
|
||||||
|
&i.TotalBranches,
|
||||||
|
&i.StatsUpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,143 @@ package dbgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const UpdateCompanyStats = `-- name: UpdateCompanyStats :exec
|
const GetCompanyStats = `-- name: GetCompanyStats :many
|
||||||
INSERT INTO company_stats (
|
SELECT DATE_TRUNC($1, interval_start)::timestamp AS interval_start,
|
||||||
company_id,
|
company_stats.company_id, company_stats.interval_start, company_stats.total_bets, company_stats.total_stake, company_stats.deducted_stake, company_stats.total_cash_out, company_stats.total_cash_backs, company_stats.number_of_unsettled, company_stats.total_unsettled_amount, company_stats.total_admins, company_stats.total_managers, company_stats.total_cashiers, company_stats.total_customers, company_stats.total_approvers, company_stats.total_branches, company_stats.updated_at
|
||||||
total_bets,
|
FROM company_stats
|
||||||
total_cash_made,
|
WHERE (
|
||||||
total_cash_backs,
|
company_stats.company_id = $2
|
||||||
updated_at
|
OR $2 IS NULL
|
||||||
)
|
)
|
||||||
SELECT b.company_id,
|
GROUP BY interval_start
|
||||||
|
ORDER BY interval_start DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCompanyStatsParams struct {
|
||||||
|
Interval pgtype.Text `json:"interval"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCompanyStatsRow struct {
|
||||||
|
IntervalStart pgtype.Timestamp `json:"interval_start"`
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
IntervalStart_2 pgtype.Timestamp `json:"interval_start_2"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalStake int64 `json:"total_stake"`
|
||||||
|
DeductedStake int64 `json:"deducted_stake"`
|
||||||
|
TotalCashOut int64 `json:"total_cash_out"`
|
||||||
|
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||||
|
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||||
|
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||||
|
TotalAdmins int64 `json:"total_admins"`
|
||||||
|
TotalManagers int64 `json:"total_managers"`
|
||||||
|
TotalCashiers int64 `json:"total_cashiers"`
|
||||||
|
TotalCustomers int64 `json:"total_customers"`
|
||||||
|
TotalApprovers int64 `json:"total_approvers"`
|
||||||
|
TotalBranches int64 `json:"total_branches"`
|
||||||
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCompanyStats(ctx context.Context, arg GetCompanyStatsParams) ([]GetCompanyStatsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetCompanyStats, arg.Interval, arg.CompanyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetCompanyStatsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetCompanyStatsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.IntervalStart,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.IntervalStart_2,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalStake,
|
||||||
|
&i.DeductedStake,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
&i.NumberOfUnsettled,
|
||||||
|
&i.TotalUnsettledAmount,
|
||||||
|
&i.TotalAdmins,
|
||||||
|
&i.TotalManagers,
|
||||||
|
&i.TotalCashiers,
|
||||||
|
&i.TotalCustomers,
|
||||||
|
&i.TotalApprovers,
|
||||||
|
&i.TotalBranches,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCompanyStatsByID = `-- name: GetCompanyStatsByID :many
|
||||||
|
SELECT company_id, interval_start, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, updated_at
|
||||||
|
FROM company_stats
|
||||||
|
WHERE company_id = $1
|
||||||
|
ORDER BY interval_start DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCompanyStatsByID(ctx context.Context, companyID int64) ([]CompanyStat, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetCompanyStatsByID, companyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CompanyStat
|
||||||
|
for rows.Next() {
|
||||||
|
var i CompanyStat
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.IntervalStart,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalStake,
|
||||||
|
&i.DeductedStake,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
&i.NumberOfUnsettled,
|
||||||
|
&i.TotalUnsettledAmount,
|
||||||
|
&i.TotalAdmins,
|
||||||
|
&i.TotalManagers,
|
||||||
|
&i.TotalCashiers,
|
||||||
|
&i.TotalCustomers,
|
||||||
|
&i.TotalApprovers,
|
||||||
|
&i.TotalBranches,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateCompanyStats = `-- name: UpdateCompanyStats :exec
|
||||||
|
WITH -- Aggregate bet data per company
|
||||||
|
bet_stats AS (
|
||||||
|
SELECT company_id,
|
||||||
COUNT(*) AS total_bets,
|
COUNT(*) AS total_bets,
|
||||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
COALESCE(SUM(amount), 0) AS total_stake,
|
||||||
|
COALESCE(
|
||||||
|
SUM(amount) * MAX(companies.deducted_percentage),
|
||||||
|
0
|
||||||
|
) AS deducted_stake,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets
|
WHEN cashed_out THEN amount
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
),
|
),
|
||||||
|
|
@ -32,27 +152,111 @@ SELECT b.company_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
WHEN b.status = 5 THEN b.amount
|
WHEN status = 3 THEN amount
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
),
|
),
|
||||||
0
|
0
|
||||||
) AS total_cash_backs,
|
) AS total_cash_backs,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE status = 5
|
||||||
|
) AS number_of_unsettled,
|
||||||
|
COALESCE(
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN status = 5 THEN amount
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0
|
||||||
|
) AS total_unsettled_amount
|
||||||
|
FROM shop_bet_detail
|
||||||
|
LEFT JOIN companies ON companies.id = shop_bet_detail.company_id
|
||||||
|
GROUP BY company_id
|
||||||
|
),
|
||||||
|
user_stats AS (
|
||||||
|
SELECT company_id,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'admin'
|
||||||
|
) AS total_admins,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'branch_manager'
|
||||||
|
) AS total_managers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'cashier'
|
||||||
|
) AS total_cashiers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'customer'
|
||||||
|
) AS total_customers,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE role = 'transaction_approver'
|
||||||
|
) AS total_approvers
|
||||||
|
FROM users
|
||||||
|
GROUP BY company_id
|
||||||
|
),
|
||||||
|
branch_stats AS (
|
||||||
|
SELECT company_id,
|
||||||
|
COUNT(*) AS total_branches
|
||||||
|
FROM branches
|
||||||
|
GROUP BY company_id
|
||||||
|
) -- Final combined aggregation
|
||||||
|
INSERT INTO company_stats (
|
||||||
|
company_id,
|
||||||
|
interval_start,
|
||||||
|
total_bets,
|
||||||
|
total_stake,
|
||||||
|
deducted_stake,
|
||||||
|
total_cash_out,
|
||||||
|
total_cash_backs,
|
||||||
|
number_of_unsettled,
|
||||||
|
total_unsettled_amount,
|
||||||
|
total_admins,
|
||||||
|
total_managers,
|
||||||
|
total_cashiers,
|
||||||
|
total_customers,
|
||||||
|
total_approvers,
|
||||||
|
total_branches,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
SELECT c.id AS company_id,
|
||||||
|
DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
||||||
|
COALESCE(b.total_bets, 0) AS total_bets,
|
||||||
|
COALESCE(b.total_stake, 0) AS total_stake,
|
||||||
|
COALESCE(b.deducted_stake, 0) AS deducted_stake,
|
||||||
|
COALESCE(b.total_cash_out, 0) AS total_cash_out,
|
||||||
|
COALESCE(b.total_cash_backs, 0) AS total_cash_backs,
|
||||||
|
COALESCE(b.number_of_unsettled, 0) AS number_of_unsettled,
|
||||||
|
COALESCE(b.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||||
|
COALESCE(u.total_admins, 0) AS total_admins,
|
||||||
|
COALESCE(u.total_managers, 0) AS total_managers,
|
||||||
|
COALESCE(u.total_cashiers, 0) AS total_cashiers,
|
||||||
|
COALESCE(u.total_customers, 0) AS total_customers,
|
||||||
|
COALESCE(u.total_approvers, 0) AS total_approvers,
|
||||||
|
COALESCE(br.total_branches, 0) AS total_branches,
|
||||||
NOW() AS updated_at
|
NOW() AS updated_at
|
||||||
FROM shop_bet_detail b
|
FROM companies c
|
||||||
JOIN companies c ON b.company_id = c.id
|
LEFT JOIN bet_stats b ON b.company_id = c.id
|
||||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
LEFT JOIN user_stats u ON u.company_id = c.id
|
||||||
GROUP BY b.company_id,
|
LEFT JOIN branch_stats br ON br.company_id = c.id ON CONFLICT (company_id, interval_start) DO
|
||||||
c.name ON CONFLICT (company_id) DO
|
|
||||||
UPDATE
|
UPDATE
|
||||||
SET total_bets = EXCLUDED.total_bets,
|
SET total_bets = EXCLUDED.total_bets,
|
||||||
total_cash_made = EXCLUDED.total_cash_made,
|
total_stake = EXCLUDED.total_stake,
|
||||||
|
deducted_stake = EXCLUDED.deducted_stake,
|
||||||
total_cash_out = EXCLUDED.total_cash_out,
|
total_cash_out = EXCLUDED.total_cash_out,
|
||||||
total_cash_back = EXCLUDED.total_cash_back,
|
total_cash_backs = EXCLUDED.total_cash_backs,
|
||||||
|
number_of_unsettled = EXCLUDED.number_of_unsettled,
|
||||||
|
total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
||||||
|
total_admins = EXCLUDED.total_admins,
|
||||||
|
total_managers = EXCLUDED.total_managers,
|
||||||
|
total_cashiers = EXCLUDED.total_cashiers,
|
||||||
|
total_customers = EXCLUDED.total_customers,
|
||||||
|
total_approvers = EXCLUDED.total_approvers,
|
||||||
|
total_branches = EXCLUDED.total_branches,
|
||||||
updated_at = EXCLUDED.updated_at
|
updated_at = EXCLUDED.updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
// Aggregate company stats
|
// Aggregate user counts per company
|
||||||
|
// Aggregate branch counts per company
|
||||||
func (q *Queries) UpdateCompanyStats(ctx context.Context) error {
|
func (q *Queries) UpdateCompanyStats(ctx context.Context) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateCompanyStats)
|
_, err := q.db.Exec(ctx, UpdateCompanyStats)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,3 @@ func (q *Queries) UpdateEventBetStats(ctx context.Context) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateEventBetStats)
|
_, err := q.db.Exec(ctx, UpdateEventBetStats)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateEventDetailedViewMat = `-- name: UpdateEventDetailedViewMat :exec
|
|
||||||
REFRESH MATERIALIZED VIEW event_detailed_mat
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) UpdateEventDetailedViewMat(ctx context.Context) error {
|
|
||||||
_, err := q.db.Exec(ctx, UpdateEventDetailedViewMat)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,6 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Accumulator struct {
|
|
||||||
OutcomeCount int64 `json:"outcome_count"`
|
|
||||||
DefaultMultiplier float32 `json:"default_multiplier"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bank struct {
|
type Bank struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
|
|
@ -123,6 +118,7 @@ type BranchDetail struct {
|
||||||
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
||||||
Balance pgtype.Int8 `json:"balance"`
|
Balance pgtype.Int8 `json:"balance"`
|
||||||
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
|
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchLocation struct {
|
type BranchLocation struct {
|
||||||
|
|
@ -153,6 +149,20 @@ type CompaniesDetail struct {
|
||||||
AdminFirstName string `json:"admin_first_name"`
|
AdminFirstName string `json:"admin_first_name"`
|
||||||
AdminLastName string `json:"admin_last_name"`
|
AdminLastName string `json:"admin_last_name"`
|
||||||
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
|
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalStake int64 `json:"total_stake"`
|
||||||
|
DeductedStake int64 `json:"deducted_stake"`
|
||||||
|
TotalCashOut int64 `json:"total_cash_out"`
|
||||||
|
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||||
|
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||||
|
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||||
|
TotalAdmins int64 `json:"total_admins"`
|
||||||
|
TotalManagers int64 `json:"total_managers"`
|
||||||
|
TotalCashiers int64 `json:"total_cashiers"`
|
||||||
|
TotalCustomers int64 `json:"total_customers"`
|
||||||
|
TotalApprovers int64 `json:"total_approvers"`
|
||||||
|
TotalBranches int64 `json:"total_branches"`
|
||||||
|
StatsUpdatedAt pgtype.Timestamp `json:"stats_updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Company struct {
|
type Company struct {
|
||||||
|
|
@ -212,10 +222,20 @@ type CompanySetting struct {
|
||||||
|
|
||||||
type CompanyStat struct {
|
type CompanyStat struct {
|
||||||
CompanyID int64 `json:"company_id"`
|
CompanyID int64 `json:"company_id"`
|
||||||
TotalBets pgtype.Int8 `json:"total_bets"`
|
IntervalStart pgtype.Timestamp `json:"interval_start"`
|
||||||
TotalCashMade pgtype.Int8 `json:"total_cash_made"`
|
TotalBets int64 `json:"total_bets"`
|
||||||
TotalCashOut pgtype.Int8 `json:"total_cash_out"`
|
TotalStake int64 `json:"total_stake"`
|
||||||
TotalCashBacks pgtype.Int8 `json:"total_cash_backs"`
|
DeductedStake int64 `json:"deducted_stake"`
|
||||||
|
TotalCashOut int64 `json:"total_cash_out"`
|
||||||
|
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||||
|
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||||
|
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||||
|
TotalAdmins int64 `json:"total_admins"`
|
||||||
|
TotalManagers int64 `json:"total_managers"`
|
||||||
|
TotalCashiers int64 `json:"total_cashiers"`
|
||||||
|
TotalCustomers int64 `json:"total_customers"`
|
||||||
|
TotalApprovers int64 `json:"total_approvers"`
|
||||||
|
TotalBranches int64 `json:"total_branches"`
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,42 +416,6 @@ type EventDetailed struct {
|
||||||
TotalPotentialWinnings int64 `json:"total_potential_winnings"`
|
TotalPotentialWinnings int64 `json:"total_potential_winnings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventDetailedMat struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
SourceEventID string `json:"source_event_id"`
|
|
||||||
SportID int32 `json:"sport_id"`
|
|
||||||
MatchName string `json:"match_name"`
|
|
||||||
HomeTeam string `json:"home_team"`
|
|
||||||
AwayTeam string `json:"away_team"`
|
|
||||||
HomeTeamID int64 `json:"home_team_id"`
|
|
||||||
AwayTeamID int64 `json:"away_team_id"`
|
|
||||||
HomeKitImage string `json:"home_kit_image"`
|
|
||||||
AwayKitImage string `json:"away_kit_image"`
|
|
||||||
LeagueID int64 `json:"league_id"`
|
|
||||||
LeagueName string `json:"league_name"`
|
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
|
||||||
Score pgtype.Text `json:"score"`
|
|
||||||
MatchMinute pgtype.Int4 `json:"match_minute"`
|
|
||||||
TimerStatus pgtype.Text `json:"timer_status"`
|
|
||||||
AddedTime pgtype.Int4 `json:"added_time"`
|
|
||||||
MatchPeriod pgtype.Int4 `json:"match_period"`
|
|
||||||
IsLive bool `json:"is_live"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
DefaultIsActive bool `json:"default_is_active"`
|
|
||||||
DefaultIsFeatured bool `json:"default_is_featured"`
|
|
||||||
DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"`
|
|
||||||
IsMonitored bool `json:"is_monitored"`
|
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
|
||||||
TotalOutcomes int64 `json:"total_outcomes"`
|
|
||||||
NumberOfBets int64 `json:"number_of_bets"`
|
|
||||||
TotalAmount int64 `json:"total_amount"`
|
|
||||||
AvgBetAmount float64 `json:"avg_bet_amount"`
|
|
||||||
TotalPotentialWinnings int64 `json:"total_potential_winnings"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventHistory struct {
|
type EventHistory struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
EventID int64 `json:"event_id"`
|
EventID int64 `json:"event_id"`
|
||||||
|
|
@ -688,16 +672,36 @@ type RefreshToken struct {
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Report struct {
|
type ReportRequest struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
FilePath pgtype.Text `json:"file_path"`
|
FilePath pgtype.Text `json:"file_path"`
|
||||||
|
Type string `json:"type"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
RejectReason pgtype.Text `json:"reject_reason"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
CompletedAt pgtype.Timestamp `json:"completed_at"`
|
CompletedAt pgtype.Timestamp `json:"completed_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReportRequestDetail struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
|
FilePath pgtype.Text `json:"file_path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
RejectReason pgtype.Text `json:"reject_reason"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
CompletedAt pgtype.Timestamp `json:"completed_at"`
|
||||||
|
CompanyName pgtype.Text `json:"company_name"`
|
||||||
|
CompanySlug pgtype.Text `json:"company_slug"`
|
||||||
|
RequesterFirstName pgtype.Text `json:"requester_first_name"`
|
||||||
|
RequesterLastName pgtype.Text `json:"requester_last_name"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReportedIssue struct {
|
type ReportedIssue struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
|
||||||
|
|
@ -11,138 +11,110 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const GetBranchWiseReport = `-- name: GetBranchWiseReport :many
|
const CreateReportRequest = `-- name: CreateReportRequest :one
|
||||||
SELECT
|
INSERT INTO report_requests (
|
||||||
b.branch_id,
|
company_id,
|
||||||
br.name AS branch_name,
|
requested_by,
|
||||||
br.company_id,
|
type,
|
||||||
COUNT(*) AS total_bets,
|
metadata
|
||||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
)
|
||||||
COALESCE(
|
VALUES ($1, $2, $3, $4)
|
||||||
SUM(
|
RETURNING id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at
|
||||||
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 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
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetBranchWiseReportParams struct {
|
type CreateReportRequestParams struct {
|
||||||
From pgtype.Timestamp `json:"from"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
To pgtype.Timestamp `json:"to"`
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBranchWiseReportRow struct {
|
func (q *Queries) CreateReportRequest(ctx context.Context, arg CreateReportRequestParams) (ReportRequest, error) {
|
||||||
BranchID int64 `json:"branch_id"`
|
row := q.db.QueryRow(ctx, CreateReportRequest,
|
||||||
BranchName string `json:"branch_name"`
|
arg.CompanyID,
|
||||||
CompanyID int64 `json:"company_id"`
|
arg.RequestedBy,
|
||||||
TotalBets int64 `json:"total_bets"`
|
arg.Type,
|
||||||
TotalCashMade interface{} `json:"total_cash_made"`
|
arg.Metadata,
|
||||||
TotalCashOut interface{} `json:"total_cash_out"`
|
)
|
||||||
TotalCashBacks interface{} `json:"total_cash_backs"`
|
var i ReportRequest
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.RequestedBy,
|
||||||
|
&i.FilePath,
|
||||||
|
&i.Type,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.RejectReason,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseReportParams) ([]GetBranchWiseReportRow, error) {
|
const GetAllReportRequests = `-- name: GetAllReportRequests :many
|
||||||
rows, err := q.db.Query(ctx, GetBranchWiseReport, arg.From, arg.To)
|
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE (
|
||||||
|
company_id = $1
|
||||||
|
OR $1 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
type = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = $3
|
||||||
|
OR $3 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
requested_by = $4
|
||||||
|
OR $4 IS NULL
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT $6 OFFSET $5
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllReportRequestsParams struct {
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Type pgtype.Text `json:"type"`
|
||||||
|
Status pgtype.Text `json:"status"`
|
||||||
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllReportRequests(ctx context.Context, arg GetAllReportRequestsParams) ([]ReportRequestDetail, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllReportRequests,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.Type,
|
||||||
|
arg.Status,
|
||||||
|
arg.RequestedBy,
|
||||||
|
arg.Offset,
|
||||||
|
arg.Limit,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []GetBranchWiseReportRow
|
var items []ReportRequestDetail
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i GetBranchWiseReportRow
|
var i ReportRequestDetail
|
||||||
if err := rows.Scan(
|
|
||||||
&i.BranchID,
|
|
||||||
&i.BranchName,
|
|
||||||
&i.CompanyID,
|
|
||||||
&i.TotalBets,
|
|
||||||
&i.TotalCashMade,
|
|
||||||
&i.TotalCashOut,
|
|
||||||
&i.TotalCashBacks,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 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
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetCompanyWiseReportParams struct {
|
|
||||||
From pgtype.Timestamp `json:"from"`
|
|
||||||
To pgtype.Timestamp `json:"to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetCompanyWiseReportRow struct {
|
|
||||||
CompanyID int64 `json:"company_id"`
|
|
||||||
CompanyName string `json:"company_name"`
|
|
||||||
TotalBets int64 `json:"total_bets"`
|
|
||||||
TotalCashMade interface{} `json:"total_cash_made"`
|
|
||||||
TotalCashOut interface{} `json:"total_cash_out"`
|
|
||||||
TotalCashBacks interface{} `json:"total_cash_backs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseReportParams) ([]GetCompanyWiseReportRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, GetCompanyWiseReport, arg.From, arg.To)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetCompanyWiseReportRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetCompanyWiseReportRow
|
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
&i.CompanyID,
|
&i.CompanyID,
|
||||||
|
&i.RequestedBy,
|
||||||
|
&i.FilePath,
|
||||||
|
&i.Type,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.RejectReason,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
&i.CompanyName,
|
&i.CompanyName,
|
||||||
&i.TotalBets,
|
&i.CompanySlug,
|
||||||
&i.TotalCashMade,
|
&i.RequesterFirstName,
|
||||||
&i.TotalCashOut,
|
&i.RequesterLastName,
|
||||||
&i.TotalCashBacks,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -153,3 +125,162 @@ func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseRe
|
||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetReportRequestByID = `-- name: GetReportRequestByID :one
|
||||||
|
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetReportRequestByID(ctx context.Context, id int64) (ReportRequestDetail, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetReportRequestByID, id)
|
||||||
|
var i ReportRequestDetail
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.RequestedBy,
|
||||||
|
&i.FilePath,
|
||||||
|
&i.Type,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.RejectReason,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CompanyName,
|
||||||
|
&i.CompanySlug,
|
||||||
|
&i.RequesterFirstName,
|
||||||
|
&i.RequesterLastName,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetReportRequestByRequestedByID = `-- name: GetReportRequestByRequestedByID :many
|
||||||
|
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE requested_by = $1
|
||||||
|
AND (
|
||||||
|
type = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = $3
|
||||||
|
OR $3 IS NULL
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT $5 OFFSET $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetReportRequestByRequestedByIDParams struct {
|
||||||
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
|
Type pgtype.Text `json:"type"`
|
||||||
|
Status pgtype.Text `json:"status"`
|
||||||
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetReportRequestByRequestedByID(ctx context.Context, arg GetReportRequestByRequestedByIDParams) ([]ReportRequestDetail, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetReportRequestByRequestedByID,
|
||||||
|
arg.RequestedBy,
|
||||||
|
arg.Type,
|
||||||
|
arg.Status,
|
||||||
|
arg.Offset,
|
||||||
|
arg.Limit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ReportRequestDetail
|
||||||
|
for rows.Next() {
|
||||||
|
var i ReportRequestDetail
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.RequestedBy,
|
||||||
|
&i.FilePath,
|
||||||
|
&i.Type,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.RejectReason,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CompanyName,
|
||||||
|
&i.CompanySlug,
|
||||||
|
&i.RequesterFirstName,
|
||||||
|
&i.RequesterLastName,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetTotalReportRequests = `-- name: GetTotalReportRequests :one
|
||||||
|
SELECT COUNT(id)
|
||||||
|
FROM report_request_detail
|
||||||
|
WHERE (
|
||||||
|
company_id = $1
|
||||||
|
OR $1 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
type = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
status = $3
|
||||||
|
OR $3 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
requested_by = $4
|
||||||
|
OR $4 IS NULL
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTotalReportRequestsParams struct {
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Type pgtype.Text `json:"type"`
|
||||||
|
Status pgtype.Text `json:"status"`
|
||||||
|
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTotalReportRequests(ctx context.Context, arg GetTotalReportRequestsParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetTotalReportRequests,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.Type,
|
||||||
|
arg.Status,
|
||||||
|
arg.RequestedBy,
|
||||||
|
)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateReportRequest = `-- name: UpdateReportRequest :exec
|
||||||
|
UPDATE report_requests
|
||||||
|
SET file_path = COALESCE($2, file_path),
|
||||||
|
reject_reason = COALESCE($3, reject_reason),
|
||||||
|
status = COALESCE($4, status),
|
||||||
|
completed_at = now()
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateReportRequestParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
FilePath pgtype.Text `json:"file_path"`
|
||||||
|
RejectReason pgtype.Text `json:"reject_reason"`
|
||||||
|
Status pgtype.Text `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateReportRequest(ctx context.Context, arg UpdateReportRequestParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, UpdateReportRequest,
|
||||||
|
arg.ID,
|
||||||
|
arg.FilePath,
|
||||||
|
arg.RejectReason,
|
||||||
|
arg.Status,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ var (
|
||||||
ErrLogLevel = errors.New("log level not set")
|
ErrLogLevel = errors.New("log level not set")
|
||||||
ErrInvalidLevel = errors.New("invalid log level")
|
ErrInvalidLevel = errors.New("invalid log level")
|
||||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||||
|
ErrInvalidReportExportPath = errors.New("report export path is invalid")
|
||||||
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
||||||
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
||||||
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
||||||
|
|
@ -183,6 +184,9 @@ func (c *Config) loadEnv() error {
|
||||||
|
|
||||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||||
|
|
||||||
|
if c.ReportExportPath == "" {
|
||||||
|
return ErrInvalidReportExportPath
|
||||||
|
}
|
||||||
c.RedisAddr = os.Getenv("REDIS_ADDR")
|
c.RedisAddr = os.Getenv("REDIS_ADDR")
|
||||||
c.KafkaBrokers = strings.Split(os.Getenv("KAFKA_BROKERS"), ",")
|
c.KafkaBrokers = strings.Split(os.Getenv("KAFKA_BROKERS"), ",")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@ package domain
|
||||||
// Branch-level aggregated report
|
// Branch-level aggregated report
|
||||||
type BranchStats struct {
|
type BranchStats struct {
|
||||||
BranchID int64
|
BranchID int64
|
||||||
BranchName string
|
|
||||||
CompanyID int64
|
CompanyID int64
|
||||||
TotalBets int64
|
TotalBets int64
|
||||||
TotalCashIn float64
|
TotalCashIn Currency
|
||||||
TotalCashOut float64
|
TotalCashOut Currency
|
||||||
TotalCashBacks float64
|
TotalCashBacks Currency
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
@ -38,6 +40,20 @@ type GetCompany struct {
|
||||||
IsWalletActive bool
|
IsWalletActive bool
|
||||||
DeductedPercentage float32
|
DeductedPercentage float32
|
||||||
IsActive bool
|
IsActive bool
|
||||||
|
TotalBets int64
|
||||||
|
TotalStake Currency
|
||||||
|
DeductedStake Currency
|
||||||
|
TotalCashOut Currency
|
||||||
|
TotalCashBacks Currency
|
||||||
|
NumberOfUnsettled int64
|
||||||
|
TotalUnsettledAmount Currency
|
||||||
|
TotalAdmins int64
|
||||||
|
TotalManagers int64
|
||||||
|
TotalCashiers int64
|
||||||
|
TotalCustomers int64
|
||||||
|
TotalApprovers int64
|
||||||
|
TotalBranches int64
|
||||||
|
StatsUpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateCompany struct {
|
type CreateCompany struct {
|
||||||
|
|
@ -96,6 +112,20 @@ type GetCompanyRes struct {
|
||||||
AdminFirstName string `json:"admin_first_name" example:"John"`
|
AdminFirstName string `json:"admin_first_name" example:"John"`
|
||||||
AdminLastName string `json:"admin_last_name" example:"Doe"`
|
AdminLastName string `json:"admin_last_name" example:"Doe"`
|
||||||
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
|
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalStake float32 `json:"total_stake"`
|
||||||
|
DeductedStake float32 `json:"deducted_stake"`
|
||||||
|
TotalCashOut float32 `json:"total_cash_out"`
|
||||||
|
TotalCashBacks float32 `json:"total_cash_backs"`
|
||||||
|
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||||
|
TotalUnsettledAmount float32 `json:"total_unsettled_amount"`
|
||||||
|
TotalAdmins int64 `json:"total_admins"`
|
||||||
|
TotalManagers int64 `json:"total_managers"`
|
||||||
|
TotalCashiers int64 `json:"total_cashiers"`
|
||||||
|
TotalCustomers int64 `json:"total_customers"`
|
||||||
|
TotalApprovers int64 `json:"total_approvers"`
|
||||||
|
TotalBranches int64 `json:"total_branches"`
|
||||||
|
StatsUpdatedAt time.Time `json:"stats_updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertCompany(company Company) CompanyRes {
|
func ConvertCompany(company Company) CompanyRes {
|
||||||
|
|
@ -116,6 +146,20 @@ func ConvertGetCompany(company GetCompany) GetCompanyRes {
|
||||||
AdminFirstName: company.AdminFirstName,
|
AdminFirstName: company.AdminFirstName,
|
||||||
AdminLastName: company.AdminLastName,
|
AdminLastName: company.AdminLastName,
|
||||||
AdminPhoneNumber: company.AdminPhoneNumber,
|
AdminPhoneNumber: company.AdminPhoneNumber,
|
||||||
|
TotalBets: company.TotalBets,
|
||||||
|
TotalStake: company.TotalStake.Float32(),
|
||||||
|
DeductedStake: company.DeductedPercentage,
|
||||||
|
TotalCashOut: company.TotalCashOut.Float32(),
|
||||||
|
TotalCashBacks: company.TotalCashBacks.Float32(),
|
||||||
|
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||||
|
TotalUnsettledAmount: company.TotalUnsettledAmount.Float32(),
|
||||||
|
TotalAdmins: company.TotalAdmins,
|
||||||
|
TotalManagers: company.TotalManagers,
|
||||||
|
TotalCashiers: company.TotalCashiers,
|
||||||
|
TotalCustomers: company.TotalCustomers,
|
||||||
|
TotalApprovers: company.TotalApprovers,
|
||||||
|
TotalBranches: company.TotalBranches,
|
||||||
|
StatsUpdatedAt: company.StatsUpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,6 +200,20 @@ func ConvertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) GetCompany {
|
||||||
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
|
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
|
||||||
DeductedPercentage: dbCompany.DeductedPercentage,
|
DeductedPercentage: dbCompany.DeductedPercentage,
|
||||||
IsActive: dbCompany.IsActive,
|
IsActive: dbCompany.IsActive,
|
||||||
|
TotalBets: dbCompany.TotalBets,
|
||||||
|
TotalStake: Currency(dbCompany.TotalStake),
|
||||||
|
DeductedStake: Currency(dbCompany.DeductedPercentage),
|
||||||
|
TotalCashOut: Currency(dbCompany.TotalCashOut),
|
||||||
|
TotalCashBacks: Currency(dbCompany.TotalCashBacks),
|
||||||
|
NumberOfUnsettled: dbCompany.NumberOfUnsettled,
|
||||||
|
TotalUnsettledAmount: Currency(dbCompany.TotalUnsettledAmount),
|
||||||
|
TotalAdmins: dbCompany.TotalAdmins,
|
||||||
|
TotalManagers: dbCompany.TotalManagers,
|
||||||
|
TotalCashiers: dbCompany.TotalCashiers,
|
||||||
|
TotalCustomers: dbCompany.TotalCustomers,
|
||||||
|
TotalApprovers: dbCompany.TotalApprovers,
|
||||||
|
TotalBranches: dbCompany.TotalBranches,
|
||||||
|
StatsUpdatedAt: dbCompany.StatsUpdatedAt.Time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
// Company-level aggregated report
|
|
||||||
type CompanyStats struct {
|
|
||||||
CompanyID int64
|
|
||||||
CompanyName string
|
|
||||||
TotalBets int64
|
|
||||||
TotalCashIn float64
|
|
||||||
TotalCashOut float64
|
|
||||||
TotalCashBacks float64
|
|
||||||
}
|
|
||||||
105
internal/domain/company_stats.go
Normal file
105
internal/domain/company_stats.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompanyStat struct {
|
||||||
|
CompanyID int64
|
||||||
|
IntervalStart time.Time
|
||||||
|
TotalBets int64
|
||||||
|
TotalStake Currency
|
||||||
|
DeductedStake Currency
|
||||||
|
TotalCashOut Currency
|
||||||
|
TotalCashBacks Currency
|
||||||
|
NumberOfUnsettled int64
|
||||||
|
TotalUnsettledAmount Currency
|
||||||
|
TotalAdmins int64
|
||||||
|
TotalManagers int64
|
||||||
|
TotalCashiers int64
|
||||||
|
TotalCustomers int64
|
||||||
|
TotalApprovers int64
|
||||||
|
TotalBranches int64
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
type CompanyStatRes struct {
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
IntervalStart time.Time `json:"interval_start"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalStake float32 `json:"total_stake"`
|
||||||
|
DeductedStake float32 `json:"deducted_stake"`
|
||||||
|
TotalCashOut float32 `json:"total_cash_out"`
|
||||||
|
TotalCashBacks float32 `json:"total_cash_backs"`
|
||||||
|
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||||
|
TotalUnsettledAmount float32 `json:"total_unsettled_amount"`
|
||||||
|
TotalAdmins int64 `json:"total_admins"`
|
||||||
|
TotalManagers int64 `json:"total_managers"`
|
||||||
|
TotalCashiers int64 `json:"total_cashiers"`
|
||||||
|
TotalCustomers int64 `json:"total_customers"`
|
||||||
|
TotalApprovers int64 `json:"total_approvers"`
|
||||||
|
TotalBranches int64 `json:"total_branches"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompanyStatFilter struct {
|
||||||
|
Interval ValidDateInterval
|
||||||
|
CompanyID ValidInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBCompanyStats(company dbgen.CompanyStat) CompanyStat {
|
||||||
|
return CompanyStat{
|
||||||
|
CompanyID: company.CompanyID,
|
||||||
|
TotalBets: company.TotalBets,
|
||||||
|
TotalStake: Currency(company.TotalStake),
|
||||||
|
DeductedStake: Currency(company.DeductedStake),
|
||||||
|
TotalCashOut: Currency(company.TotalCashOut),
|
||||||
|
TotalCashBacks: Currency(company.TotalCashBacks),
|
||||||
|
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||||
|
TotalUnsettledAmount: Currency(company.TotalUnsettledAmount),
|
||||||
|
TotalAdmins: company.TotalAdmins,
|
||||||
|
TotalManagers: company.TotalManagers,
|
||||||
|
TotalCashiers: company.TotalCashiers,
|
||||||
|
TotalCustomers: company.TotalCustomers,
|
||||||
|
TotalApprovers: company.TotalApprovers,
|
||||||
|
TotalBranches: company.TotalBranches,
|
||||||
|
UpdatedAt: company.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBCompanyStatsList(stats []dbgen.CompanyStat) []CompanyStat {
|
||||||
|
result := make([]CompanyStat, len(stats))
|
||||||
|
for i, stat := range stats {
|
||||||
|
result[i] = ConvertDBCompanyStats(stat)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBCompanyStatsByInterval(company dbgen.GetCompanyStatsRow) CompanyStat {
|
||||||
|
return CompanyStat{
|
||||||
|
CompanyID: company.CompanyID,
|
||||||
|
IntervalStart: company.IntervalStart.Time,
|
||||||
|
TotalBets: company.TotalBets,
|
||||||
|
TotalStake: Currency(company.TotalStake),
|
||||||
|
DeductedStake: Currency(company.DeductedStake),
|
||||||
|
TotalCashOut: Currency(company.TotalCashOut),
|
||||||
|
TotalCashBacks: Currency(company.TotalCashBacks),
|
||||||
|
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||||
|
TotalUnsettledAmount: Currency(company.TotalUnsettledAmount),
|
||||||
|
TotalAdmins: company.TotalAdmins,
|
||||||
|
TotalManagers: company.TotalManagers,
|
||||||
|
TotalCashiers: company.TotalCashiers,
|
||||||
|
TotalCustomers: company.TotalCustomers,
|
||||||
|
TotalApprovers: company.TotalApprovers,
|
||||||
|
TotalBranches: company.TotalBranches,
|
||||||
|
UpdatedAt: company.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBCompanyStatsByIntervalList(stats []dbgen.GetCompanyStatsRow) []CompanyStat {
|
||||||
|
result := make([]CompanyStat, len(stats))
|
||||||
|
for i, stat := range stats {
|
||||||
|
result[i] = ConvertDBCompanyStatsByInterval(stat)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -28,12 +28,12 @@ type EventStats struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventStatsFilter struct {
|
type EventStatsFilter struct {
|
||||||
Interval DateInterval
|
Interval ValidDateInterval
|
||||||
LeagueID ValidInt64
|
LeagueID ValidInt64
|
||||||
SportID ValidInt32
|
SportID ValidInt32
|
||||||
}
|
}
|
||||||
type EventStatsByIntervalFilter struct {
|
type EventStatsByIntervalFilter struct {
|
||||||
Interval DateInterval
|
Interval ValidDateInterval
|
||||||
LeagueID ValidInt64
|
LeagueID ValidInt64
|
||||||
SportID ValidInt32
|
SportID ValidInt32
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +60,7 @@ type EventStatsByInterval struct {
|
||||||
Removed int64 `json:"removed"`
|
Removed int64 `json:"removed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertDBEventStats(stats dbgen.GetEventStatsRow) EventStats {
|
func ConvertDBEventStats(stats dbgen.GetTotalEventStatsRow) EventStats {
|
||||||
return EventStats{
|
return EventStats{
|
||||||
EventCount: stats.EventCount,
|
EventCount: stats.EventCount,
|
||||||
TotalActiveEvents: stats.TotalActiveEvents,
|
TotalActiveEvents: stats.TotalActiveEvents,
|
||||||
|
|
@ -83,7 +83,7 @@ func ConvertDBEventStats(stats dbgen.GetEventStatsRow) EventStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertDBEventStatsByInterval(stats dbgen.GetEventStatsByIntervalRow) EventStatsByInterval {
|
func ConvertDBEventStatsByInterval(stats dbgen.GetTotalEventStatsByIntervalRow) EventStatsByInterval {
|
||||||
return EventStatsByInterval{
|
return EventStatsByInterval{
|
||||||
Date: stats.Date.Time,
|
Date: stats.Date.Time,
|
||||||
EventCount: stats.EventCount,
|
EventCount: stats.EventCount,
|
||||||
|
|
@ -107,19 +107,10 @@ func ConvertDBEventStatsByInterval(stats dbgen.GetEventStatsByIntervalRow) Event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertDBEventStatsByIntervalList(stats []dbgen.GetEventStatsByIntervalRow) []EventStatsByInterval {
|
func ConvertDBEventStatsByIntervalList(stats []dbgen.GetTotalEventStatsByIntervalRow) []EventStatsByInterval {
|
||||||
result := make([]EventStatsByInterval, len(stats))
|
result := make([]EventStatsByInterval, len(stats))
|
||||||
for i, e := range stats {
|
for i, e := range stats {
|
||||||
result[i] = ConvertDBEventStatsByInterval(e)
|
result[i] = ConvertDBEventStatsByInterval(e)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggregateEventBetStats struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
MatchName string `json:"match_name"`
|
|
||||||
NumberOfBets int64 `json:"number_of_bets"`
|
|
||||||
TotalAmount Currency `json:"total_amount"`
|
|
||||||
AvgBetAmount Currency `json:"avg_bet_amount"`
|
|
||||||
TotalPotentialWinnings Currency `json:"total_potential_winnings"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
type DateInterval string
|
type DateInterval string
|
||||||
|
|
||||||
|
|
@ -29,3 +34,46 @@ func ParseDateInterval(val string) (DateInterval, error) {
|
||||||
}
|
}
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidDateInterval struct {
|
||||||
|
Value DateInterval
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ValidDateInterval) ToPG() pgtype.Text {
|
||||||
|
return pgtype.Text{
|
||||||
|
String: string(v.Value),
|
||||||
|
Valid: v.Valid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertStringPtrInterval(value *string) (ValidDateInterval, error) {
|
||||||
|
if value == nil {
|
||||||
|
return ValidDateInterval{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedDateInterval, err := ParseDateInterval(*value)
|
||||||
|
if err != nil {
|
||||||
|
return ValidDateInterval{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidDateInterval{
|
||||||
|
Value: parsedDateInterval,
|
||||||
|
Valid: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEndDateFromInterval(interval DateInterval, startDate time.Time) (time.Time, error) {
|
||||||
|
var endDate time.Time
|
||||||
|
switch interval {
|
||||||
|
case "day":
|
||||||
|
endDate = startDate
|
||||||
|
case "week":
|
||||||
|
endDate = startDate.AddDate(0, 0, 6) // 7-day window
|
||||||
|
case "month":
|
||||||
|
endDate = startDate.AddDate(0, 1, -1) // till end of month
|
||||||
|
default:
|
||||||
|
return time.Time{}, fmt.Errorf("unknown date interval")
|
||||||
|
}
|
||||||
|
return endDate, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const (
|
||||||
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
||||||
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
||||||
NotificationTypeDailyReport NotificationType = "daily_report"
|
NotificationTypeDailyReport NotificationType = "daily_report"
|
||||||
|
NotificationTypeReportRequest NotificationType = "report_request"
|
||||||
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
||||||
NotificationTypeBetOverload NotificationType = "bet_overload"
|
NotificationTypeBetOverload NotificationType = "bet_overload"
|
||||||
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ type PaginatedFileResponse struct {
|
||||||
Pagination Pagination `json:"pagination"`
|
Pagination Pagination `json:"pagination"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportRequest struct {
|
// type ReportRequest struct {
|
||||||
Frequency ReportFrequency
|
// Frequency ReportFrequency
|
||||||
StartDate time.Time
|
// StartDate time.Time
|
||||||
EndDate time.Time
|
// EndDate time.Time
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ReportData struct {
|
type ReportData struct {
|
||||||
TotalBets int64
|
TotalBets int64
|
||||||
|
|
@ -39,7 +39,7 @@ type ReportData struct {
|
||||||
Deposits float64
|
Deposits float64
|
||||||
TotalTickets int64
|
TotalTickets int64
|
||||||
VirtualGameStats []VirtualGameStat
|
VirtualGameStats []VirtualGameStat
|
||||||
CompanyReports []CompanyStats
|
CompanyReports []CompanyStat
|
||||||
BranchReports []BranchStats
|
BranchReports []BranchStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
296
internal/domain/report_request.go
Normal file
296
internal/domain/report_request.go
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportRequest struct {
|
||||||
|
ID int64
|
||||||
|
CompanyID ValidInt64
|
||||||
|
RequestedBy ValidInt64
|
||||||
|
FilePath ValidString
|
||||||
|
Type ReportRequestType
|
||||||
|
Status ReportRequestStatus
|
||||||
|
Metadata ReportMetadataJSON
|
||||||
|
RejectReason ValidString
|
||||||
|
CreatedAt time.Time
|
||||||
|
CompletedAt ValidTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportRequestDetail struct {
|
||||||
|
ID int64
|
||||||
|
CompanyID ValidInt64
|
||||||
|
CompanyName ValidString
|
||||||
|
CompanySlug ValidString
|
||||||
|
RequestedBy ValidInt64
|
||||||
|
RequesterFirstName ValidString
|
||||||
|
RequesterLastName ValidString
|
||||||
|
FilePath ValidString
|
||||||
|
Type ReportRequestType
|
||||||
|
Status ReportRequestStatus
|
||||||
|
Metadata ReportMetadataJSON
|
||||||
|
RejectReason ValidString
|
||||||
|
CreatedAt time.Time
|
||||||
|
CompletedAt ValidTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateReportRequest struct {
|
||||||
|
CompanyID ValidInt64
|
||||||
|
RequestedBy ValidInt64
|
||||||
|
Type ReportRequestType
|
||||||
|
Metadata ReportMetadataJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateReportRequestReq struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Metadata ReportMetadataJSON `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportRequestRes struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
RequestedBy *int64 `json:"requested_by,omitempty"`
|
||||||
|
CompanyID *int64 `json:"company_id,omitempty"`
|
||||||
|
FilePath *string `json:"file_path,omitempty"`
|
||||||
|
Type ReportRequestType `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
RejectReason *string `json:"reject_reason,omitempty"`
|
||||||
|
Metadata ReportMetadataJSON `json:"metadata"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
CompletedAt *string `json:"completed_at,omitempty"`
|
||||||
|
}
|
||||||
|
type ReportRequestDetailRes struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CompanyID *int64 `json:"company_id,omitempty"`
|
||||||
|
CompanyName *string `json:"company_name,omitempty"`
|
||||||
|
CompanySlug *string `json:"company_slug,omitempty"`
|
||||||
|
RequestedBy *int64 `json:"requested_by,omitempty"`
|
||||||
|
RequesterFirstName *string `json:"requester_first_name,omitempty"`
|
||||||
|
RequesterLastName *string `json:"requester_last_name,omitempty"`
|
||||||
|
FilePath *string `json:"file_path,omitempty"`
|
||||||
|
Type ReportRequestType `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
RejectReason *string `json:"reject_reason,omitempty"`
|
||||||
|
Metadata ReportMetadataJSON `json:"metadata"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
CompletedAt *string `json:"completed_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportRequestFilter struct {
|
||||||
|
CompanyID ValidInt64
|
||||||
|
Type ValidReportRequestType
|
||||||
|
Status ValidReportRequestStatus
|
||||||
|
Limit ValidInt32
|
||||||
|
Offset ValidInt32
|
||||||
|
RequestedBy ValidInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRequestRequest struct {
|
||||||
|
ID int64
|
||||||
|
FilePath ValidString
|
||||||
|
RejectReason ValidString
|
||||||
|
Status ValidReportRequestStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBReportRequest(report dbgen.ReportRequest) (ReportRequest, error) {
|
||||||
|
parsedMetadataJSON, err := ParseReportMetadataJSON(report.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return ReportRequest{}, fmt.Errorf("failed to parse report metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReportRequest{
|
||||||
|
ID: report.ID,
|
||||||
|
CompanyID: ValidInt64{
|
||||||
|
Value: report.CompanyID.Int64,
|
||||||
|
Valid: report.CompanyID.Valid,
|
||||||
|
},
|
||||||
|
RequestedBy: ValidInt64{
|
||||||
|
Value: report.RequestedBy.Int64,
|
||||||
|
Valid: report.RequestedBy.Valid,
|
||||||
|
},
|
||||||
|
FilePath: ValidString{
|
||||||
|
Value: report.FilePath.String,
|
||||||
|
Valid: report.FilePath.Valid,
|
||||||
|
},
|
||||||
|
Type: ReportRequestType(report.Type),
|
||||||
|
Status: ReportRequestStatus(report.Status),
|
||||||
|
Metadata: parsedMetadataJSON,
|
||||||
|
RejectReason: ValidString{
|
||||||
|
Value: report.RejectReason.String,
|
||||||
|
Valid: report.RejectReason.Valid,
|
||||||
|
},
|
||||||
|
CreatedAt: report.CreatedAt.Time,
|
||||||
|
CompletedAt: ValidTime{
|
||||||
|
Value: report.CompletedAt.Time,
|
||||||
|
Valid: report.CompletedAt.Valid,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBReportRequestList(reports []dbgen.ReportRequest) ([]ReportRequest, error) {
|
||||||
|
result := make([]ReportRequest, len(reports))
|
||||||
|
var err error
|
||||||
|
for i, request := range reports {
|
||||||
|
result[i], err = ConvertDBReportRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBReportRequestDetail(report dbgen.ReportRequestDetail) (ReportRequestDetail, error) {
|
||||||
|
parsedMetadataJSON, err := ParseReportMetadataJSON(report.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return ReportRequestDetail{}, fmt.Errorf("failed to parse report metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReportRequestDetail{
|
||||||
|
ID: report.ID,
|
||||||
|
CompanyID: ValidInt64{
|
||||||
|
Value: report.CompanyID.Int64,
|
||||||
|
Valid: report.CompanyID.Valid,
|
||||||
|
},
|
||||||
|
CompanyName: ValidString{
|
||||||
|
Value: report.CompanyName.String,
|
||||||
|
Valid: report.CompanyName.Valid,
|
||||||
|
},
|
||||||
|
CompanySlug: ValidString{
|
||||||
|
Value: report.CompanySlug.String,
|
||||||
|
Valid: report.CompanySlug.Valid,
|
||||||
|
},
|
||||||
|
RequestedBy: ValidInt64{
|
||||||
|
Value: report.RequestedBy.Int64,
|
||||||
|
Valid: report.RequestedBy.Valid,
|
||||||
|
},
|
||||||
|
RequesterFirstName: ValidString{
|
||||||
|
Value: report.RequesterFirstName.String,
|
||||||
|
Valid: report.RequesterFirstName.Valid,
|
||||||
|
},
|
||||||
|
RequesterLastName: ValidString{
|
||||||
|
Value: report.RequesterLastName.String,
|
||||||
|
Valid: report.RequesterLastName.Valid,
|
||||||
|
},
|
||||||
|
FilePath: ValidString{
|
||||||
|
Value: report.FilePath.String,
|
||||||
|
Valid: report.FilePath.Valid,
|
||||||
|
},
|
||||||
|
Type: ReportRequestType(report.Type),
|
||||||
|
Status: ReportRequestStatus(report.Status),
|
||||||
|
Metadata: parsedMetadataJSON,
|
||||||
|
RejectReason: ValidString{
|
||||||
|
Value: report.RejectReason.String,
|
||||||
|
Valid: report.RejectReason.Valid,
|
||||||
|
},
|
||||||
|
CreatedAt: report.CreatedAt.Time,
|
||||||
|
CompletedAt: ValidTime{
|
||||||
|
Value: report.CompletedAt.Time,
|
||||||
|
Valid: report.CompletedAt.Valid,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBReportRequestDetailList(reports []dbgen.ReportRequestDetail) ([]ReportRequestDetail, error) {
|
||||||
|
result := make([]ReportRequestDetail, len(reports))
|
||||||
|
var err error
|
||||||
|
for i, request := range reports {
|
||||||
|
result[i], err = ConvertDBReportRequestDetail(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertReportRequest(request ReportRequest) ReportRequestRes {
|
||||||
|
var res ReportRequestRes
|
||||||
|
|
||||||
|
if request.RequestedBy.Valid {
|
||||||
|
res.RequestedBy = &request.RequestedBy.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.CompanyID.Valid {
|
||||||
|
res.CompanyID = &request.CompanyID.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.FilePath.Valid {
|
||||||
|
res.FilePath = &request.FilePath.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.RejectReason.Valid {
|
||||||
|
res.RejectReason = &request.RejectReason.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.CompletedAt.Valid {
|
||||||
|
str := request.CompletedAt.Value.Format(time.RFC3339)
|
||||||
|
res.CompletedAt = &str
|
||||||
|
}
|
||||||
|
|
||||||
|
res.ID = request.ID
|
||||||
|
res.Type = request.Type
|
||||||
|
res.Status = string(request.Status)
|
||||||
|
res.Metadata = request.Metadata
|
||||||
|
res.CreatedAt = request.CreatedAt
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertReportRequestList(requests []ReportRequest) []ReportRequestRes {
|
||||||
|
result := make([]ReportRequestRes, len(requests))
|
||||||
|
for i, request := range requests {
|
||||||
|
result[i] = ConvertReportRequest(request)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertReportRequestDetail(request ReportRequestDetail) ReportRequestDetailRes {
|
||||||
|
var res ReportRequestDetailRes
|
||||||
|
|
||||||
|
if request.RequestedBy.Valid {
|
||||||
|
res.RequestedBy = &request.RequestedBy.Value
|
||||||
|
}
|
||||||
|
if request.RequesterFirstName.Valid {
|
||||||
|
res.RequesterFirstName = &request.RequesterFirstName.Value
|
||||||
|
}
|
||||||
|
if request.RequesterLastName.Valid {
|
||||||
|
res.RequesterLastName = &request.RequesterLastName.Value
|
||||||
|
}
|
||||||
|
if request.CompanyID.Valid {
|
||||||
|
res.CompanyID = &request.CompanyID.Value
|
||||||
|
}
|
||||||
|
if request.CompanyName.Valid {
|
||||||
|
res.CompanyName = &request.CompanyName.Value
|
||||||
|
}
|
||||||
|
if request.CompanySlug.Valid {
|
||||||
|
res.CompanySlug = &request.CompanySlug.Value
|
||||||
|
}
|
||||||
|
if request.FilePath.Valid {
|
||||||
|
res.FilePath = &request.FilePath.Value
|
||||||
|
}
|
||||||
|
if request.RejectReason.Valid {
|
||||||
|
res.RejectReason = &request.RejectReason.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.CompletedAt.Valid {
|
||||||
|
str := request.CompletedAt.Value.Format(time.RFC3339)
|
||||||
|
res.CompletedAt = &str
|
||||||
|
}
|
||||||
|
|
||||||
|
res.ID = request.ID
|
||||||
|
res.Type = request.Type
|
||||||
|
res.Status = string(request.Status)
|
||||||
|
res.Metadata = request.Metadata
|
||||||
|
res.CreatedAt = request.CreatedAt
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertReportRequestDetailList(requests []ReportRequestDetail) []ReportRequestDetailRes {
|
||||||
|
result := make([]ReportRequestDetailRes, len(requests))
|
||||||
|
for i, request := range requests {
|
||||||
|
result[i] = ConvertReportRequestDetail(request)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
29
internal/domain/report_request_metadata.go
Normal file
29
internal/domain/report_request_metadata.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportMetadataJSON struct {
|
||||||
|
BranchID *int64 `json:"branch_id,omitempty"`
|
||||||
|
Interval *string `json:"interval,omitempty"`
|
||||||
|
IntervalStart *string `json:"interval_start,omitempty"`
|
||||||
|
IntervalEnd *string `json:"interval_end,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ReportMetadataJSON) ToPG() ([]byte, error) {
|
||||||
|
metadata, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal report request metadata: %w", err)
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseReportMetadataJSON(jsonData []byte) (ReportMetadataJSON, error) {
|
||||||
|
var metadata ReportMetadataJSON
|
||||||
|
if err := json.Unmarshal(jsonData, &metadata); err != nil {
|
||||||
|
return ReportMetadataJSON{}, err
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
45
internal/domain/report_request_status.go
Normal file
45
internal/domain/report_request_status.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type ReportRequestStatus string
|
||||||
|
|
||||||
|
var (
|
||||||
|
PendingReportRequest ReportRequestStatus = "pending"
|
||||||
|
SuccessReportRequest ReportRequestStatus = "success"
|
||||||
|
RejectReportRequest ReportRequestStatus = "reject"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ReportRequestStatus) IsValid() bool {
|
||||||
|
switch r {
|
||||||
|
case PendingReportRequest, SuccessReportRequest, RejectReportRequest:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseReportRequestStatus(val string) (ReportRequestStatus, error) {
|
||||||
|
r := ReportRequestStatus(val)
|
||||||
|
if !r.IsValid() {
|
||||||
|
return "", fmt.Errorf("invalid ReportRequestStatus: %q", val)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidReportRequestStatus struct {
|
||||||
|
Value ReportRequestStatus
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ValidReportRequestStatus) ToPG() pgtype.Text {
|
||||||
|
return pgtype.Text{
|
||||||
|
String: string(v.Value),
|
||||||
|
Valid: v.Valid,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
internal/domain/report_request_type.go
Normal file
44
internal/domain/report_request_type.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportRequestType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
EventIntervalReportRequest ReportRequestType = "event_interval"
|
||||||
|
EventBetReportRequest ReportRequestType = "event_bet"
|
||||||
|
CompanyReportRequest ReportRequestType = "company"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ReportRequestType) IsValid() bool {
|
||||||
|
switch r {
|
||||||
|
case EventIntervalReportRequest, CompanyReportRequest:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseReportRequestType(val string) (ReportRequestType, error) {
|
||||||
|
r := ReportRequestType(val)
|
||||||
|
if !r.IsValid() {
|
||||||
|
return "", fmt.Errorf("invalid ReportRequestType: %q", val)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidReportRequestType struct {
|
||||||
|
Value ReportRequestType
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ValidReportRequestType) ToPG() pgtype.Text {
|
||||||
|
return pgtype.Text{
|
||||||
|
String: string(v.Value),
|
||||||
|
Valid: v.Valid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ const (
|
||||||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||||
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
||||||
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
||||||
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Half Win and Half Given Back
|
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Error (Unsettled Bet)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (o OutcomeStatus) IsValid() bool {
|
func (o OutcomeStatus) IsValid() bool {
|
||||||
|
|
|
||||||
|
|
@ -244,3 +244,18 @@ func ConvertRolePtr(value *Role) ValidRole {
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConvertStringPtrToTime(value *string) (ValidTime, error) {
|
||||||
|
if value == nil {
|
||||||
|
return ValidTime{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedIntervalStart, err := time.Parse(time.RFC3339, *value)
|
||||||
|
if err != nil {
|
||||||
|
return ValidTime{}, nil
|
||||||
|
}
|
||||||
|
return ValidTime{
|
||||||
|
Value: parsedIntervalStart,
|
||||||
|
Valid: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,25 +9,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
|
func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
|
||||||
// baseSlug := helpers.GenerateSlug(company.Name)
|
|
||||||
// uniqueSlug := baseSlug
|
|
||||||
// i := 1
|
|
||||||
|
|
||||||
// for {
|
|
||||||
// _, err := s.queries.GetCompanyUsingSlug(ctx, uniqueSlug)
|
|
||||||
// if err != nil {
|
|
||||||
// if errors.Is(err, pgx.ErrNoRows) {
|
|
||||||
// // slug is unique
|
|
||||||
// break
|
|
||||||
// } else {
|
|
||||||
// // real DB error
|
|
||||||
// return domain.Company{}, err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// uniqueSlug = fmt.Sprintf("%s-%d", baseSlug, i)
|
|
||||||
// i++
|
|
||||||
// }
|
|
||||||
fmt.Printf("\ncompany %v\n\n", company)
|
|
||||||
dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company))
|
dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,34 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
params := dbgen.GetCompanyWiseReportParams{
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
From: ToPgTimestamp(from),
|
)
|
||||||
To: ToPgTimestamp(to),
|
|
||||||
|
func (s *Store) UpdateCompanyStats(ctx context.Context) error {
|
||||||
|
return s.queries.UpdateCompanyStats(ctx)
|
||||||
}
|
}
|
||||||
return r.store.queries.GetCompanyWiseReport(ctx, params)
|
|
||||||
|
func (s *Store) GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error) {
|
||||||
|
stats, err := s.queries.GetCompanyStatsByID(ctx, companyID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domain.ConvertDBCompanyStatsList(stats), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error) {
|
||||||
|
stats, err := s.queries.GetCompanyStats(ctx, dbgen.GetCompanyStatsParams{
|
||||||
|
Interval: filter.Interval.ToPG(),
|
||||||
|
CompanyID: filter.CompanyID.ToPG(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.ConvertDBCompanyStatsByIntervalList(stats), nil
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +5,10 @@ import (
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Store) GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
func (s *Store) GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||||
stats, err := s.queries.GetEventStats(ctx, dbgen.GetEventStatsParams{
|
stats, err := s.queries.GetTotalEventStats(ctx, dbgen.GetTotalEventStatsParams{
|
||||||
LeagueID: filter.LeagueID.ToPG(),
|
LeagueID: filter.LeagueID.ToPG(),
|
||||||
SportID: filter.SportID.ToPG(),
|
SportID: filter.SportID.ToPG(),
|
||||||
})
|
})
|
||||||
|
|
@ -20,12 +19,9 @@ func (s *Store) GetEventStats(ctx context.Context, filter domain.EventStatsFilte
|
||||||
return domain.ConvertDBEventStats(stats), nil
|
return domain.ConvertDBEventStats(stats), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
func (s *Store) GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||||
stats, err := s.queries.GetEventStatsByInterval(ctx, dbgen.GetEventStatsByIntervalParams{
|
stats, err := s.queries.GetTotalEventStatsByInterval(ctx, dbgen.GetTotalEventStatsByIntervalParams{
|
||||||
Interval: pgtype.Text{
|
Interval: filter.Interval.ToPG(),
|
||||||
String: string(filter.Interval),
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
LeagueID: filter.LeagueID.ToPG(),
|
LeagueID: filter.LeagueID.ToPG(),
|
||||||
SportID: filter.SportID.ToPG(),
|
SportID: filter.SportID.ToPG(),
|
||||||
})
|
})
|
||||||
|
|
@ -36,3 +32,7 @@ func (s *Store) GetEventStatsByInterval(ctx context.Context, filter domain.Event
|
||||||
|
|
||||||
return domain.ConvertDBEventStatsByIntervalList(stats), nil
|
return domain.ConvertDBEventStatsByIntervalList(stats), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateEventBetStats(ctx context.Context) error {
|
||||||
|
return s.queries.UpdateEventBetStats(ctx)
|
||||||
|
}
|
||||||
|
|
|
||||||
234
internal/repository/old_report.go
Normal file
234
internal/repository/old_report.go
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "context"
|
||||||
|
// "fmt"
|
||||||
|
// "time"
|
||||||
|
|
||||||
|
// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
// "github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportRepository interface {
|
||||||
|
// GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error)
|
||||||
|
// SaveReport(report *domain.Report) error
|
||||||
|
// FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error)
|
||||||
|
|
||||||
|
// GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
// GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
// GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
// GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error)
|
||||||
|
// GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error)
|
||||||
|
// GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error)
|
||||||
|
// GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error)
|
||||||
|
// GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error)
|
||||||
|
// GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportRepo struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReportRepo(store *Store) ReportRepository {
|
||||||
|
return &ReportRepo{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error) {
|
||||||
|
// // Implement SQL queries to calculate metrics
|
||||||
|
// var report domain.Report
|
||||||
|
|
||||||
|
// // Total Bets
|
||||||
|
// err := r.store.conn.QueryRow(
|
||||||
|
// context.Background(),
|
||||||
|
// `SELECT COUNT(*) FROM bets
|
||||||
|
// WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Total Cash In
|
||||||
|
// err = r.store.conn.QueryRow(
|
||||||
|
// context.Background(),
|
||||||
|
// `SELECT COALESCE(SUM(amount), 0) FROM transactions
|
||||||
|
// WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Similar queries for Cash Out and Cash Back...
|
||||||
|
|
||||||
|
// report.TimeFrame = timeFrame
|
||||||
|
// report.PeriodStart = start
|
||||||
|
// report.PeriodEnd = end
|
||||||
|
// report.GeneratedAt = time.Now()
|
||||||
|
|
||||||
|
// return &report, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) SaveReport(report *domain.Report) error {
|
||||||
|
// _, err := r.store.conn.Exec(
|
||||||
|
// context.Background(),
|
||||||
|
// `INSERT INTO reports
|
||||||
|
// (id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at)
|
||||||
|
// VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||||
|
// report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
|
||||||
|
// report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error) {
|
||||||
|
// rows, err := r.store.conn.Query(
|
||||||
|
// context.Background(),
|
||||||
|
// `SELECT id, time_frame, period_start, period_end, total_bets,
|
||||||
|
// total_cash_in, total_cash_out, total_cash_back, generated_at
|
||||||
|
// FROM reports
|
||||||
|
// WHERE time_frame = $1
|
||||||
|
// ORDER BY generated_at DESC
|
||||||
|
// LIMIT $2`,
|
||||||
|
// timeFrame, limit)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// defer rows.Close()
|
||||||
|
|
||||||
|
// var reports []*domain.Report
|
||||||
|
// for rows.Next() {
|
||||||
|
// var report domain.Report
|
||||||
|
// err := rows.Scan(
|
||||||
|
// &report.ID,
|
||||||
|
// &report.TimeFrame,
|
||||||
|
// &report.PeriodStart,
|
||||||
|
// &report.PeriodEnd,
|
||||||
|
// &report.TotalBets,
|
||||||
|
// &report.TotalCashIn,
|
||||||
|
// &report.TotalCashOut,
|
||||||
|
// &report.TotalCashBack,
|
||||||
|
// &report.GeneratedAt,
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// reports = append(reports, &report)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := rows.Err(); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return reports, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) {
|
||||||
|
// params := dbgen.GetTotalBetsMadeInRangeParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetTotalBetsMadeInRange(ctx, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
// params := dbgen.GetTotalCashBacksInRangeParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
// return parseFloat(value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
// params := dbgen.GetTotalCashMadeInRangeParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
// return parseFloat(value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
// params := dbgen.GetTotalCashOutInRangeParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// value, err := r.store.queries.GetTotalCashOutInRange(ctx, params)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
// return parseFloat(value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) {
|
||||||
|
// params := dbgen.GetWalletTransactionsInRangeParams{
|
||||||
|
// CreatedAt: ToPgTimestamp(from),
|
||||||
|
// CreatedAt_2: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetWalletTransactionsInRange(ctx, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) {
|
||||||
|
// params := dbgen.GetAllTicketsInRangeParams{
|
||||||
|
// CreatedAt: ToPgTimestamp(from),
|
||||||
|
// CreatedAt_2: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetAllTicketsInRange(ctx, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) {
|
||||||
|
// params := dbgen.GetVirtualGameSummaryInRangeParams{
|
||||||
|
// CreatedAt: ToPgTimestamptz(from),
|
||||||
|
// CreatedAt_2: ToPgTimestamptz(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetVirtualGameSummaryInRange(ctx, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ToPgTimestamp(t time.Time) pgtype.Timestamp {
|
||||||
|
// return pgtype.Timestamp{Time: t, Valid: true}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ToPgTimestamptz(t time.Time) pgtype.Timestamptz {
|
||||||
|
// return pgtype.Timestamptz{Time: t, Valid: true}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func parseFloat(value interface{}) (float64, error) {
|
||||||
|
// switch v := value.(type) {
|
||||||
|
// case float64:
|
||||||
|
// return v, nil
|
||||||
|
// case int64:
|
||||||
|
// return float64(v), nil
|
||||||
|
// case pgtype.Numeric:
|
||||||
|
// if !v.Valid {
|
||||||
|
// return 0, nil
|
||||||
|
// }
|
||||||
|
// f, err := v.Float64Value()
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, fmt.Errorf("failed to convert pgtype.Numeric to float64: %w", err)
|
||||||
|
// }
|
||||||
|
// return f.Float64, nil
|
||||||
|
// case nil:
|
||||||
|
// return 0, nil
|
||||||
|
// default:
|
||||||
|
// return 0, fmt.Errorf("unexpected type %T for value: %+v", v, v)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
|
||||||
|
// params := dbgen.GetCompanyWiseReportParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetCompanyWiseReport(ctx, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) {
|
||||||
|
// params := dbgen.GetBranchWiseReportParams{
|
||||||
|
// From: ToPgTimestamp(from),
|
||||||
|
// To: ToPgTimestamp(to),
|
||||||
|
// }
|
||||||
|
// return r.store.queries.GetBranchWiseReport(ctx, params)
|
||||||
|
// }
|
||||||
|
|
@ -1,234 +1,103 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "context"
|
"context"
|
||||||
// "fmt"
|
"fmt"
|
||||||
// "time"
|
|
||||||
|
|
||||||
// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
// "github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReportRepository interface {
|
func (s *Store) CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error) {
|
||||||
// GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error)
|
|
||||||
// SaveReport(report *domain.Report) error
|
|
||||||
// FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error)
|
|
||||||
|
|
||||||
// GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error)
|
reportMetadata, err := report.Metadata.ToPG()
|
||||||
// GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error)
|
if err != nil {
|
||||||
// GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error)
|
return domain.ReportRequest{}, err
|
||||||
// GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error)
|
|
||||||
// GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error)
|
|
||||||
// GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error)
|
|
||||||
// GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error)
|
|
||||||
// GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error)
|
|
||||||
// GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportRepo struct {
|
dbReportRequest, err := s.queries.CreateReportRequest(ctx, dbgen.CreateReportRequestParams{
|
||||||
store *Store
|
CompanyID: report.CompanyID.ToPG(),
|
||||||
|
RequestedBy: report.RequestedBy.ToPG(),
|
||||||
|
Type: string(report.Type),
|
||||||
|
Metadata: reportMetadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.ReportRequest{}, fmt.Errorf("failed to create report request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReportRepo(store *Store) ReportRepository {
|
return domain.ConvertDBReportRequest(dbReportRequest)
|
||||||
return &ReportRepo{store: store}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (r *ReportRepo) GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error) {
|
func (s *Store) GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error) {
|
||||||
// // Implement SQL queries to calculate metrics
|
dbReportRequests, err := s.queries.GetAllReportRequests(ctx, dbgen.GetAllReportRequestsParams{
|
||||||
// var report domain.Report
|
CompanyID: filter.CompanyID.ToPG(),
|
||||||
|
Type: filter.Type.ToPG(),
|
||||||
|
Status: filter.Status.ToPG(),
|
||||||
|
Limit: filter.Limit.ToPG(),
|
||||||
|
Offset: pgtype.Int4{
|
||||||
|
Int32: int32(filter.Offset.Value * filter.Limit.Value),
|
||||||
|
Valid: filter.Offset.Valid,
|
||||||
|
},
|
||||||
|
RequestedBy: filter.RequestedBy.ToPG(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
total, err := s.queries.GetTotalReportRequests(ctx, dbgen.GetTotalReportRequestsParams{
|
||||||
|
CompanyID: filter.CompanyID.ToPG(),
|
||||||
|
Type: filter.Type.ToPG(),
|
||||||
|
Status: filter.Status.ToPG(),
|
||||||
|
RequestedBy: filter.RequestedBy.ToPG(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
result, err := domain.ConvertDBReportRequestDetailList(dbReportRequests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return result, total, nil
|
||||||
|
}
|
||||||
|
func (s *Store) GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error) {
|
||||||
|
dbReportRequests, err := s.queries.GetReportRequestByRequestedByID(ctx, dbgen.GetReportRequestByRequestedByIDParams{
|
||||||
|
RequestedBy: pgtype.Int8{
|
||||||
|
Int64: requestedBy,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Type: filter.Type.ToPG(),
|
||||||
|
Status: filter.Status.ToPG(),
|
||||||
|
Limit: filter.Limit.ToPG(),
|
||||||
|
Offset: pgtype.Int4{
|
||||||
|
Int32: int32(filter.Offset.Value * filter.Limit.Value),
|
||||||
|
Valid: filter.Offset.Valid,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domain.ConvertDBReportRequestDetailList(dbReportRequests)
|
||||||
|
}
|
||||||
|
|
||||||
// // Total Bets
|
func (s *Store) GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error) {
|
||||||
// err := r.store.conn.QueryRow(
|
dbReportRequest, err := s.queries.GetReportRequestByID(ctx, ID)
|
||||||
// context.Background(),
|
if err != nil {
|
||||||
// `SELECT COUNT(*) FROM bets
|
return domain.ReportRequestDetail{}, err
|
||||||
// WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
|
}
|
||||||
// if err != nil {
|
return domain.ConvertDBReportRequestDetail(dbReportRequest)
|
||||||
// return nil, err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// // Total Cash In
|
func (s *Store) UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error {
|
||||||
// err = r.store.conn.QueryRow(
|
err := s.queries.UpdateReportRequest(ctx, dbgen.UpdateReportRequestParams{
|
||||||
// context.Background(),
|
ID: report.ID,
|
||||||
// `SELECT COALESCE(SUM(amount), 0) FROM transactions
|
FilePath: report.FilePath.ToPG(),
|
||||||
// WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
|
RejectReason: report.RejectReason.ToPG(),
|
||||||
// if err != nil {
|
Status: report.Status.ToPG(),
|
||||||
// return nil, err
|
})
|
||||||
// }
|
|
||||||
|
|
||||||
// // Similar queries for Cash Out and Cash Back...
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update report request: %w", err)
|
||||||
// report.TimeFrame = timeFrame
|
}
|
||||||
// report.PeriodStart = start
|
return nil
|
||||||
// report.PeriodEnd = end
|
}
|
||||||
// report.GeneratedAt = time.Now()
|
|
||||||
|
|
||||||
// return &report, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) SaveReport(report *domain.Report) error {
|
|
||||||
// _, err := r.store.conn.Exec(
|
|
||||||
// context.Background(),
|
|
||||||
// `INSERT INTO reports
|
|
||||||
// (id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at)
|
|
||||||
// VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
||||||
// report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
|
|
||||||
// report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error) {
|
|
||||||
// rows, err := r.store.conn.Query(
|
|
||||||
// context.Background(),
|
|
||||||
// `SELECT id, time_frame, period_start, period_end, total_bets,
|
|
||||||
// total_cash_in, total_cash_out, total_cash_back, generated_at
|
|
||||||
// FROM reports
|
|
||||||
// WHERE time_frame = $1
|
|
||||||
// ORDER BY generated_at DESC
|
|
||||||
// LIMIT $2`,
|
|
||||||
// timeFrame, limit)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// defer rows.Close()
|
|
||||||
|
|
||||||
// var reports []*domain.Report
|
|
||||||
// for rows.Next() {
|
|
||||||
// var report domain.Report
|
|
||||||
// err := rows.Scan(
|
|
||||||
// &report.ID,
|
|
||||||
// &report.TimeFrame,
|
|
||||||
// &report.PeriodStart,
|
|
||||||
// &report.PeriodEnd,
|
|
||||||
// &report.TotalBets,
|
|
||||||
// &report.TotalCashIn,
|
|
||||||
// &report.TotalCashOut,
|
|
||||||
// &report.TotalCashBack,
|
|
||||||
// &report.GeneratedAt,
|
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// reports = append(reports, &report)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err := rows.Err(); err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return reports, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) {
|
|
||||||
// params := dbgen.GetTotalBetsMadeInRangeParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetTotalBetsMadeInRange(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
|
||||||
// params := dbgen.GetTotalCashBacksInRangeParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params)
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, err
|
|
||||||
// }
|
|
||||||
// return parseFloat(value)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
|
||||||
// params := dbgen.GetTotalCashMadeInRangeParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params)
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, err
|
|
||||||
// }
|
|
||||||
// return parseFloat(value)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
|
||||||
// params := dbgen.GetTotalCashOutInRangeParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// value, err := r.store.queries.GetTotalCashOutInRange(ctx, params)
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, err
|
|
||||||
// }
|
|
||||||
// return parseFloat(value)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) {
|
|
||||||
// params := dbgen.GetWalletTransactionsInRangeParams{
|
|
||||||
// CreatedAt: ToPgTimestamp(from),
|
|
||||||
// CreatedAt_2: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetWalletTransactionsInRange(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) {
|
|
||||||
// params := dbgen.GetAllTicketsInRangeParams{
|
|
||||||
// CreatedAt: ToPgTimestamp(from),
|
|
||||||
// CreatedAt_2: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetAllTicketsInRange(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) {
|
|
||||||
// params := dbgen.GetVirtualGameSummaryInRangeParams{
|
|
||||||
// CreatedAt: ToPgTimestamptz(from),
|
|
||||||
// CreatedAt_2: ToPgTimestamptz(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetVirtualGameSummaryInRange(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ToPgTimestamp(t time.Time) pgtype.Timestamp {
|
|
||||||
// return pgtype.Timestamp{Time: t, Valid: true}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ToPgTimestamptz(t time.Time) pgtype.Timestamptz {
|
|
||||||
// return pgtype.Timestamptz{Time: t, Valid: true}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func parseFloat(value interface{}) (float64, error) {
|
|
||||||
// switch v := value.(type) {
|
|
||||||
// case float64:
|
|
||||||
// return v, nil
|
|
||||||
// case int64:
|
|
||||||
// return float64(v), nil
|
|
||||||
// case pgtype.Numeric:
|
|
||||||
// if !v.Valid {
|
|
||||||
// return 0, nil
|
|
||||||
// }
|
|
||||||
// f, err := v.Float64Value()
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, fmt.Errorf("failed to convert pgtype.Numeric to float64: %w", err)
|
|
||||||
// }
|
|
||||||
// return f.Float64, nil
|
|
||||||
// case nil:
|
|
||||||
// return 0, nil
|
|
||||||
// default:
|
|
||||||
// return 0, fmt.Errorf("unexpected type %T for value: %+v", v, v)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
|
|
||||||
// params := dbgen.GetCompanyWiseReportParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetCompanyWiseReport(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) {
|
|
||||||
// params := dbgen.GetBranchWiseReportParams{
|
|
||||||
// From: ToPgTimestamp(from),
|
|
||||||
// To: ToPgTimestamp(to),
|
|
||||||
// }
|
|
||||||
// return r.store.queries.GetBranchWiseReport(ctx, params)
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ var (
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
betStore BetStore
|
betStore BetStore
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
prematchSvc odds.ServiceImpl
|
prematchSvc odds.ServiceImpl
|
||||||
walletSvc wallet.Service
|
walletSvc wallet.Service
|
||||||
branchSvc branch.Service
|
branchSvc branch.Service
|
||||||
|
|
@ -68,7 +68,7 @@ type Service struct {
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
betStore BetStore,
|
betStore BetStore,
|
||||||
eventSvc event.Service,
|
eventSvc *event.Service,
|
||||||
prematchSvc odds.ServiceImpl,
|
prematchSvc odds.ServiceImpl,
|
||||||
walletSvc wallet.Service,
|
walletSvc wallet.Service,
|
||||||
branchSvc branch.Service,
|
branchSvc branch.Service,
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool {
|
||||||
|
|
||||||
func (s *Service) SendBonusNotification(ctx context.Context, param SendBonusNotificationParam) error {
|
func (s *Service) SendBonusNotification(ctx context.Context, param SendBonusNotificationParam) error {
|
||||||
|
|
||||||
var headline string
|
var (
|
||||||
var message string
|
headline string
|
||||||
|
message string
|
||||||
|
)
|
||||||
|
|
||||||
switch param.Type {
|
switch param.Type {
|
||||||
case domain.WelcomeBonus:
|
case domain.WelcomeBonus:
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@ type CompanyStore interface {
|
||||||
SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error)
|
SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error)
|
||||||
GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error)
|
GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error)
|
||||||
GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error)
|
GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error)
|
||||||
UpdateCompany(ctx context.Context, company domain.UpdateCompany) (error)
|
UpdateCompany(ctx context.Context, company domain.UpdateCompany) error
|
||||||
DeleteCompany(ctx context.Context, id int64) error
|
DeleteCompany(ctx context.Context, id int64) error
|
||||||
|
|
||||||
GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
|
UpdateCompanyStats(ctx context.Context) error
|
||||||
|
GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error)
|
||||||
|
GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
internal/services/company/stats.go
Normal file
19
internal/services/company/stats.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package company
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) UpdateCompanyStats(ctx context.Context) error {
|
||||||
|
return s.companyStore.UpdateCompanyStats(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error) {
|
||||||
|
return s.companyStore.GetCompanyStatByID(ctx, companyID);
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error) {
|
||||||
|
return s.companyStore.GetCompanyStatsByInterval(ctx, filter)
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type EventStore interface {
|
||||||
// FetchLiveEvents(ctx context.Context) error
|
// FetchLiveEvents(ctx context.Context) error
|
||||||
FetchUpcomingEvents(ctx context.Context) error
|
FetchUpcomingEvents(ctx context.Context) error
|
||||||
GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error)
|
GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error)
|
||||||
|
|
@ -25,6 +25,7 @@ type Service interface {
|
||||||
UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error
|
UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error)
|
GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error)
|
||||||
GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error)
|
GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error)
|
||||||
|
UpdateEventBetStats(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,16 @@ import (
|
||||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type service struct {
|
type Service struct {
|
||||||
token string
|
token string
|
||||||
store *repository.Store
|
store *repository.Store
|
||||||
settingSvc settings.Service
|
settingSvc *settings.Service
|
||||||
mongoLogger *zap.Logger
|
mongoLogger *zap.Logger
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(token string, store *repository.Store, settingSvc settings.Service, mongoLogger *zap.Logger, cfg *config.Config) Service {
|
func New(token string, store *repository.Store, settingSvc *settings.Service, mongoLogger *zap.Logger, cfg *config.Config) *Service {
|
||||||
return &service{
|
return &Service{
|
||||||
token: token,
|
token: token,
|
||||||
store: store,
|
store: store,
|
||||||
settingSvc: settingSvc,
|
settingSvc: settingSvc,
|
||||||
|
|
@ -187,7 +187,7 @@ func New(token string, store *repository.Store, settingSvc settings.Service, mon
|
||||||
// return events
|
// return events
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
func (s *Service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
urls := []struct {
|
urls := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -211,7 +211,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_url string, source domain.EventSource) {
|
func (s *Service) fetchUpcomingEventsFromProvider(ctx context.Context, source_url string, source domain.EventSource) {
|
||||||
|
|
||||||
settingsList, err := s.settingSvc.GetGlobalSettingList(ctx)
|
settingsList, err := s.settingSvc.GetGlobalSettingList(ctx)
|
||||||
|
|
||||||
|
|
@ -391,7 +391,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_ur
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) CheckAndInsertEventHistory(ctx context.Context, newEvent domain.CreateEvent) (bool, error) {
|
func (s *Service) CheckAndInsertEventHistory(ctx context.Context, newEvent domain.CreateEvent) (bool, error) {
|
||||||
|
|
||||||
eventLogger := s.mongoLogger.With(
|
eventLogger := s.mongoLogger.With(
|
||||||
zap.String("sourceEventID", newEvent.SourceEventID),
|
zap.String("sourceEventID", newEvent.SourceEventID),
|
||||||
|
|
@ -461,30 +461,28 @@ func convertInt64(num string) int64 {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
func (s *service) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error) {
|
func (s *Service) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error) {
|
||||||
return s.store.GetAllEvents(ctx, filter)
|
return s.store.GetAllEvents(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) {
|
func (s *Service) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) {
|
||||||
return s.store.GetEventByID(ctx, ID)
|
return s.store.GetEventByID(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error {
|
func (s *Service) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error {
|
||||||
return s.store.UpdateFinalScore(ctx, eventID, fullScore, status)
|
return s.store.UpdateFinalScore(ctx, eventID, fullScore, status)
|
||||||
}
|
}
|
||||||
func (s *service) UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error {
|
func (s *Service) UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error {
|
||||||
return s.store.UpdateEventStatus(ctx, eventID, status)
|
return s.store.UpdateEventStatus(ctx, eventID, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) IsEventMonitored(ctx context.Context, eventID int64) (bool, error) {
|
func (s *Service) IsEventMonitored(ctx context.Context, eventID int64) (bool, error) {
|
||||||
return s.store.IsEventMonitored(ctx, eventID)
|
return s.store.IsEventMonitored(ctx, eventID)
|
||||||
}
|
}
|
||||||
func (s *service) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error {
|
func (s *Service) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error {
|
||||||
return s.store.UpdateEventMonitored(ctx, eventID, IsMonitored)
|
return s.store.UpdateEventMonitored(ctx, eventID, IsMonitored)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error) {
|
func (s *Service) GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error) {
|
||||||
return s.store.GetSportAndLeagueIDs(ctx, eventID)
|
return s.store.GetSportAndLeagueIDs(ctx, eventID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
|
func (s *Service) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
|
||||||
return s.store.GetEventsWithSettings(ctx, companyID, filter)
|
return s.store.GetEventsWithSettings(ctx, companyID, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
|
func (s *Service) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
|
||||||
return s.store.GetEventWithSettingByID(ctx, ID, companyID)
|
return s.store.GetEventWithSettingByID(ctx, ID, companyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
|
func (s *Service) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
|
||||||
return s.store.UpdateTenantEventSettings(ctx, event)
|
return s.store.UpdateTenantEventSettings(ctx, event)
|
||||||
}
|
}
|
||||||
func (s *service) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
|
func (s *Service) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
|
||||||
return s.store.UpdateGlobalEventSettings(ctx, event)
|
return s.store.UpdateGlobalEventSettings(ctx, event)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,14 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
func (s *Service) GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||||
return s.store.GetEventStats(ctx, filter)
|
return s.store.GetTotalEventStats(ctx, filter)
|
||||||
}
|
}
|
||||||
func (s *service) GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
func (s *Service) GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||||
return s.store.GetEventStatsByInterval(ctx, filter)
|
return s.store.GetTotalEventStatsByInterval(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateEventBetStats(ctx context.Context) error {
|
||||||
|
return s.store.UpdateEventBetStats(ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type LeagueStore interface {
|
||||||
SaveLeague(ctx context.Context, league domain.CreateLeague) error
|
SaveLeague(ctx context.Context, league domain.CreateLeague) error
|
||||||
SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error
|
SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error
|
||||||
GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error)
|
GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error)
|
||||||
|
|
|
||||||
|
|
@ -7,40 +7,40 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
type service struct {
|
type Service struct {
|
||||||
store *repository.Store
|
store *repository.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(store *repository.Store) Service {
|
func New(store *repository.Store) *Service {
|
||||||
return &service{
|
return &Service{
|
||||||
store: store,
|
store: store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) SaveLeague(ctx context.Context, league domain.CreateLeague) error {
|
func (s *Service) SaveLeague(ctx context.Context, league domain.CreateLeague) error {
|
||||||
return s.store.SaveLeague(ctx, league)
|
return s.store.SaveLeague(ctx, league)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error {
|
func (s *Service) SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error {
|
||||||
return s.store.SaveLeagueSettings(ctx, leagueSettings)
|
return s.store.SaveLeagueSettings(ctx, leagueSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) {
|
func (s *Service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) {
|
||||||
return s.store.GetAllLeagues(ctx, filter)
|
return s.store.GetAllLeagues(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) {
|
func (s *Service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) {
|
||||||
return s.store.GetAllLeaguesByCompany(ctx, companyID, filter)
|
return s.store.GetAllLeaguesByCompany(ctx, companyID, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) {
|
func (s *Service) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) {
|
||||||
return s.store.CheckLeagueSupport(ctx, leagueID, companyID)
|
return s.store.CheckLeagueSupport(ctx, leagueID, companyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
|
func (s *Service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
|
||||||
return s.store.UpdateLeague(ctx, league)
|
return s.store.UpdateLeague(ctx, league)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error {
|
func (s *Service) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error {
|
||||||
return s.store.UpdateGlobalLeagueSettings(ctx, league)
|
return s.store.UpdateGlobalLeagueSettings(ctx, league)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ import (
|
||||||
type ServiceImpl struct {
|
type ServiceImpl struct {
|
||||||
store *repository.Store
|
store *repository.Store
|
||||||
config *config.Config
|
config *config.Config
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
mongoLogger *zap.Logger
|
mongoLogger *zap.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(store *repository.Store, cfg *config.Config, eventSvc event.Service, logger *slog.Logger, mongoLogger *zap.Logger) *ServiceImpl {
|
func New(store *repository.Store, cfg *config.Config, eventSvc *event.Service, logger *slog.Logger, mongoLogger *zap.Logger) *ServiceImpl {
|
||||||
return &ServiceImpl{
|
return &ServiceImpl{
|
||||||
store: store,
|
store: store,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
|
|
||||||
104
internal/services/report/csv.go
Normal file
104
internal/services/report/csv.go
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrReportFileNotFound = errors.New("failed to find report file")
|
||||||
|
ErrReportFileError = errors.New("unknown error with report file")
|
||||||
|
ErrReportNotComplete = errors.New("report is not completed")
|
||||||
|
ErrReportFilePathInvalid = errors.New("report file path is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) WriteCSV(rows [][]string, filePrefix string) (string, error) {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
s.mongoLogger.Error("[WriteCSV] CSV with no data",
|
||||||
|
zap.String("file_prefix", filePrefix),
|
||||||
|
)
|
||||||
|
return "", errors.New("no data to write")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%s_%s_%s.csv",
|
||||||
|
filePrefix,
|
||||||
|
time.Now().Format("2006-01-02_15-04-05"),
|
||||||
|
uuid.NewString()[:8],
|
||||||
|
)
|
||||||
|
|
||||||
|
filePath := filepath.Join(s.cfg.ReportExportPath, filename)
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("[WriteCSV] Failed to create file",
|
||||||
|
zap.String("file", filename),
|
||||||
|
zap.String("path", s.cfg.ReportExportPath),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", fmt.Errorf("create csv: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
writer := csv.NewWriter(file)
|
||||||
|
if err := writer.WriteAll(rows); err != nil {
|
||||||
|
s.mongoLogger.Error("[WriteCSV] Error while writing csv",
|
||||||
|
zap.String("file", filename),
|
||||||
|
zap.String("path", s.cfg.ReportExportPath),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", fmt.Errorf("write csv: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CheckAndFetchReportFile(ctx context.Context, ID int64) (string, error) {
|
||||||
|
report, err := s.GetReportRequestByID(ctx, ID)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("[CheckAndFetchReportFile] Failed to get report request by id",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", fmt.Errorf("failed to get report request:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if report.Status != domain.SuccessReportRequest {
|
||||||
|
s.mongoLogger.Error("[CheckAndFetchReportFile] Attempted download of report that isn't completed",
|
||||||
|
zap.String("status", string(report.Status)),
|
||||||
|
)
|
||||||
|
return "", ErrReportNotComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
if !report.FilePath.Valid {
|
||||||
|
s.mongoLogger.Error("[CheckAndFetchReportFile] File Path is invalid even though the report is a success",
|
||||||
|
zap.String("file path", report.FilePath.Value),
|
||||||
|
)
|
||||||
|
return "", ErrReportFilePathInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the report file exists
|
||||||
|
if _, err := os.Stat(report.FilePath.Value); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to find report file",
|
||||||
|
zap.String("file path", report.FilePath.Value),
|
||||||
|
)
|
||||||
|
return "", ErrReportFileNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to check report file",
|
||||||
|
zap.String("file path", report.FilePath.Value),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", ErrReportFileError
|
||||||
|
}
|
||||||
|
|
||||||
|
return report.FilePath.Value, nil
|
||||||
|
}
|
||||||
91
internal/services/report/event.go
Normal file
91
internal/services/report/event.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidInterval = errors.New("invalid interval provided")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) GenerateEventIntervalReport(ctx context.Context, request domain.ReportRequestDetail) (string, error) {
|
||||||
|
if request.Metadata.Interval == nil {
|
||||||
|
s.mongoLogger.Error("[GenerateEventIntervalReport] Metadata interval is empty")
|
||||||
|
return "", ErrInvalidInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
interval, err := domain.ParseDateInterval(*request.Metadata.Interval)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to parse date interval",
|
||||||
|
zap.String("interval", *request.Metadata.Interval),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", ErrInvalidInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := s.eventSvc.GetTotalEventStatsByInterval(ctx, domain.EventStatsByIntervalFilter{
|
||||||
|
Interval: domain.ValidDateInterval{
|
||||||
|
Value: interval,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to fetch event stats",
|
||||||
|
zap.String("interval", string(interval)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", fmt.Errorf("fetching event stats: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := [][]string{{
|
||||||
|
"Period", "Total Events", "Active Events", "In-Active Events", "Featured Events", "Leagues",
|
||||||
|
"Pending", "In-Play", "To-Be-Fixed", "Ended", "Postponed", "Cancelled", "Walkover",
|
||||||
|
"Interrupted", "Abandoned", "Retired", "Suspended", "Decided-By-FA", "Removed",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, stat := range stats {
|
||||||
|
endDate, err := domain.GetEndDateFromInterval(interval, stat.Date)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to get end date from interval",
|
||||||
|
zap.String("interval", string(interval)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return "", fmt.Errorf("invalid interval end date: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
period := fmt.Sprintf("%s to %s",
|
||||||
|
stat.Date.Format("2006-01-02"),
|
||||||
|
endDate.Format("2006-01-02"),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = append(rows, []string{
|
||||||
|
period,
|
||||||
|
fmt.Sprint(stat.EventCount),
|
||||||
|
fmt.Sprint(stat.TotalActiveEvents),
|
||||||
|
fmt.Sprint(stat.TotalInActiveEvents),
|
||||||
|
fmt.Sprint(stat.TotalFeaturedEvents),
|
||||||
|
fmt.Sprint(stat.TotalLeagues),
|
||||||
|
fmt.Sprint(stat.Pending),
|
||||||
|
fmt.Sprint(stat.InPlay),
|
||||||
|
fmt.Sprint(stat.ToBeFixed),
|
||||||
|
fmt.Sprint(stat.Ended),
|
||||||
|
fmt.Sprint(stat.Postponed),
|
||||||
|
fmt.Sprint(stat.Cancelled),
|
||||||
|
fmt.Sprint(stat.Walkover),
|
||||||
|
fmt.Sprint(stat.Interrupted),
|
||||||
|
fmt.Sprint(stat.Abandoned),
|
||||||
|
fmt.Sprint(stat.Retired),
|
||||||
|
fmt.Sprint(stat.Suspended),
|
||||||
|
fmt.Sprint(stat.DecidedByFa),
|
||||||
|
fmt.Sprint(stat.Removed),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.WriteCSV(rows, "event_interval")
|
||||||
|
}
|
||||||
72
internal/services/report/notification.go
Normal file
72
internal/services/report/notification.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidRequestedByID = errors.New("requested_by needs to be filled in to send report notification")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) SendReportRequestNotification(ctx context.Context, param domain.ReportRequestDetail) error {
|
||||||
|
if !param.RequestedBy.Valid {
|
||||||
|
return ErrInvalidRequestedByID
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
headline string
|
||||||
|
message string
|
||||||
|
level domain.NotificationLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
switch param.Status {
|
||||||
|
case domain.SuccessReportRequest:
|
||||||
|
headline = "Report Ready for Download"
|
||||||
|
message = fmt.Sprintf(
|
||||||
|
"Your %s report has been successfully generated and is now available for download.",
|
||||||
|
strings.ToLower(string(param.Type)),
|
||||||
|
)
|
||||||
|
level = domain.NotificationLevelSuccess
|
||||||
|
|
||||||
|
case domain.RejectReportRequest:
|
||||||
|
headline = "Report Generation Failed"
|
||||||
|
message = fmt.Sprintf(
|
||||||
|
"We were unable to generate your %s report. Please review your request and try again.",
|
||||||
|
strings.ToLower(string(param.Type)),
|
||||||
|
)
|
||||||
|
level = domain.NotificationLevelError
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported request status: %v", param.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, _ := json.Marshal(map[string]any{
|
||||||
|
"report_id": param.ID,
|
||||||
|
"type": param.Type,
|
||||||
|
"status": param.Status,
|
||||||
|
})
|
||||||
|
|
||||||
|
n := &domain.Notification{
|
||||||
|
RecipientID: param.RequestedBy.Value,
|
||||||
|
DeliveryStatus: domain.DeliveryStatusPending,
|
||||||
|
IsRead: false,
|
||||||
|
Type: domain.NotificationTypeReportRequest,
|
||||||
|
Level: level,
|
||||||
|
Reciever: domain.NotificationRecieverSideCustomer,
|
||||||
|
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||||
|
Payload: domain.NotificationPayload{
|
||||||
|
Headline: headline,
|
||||||
|
Message: message,
|
||||||
|
},
|
||||||
|
Priority: 2,
|
||||||
|
Metadata: raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.notificationSvc.SendNotification(ctx, n)
|
||||||
|
}
|
||||||
|
|
@ -15,4 +15,10 @@ type ReportStore interface {
|
||||||
// GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error)
|
// GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error)
|
||||||
// GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error)
|
// GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error)
|
||||||
// GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error)
|
// GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error)
|
||||||
|
|
||||||
|
CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error)
|
||||||
|
GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error)
|
||||||
|
GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error)
|
||||||
|
GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error)
|
||||||
|
UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
107
internal/services/report/process.go
Normal file
107
internal/services/report/process.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) ProcessReportRequests(ctx context.Context) error {
|
||||||
|
requests, total, err := s.GetAllReportRequests(ctx, domain.ReportRequestFilter{
|
||||||
|
Status: domain.ValidReportRequestStatus{
|
||||||
|
Value: domain.PendingReportRequest,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to get pending report requests", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, req := range requests {
|
||||||
|
if err := s.processSingleReportRequest(ctx, req); err != nil {
|
||||||
|
s.mongoLogger.Error("failed to process report request",
|
||||||
|
zap.Int64("id", req.ID),
|
||||||
|
zap.Int("index", i),
|
||||||
|
zap.Int64("total", total),
|
||||||
|
zap.String("type", string(req.Type)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) processSingleReportRequest(ctx context.Context, req domain.ReportRequestDetail) error {
|
||||||
|
var (
|
||||||
|
filePath string
|
||||||
|
rejectReason string
|
||||||
|
status = domain.SuccessReportRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
s.mongoLogger.Info("report request processed",
|
||||||
|
zap.Int64("id", req.ID),
|
||||||
|
zap.String("type", string(req.Type)),
|
||||||
|
zap.String("status", string(status)),
|
||||||
|
zap.Duration("duration", time.Since(start)),
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case domain.EventIntervalReportRequest:
|
||||||
|
if req.Metadata.Interval == nil {
|
||||||
|
status = domain.RejectReportRequest
|
||||||
|
rejectReason = "invalid interval provided"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, genErr := s.GenerateEventIntervalReport(ctx, req)
|
||||||
|
if genErr != nil {
|
||||||
|
status = domain.RejectReportRequest
|
||||||
|
rejectReason = fmt.Sprintf("failed to generate report: %v", genErr)
|
||||||
|
} else {
|
||||||
|
filePath = fp
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
status = domain.RejectReportRequest
|
||||||
|
rejectReason = fmt.Sprintf("unsupported report type: %s", req.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
update := domain.UpdateRequestRequest{
|
||||||
|
ID: req.ID,
|
||||||
|
Status: domain.ValidReportRequestStatus{
|
||||||
|
Value: status,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
FilePath: domain.ValidString{
|
||||||
|
Value: filePath,
|
||||||
|
Valid: filePath != "",
|
||||||
|
},
|
||||||
|
RejectReason: domain.ValidString{
|
||||||
|
Value: rejectReason,
|
||||||
|
Valid: rejectReason != "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.UpdateReportRequest(ctx, update); err != nil {
|
||||||
|
return fmt.Errorf("failed to update report request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare updated object for notification
|
||||||
|
updatedReq := req
|
||||||
|
updatedReq.FilePath = update.FilePath
|
||||||
|
updatedReq.Status = update.Status.Value
|
||||||
|
updatedReq.RejectReason = update.RejectReason
|
||||||
|
|
||||||
|
if err := s.SendReportRequestNotification(ctx, updatedReq); err != nil {
|
||||||
|
s.mongoLogger.Warn("failed to send notification", zap.Int64("id", req.ID), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
24
internal/services/report/request.go
Normal file
24
internal/services/report/request.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (s *Service) CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error) {
|
||||||
|
return s.store.CreateReportRequest(ctx, report)
|
||||||
|
}
|
||||||
|
func (s *Service) GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error) {
|
||||||
|
return s.store.GetAllReportRequests(ctx, filter)
|
||||||
|
}
|
||||||
|
func (s *Service) GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error) {
|
||||||
|
return s.store.GetReportRequestByRequestedByID(ctx, requestedBy, filter)
|
||||||
|
}
|
||||||
|
func (s *Service) GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error) {
|
||||||
|
return s.store.GetReportRequestByID(ctx, ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error {
|
||||||
|
return s.store.UpdateReportRequest(ctx, report)
|
||||||
|
}
|
||||||
|
|
@ -2,23 +2,26 @@ package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
// "encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
// "os"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
// notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
|
|
||||||
|
|
@ -32,6 +35,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
store *repository.Store
|
||||||
betStore bet.BetStore
|
betStore bet.BetStore
|
||||||
walletStore wallet.WalletStore
|
walletStore wallet.WalletStore
|
||||||
transactionStore transaction.TransactionStore
|
transactionStore transaction.TransactionStore
|
||||||
|
|
@ -41,10 +45,16 @@ type Service struct {
|
||||||
companyStore company.CompanyStore
|
companyStore company.CompanyStore
|
||||||
virtulaGamesStore repository.VirtualGameRepository
|
virtulaGamesStore repository.VirtualGameRepository
|
||||||
notificationStore repository.NotificationRepository
|
notificationStore repository.NotificationRepository
|
||||||
|
notificationSvc *notificationservice.Service
|
||||||
|
eventSvc *event.Service
|
||||||
|
companySvc *company.Service
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
mongoLogger *zap.Logger
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
|
store *repository.Store,
|
||||||
betStore bet.BetStore,
|
betStore bet.BetStore,
|
||||||
walletStore wallet.WalletStore,
|
walletStore wallet.WalletStore,
|
||||||
transactionStore transaction.TransactionStore,
|
transactionStore transaction.TransactionStore,
|
||||||
|
|
@ -54,9 +64,15 @@ func NewService(
|
||||||
companyStore company.CompanyStore,
|
companyStore company.CompanyStore,
|
||||||
virtulaGamesStore repository.VirtualGameRepository,
|
virtulaGamesStore repository.VirtualGameRepository,
|
||||||
notificationStore repository.NotificationRepository,
|
notificationStore repository.NotificationRepository,
|
||||||
|
notificationSvc *notificationservice.Service,
|
||||||
|
eventSvc *event.Service,
|
||||||
|
companySvc *company.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
|
mongoLogger *zap.Logger,
|
||||||
|
cfg *config.Config,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
|
store: store,
|
||||||
betStore: betStore,
|
betStore: betStore,
|
||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
transactionStore: transactionStore,
|
transactionStore: transactionStore,
|
||||||
|
|
@ -66,7 +82,12 @@ func NewService(
|
||||||
companyStore: companyStore,
|
companyStore: companyStore,
|
||||||
virtulaGamesStore: virtulaGamesStore,
|
virtulaGamesStore: virtulaGamesStore,
|
||||||
notificationStore: notificationStore,
|
notificationStore: notificationStore,
|
||||||
|
notificationSvc: notificationSvc,
|
||||||
|
eventSvc: eventSvc,
|
||||||
|
companySvc: companySvc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
mongoLogger: mongoLogger,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -459,199 +480,199 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF
|
||||||
return performances, nil
|
return performances, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error {
|
// func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error {
|
||||||
// Hardcoded output directory
|
// // Hardcoded output directory
|
||||||
outputDir := "reports"
|
// outputDir := "reports"
|
||||||
|
|
||||||
// Ensure directory exists
|
// // Ensure directory exists
|
||||||
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
|
// if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("failed to create report directory: %w", err)
|
// return fmt.Errorf("failed to create report directory: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
companies, branchMap, err := s.fetchReportData(ctx, from, to)
|
// companies, branchMap, err := s.fetchReportData(ctx, from, to)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// per-company reports
|
// // per-company reports
|
||||||
for _, company := range companies {
|
// for _, company := range companies {
|
||||||
branches := branchMap[company.CompanyID]
|
// branches := branchMap[company.CompanyID]
|
||||||
if err := writeCompanyCSV(company, branches, from, to, outputDir); err != nil {
|
// if err := writeCompanyCSV(company, branches, from, to, outputDir); err != nil {
|
||||||
return fmt.Errorf("company %d CSV: %w", company.CompanyID, err)
|
// return fmt.Errorf("company %d CSV: %w", company.CompanyID, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// summary report
|
// // summary report
|
||||||
if err := writeSummaryCSV(companies, from, to, outputDir); err != nil {
|
// if err := writeSummaryCSV(companies, from, to, outputDir); err != nil {
|
||||||
return fmt.Errorf("summary CSV: %w", err)
|
// return fmt.Errorf("summary CSV: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// writeCompanyCSV writes the company report to CSV in the hardcoded folder
|
// // 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 {
|
// 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"))
|
// 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",
|
// filePath := fmt.Sprintf("%s/company_%d_%s_%s_%s.csv",
|
||||||
outputDir,
|
// outputDir,
|
||||||
company.CompanyID,
|
// company.CompanyID,
|
||||||
from.Format("2006-01-02"),
|
// from.Format("2006-01-02"),
|
||||||
to.Format("2006-01-02"),
|
// to.Format("2006-01-02"),
|
||||||
time.Now().Format("2006-01-02_15-04"),
|
// time.Now().Format("2006-01-02_15-04"),
|
||||||
)
|
// )
|
||||||
|
|
||||||
file, err := os.Create(filePath)
|
// file, err := os.Create(filePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("create company csv: %w", err)
|
// return fmt.Errorf("create company csv: %w", err)
|
||||||
}
|
// }
|
||||||
defer file.Close()
|
// defer file.Close()
|
||||||
|
|
||||||
writer := csv.NewWriter(file)
|
// writer := csv.NewWriter(file)
|
||||||
defer writer.Flush()
|
// defer writer.Flush()
|
||||||
|
|
||||||
// Company summary section
|
// // Company summary section
|
||||||
writer.Write([]string{"Company Betting Report"})
|
// 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", "Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
writer.Write([]string{
|
// writer.Write([]string{
|
||||||
period,
|
// period,
|
||||||
fmt.Sprintf("%d", company.CompanyID),
|
// fmt.Sprintf("%d", company.CompanyID),
|
||||||
company.CompanyName,
|
// company.CompanyName,
|
||||||
fmt.Sprintf("%d", company.TotalBets),
|
// fmt.Sprintf("%d", company.TotalBets),
|
||||||
fmt.Sprintf("%.2f", company.TotalCashIn),
|
// fmt.Sprintf("%.2f", company.TotalCashIn),
|
||||||
fmt.Sprintf("%.2f", company.TotalCashOut),
|
// fmt.Sprintf("%.2f", company.TotalCashOut),
|
||||||
fmt.Sprintf("%.2f", company.TotalCashBacks),
|
// fmt.Sprintf("%.2f", company.TotalCashBacks),
|
||||||
})
|
// })
|
||||||
writer.Write([]string{}) // Empty line
|
// writer.Write([]string{}) // Empty line
|
||||||
|
|
||||||
// Branch reports
|
// // Branch reports
|
||||||
writer.Write([]string{"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"})
|
// writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
for _, br := range branches {
|
// for _, br := range branches {
|
||||||
writer.Write([]string{
|
// writer.Write([]string{
|
||||||
fmt.Sprintf("%d", br.BranchID),
|
// fmt.Sprintf("%d", br.BranchID),
|
||||||
br.BranchName,
|
// br.BranchName,
|
||||||
fmt.Sprintf("%d", br.CompanyID),
|
// fmt.Sprintf("%d", br.CompanyID),
|
||||||
fmt.Sprintf("%d", br.TotalBets),
|
// fmt.Sprintf("%d", br.TotalBets),
|
||||||
fmt.Sprintf("%.2f", br.TotalCashIn),
|
// fmt.Sprintf("%.2f", br.TotalCashIn),
|
||||||
fmt.Sprintf("%.2f", br.TotalCashOut),
|
// fmt.Sprintf("%.2f", br.TotalCashOut),
|
||||||
fmt.Sprintf("%.2f", br.TotalCashBacks),
|
// fmt.Sprintf("%.2f", br.TotalCashBacks),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := writer.Error(); err != nil {
|
// if err := writer.Error(); err != nil {
|
||||||
return fmt.Errorf("flush error: %w", err)
|
// return fmt.Errorf("flush error: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// writeSummaryCSV writes the summary report to CSV in the hardcoded folder
|
// writeSummaryCSV writes the summary report to CSV in the hardcoded folder
|
||||||
func writeSummaryCSV(companies []domain.CompanyReport, from, to time.Time, outputDir string) error {
|
// 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"))
|
// 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",
|
// filePath := fmt.Sprintf("%s/summary_%s_%s_%s.csv",
|
||||||
outputDir,
|
// outputDir,
|
||||||
from.Format("2006-01-02"),
|
// from.Format("2006-01-02"),
|
||||||
to.Format("2006-01-02"),
|
// to.Format("2006-01-02"),
|
||||||
time.Now().Format("2006-01-02_15-04"),
|
// time.Now().Format("2006-01-02_15-04"),
|
||||||
)
|
// )
|
||||||
|
|
||||||
file, err := os.Create(filePath)
|
// file, err := os.Create(filePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("create summary csv: %w", err)
|
// return fmt.Errorf("create summary csv: %w", err)
|
||||||
}
|
// }
|
||||||
defer file.Close()
|
// defer file.Close()
|
||||||
|
|
||||||
writer := csv.NewWriter(file)
|
// writer := csv.NewWriter(file)
|
||||||
defer writer.Flush()
|
// defer writer.Flush()
|
||||||
|
|
||||||
// Global summary
|
// // Global summary
|
||||||
writer.Write([]string{"Global Betting Summary"})
|
// writer.Write([]string{"Global Betting Summary"})
|
||||||
writer.Write([]string{"Period", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
// writer.Write([]string{"Period", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
|
|
||||||
var totalBets int64
|
// var totalBets int64
|
||||||
var totalIn, totalOut, totalBack float64
|
// var totalIn, totalOut, totalBack float64
|
||||||
for _, c := range companies {
|
// for _, c := range companies {
|
||||||
totalBets += c.TotalBets
|
// totalBets += c.TotalBets
|
||||||
totalIn += c.TotalCashIn
|
// totalIn += c.TotalCashIn
|
||||||
totalOut += c.TotalCashOut
|
// totalOut += c.TotalCashOut
|
||||||
totalBack += c.TotalCashBacks
|
// totalBack += c.TotalCashBacks
|
||||||
}
|
// }
|
||||||
|
|
||||||
writer.Write([]string{
|
// writer.Write([]string{
|
||||||
period,
|
// period,
|
||||||
fmt.Sprintf("%d", totalBets),
|
// fmt.Sprintf("%d", totalBets),
|
||||||
fmt.Sprintf("%.2f", totalIn),
|
// fmt.Sprintf("%.2f", totalIn),
|
||||||
fmt.Sprintf("%.2f", totalOut),
|
// fmt.Sprintf("%.2f", totalOut),
|
||||||
fmt.Sprintf("%.2f", totalBack),
|
// fmt.Sprintf("%.2f", totalBack),
|
||||||
})
|
// })
|
||||||
writer.Write([]string{}) // Empty line
|
// writer.Write([]string{}) // Empty line
|
||||||
|
|
||||||
// Company breakdown
|
// // Company breakdown
|
||||||
writer.Write([]string{"Company Reports"})
|
// writer.Write([]string{"Company Reports"})
|
||||||
writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
// writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
for _, cr := range companies {
|
// for _, cr := range companies {
|
||||||
writer.Write([]string{
|
// writer.Write([]string{
|
||||||
fmt.Sprintf("%d", cr.CompanyID),
|
// fmt.Sprintf("%d", cr.CompanyID),
|
||||||
cr.CompanyName,
|
// cr.CompanyName,
|
||||||
fmt.Sprintf("%d", cr.TotalBets),
|
// fmt.Sprintf("%d", cr.TotalBets),
|
||||||
fmt.Sprintf("%.2f", cr.TotalCashIn),
|
// fmt.Sprintf("%.2f", cr.TotalCashIn),
|
||||||
fmt.Sprintf("%.2f", cr.TotalCashOut),
|
// fmt.Sprintf("%.2f", cr.TotalCashOut),
|
||||||
fmt.Sprintf("%.2f", cr.TotalCashBacks),
|
// fmt.Sprintf("%.2f", cr.TotalCashBacks),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := writer.Error(); err != nil {
|
// if err := writer.Error(); err != nil {
|
||||||
return fmt.Errorf("flush error: %w", err)
|
// return fmt.Errorf("flush error: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
|
// func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
|
||||||
[]domain.CompanyReport, map[int64][]domain.BranchReport, error,
|
// []domain.CompanyReport, map[int64][]domain.BranchReport, error,
|
||||||
) {
|
// ) {
|
||||||
// --- company level ---
|
// // --- company level ---
|
||||||
companyRows, err := s.repo.GetCompanyWiseReport(ctx, from, to)
|
// companyRows, err := s.repo.GetCompanyWiseReport(ctx, from, to)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, fmt.Errorf("company-wise report: %w", err)
|
// return nil, nil, fmt.Errorf("company-wise report: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
companies := make([]domain.CompanyReport, 0, len(companyRows))
|
// companies := make([]domain.CompanyReport, 0, len(companyRows))
|
||||||
for _, row := range companyRows {
|
// for _, row := range companyRows {
|
||||||
companies = append(companies, domain.CompanyReport{
|
// companies = append(companies, domain.CompanyReport{
|
||||||
CompanyID: row.CompanyID,
|
// CompanyID: row.CompanyID,
|
||||||
CompanyName: row.CompanyName,
|
// CompanyName: row.CompanyName,
|
||||||
TotalBets: row.TotalBets,
|
// TotalBets: row.TotalBets,
|
||||||
TotalCashIn: toFloat(row.TotalCashMade),
|
// TotalCashIn: toFloat(row.TotalCashMade),
|
||||||
TotalCashOut: toFloat(row.TotalCashOut),
|
// TotalCashOut: toFloat(row.TotalCashOut),
|
||||||
TotalCashBacks: toFloat(row.TotalCashBacks),
|
// TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// --- branch level ---
|
// // --- branch level ---
|
||||||
branchRows, err := s.repo.GetBranchWiseReport(ctx, from, to)
|
// branchRows, err := s.repo.GetBranchWiseReport(ctx, from, to)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, fmt.Errorf("branch-wise report: %w", err)
|
// return nil, nil, fmt.Errorf("branch-wise report: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
branchMap := make(map[int64][]domain.BranchReport)
|
// branchMap := make(map[int64][]domain.BranchReport)
|
||||||
for _, row := range branchRows {
|
// for _, row := range branchRows {
|
||||||
branch := domain.BranchReport{
|
// branch := domain.BranchReport{
|
||||||
BranchID: row.BranchID,
|
// BranchID: row.BranchID,
|
||||||
BranchName: row.BranchName,
|
// BranchName: row.BranchName,
|
||||||
CompanyID: row.CompanyID,
|
// CompanyID: row.CompanyID,
|
||||||
TotalBets: row.TotalBets,
|
// TotalBets: row.TotalBets,
|
||||||
TotalCashIn: toFloat(row.TotalCashMade),
|
// TotalCashIn: toFloat(row.TotalCashMade),
|
||||||
TotalCashOut: toFloat(row.TotalCashOut),
|
// TotalCashOut: toFloat(row.TotalCashOut),
|
||||||
TotalCashBacks: toFloat(row.TotalCashBacks),
|
// TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||||
}
|
// }
|
||||||
branchMap[row.CompanyID] = append(branchMap[row.CompanyID], branch)
|
// branchMap[row.CompanyID] = append(branchMap[row.CompanyID], branch)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return companies, branchMap, nil
|
// return companies, branchMap, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// helper to unify float conversions
|
// helper to unify float conversions
|
||||||
func toFloat(val interface{}) float64 {
|
func toFloat(val interface{}) float64 {
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ type Service struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
betSvc bet.Service
|
betSvc bet.Service
|
||||||
oddSvc odds.ServiceImpl
|
oddSvc odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
leagueSvc league.Service
|
leagueSvc *league.Service
|
||||||
notificationSvc *notificationservice.Service
|
notificationSvc *notificationservice.Service
|
||||||
messengerSvc *messenger.Service
|
messengerSvc *messenger.Service
|
||||||
userSvc user.Service
|
userSvc user.Service
|
||||||
|
|
@ -46,8 +46,8 @@ func NewService(
|
||||||
mongoLogger *zap.Logger,
|
mongoLogger *zap.Logger,
|
||||||
betSvc bet.Service,
|
betSvc bet.Service,
|
||||||
oddSvc odds.ServiceImpl,
|
oddSvc odds.ServiceImpl,
|
||||||
eventSvc event.Service,
|
eventSvc *event.Service,
|
||||||
leagueSvc league.Service,
|
leagueSvc *league.Service,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
messengerSvc *messenger.Service,
|
messengerSvc *messenger.Service,
|
||||||
userSvc user.Service,
|
userSvc user.Service,
|
||||||
|
|
|
||||||
|
|
@ -31,19 +31,19 @@ var (
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ticketStore TicketStore
|
ticketStore TicketStore
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
prematchSvc odds.ServiceImpl
|
prematchSvc odds.ServiceImpl
|
||||||
mongoLogger *zap.Logger
|
mongoLogger *zap.Logger
|
||||||
settingSvc settings.Service
|
settingSvc *settings.Service
|
||||||
notificationSvc *notificationservice.Service
|
notificationSvc *notificationservice.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
ticketStore TicketStore,
|
ticketStore TicketStore,
|
||||||
eventSvc event.Service,
|
eventSvc *event.Service,
|
||||||
prematchSvc odds.ServiceImpl,
|
prematchSvc odds.ServiceImpl,
|
||||||
mongoLogger *zap.Logger,
|
mongoLogger *zap.Logger,
|
||||||
settingSvc settings.Service,
|
settingSvc *settings.Service,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ type App struct {
|
||||||
JwtConfig jwtutil.JwtConfig
|
JwtConfig jwtutil.JwtConfig
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
prematchSvc *odds.ServiceImpl
|
prematchSvc *odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
leagueSvc league.Service
|
leagueSvc *league.Service
|
||||||
resultSvc *result.Service
|
resultSvc *result.Service
|
||||||
mongoLoggerSvc *zap.Logger
|
mongoLoggerSvc *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +113,8 @@ func NewApp(
|
||||||
companySvc *company.Service,
|
companySvc *company.Service,
|
||||||
notidicationStore *notificationservice.Service,
|
notidicationStore *notificationservice.Service,
|
||||||
prematchSvc *odds.ServiceImpl,
|
prematchSvc *odds.ServiceImpl,
|
||||||
eventSvc event.Service,
|
eventSvc *event.Service,
|
||||||
leagueSvc league.Service,
|
leagueSvc *league.Service,
|
||||||
referralSvc *referralservice.Service,
|
referralSvc *referralservice.Service,
|
||||||
raffleSvc raffle.RaffleStore,
|
raffleSvc raffle.RaffleStore,
|
||||||
bonusSvc *bonus.Service,
|
bonusSvc *bonus.Service,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ package httpserver
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
// "time"
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
betSvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
betSvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
||||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||||
|
|
@ -21,26 +23,26 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.ServiceImpl, resultService *resultsvc.Service, mongoLogger *zap.Logger) {
|
func StartDataFetchingCrons(eventService *eventsvc.Service, oddsService oddssvc.ServiceImpl, resultService *resultsvc.Service, mongoLogger *zap.Logger) {
|
||||||
c := cron.New(cron.WithSeconds())
|
c := cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
schedule := []struct {
|
schedule := []struct {
|
||||||
spec string
|
spec string
|
||||||
task func()
|
task func()
|
||||||
}{
|
}{
|
||||||
// {
|
{
|
||||||
// spec: "0 0 * * * *", // Every 1 hour
|
spec: "0 0 * * * *", // Every 1 hour
|
||||||
// task: func() {
|
task: func() {
|
||||||
// mongoLogger.Info("Began fetching upcoming events cron task")
|
mongoLogger.Info("Began fetching upcoming events cron task")
|
||||||
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||||
// mongoLogger.Error("Failed to fetch upcoming events",
|
mongoLogger.Error("Failed to fetch upcoming events",
|
||||||
// zap.Error(err),
|
zap.Error(err),
|
||||||
// )
|
)
|
||||||
// } else {
|
} else {
|
||||||
// mongoLogger.Info("Completed fetching upcoming events without errors")
|
mongoLogger.Info("Completed fetching upcoming events without errors")
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// {
|
// {
|
||||||
// spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events)
|
// spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events)
|
||||||
// task: func() {
|
// task: func() {
|
||||||
|
|
@ -96,7 +98,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range schedule {
|
for _, job := range schedule {
|
||||||
// job.task()
|
job.task()
|
||||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||||
mongoLogger.Error("Failed to schedule data fetching cron job",
|
mongoLogger.Error("Failed to schedule data fetching cron job",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
|
@ -105,8 +107,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
log.Println("Cron jobs started for event and odds services")
|
mongoLogger.Info("Data Fetching Cron jobs started")
|
||||||
mongoLogger.Info("Cron jobs started for event and odds services")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartCleanupCrons(ticketService ticket.Service, notificationSvc *notificationservice.Service, mongoLogger *zap.Logger) {
|
func StartCleanupCrons(ticketService ticket.Service, notificationSvc *notificationservice.Service, mongoLogger *zap.Logger) {
|
||||||
|
|
@ -156,6 +157,89 @@ func StartCleanupCrons(ticketService ticket.Service, notificationSvc *notificati
|
||||||
mongoLogger.Info("Cron jobs started for ticket service")
|
mongoLogger.Info("Cron jobs started for ticket service")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartStatCrons(companyService company.Service, eventService *eventsvc.Service, mongoLogger *zap.Logger) {
|
||||||
|
c := cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
schedule := []struct {
|
||||||
|
spec string
|
||||||
|
task func()
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
spec: "0 0 * * * *", // Every hour
|
||||||
|
task: func() {
|
||||||
|
mongoLogger.Info("[Company Stats Crons] Updating company stats")
|
||||||
|
if err := companyService.UpdateCompanyStats(context.Background()); err != nil {
|
||||||
|
mongoLogger.Error("[Company Stats Crons] Failed to update company stats",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mongoLogger.Info("[Company Stats Crons] Successfully updated company stats")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: "0 0 * * * *", // Hourly
|
||||||
|
task: func() {
|
||||||
|
mongoLogger.Info("[Event Stats Crons] Updating event stats")
|
||||||
|
if err := eventService.UpdateEventBetStats(context.Background()); err != nil {
|
||||||
|
mongoLogger.Error("[Event Stats Crons] Failed to update event bet stats",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mongoLogger.Info("[Event Stats Crons] Successfully updated event stats")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range schedule {
|
||||||
|
job.task()
|
||||||
|
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||||
|
mongoLogger.Error("[Stats Crons] Failed to schedule stats cron job",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
mongoLogger.Info("Cron jobs started for stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartReportCrons(reportService *report.Service, mongoLogger *zap.Logger) {
|
||||||
|
c := cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
schedule := []struct {
|
||||||
|
spec string
|
||||||
|
task func()
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
spec: "0 * * * * *", // Every 5 Minutes
|
||||||
|
task: func() {
|
||||||
|
mongoLogger.Info("[Process Report Crons] Started Checking and Processing Reports")
|
||||||
|
if err := reportService.ProcessReportRequests(context.Background()); err != nil {
|
||||||
|
mongoLogger.Error("[Process Report Crons] Failed to process reports",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mongoLogger.Info("[Process Report Crons] Successfully processed all reports")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range schedule {
|
||||||
|
job.task()
|
||||||
|
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||||
|
mongoLogger.Error("[Report Crons] Failed to schedule report cron job",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
mongoLogger.Info("[Report Crons] Cron jobs started for reports")
|
||||||
|
}
|
||||||
|
|
||||||
// SetupReportCronJobs schedules periodic report generation
|
// SetupReportCronJobs schedules periodic report generation
|
||||||
func SetupReportandVirtualGameCronJobs(
|
func SetupReportandVirtualGameCronJobs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
@ -207,18 +291,18 @@ func SetupReportandVirtualGameCronJobs(
|
||||||
log.Printf("[%s] Successfully fetched & stored %d virtual games", period, len(allGames))
|
log.Printf("[%s] Successfully fetched & stored %d virtual games", period, len(allGames))
|
||||||
|
|
||||||
// --- Generate reports only for daily runs ---
|
// --- Generate reports only for daily runs ---
|
||||||
if period == "daily" {
|
// if period == "daily" {
|
||||||
now := time.Now()
|
// now := time.Now()
|
||||||
from := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location())
|
// 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())
|
// to := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location())
|
||||||
|
|
||||||
log.Printf("Running daily report for period %s -> %s", from.Format(time.RFC3339), to.Format(time.RFC3339))
|
// log.Printf("Running daily report for period %s -> %s", from.Format(time.RFC3339), to.Format(time.RFC3339))
|
||||||
if err := reportService.GenerateReport(ctx, from, to); err != nil {
|
// if err := reportService.GenerateReport(ctx, from, to); err != nil {
|
||||||
log.Printf("Error generating daily report: %v", err)
|
// log.Printf("Error generating daily report: %v", err)
|
||||||
} else {
|
// } else {
|
||||||
log.Printf("Successfully generated daily report")
|
// log.Printf("Successfully generated daily report")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
|
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
|
||||||
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
|
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
||||||
zap.String("is_featured", isFeaturedQuery),
|
zap.String("status", statusQuery),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
|
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
|
||||||
|
|
@ -222,204 +222,6 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
|
||||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
|
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Retrieve all upcoming events
|
|
||||||
// @Description Retrieve all upcoming events from the database
|
|
||||||
// @Tags prematch
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param page query int false "Page number"
|
|
||||||
// @Param page_size query int false "Page size"
|
|
||||||
// @Param league_id query string false "League ID Filter"
|
|
||||||
// @Param sport_id query string false "Sport ID Filter"
|
|
||||||
// @Param cc query string false "Country Code Filter"
|
|
||||||
// @Param first_start_time query string false "Start Time"
|
|
||||||
// @Param last_start_time query string false "End Time"
|
|
||||||
// @Success 200 {array} domain.BaseEvent
|
|
||||||
// @Failure 500 {object} response.APIResponse
|
|
||||||
// @Router /api/v1/detailed/events [get]
|
|
||||||
func (h *Handler) GetAllDetailedEvents(c *fiber.Ctx) error {
|
|
||||||
page := c.QueryInt("page", 1)
|
|
||||||
pageSize := c.QueryInt("page_size", 10)
|
|
||||||
limit := domain.ValidInt32{
|
|
||||||
Value: int32(pageSize),
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
offset := domain.ValidInt32{
|
|
||||||
Value: int32(page - 1),
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
leagueIDQuery := c.Query("league_id")
|
|
||||||
var leagueID domain.ValidInt64
|
|
||||||
if leagueIDQuery != "" {
|
|
||||||
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Error("invalid league id",
|
|
||||||
zap.String("league_id", leagueIDQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
|
|
||||||
}
|
|
||||||
leagueID = domain.ValidInt64{
|
|
||||||
Value: leagueIDInt,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Go through the all the handler functions and change them into something like this
|
|
||||||
// leagueID, err := ParseLeagueIDFromQuery(c)
|
|
||||||
// if err != nil {
|
|
||||||
// h.BadRequestLogger().Info("invalid league id", zap.Error(err))
|
|
||||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
|
|
||||||
// }
|
|
||||||
|
|
||||||
sportIDQuery := c.Query("sport_id")
|
|
||||||
var sportID domain.ValidInt32
|
|
||||||
if sportIDQuery != "" {
|
|
||||||
sportIDint, err := strconv.Atoi(sportIDQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Info("invalid sport id",
|
|
||||||
zap.String("sportID", sportIDQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
|
|
||||||
}
|
|
||||||
sportID = domain.ValidInt32{
|
|
||||||
Value: int32(sportIDint),
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchQuery := c.Query("query")
|
|
||||||
searchString := domain.ValidString{
|
|
||||||
Value: searchQuery,
|
|
||||||
Valid: searchQuery != "",
|
|
||||||
}
|
|
||||||
|
|
||||||
firstStartTimeQuery := c.Query("first_start_time")
|
|
||||||
var firstStartTime domain.ValidTime
|
|
||||||
if firstStartTimeQuery != "" {
|
|
||||||
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Info("invalid start_time format",
|
|
||||||
zap.String("first_start_time", firstStartTimeQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
|
||||||
}
|
|
||||||
firstStartTime = domain.ValidTime{
|
|
||||||
Value: firstStartTimeParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastStartTimeQuery := c.Query("last_start_time")
|
|
||||||
var lastStartTime domain.ValidTime
|
|
||||||
if lastStartTimeQuery != "" {
|
|
||||||
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Info("invalid last_start_time format",
|
|
||||||
zap.String("last_start_time", lastStartTimeQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
|
||||||
}
|
|
||||||
lastStartTime = domain.ValidTime{
|
|
||||||
Value: lastStartTimeParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
countryCodeQuery := c.Query("cc")
|
|
||||||
countryCode := domain.ValidString{
|
|
||||||
Value: countryCodeQuery,
|
|
||||||
Valid: countryCodeQuery != "",
|
|
||||||
}
|
|
||||||
|
|
||||||
isFeaturedQuery := c.Query("is_featured")
|
|
||||||
var isFeatured domain.ValidBool
|
|
||||||
if isFeaturedQuery != "" {
|
|
||||||
isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Error("Failed to parse isFeatured",
|
|
||||||
zap.String("is_featured", isFeaturedQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
|
|
||||||
}
|
|
||||||
|
|
||||||
isFeatured = domain.ValidBool{
|
|
||||||
Value: isFeaturedParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isActiveQuery := c.Query("is_active")
|
|
||||||
var isActive domain.ValidBool
|
|
||||||
if isActiveQuery != "" {
|
|
||||||
isActiveParsed, err := strconv.ParseBool(isActiveQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Error("Failed to parse isActive",
|
|
||||||
zap.String("is_active", isActiveQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_active")
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive = domain.ValidBool{
|
|
||||||
Value: isActiveParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statusQuery := c.Query("status")
|
|
||||||
var eventStatus domain.ValidEventStatus
|
|
||||||
if statusQuery != "" {
|
|
||||||
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
|
||||||
zap.String("is_featured", isFeaturedQuery),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
|
|
||||||
}
|
|
||||||
eventStatus = domain.ValidEventStatus{
|
|
||||||
Value: eventStatusParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
events, total, err := h.eventSvc.GetAllDetailedEvents(
|
|
||||||
c.Context(), domain.EventFilter{
|
|
||||||
SportID: sportID,
|
|
||||||
LeagueID: leagueID,
|
|
||||||
Query: searchString,
|
|
||||||
FirstStartTime: firstStartTime,
|
|
||||||
LastStartTime: lastStartTime,
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
CountryCode: countryCode,
|
|
||||||
Featured: isFeatured,
|
|
||||||
Active: isActive,
|
|
||||||
Status: eventStatus,
|
|
||||||
})
|
|
||||||
|
|
||||||
// fmt.Printf("League ID: %v", leagueID)
|
|
||||||
if err != nil {
|
|
||||||
h.InternalServerErrorLogger().Error("Failed to retrieve all upcoming events",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
res := domain.ConvertDetailedEventResList(events)
|
|
||||||
|
|
||||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) ExportEvents(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// @Summary Retrieve all upcoming events with settings
|
// @Summary Retrieve all upcoming events with settings
|
||||||
// @Description Retrieve all upcoming events settings from the database
|
// @Description Retrieve all upcoming events settings from the database
|
||||||
|
|
@ -899,40 +701,6 @@ func (h *Handler) GetEventByID(c *fiber.Ctx) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Retrieve an upcoming by ID
|
|
||||||
// @Description Retrieve an upcoming event by ID
|
|
||||||
// @Tags prematch
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param id path string true "ID"
|
|
||||||
// @Success 200 {object} domain.BaseEvent
|
|
||||||
// @Failure 400 {object} response.APIResponse
|
|
||||||
// @Failure 500 {object} response.APIResponse
|
|
||||||
// @Router /api/v1/detailed/events/{id} [get]
|
|
||||||
func (h *Handler) GetDetailedEventByID(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
idStr := c.Params("id")
|
|
||||||
eventID, err := strconv.ParseInt(idStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
h.BadRequestLogger().Info("Failed to parse event id", zap.String("id", idStr))
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Missing id")
|
|
||||||
}
|
|
||||||
|
|
||||||
event, err := h.eventSvc.GetDetailedEventByID(c.Context(), eventID)
|
|
||||||
if err != nil {
|
|
||||||
h.InternalServerErrorLogger().Error("Failed to get event by id",
|
|
||||||
zap.Int64("eventID", eventID),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
res := domain.ConvertDetailedEventRes(event)
|
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", res, nil)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Retrieve an upcoming by ID
|
// @Summary Retrieve an upcoming by ID
|
||||||
// @Description Retrieve an upcoming event by ID
|
// @Description Retrieve an upcoming event by ID
|
||||||
// @Tags prematch
|
// @Tags prematch
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
func (h *Handler) GetTotalEventStats(c *fiber.Ctx) error {
|
||||||
leagueIDQuery := c.Query("league_id")
|
leagueIDQuery := c.Query("league_id")
|
||||||
var leagueID domain.ValidInt64
|
var leagueID domain.ValidInt64
|
||||||
if leagueIDQuery != "" {
|
if leagueIDQuery != "" {
|
||||||
|
|
@ -44,7 +44,7 @@ func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.eventSvc.GetEventStats(c.Context(), domain.EventStatsFilter{
|
stats, err := h.eventSvc.GetTotalEventStats(c.Context(), domain.EventStatsFilter{
|
||||||
LeagueID: leagueID,
|
LeagueID: leagueID,
|
||||||
SportID: sportID,
|
SportID: sportID,
|
||||||
})
|
})
|
||||||
|
|
@ -59,7 +59,7 @@ func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Event Statistics retrieved successfully", stats, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Event Statistics retrieved successfully", stats, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) GetEventStatsByInterval(c *fiber.Ctx) error {
|
func (h *Handler) GetTotalEventStatsByInterval(c *fiber.Ctx) error {
|
||||||
intervalParam := c.Query("interval", "day")
|
intervalParam := c.Query("interval", "day")
|
||||||
interval, err := domain.ParseDateInterval(intervalParam)
|
interval, err := domain.ParseDateInterval(intervalParam)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -103,8 +103,11 @@ func (h *Handler) GetEventStatsByInterval(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.eventSvc.GetEventStatsByInterval(c.Context(), domain.EventStatsByIntervalFilter{
|
stats, err := h.eventSvc.GetTotalEventStatsByInterval(c.Context(), domain.EventStatsByIntervalFilter{
|
||||||
Interval: interval,
|
Interval: domain.ValidDateInterval{
|
||||||
|
Value: interval,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
LeagueID: leagueID,
|
LeagueID: leagueID,
|
||||||
SportID: sportID,
|
SportID: sportID,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ type Handler struct {
|
||||||
referralSvc *referralservice.Service
|
referralSvc *referralservice.Service
|
||||||
raffleSvc raffle.RaffleStore
|
raffleSvc raffle.RaffleStore
|
||||||
bonusSvc *bonus.Service
|
bonusSvc *bonus.Service
|
||||||
reportSvc report.ReportStore
|
reportSvc *report.Service
|
||||||
chapaSvc *chapa.Service
|
chapaSvc *chapa.Service
|
||||||
walletSvc *wallet.Service
|
walletSvc *wallet.Service
|
||||||
transactionSvc *transaction.Service
|
transactionSvc *transaction.Service
|
||||||
|
|
@ -64,8 +64,8 @@ type Handler struct {
|
||||||
branchSvc *branch.Service
|
branchSvc *branch.Service
|
||||||
companySvc *company.Service
|
companySvc *company.Service
|
||||||
prematchSvc *odds.ServiceImpl
|
prematchSvc *odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc *event.Service
|
||||||
leagueSvc league.Service
|
leagueSvc *league.Service
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||||
|
|
@ -91,7 +91,7 @@ func New(
|
||||||
settingSvc *settings.Service,
|
settingSvc *settings.Service,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
validator *customvalidator.CustomValidator,
|
validator *customvalidator.CustomValidator,
|
||||||
reportSvc report.ReportStore,
|
reportSvc *report.Service,
|
||||||
chapaSvc *chapa.Service,
|
chapaSvc *chapa.Service,
|
||||||
walletSvc *wallet.Service,
|
walletSvc *wallet.Service,
|
||||||
referralSvc *referralservice.Service,
|
referralSvc *referralservice.Service,
|
||||||
|
|
@ -111,8 +111,8 @@ func New(
|
||||||
branchSvc *branch.Service,
|
branchSvc *branch.Service,
|
||||||
companySvc *company.Service,
|
companySvc *company.Service,
|
||||||
prematchSvc *odds.ServiceImpl,
|
prematchSvc *odds.ServiceImpl,
|
||||||
eventSvc event.Service,
|
eventSvc *event.Service,
|
||||||
leagueSvc league.Service,
|
leagueSvc *league.Service,
|
||||||
resultSvc result.Service,
|
resultSvc result.Service,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
mongoLoggerSvc *zap.Logger,
|
mongoLoggerSvc *zap.Logger,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
@ -315,3 +316,166 @@ func (h *Handler) ListReportFiles(c *fiber.Ctx) error {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) CreateReportRequest(c *fiber.Ctx) error {
|
||||||
|
userID := c.Locals("user_id").(int64)
|
||||||
|
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||||
|
|
||||||
|
var req domain.CreateReportRequestReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
h.BadRequestLogger().Error(
|
||||||
|
"Failed to parse CreateReportRequestReq",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request:", err.Error())
|
||||||
|
}
|
||||||
|
valErrs, ok := h.validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
var errMsg string
|
||||||
|
for field, msg := range valErrs {
|
||||||
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||||
|
}
|
||||||
|
h.BadRequestLogger().Error(
|
||||||
|
"Failed to validate CreateReportRequestReq",
|
||||||
|
zap.String("errMsg", errMsg),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := h.reportSvc.CreateReportRequest(c.Context(), domain.CreateReportRequest{
|
||||||
|
CompanyID: companyID,
|
||||||
|
RequestedBy: domain.ValidInt64{
|
||||||
|
Value: userID,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Type: domain.ReportRequestType(req.Type),
|
||||||
|
Metadata: req.Metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to create report request", zap.Error(err))
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
res := domain.ConvertReportRequest(request)
|
||||||
|
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Report Request has been created", res, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetAllReportRequests(c *fiber.Ctx) error {
|
||||||
|
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||||
|
|
||||||
|
page := c.QueryInt("page", 1)
|
||||||
|
pageSize := c.QueryInt("page_size", 10)
|
||||||
|
limit := domain.ValidInt32{
|
||||||
|
Value: int32(pageSize),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
offset := domain.ValidInt32{
|
||||||
|
Value: int32(page - 1),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
statusQuery := c.Query("status")
|
||||||
|
var reportStatus domain.ValidReportRequestStatus
|
||||||
|
if statusQuery != "" {
|
||||||
|
reportStatusParsed, err := domain.ParseReportRequestStatus(statusQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
||||||
|
zap.String("status", statusQuery),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "invalid report status")
|
||||||
|
}
|
||||||
|
reportStatus = domain.ValidReportRequestStatus{
|
||||||
|
Value: reportStatusParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeQuery := c.Query("type")
|
||||||
|
var reportType domain.ValidReportRequestType
|
||||||
|
if typeQuery != "" {
|
||||||
|
reportTypeParsed, err := domain.ParseReportRequestType(typeQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.BadRequestLogger().Error("Failed to parse typeQuery",
|
||||||
|
zap.String("type", typeQuery),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "invalid report type")
|
||||||
|
}
|
||||||
|
reportType = domain.ValidReportRequestType{
|
||||||
|
Value: reportTypeParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requesterQuery := c.Query("requester")
|
||||||
|
var requestedBy domain.ValidInt64
|
||||||
|
if requesterQuery != "" {
|
||||||
|
parsedRequestedBy, err := strconv.ParseInt(requesterQuery, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.BadRequestLogger().Error("Failed to parse requester",
|
||||||
|
zap.String("requester", requesterQuery),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "invalid report requester")
|
||||||
|
}
|
||||||
|
requestedBy = domain.ValidInt64{
|
||||||
|
Value: parsedRequestedBy,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requests, total, err := h.reportSvc.GetAllReportRequests(c.Context(), domain.ReportRequestFilter{
|
||||||
|
CompanyID: companyID,
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
|
Status: reportStatus,
|
||||||
|
Type: reportType,
|
||||||
|
RequestedBy: requestedBy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to retrieve all report requests",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
res := domain.ConvertReportRequestDetailList(requests)
|
||||||
|
|
||||||
|
return response.WritePaginatedJSON(c, fiber.StatusOK, "All Report Requests successfully retrieved", res, nil, page, int(total))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) DownloadReportByID(c *fiber.Ctx) error {
|
||||||
|
requestID := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(requestID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.BadRequestLogger().Info("Invalid report request ID",
|
||||||
|
zap.String("requestID", requestID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := h.reportSvc.CheckAndFetchReportFile(c.Context(), id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to check and fetch report file",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("requestID", requestID),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check and fetch report file:%v", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/csv")
|
||||||
|
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file))
|
||||||
|
|
||||||
|
if err := c.SendFile(file); err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Unable to download report file",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("requestID", requestID),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Unable to download report file:%v", err.Error()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -276,12 +276,6 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
|
groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
|
||||||
groupV1.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID)
|
groupV1.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID)
|
||||||
|
|
||||||
groupV1.Get("/detailed/events", a.authMiddleware, h.GetAllDetailedEvents)
|
|
||||||
groupV1.Get("/detailed/events/:id", a.authMiddleware, h.GetDetailedEventByID)
|
|
||||||
|
|
||||||
groupV1.Get("/stats/total/events", h.GetEventStats)
|
|
||||||
groupV1.Get("/stats/interval/events", h.GetEventStatsByInterval)
|
|
||||||
|
|
||||||
tenant.Get("/upcoming-events", h.GetTenantUpcomingEvents)
|
tenant.Get("/upcoming-events", h.GetTenantUpcomingEvents)
|
||||||
tenant.Get("/top-leagues", h.GetTopLeagues)
|
tenant.Get("/top-leagues", h.GetTopLeagues)
|
||||||
tenant.Get("/events", h.GetTenantEvents)
|
tenant.Get("/events", h.GetTenantEvents)
|
||||||
|
|
@ -395,6 +389,10 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Get("/report-files/download/:filename", h.DownloadReportFile)
|
groupV1.Get("/report-files/download/:filename", h.DownloadReportFile)
|
||||||
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
|
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
|
||||||
|
|
||||||
|
groupV1.Post("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateReportRequest)
|
||||||
|
groupV1.Get("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllReportRequests)
|
||||||
|
groupV1.Get("/reports/download/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportByID)
|
||||||
|
|
||||||
//Alea Play Virtual Game Routes
|
//Alea Play Virtual Game Routes
|
||||||
groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
||||||
groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
|
groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
|
||||||
|
|
@ -488,4 +486,7 @@ func (a *App) initAppRoutes() {
|
||||||
tenant.Delete("/settings/:key", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteCompanySetting)
|
tenant.Delete("/settings/:key", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteCompanySetting)
|
||||||
tenant.Delete("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteAllCompanySetting)
|
tenant.Delete("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteAllCompanySetting)
|
||||||
|
|
||||||
|
groupV1.Get("/stats/total/events", h.GetTotalEventStats)
|
||||||
|
groupV1.Get("/stats/interval/events", h.GetTotalEventStatsByInterval)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user