diff --git a/cmd/main.go b/cmd/main.go index bbe042e..7ffe4dc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -119,7 +119,6 @@ func main() { logger, ) - transactionSvc := transaction.NewService(store) branchSvc := branch.NewService(store) companySvc := company.NewService(store) leagueSvc := league.New(store) @@ -153,6 +152,7 @@ func main() { cfg.FIXER_API_KEY, cfg.FIXER_BASE_URL, ) + transactionSvc := transaction.NewService(store, *branchSvc) reportSvc := report.NewService( bet.BetStore(store), @@ -167,6 +167,8 @@ func main() { logger, ) + + go httpserver.SetupReportCronJobs(context.Background(), reportSvc) bankRepository := repository.NewBankRepository(store) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 981471c..a44ca00 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -172,28 +172,45 @@ CREATE TABLE IF NOT EXISTS shop_transactions ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, branch_id BIGINT NOT NULL, - company_id BIGINT, - cashier_id BIGINT, - cashier_name VARCHAR(255), - bet_id BIGINT, - number_of_outcomes BIGINT, - type BIGINT, - payment_option BIGINT, - full_name VARCHAR(255), - phone_number VARCHAR(255), + company_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + type BIGINT NOT NULL, + full_name VARCHAR(255) NOT NULL, + phone_number VARCHAR(255) NOT NULL, + payment_option BIGINT NOT NULL, bank_code VARCHAR(255), beneficiary_name VARCHAR(255), account_name VARCHAR(255), account_number VARCHAR(255), reference_number VARCHAR(255), - verified BOOLEAN NOT NULL DEFAULT false, approved_by BIGINT, - approver_name VARCHAR(255), - branch_location VARCHAR(255), - branch_name VARCHAR(255), + verified BOOLEAN DEFAULT false NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE IF NOT EXISTS shop_bets ( + id BIGSERIAL PRIMARY KEY, + shop_transaction_id BIGINT NOT NULL, + cashout_id VARCHAR(255) NOT NULL, + cashed_out_by BIGINT, + bet_id BIGINT NOT NULL, + number_of_outcomes BIGINT NOT NULL, + cashed_out BOOLEAN DEFAULT FALSE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(shop_transaction_id), + UNIQUE(bet_id), + UNIQUE(cashout_id) +); +CREATE TABLE IF NOT EXISTS shop_deposits ( + id BIGSERIAL PRIMARY KEY, + shop_transaction_id BIGINT NOT NULL, + customer_id BIGINT NOT NULL, + wallet_transfer_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(shop_transaction_id) +); CREATE TABLE IF NOT EXISTS branches ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, @@ -318,7 +335,7 @@ SELECT branches.*, wallets.is_active AS wallet_is_active FROM branches 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; CREATE TABLE IF NOT EXISTS supported_operations ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, @@ -362,6 +379,54 @@ SELECT wt.*, users.phone_number FROM wallet_transfer wt LEFT JOIN users ON users.id = wt.cashier_id; +CREATE VIEW shop_transaction_detail AS +SELECT st.*, + cr.first_name AS creator_first_name, + cr.last_name AS creator_last_name, + cr.phone_number AS creator_phone_number, + ap.first_name AS approver_first_name, + ap.last_name AS approver_last_name, + ap.phone_number AS approver_phone_number, + branches.name AS branch_name, + branches.location AS branch_location +FROM shop_transactions st + LEFT JOIN users cr ON cr.id = st.user_id + LEFT JOIN users ap ON ap.id = st.approved_by + LEFT JOIN branches ON branches.id = st.branch_id; +CREATE VIEW shop_bet_detail AS +SELECT sb.*, + st.full_name AS customer_full_name, + st.phone_number AS customer_phone_number, + st.branch_id, + st.company_id, + st.amount, + st.verified AS transaction_verified, + bets.status, + bets.total_odds, + JSON_AGG(bet_outcomes.*) AS outcomes +FROM shop_bets AS sb + JOIN shop_transactions st ON st.id = sb.shop_transaction_id + JOIN bets ON bets.id = sb.bet_id + LEFT JOIN bet_outcomes ON bet_outcomes.bet_id = sb.bet_id +GROUP BY sb.id, + st.full_name, + st.phone_number, + st.branch_id, + st.company_id, + st.amount, + st.verified, + bets.status, + bets.total_odds; +CREATE VIEW shop_deposit_detail AS +SELECT sd.*, + st.full_name, + st.phone_number, + st.branch_id, + st.company_id, + st.amount, + st.verified AS transaction_verified +FROM shop_deposits AS sd + JOIN shop_transactions st ON st.id = sd.shop_transaction_id; -- Foreign Keys ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email), @@ -384,8 +449,13 @@ ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_i ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); ALTER TABLE shop_transactions ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), - ADD CONSTRAINT fk_shop_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), - ADD CONSTRAINT fk_shop_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id); + ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users(id); +ALTER TABLE shop_bets +ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), + ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets(id); +ALTER TABLE shop_deposits +ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), + ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id); ALTER TABLE branches ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id); diff --git a/db/query/shop_transactions.sql b/db/query/shop_transactions.sql new file mode 100644 index 0000000..80d9848 --- /dev/null +++ b/db/query/shop_transactions.sql @@ -0,0 +1,169 @@ +-- name: CreateShopTransaction :one +INSERT INTO shop_transactions ( + amount, + branch_id, + company_id, + user_id, + type, + full_name, + phone_number, + payment_option, + bank_code, + beneficiary_name, + account_name, + account_number, + reference_number + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13 + ) +RETURNING *; +-- name: GetAllShopTransactions :many +SELECT * +FROM shop_transaction_detail +wHERE ( + branch_id = sqlc.narg('branch_id') + OR sqlc.narg('branch_id') IS NULL + ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + user_id = sqlc.narg('user_id') + OR sqlc.narg('user_id') IS NULL + ) + AND ( + full_name ILIKE '%' || sqlc.narg('query') || '%' + OR phone_number ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL + ); +-- name: GetShopTransactionByID :one +SELECT * +FROM shop_transaction_detail +WHERE id = $1; +-- name: GetShopTransactionByBranch :many +SELECT * +FROM shop_transaction_detail +WHERE branch_id = $1; +-- name: UpdateShopTransactionVerified :exec +UPDATE shop_transactions +SET verified = $2, + approved_by = $3, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; +-- name: CreateShopBet :one +INSERT INTO shop_bets ( + shop_transaction_id, + cashout_id, + bet_id, + number_of_outcomes + ) +VALUES ($1, $2, $3, $4) +RETURNING *; +-- name: GetAllShopBets :many +SELECT * +FROM shop_bet_detail +WHERE ( + full_name ILIKE '%' || sqlc.narg('query') || '%' + OR phone_number ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + branch_id = sqlc.narg('branch_id') + OR sqlc.narg('branch_id') IS NULL + ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL + ); +-- name: GetShopBetByID :one +SELECT * +FROM shop_bet_detail +WHERE id = $1; +-- name: GetShopBetByBetID :one +SELECT * +FROM shop_bet_detail +WHERE bet_id = $1; +-- name: GetShopBetByShopTransactionID :one +SELECT * +FROM shop_bet_detail +WHERE shop_transaction_id = $1; +-- name: UpdateShopBetCashOut :exec +UPDATE shop_bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; +-- name: UpdateShopBetCashoutID :exec +UPDATE shop_bets +SET cashout_id = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; +-- name: CreateShopDeposit :one +INSERT INTO shop_deposits ( + shop_transaction_id, + customer_id, + wallet_transfer_id + ) +VALUES ($1, $2, $3) +RETURNING *; +-- name: GetAllShopDeposit :many +SELECT * +FROM shop_deposit_detail +WHERE ( + full_name ILIKE '%' || sqlc.narg('query') || '%' + OR phone_number ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + branch_id = sqlc.narg('branch_id') + OR sqlc.narg('branch_id') IS NULL + ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL + ); +-- name: GetShopDepositByID :one +SELECT * +FROM shop_deposit_detail +WHERE id = $1; +-- name: GetShopDepositByShopTransactionID :one +SELECT * +FROM shop_deposit_detail +WHERE shop_transaction_id = $1; \ No newline at end of file diff --git a/db/query/transactions.sql b/db/query/transactions.sql deleted file mode 100644 index 820c096..0000000 --- a/db/query/transactions.sql +++ /dev/null @@ -1,85 +0,0 @@ --- name: CreateShopTransaction :one -INSERT INTO shop_transactions ( - amount, - branch_id, - cashier_id, - bet_id, - type, - payment_option, - full_name, - phone_number, - bank_code, - beneficiary_name, - account_name, - account_number, - reference_number, - number_of_outcomes, - branch_name, - branch_location, - company_id, - cashier_name - ) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6, - $7, - $8, - $9, - $10, - $11, - $12, - $13, - $14, - $15, - $16, - $17, - $18 - ) -RETURNING *; --- name: GetAllShopTransactions :many -SELECT * -FROM shop_transactions -wHERE ( - branch_id = sqlc.narg('branch_id') - OR sqlc.narg('branch_id') IS NULL - ) - AND ( - company_id = sqlc.narg('company_id') - OR sqlc.narg('company_id') IS NULL - ) - AND ( - cashier_id = sqlc.narg('cashier_id') - OR sqlc.narg('cashier_id') IS NULL - ) - AND ( - full_name ILIKE '%' || sqlc.narg('query') || '%' - OR phone_number ILIKE '%' || sqlc.narg('query') || '%' - OR sqlc.narg('query') IS NULL - ) - AND ( - created_at > sqlc.narg('created_before') - OR sqlc.narg('created_before') IS NULL - ) - AND ( - created_at < sqlc.narg('created_after') - OR sqlc.narg('created_after') IS NULL - ); --- name: GetShopTransactionByID :one -SELECT * -FROM shop_transactions -WHERE id = $1; --- name: GetShopTransactionByBranch :many -SELECT * -FROM shop_transactions -WHERE branch_id = $1; --- name: UpdateShopTransactionVerified :exec -UPDATE shop_transactions -SET verified = $2, - approved_by = $3, - approver_name = $4, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1; \ No newline at end of file diff --git a/gen/db/models.go b/gen/db/models.go index f139a7e..f5be831 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -392,31 +392,111 @@ type Setting struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } +type ShopBet struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + CashoutID string `json:"cashout_id"` + CashedOutBy pgtype.Int8 `json:"cashed_out_by"` + BetID int64 `json:"bet_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + CashedOut bool `json:"cashed_out"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type ShopBetDetail struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + CashoutID string `json:"cashout_id"` + CashedOutBy pgtype.Int8 `json:"cashed_out_by"` + BetID int64 `json:"bet_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + CashedOut bool `json:"cashed_out"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + CustomerFullName string `json:"customer_full_name"` + CustomerPhoneNumber string `json:"customer_phone_number"` + BranchID int64 `json:"branch_id"` + CompanyID int64 `json:"company_id"` + Amount int64 `json:"amount"` + TransactionVerified bool `json:"transaction_verified"` + Status int32 `json:"status"` + TotalOdds float32 `json:"total_odds"` + Outcomes []BetOutcome `json:"outcomes"` +} + +type ShopDeposit struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + CustomerID int64 `json:"customer_id"` + WalletTransferID int64 `json:"wallet_transfer_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type ShopDepositDetail struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + CustomerID int64 `json:"customer_id"` + WalletTransferID int64 `json:"wallet_transfer_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BranchID int64 `json:"branch_id"` + CompanyID int64 `json:"company_id"` + Amount int64 `json:"amount"` + TransactionVerified bool `json:"transaction_verified"` +} + type ShopTransaction struct { - ID int64 `json:"id"` - Amount int64 `json:"amount"` - BranchID int64 `json:"branch_id"` - CompanyID pgtype.Int8 `json:"company_id"` - CashierID pgtype.Int8 `json:"cashier_id"` - CashierName pgtype.Text `json:"cashier_name"` - BetID pgtype.Int8 `json:"bet_id"` - NumberOfOutcomes pgtype.Int8 `json:"number_of_outcomes"` - Type pgtype.Int8 `json:"type"` - PaymentOption pgtype.Int8 `json:"payment_option"` - FullName pgtype.Text `json:"full_name"` - PhoneNumber pgtype.Text `json:"phone_number"` - BankCode pgtype.Text `json:"bank_code"` - BeneficiaryName pgtype.Text `json:"beneficiary_name"` - AccountName pgtype.Text `json:"account_name"` - AccountNumber pgtype.Text `json:"account_number"` - ReferenceNumber pgtype.Text `json:"reference_number"` - Verified bool `json:"verified"` - ApprovedBy pgtype.Int8 `json:"approved_by"` - ApproverName pgtype.Text `json:"approver_name"` - BranchLocation pgtype.Text `json:"branch_location"` - BranchName pgtype.Text `json:"branch_name"` - CreatedAt pgtype.Timestamp `json:"created_at"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CompanyID int64 `json:"company_id"` + UserID int64 `json:"user_id"` + Type int64 `json:"type"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + PaymentOption int64 `json:"payment_option"` + BankCode pgtype.Text `json:"bank_code"` + BeneficiaryName pgtype.Text `json:"beneficiary_name"` + AccountName pgtype.Text `json:"account_name"` + AccountNumber pgtype.Text `json:"account_number"` + ReferenceNumber pgtype.Text `json:"reference_number"` + ApprovedBy pgtype.Int8 `json:"approved_by"` + Verified bool `json:"verified"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type ShopTransactionDetail struct { + ID int64 `json:"id"` + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CompanyID int64 `json:"company_id"` + UserID int64 `json:"user_id"` + Type int64 `json:"type"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + PaymentOption int64 `json:"payment_option"` + BankCode pgtype.Text `json:"bank_code"` + BeneficiaryName pgtype.Text `json:"beneficiary_name"` + AccountName pgtype.Text `json:"account_name"` + AccountNumber pgtype.Text `json:"account_number"` + ReferenceNumber pgtype.Text `json:"reference_number"` + ApprovedBy pgtype.Int8 `json:"approved_by"` + Verified bool `json:"verified"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + CreatorFirstName pgtype.Text `json:"creator_first_name"` + CreatorLastName pgtype.Text `json:"creator_last_name"` + CreatorPhoneNumber pgtype.Text `json:"creator_phone_number"` + ApproverFirstName pgtype.Text `json:"approver_first_name"` + ApproverLastName pgtype.Text `json:"approver_last_name"` + ApproverPhoneNumber pgtype.Text `json:"approver_phone_number"` + BranchName pgtype.Text `json:"branch_name"` + BranchLocation pgtype.Text `json:"branch_location"` } type SupportedOperation struct { diff --git a/gen/db/shop_transactions.sql.go b/gen/db/shop_transactions.sql.go new file mode 100644 index 0000000..84f4c87 --- /dev/null +++ b/gen/db/shop_transactions.sql.go @@ -0,0 +1,711 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: shop_transactions.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const CreateShopBet = `-- name: CreateShopBet :one +INSERT INTO shop_bets ( + shop_transaction_id, + cashout_id, + bet_id, + number_of_outcomes + ) +VALUES ($1, $2, $3, $4) +RETURNING id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at +` + +type CreateShopBetParams struct { + ShopTransactionID int64 `json:"shop_transaction_id"` + CashoutID string `json:"cashout_id"` + BetID int64 `json:"bet_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` +} + +func (q *Queries) CreateShopBet(ctx context.Context, arg CreateShopBetParams) (ShopBet, error) { + row := q.db.QueryRow(ctx, CreateShopBet, + arg.ShopTransactionID, + arg.CashoutID, + arg.BetID, + arg.NumberOfOutcomes, + ) + var i ShopBet + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CashoutID, + &i.CashedOutBy, + &i.BetID, + &i.NumberOfOutcomes, + &i.CashedOut, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateShopDeposit = `-- name: CreateShopDeposit :one +INSERT INTO shop_deposits ( + shop_transaction_id, + customer_id, + wallet_transfer_id + ) +VALUES ($1, $2, $3) +RETURNING id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at +` + +type CreateShopDepositParams struct { + ShopTransactionID int64 `json:"shop_transaction_id"` + CustomerID int64 `json:"customer_id"` + WalletTransferID int64 `json:"wallet_transfer_id"` +} + +func (q *Queries) CreateShopDeposit(ctx context.Context, arg CreateShopDepositParams) (ShopDeposit, error) { + row := q.db.QueryRow(ctx, CreateShopDeposit, arg.ShopTransactionID, arg.CustomerID, arg.WalletTransferID) + var i ShopDeposit + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CustomerID, + &i.WalletTransferID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateShopTransaction = `-- name: CreateShopTransaction :one +INSERT INTO shop_transactions ( + amount, + branch_id, + company_id, + user_id, + type, + full_name, + phone_number, + payment_option, + bank_code, + beneficiary_name, + account_name, + account_number, + reference_number + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13 + ) +RETURNING id, amount, branch_id, company_id, user_id, type, full_name, phone_number, payment_option, bank_code, beneficiary_name, account_name, account_number, reference_number, approved_by, verified, created_at, updated_at +` + +type CreateShopTransactionParams struct { + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CompanyID int64 `json:"company_id"` + UserID int64 `json:"user_id"` + Type int64 `json:"type"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + PaymentOption int64 `json:"payment_option"` + BankCode pgtype.Text `json:"bank_code"` + BeneficiaryName pgtype.Text `json:"beneficiary_name"` + AccountName pgtype.Text `json:"account_name"` + AccountNumber pgtype.Text `json:"account_number"` + ReferenceNumber pgtype.Text `json:"reference_number"` +} + +func (q *Queries) CreateShopTransaction(ctx context.Context, arg CreateShopTransactionParams) (ShopTransaction, error) { + row := q.db.QueryRow(ctx, CreateShopTransaction, + arg.Amount, + arg.BranchID, + arg.CompanyID, + arg.UserID, + arg.Type, + arg.FullName, + arg.PhoneNumber, + arg.PaymentOption, + arg.BankCode, + arg.BeneficiaryName, + arg.AccountName, + arg.AccountNumber, + arg.ReferenceNumber, + ) + var i ShopTransaction + err := row.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CompanyID, + &i.UserID, + &i.Type, + &i.FullName, + &i.PhoneNumber, + &i.PaymentOption, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.ApprovedBy, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetAllShopBets = `-- name: GetAllShopBets :many +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +FROM shop_bet_detail +WHERE ( + full_name ILIKE '%' || $1 || '%' + OR phone_number ILIKE '%' || $1 || '%' + OR $1 IS NULL + ) + AND ( + branch_id = $2 + OR $2 IS NULL + ) + AND ( + company_id = $3 + OR $3 IS NULL + ) + AND ( + created_at > $4 + OR $4 IS NULL + ) + AND ( + created_at < $5 + OR $5 IS NULL + ) +` + +type GetAllShopBetsParams struct { + Query pgtype.Text `json:"query"` + BranchID pgtype.Int8 `json:"branch_id"` + CompanyID pgtype.Int8 `json:"company_id"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` +} + +func (q *Queries) GetAllShopBets(ctx context.Context, arg GetAllShopBetsParams) ([]ShopBetDetail, error) { + rows, err := q.db.Query(ctx, GetAllShopBets, + arg.Query, + arg.BranchID, + arg.CompanyID, + arg.CreatedBefore, + arg.CreatedAfter, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ShopBetDetail + for rows.Next() { + var i ShopBetDetail + if err := rows.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CashoutID, + &i.CashedOutBy, + &i.BetID, + &i.NumberOfOutcomes, + &i.CashedOut, + &i.CreatedAt, + &i.UpdatedAt, + &i.CustomerFullName, + &i.CustomerPhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + &i.Status, + &i.TotalOdds, + &i.Outcomes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAllShopDeposit = `-- name: GetAllShopDeposit :many +SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified +FROM shop_deposit_detail +WHERE ( + full_name ILIKE '%' || $1 || '%' + OR phone_number ILIKE '%' || $1 || '%' + OR $1 IS NULL + ) + AND ( + branch_id = $2 + OR $2 IS NULL + ) + AND ( + company_id = $3 + OR $3 IS NULL + ) + AND ( + created_at > $4 + OR $4 IS NULL + ) + AND ( + created_at < $5 + OR $5 IS NULL + ) +` + +type GetAllShopDepositParams struct { + Query pgtype.Text `json:"query"` + BranchID pgtype.Int8 `json:"branch_id"` + CompanyID pgtype.Int8 `json:"company_id"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` +} + +func (q *Queries) GetAllShopDeposit(ctx context.Context, arg GetAllShopDepositParams) ([]ShopDepositDetail, error) { + rows, err := q.db.Query(ctx, GetAllShopDeposit, + arg.Query, + arg.BranchID, + arg.CompanyID, + arg.CreatedBefore, + arg.CreatedAfter, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ShopDepositDetail + for rows.Next() { + var i ShopDepositDetail + if err := rows.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CustomerID, + &i.WalletTransferID, + &i.CreatedAt, + &i.UpdatedAt, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAllShopTransactions = `-- name: GetAllShopTransactions :many +SELECT id, amount, branch_id, company_id, user_id, type, full_name, phone_number, payment_option, bank_code, beneficiary_name, account_name, account_number, reference_number, approved_by, verified, created_at, updated_at, creator_first_name, creator_last_name, creator_phone_number, approver_first_name, approver_last_name, approver_phone_number, branch_name, branch_location +FROM shop_transaction_detail +wHERE ( + branch_id = $1 + OR $1 IS NULL + ) + AND ( + company_id = $2 + OR $2 IS NULL + ) + AND ( + user_id = $3 + OR $3 IS NULL + ) + AND ( + full_name ILIKE '%' || $4 || '%' + OR phone_number ILIKE '%' || $4 || '%' + OR $4 IS NULL + ) + AND ( + created_at > $5 + OR $5 IS NULL + ) + AND ( + created_at < $6 + OR $6 IS NULL + ) +` + +type GetAllShopTransactionsParams struct { + BranchID pgtype.Int8 `json:"branch_id"` + CompanyID pgtype.Int8 `json:"company_id"` + UserID pgtype.Int8 `json:"user_id"` + Query pgtype.Text `json:"query"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` +} + +func (q *Queries) GetAllShopTransactions(ctx context.Context, arg GetAllShopTransactionsParams) ([]ShopTransactionDetail, error) { + rows, err := q.db.Query(ctx, GetAllShopTransactions, + arg.BranchID, + arg.CompanyID, + arg.UserID, + arg.Query, + arg.CreatedBefore, + arg.CreatedAfter, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ShopTransactionDetail + for rows.Next() { + var i ShopTransactionDetail + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CompanyID, + &i.UserID, + &i.Type, + &i.FullName, + &i.PhoneNumber, + &i.PaymentOption, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.ApprovedBy, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CreatorFirstName, + &i.CreatorLastName, + &i.CreatorPhoneNumber, + &i.ApproverFirstName, + &i.ApproverLastName, + &i.ApproverPhoneNumber, + &i.BranchName, + &i.BranchLocation, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetShopBetByBetID = `-- name: GetShopBetByBetID :one +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +FROM shop_bet_detail +WHERE bet_id = $1 +` + +func (q *Queries) GetShopBetByBetID(ctx context.Context, betID int64) (ShopBetDetail, error) { + row := q.db.QueryRow(ctx, GetShopBetByBetID, betID) + var i ShopBetDetail + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CashoutID, + &i.CashedOutBy, + &i.BetID, + &i.NumberOfOutcomes, + &i.CashedOut, + &i.CreatedAt, + &i.UpdatedAt, + &i.CustomerFullName, + &i.CustomerPhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + &i.Status, + &i.TotalOdds, + &i.Outcomes, + ) + return i, err +} + +const GetShopBetByID = `-- name: GetShopBetByID :one +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +FROM shop_bet_detail +WHERE id = $1 +` + +func (q *Queries) GetShopBetByID(ctx context.Context, id int64) (ShopBetDetail, error) { + row := q.db.QueryRow(ctx, GetShopBetByID, id) + var i ShopBetDetail + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CashoutID, + &i.CashedOutBy, + &i.BetID, + &i.NumberOfOutcomes, + &i.CashedOut, + &i.CreatedAt, + &i.UpdatedAt, + &i.CustomerFullName, + &i.CustomerPhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + &i.Status, + &i.TotalOdds, + &i.Outcomes, + ) + return i, err +} + +const GetShopBetByShopTransactionID = `-- name: GetShopBetByShopTransactionID :one +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +FROM shop_bet_detail +WHERE shop_transaction_id = $1 +` + +func (q *Queries) GetShopBetByShopTransactionID(ctx context.Context, shopTransactionID int64) (ShopBetDetail, error) { + row := q.db.QueryRow(ctx, GetShopBetByShopTransactionID, shopTransactionID) + var i ShopBetDetail + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CashoutID, + &i.CashedOutBy, + &i.BetID, + &i.NumberOfOutcomes, + &i.CashedOut, + &i.CreatedAt, + &i.UpdatedAt, + &i.CustomerFullName, + &i.CustomerPhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + &i.Status, + &i.TotalOdds, + &i.Outcomes, + ) + return i, err +} + +const GetShopDepositByID = `-- name: GetShopDepositByID :one +SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified +FROM shop_deposit_detail +WHERE id = $1 +` + +func (q *Queries) GetShopDepositByID(ctx context.Context, id int64) (ShopDepositDetail, error) { + row := q.db.QueryRow(ctx, GetShopDepositByID, id) + var i ShopDepositDetail + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CustomerID, + &i.WalletTransferID, + &i.CreatedAt, + &i.UpdatedAt, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + ) + return i, err +} + +const GetShopDepositByShopTransactionID = `-- name: GetShopDepositByShopTransactionID :one +SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified +FROM shop_deposit_detail +WHERE shop_transaction_id = $1 +` + +func (q *Queries) GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (ShopDepositDetail, error) { + row := q.db.QueryRow(ctx, GetShopDepositByShopTransactionID, shopTransactionID) + var i ShopDepositDetail + err := row.Scan( + &i.ID, + &i.ShopTransactionID, + &i.CustomerID, + &i.WalletTransferID, + &i.CreatedAt, + &i.UpdatedAt, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.CompanyID, + &i.Amount, + &i.TransactionVerified, + ) + return i, err +} + +const GetShopTransactionByBranch = `-- name: GetShopTransactionByBranch :many +SELECT id, amount, branch_id, company_id, user_id, type, full_name, phone_number, payment_option, bank_code, beneficiary_name, account_name, account_number, reference_number, approved_by, verified, created_at, updated_at, creator_first_name, creator_last_name, creator_phone_number, approver_first_name, approver_last_name, approver_phone_number, branch_name, branch_location +FROM shop_transaction_detail +WHERE branch_id = $1 +` + +func (q *Queries) GetShopTransactionByBranch(ctx context.Context, branchID int64) ([]ShopTransactionDetail, error) { + rows, err := q.db.Query(ctx, GetShopTransactionByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ShopTransactionDetail + for rows.Next() { + var i ShopTransactionDetail + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CompanyID, + &i.UserID, + &i.Type, + &i.FullName, + &i.PhoneNumber, + &i.PaymentOption, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.ApprovedBy, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CreatorFirstName, + &i.CreatorLastName, + &i.CreatorPhoneNumber, + &i.ApproverFirstName, + &i.ApproverLastName, + &i.ApproverPhoneNumber, + &i.BranchName, + &i.BranchLocation, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetShopTransactionByID = `-- name: GetShopTransactionByID :one +SELECT id, amount, branch_id, company_id, user_id, type, full_name, phone_number, payment_option, bank_code, beneficiary_name, account_name, account_number, reference_number, approved_by, verified, created_at, updated_at, creator_first_name, creator_last_name, creator_phone_number, approver_first_name, approver_last_name, approver_phone_number, branch_name, branch_location +FROM shop_transaction_detail +WHERE id = $1 +` + +func (q *Queries) GetShopTransactionByID(ctx context.Context, id int64) (ShopTransactionDetail, error) { + row := q.db.QueryRow(ctx, GetShopTransactionByID, id) + var i ShopTransactionDetail + err := row.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CompanyID, + &i.UserID, + &i.Type, + &i.FullName, + &i.PhoneNumber, + &i.PaymentOption, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.ApprovedBy, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CreatorFirstName, + &i.CreatorLastName, + &i.CreatorPhoneNumber, + &i.ApproverFirstName, + &i.ApproverLastName, + &i.ApproverPhoneNumber, + &i.BranchName, + &i.BranchLocation, + ) + return i, err +} + +const UpdateShopBetCashOut = `-- name: UpdateShopBetCashOut :exec +UPDATE shop_bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateShopBetCashOutParams struct { + ID int64 `json:"id"` + CashedOut bool `json:"cashed_out"` +} + +func (q *Queries) UpdateShopBetCashOut(ctx context.Context, arg UpdateShopBetCashOutParams) error { + _, err := q.db.Exec(ctx, UpdateShopBetCashOut, arg.ID, arg.CashedOut) + return err +} + +const UpdateShopBetCashoutID = `-- name: UpdateShopBetCashoutID :exec +UPDATE shop_bets +SET cashout_id = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateShopBetCashoutIDParams struct { + ID int64 `json:"id"` + CashoutID string `json:"cashout_id"` +} + +func (q *Queries) UpdateShopBetCashoutID(ctx context.Context, arg UpdateShopBetCashoutIDParams) error { + _, err := q.db.Exec(ctx, UpdateShopBetCashoutID, arg.ID, arg.CashoutID) + return err +} + +const UpdateShopTransactionVerified = `-- name: UpdateShopTransactionVerified :exec +UPDATE shop_transactions +SET verified = $2, + approved_by = $3, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateShopTransactionVerifiedParams struct { + ID int64 `json:"id"` + Verified bool `json:"verified"` + ApprovedBy pgtype.Int8 `json:"approved_by"` +} + +func (q *Queries) UpdateShopTransactionVerified(ctx context.Context, arg UpdateShopTransactionVerifiedParams) error { + _, err := q.db.Exec(ctx, UpdateShopTransactionVerified, arg.ID, arg.Verified, arg.ApprovedBy) + return err +} diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 5571fcb..2ebd43d 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -106,7 +106,6 @@ type CreateBetOutcomeReq struct { type CreateBetReq struct { Outcomes []CreateBetOutcomeReq `json:"outcomes"` Amount float32 `json:"amount" example:"100.0"` - Status OutcomeStatus `json:"status" example:"1"` FullName string `json:"full_name" example:"John"` PhoneNumber string `json:"phone_number" example:"1234567890"` BranchID *int64 `json:"branch_id,omitempty" example:"1"` diff --git a/internal/domain/shop_bet.go b/internal/domain/shop_bet.go new file mode 100644 index 0000000..30a6b4e --- /dev/null +++ b/internal/domain/shop_bet.go @@ -0,0 +1,125 @@ +package domain + +import "time" + +type ShopBet struct { + ID int64 + ShopTransactionID int64 + CashoutID string + CashedOut bool + BetID int64 + NumberOfOutcomes int64 +} + +type ShopBetFilter struct { + CompanyID ValidInt64 + BranchID ValidInt64 + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime +} + +type CreateShopBet struct { + ShopTransactionID int64 + CashoutID string + BetID int64 + NumberOfOutcomes int64 +} + +type ShopBetDetail struct { + ID int64 + ShopTransactionID int64 + TotalOdds float32 + BranchID int64 + CompanyID int64 + FullName string + PhoneNumber string + CashoutID string + CashedOut bool + BetID int64 + NumberOfOutcomes int64 + Status OutcomeStatus + Amount Currency + Outcomes []BetOutcome + TransactionVerified bool + UpdatedAt time.Time + CreatedAt time.Time +} + +type ShopBetReq struct { + Outcomes []CreateBetOutcomeReq `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + BetID int64 `json:"bet_id" example:"1"` + PaymentOption PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + BranchID *int64 `json:"branch_id,omitempty" example:"1"` +} + +type CashoutReq struct { + CashoutID string `json:"cashout_id" example:"1234"` + PaymentOption PaymentOption `json:"payment_option" example:"1"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + BranchID *int64 `json:"branch_id,omitempty" example:"1"` +} + +type ShopBetRes struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + BranchID int64 `json:"branch_id" example:"2"` + CompanyID int64 `json:"company_id" example:"2"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + CashoutID string `json:"cashout_id" example:"21234"` + CashedOut bool `json:"cashed_out" example:"false"` + BetID int64 `json:"bet_id" example:"1"` + NumberOfOutcomes int64 `json:"number_of_outcomes" example:"1"` + Status OutcomeStatus `json:"status" example:"1"` + Amount Currency `json:"amount" example:"100.0"` + Outcomes []BetOutcome `json:"outcomes"` + TransactionVerified bool `json:"transaction_verified" example:"true"` + UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:00:00Z"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` +} + +func ConvertShopBet(shopBet ShopBet) ShopBetRes { + return ShopBetRes{ + ID: shopBet.ID, + ShopTransactionID: shopBet.ShopTransactionID, + CashoutID: shopBet.CashoutID, + CashedOut: shopBet.CashedOut, + BetID: shopBet.BetID, + NumberOfOutcomes: shopBet.NumberOfOutcomes, + } +} +func ConvertShopBetDetail(shopBet ShopBetDetail) ShopBetRes { + return ShopBetRes{ + ID: shopBet.ID, + ShopTransactionID: shopBet.ShopTransactionID, + TotalOdds: shopBet.TotalOdds, + BranchID: shopBet.BranchID, + CompanyID: shopBet.CompanyID, + FullName: shopBet.FullName, + PhoneNumber: shopBet.PhoneNumber, + CashoutID: shopBet.CashoutID, + CashedOut: shopBet.CashedOut, + BetID: shopBet.BetID, + NumberOfOutcomes: shopBet.NumberOfOutcomes, + Status: shopBet.Status, + Amount: shopBet.Amount, + Outcomes: shopBet.Outcomes, + TransactionVerified: shopBet.TransactionVerified, + UpdatedAt: shopBet.UpdatedAt, + CreatedAt: shopBet.UpdatedAt, + } +} diff --git a/internal/domain/shop_deposit.go b/internal/domain/shop_deposit.go new file mode 100644 index 0000000..6703ba7 --- /dev/null +++ b/internal/domain/shop_deposit.go @@ -0,0 +1,68 @@ +package domain + +import "time" + +type ShopDeposit struct { + ID int64 + ShopTransactionID int64 + CustomerID int64 + WalletTransferID int64 +} +type CreateShopDeposit struct { + ShopTransactionID int64 + CustomerID int64 + WalletTransferID int64 +} + +type ShopDepositFilter struct { + CompanyID ValidInt64 + BranchID ValidInt64 + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime +} + +type ShopDepositDetail struct { + ID int64 + ShopTransactionID int64 + CustomerID int64 + WalletTransferID int64 + FullName string + PhoneNumber string + Amount Currency + BranchID int64 + CompanyID int64 + TransactionVerified bool + UpdatedAt time.Time + CreatedAt time.Time +} + +type ShopDepositReq struct { + CustomerID int64 `json:"customer_id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + PaymentOption PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + BranchID *int64 `json:"branch_id,omitempty" example:"1"` +} + +type ShopDepositRes struct { + ID int64 `json:"id"` + ShopTransactionID int64 `json:"shop_transaction_id"` + CustomerID int64 `json:"customer_id"` + WalletTransferID int64 `json:"wallet_transfer_id"` +} + +func ConvertShopDeposit(shopDeposit ShopDeposit) ShopDepositRes { + return ShopDepositRes{ + ID: shopDeposit.ID, + ShopTransactionID: shopDeposit.ShopTransactionID, + CustomerID: shopDeposit.CustomerID, + WalletTransferID: shopDeposit.WalletTransferID, + } +} diff --git a/internal/domain/shop_transaction.go b/internal/domain/shop_transaction.go new file mode 100644 index 0000000..ea4fde5 --- /dev/null +++ b/internal/domain/shop_transaction.go @@ -0,0 +1,189 @@ +package domain + +import "time" + +type ShopTransactionType int + +const ( + TRANSACTION_CASHOUT ShopTransactionType = iota + TRANSACTION_DEPOSIT + TRANSACTION_BET +) + +type PaymentOption int64 + +const ( + CASH_TRANSACTION PaymentOption = iota + TELEBIRR_TRANSACTION + ARIFPAY_TRANSACTION + BANK +) + +// ShopTransaction only represents branch transactions +// This is only used for statistic data +type ShopTransaction struct { + ID int64 + Amount Currency + BranchID int64 + CompanyID int64 + UserID int64 + Type ShopTransactionType + PaymentOption PaymentOption + FullName string + PhoneNumber string + // Payment Details for bank + BankCode ValidString + BeneficiaryName ValidString + AccountName ValidString + AccountNumber ValidString + ReferenceNumber ValidString + Verified bool + ApprovedBy ValidInt64 + UpdatedAt time.Time + CreatedAt time.Time +} + +type ShopTransactionRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + BranchID int64 `json:"branch_id" example:"1"` + BranchName string `json:"branch_name" example:"Branch Name"` + BranchLocation string `json:"branch_location" example:"Branch Location"` + CompanyID int64 `json:"company_id" example:"1"` + UserID int64 `json:"user_id" example:"1"` + CreatorFirstName string `json:"creator_first_name" example:"John"` + CreatorLastName string `json:"creator_last_name" example:"Smith"` + CreatorPhoneNumber string `json:"creator_phone_number" example:"0911111111"` + CashierName string `json:"cashier_name" example:"John Smith"` + Type int64 `json:"type" example:"1"` + PaymentOption PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + Verified bool `json:"verified" example:"true"` + ApprovedBy int64 `json:"approved_by" example:"1"` + ApproverFirstName string `json:"approver_first_name" example:"John"` + ApproverLastName string `json:"approver_last_name" example:"Smith"` + ApproverPhoneNumber string `json:"approver_phone_number" example:"0911111111"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +type ShopTransactionDetail struct { + ID int64 + Amount Currency + BranchID int64 + BranchName string + BranchLocation string + CompanyID int64 + UserID int64 + CreatorFirstName string + CreatorLastName string + CreatorPhoneNumber string + Type ShopTransactionType + PaymentOption PaymentOption + FullName string + PhoneNumber string + // Payment Details for bank + BankCode ValidString + BeneficiaryName ValidString + AccountName ValidString + AccountNumber ValidString + ReferenceNumber ValidString + Verified bool + ApprovedBy ValidInt64 + ApproverFirstName ValidString + ApproverLastName ValidString + ApproverPhoneNumber ValidString + UpdatedAt time.Time + CreatedAt time.Time +} + +type ShopTransactionFilter struct { + CompanyID ValidInt64 + BranchID ValidInt64 + UserID ValidInt64 + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime +} +type CreateShopTransaction struct { + Amount Currency + BranchID int64 + CompanyID int64 + UserID int64 + Type ShopTransactionType + PaymentOption PaymentOption + FullName string + PhoneNumber string + // Payment Details for bank + BankCode ValidString + BeneficiaryName ValidString + AccountName ValidString + AccountNumber ValidString + ReferenceNumber ValidString + Verified bool + ApprovedBy ValidInt64 +} + +func ConvertShopTransaction(transaction ShopTransaction) ShopTransactionRes { + newTransaction := ShopTransactionRes{ + ID: transaction.ID, + Amount: transaction.Amount.Float32(), + BranchID: transaction.BranchID, + CompanyID: transaction.CompanyID, + UserID: transaction.UserID, + Type: int64(transaction.Type), + PaymentOption: transaction.PaymentOption, + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: transaction.BankCode.Value, + BeneficiaryName: transaction.BeneficiaryName.Value, + AccountName: transaction.AccountName.Value, + AccountNumber: transaction.AccountNumber.Value, + ReferenceNumber: transaction.ReferenceNumber.Value, + Verified: transaction.Verified, + ApprovedBy: transaction.ApprovedBy.Value, + CreatedAt: transaction.CreatedAt, + UpdatedAt: transaction.UpdatedAt, + } + + return newTransaction +} + +func ConvertShopTransactionDetail(transaction ShopTransactionDetail) ShopTransactionRes { + newTransaction := ShopTransactionRes{ + ID: transaction.ID, + Amount: transaction.Amount.Float32(), + BranchID: transaction.BranchID, + BranchName: transaction.BranchName, + BranchLocation: transaction.BranchLocation, + CompanyID: transaction.CompanyID, + UserID: transaction.UserID, + CreatorFirstName: transaction.CreatorFirstName, + CreatorLastName: transaction.CreatorLastName, + CreatorPhoneNumber: transaction.CreatorPhoneNumber, + Type: int64(transaction.Type), + PaymentOption: transaction.PaymentOption, + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: transaction.BankCode.Value, + BeneficiaryName: transaction.BeneficiaryName.Value, + AccountName: transaction.AccountName.Value, + AccountNumber: transaction.AccountNumber.Value, + ReferenceNumber: transaction.ReferenceNumber.Value, + Verified: transaction.Verified, + ApprovedBy: transaction.ApprovedBy.Value, + ApproverFirstName: transaction.ApproverFirstName.Value, + ApproverLastName: transaction.ApproverLastName.Value, + ApproverPhoneNumber: transaction.ApproverPhoneNumber.Value, + CreatedAt: transaction.CreatedAt, + UpdatedAt: transaction.UpdatedAt, + } + + return newTransaction +} diff --git a/internal/domain/transaction.go b/internal/domain/transaction.go deleted file mode 100644 index 9e632d6..0000000 --- a/internal/domain/transaction.go +++ /dev/null @@ -1,78 +0,0 @@ -package domain - -import "time" - -type ShopTransactionType int - -const ( - TRANSACTION_CASHOUT ShopTransactionType = iota - TRANSACTION_DEPOSIT -) - -type PaymentOption int64 - -const ( - CASH_TRANSACTION PaymentOption = iota - TELEBIRR_TRANSACTION - ARIFPAY_TRANSACTION - BANK -) - -// ShopTransaction only represents branch transactions -// This is only used for statistic data -type ShopTransaction struct { - ID int64 - Amount Currency - BranchID int64 - BranchName string - BranchLocation string - CompanyID int64 - CashierID int64 - CashierName string - BetID int64 - NumberOfOutcomes int64 - Type ShopTransactionType - PaymentOption PaymentOption - FullName string - PhoneNumber string - // Payment Details for bank - BankCode string - BeneficiaryName string - AccountName string - AccountNumber string - ReferenceNumber string - Verified bool - ApprovedBy ValidInt64 - ApproverName ValidString - UpdatedAt time.Time - CreatedAt time.Time -} - -type ShopTransactionFilter struct { - CompanyID ValidInt64 - BranchID ValidInt64 - CashierID ValidInt64 - Query ValidString - CreatedBefore ValidTime - CreatedAfter ValidTime -} -type CreateShopTransaction struct { - Amount Currency - BranchID int64 - CashierID int64 - BetID int64 - NumberOfOutcomes int64 - Type ShopTransactionType - PaymentOption PaymentOption - FullName string - PhoneNumber string - BankCode string - BeneficiaryName string - AccountName string - AccountNumber string - ReferenceNumber string - BranchName string - BranchLocation string - CompanyID int64 - CashierName string -} diff --git a/internal/repository/shop_bet.go b/internal/repository/shop_bet.go new file mode 100644 index 0000000..e436a7e --- /dev/null +++ b/internal/repository/shop_bet.go @@ -0,0 +1,155 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" + "go.uber.org/zap" +) + +func convertDBShopBet(bet dbgen.ShopBet) domain.ShopBet { + return domain.ShopBet{ + ID: bet.ID, + ShopTransactionID: bet.ShopTransactionID, + CashoutID: bet.CashoutID, + CashedOut: bet.CashedOut, + BetID: bet.BetID, + NumberOfOutcomes: bet.NumberOfOutcomes, + } +} + +func convertDBShopBetDetail(bet dbgen.ShopBetDetail) domain.ShopBetDetail { + var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes)) + + for _, outcome := range bet.Outcomes { + outcomes = append(outcomes, convertDBBetOutcomes(outcome)) + } + return domain.ShopBetDetail{ + ID: bet.ID, + ShopTransactionID: bet.ShopTransactionID, + TotalOdds: bet.TotalOdds, + BranchID: bet.BranchID, + CompanyID: bet.CompanyID, + FullName: bet.CustomerFullName, + PhoneNumber: bet.CustomerPhoneNumber, + CashoutID: bet.CashoutID, + CashedOut: bet.CashedOut, + BetID: bet.BetID, + NumberOfOutcomes: bet.NumberOfOutcomes, + Status: domain.OutcomeStatus(bet.Status), + Amount: domain.Currency(bet.Amount), + Outcomes: outcomes, + TransactionVerified: bet.TransactionVerified, + UpdatedAt: bet.UpdatedAt.Time, + CreatedAt: bet.CreatedAt.Time, + } +} + +func convertCreateShopBet(bet domain.CreateShopBet) dbgen.CreateShopBetParams { + return dbgen.CreateShopBetParams{ + ShopTransactionID: bet.ShopTransactionID, + CashoutID: bet.CashoutID, + BetID: bet.BetID, + NumberOfOutcomes: bet.NumberOfOutcomes, + } +} + +func (s *Store) CreateShopBet(ctx context.Context, bet domain.CreateShopBet) (domain.ShopBet, error) { + newShopBet, err := s.queries.CreateShopBet(ctx, convertCreateShopBet(bet)) + + if err != nil { + return domain.ShopBet{}, err + } + + return convertDBShopBet(newShopBet), err +} + +func (s *Store) GetAllShopBet(ctx context.Context, filter domain.ShopBetFilter) ([]domain.ShopBetDetail, error) { + bets, err := s.queries.GetAllShopBets(ctx, dbgen.GetAllShopBetsParams{ + BranchID: pgtype.Int8{ + Int64: filter.BranchID.Value, + Valid: filter.BranchID.Valid, + }, + CompanyID: pgtype.Int8{ + Int64: filter.CompanyID.Value, + Valid: filter.CompanyID.Valid, + }, + Query: pgtype.Text{ + String: filter.Query.Value, + Valid: filter.Query.Valid, + }, + CreatedBefore: pgtype.Timestamp{ + Time: filter.CreatedBefore.Value, + Valid: filter.CreatedBefore.Valid, + }, + CreatedAfter: pgtype.Timestamp{ + Time: filter.CreatedAfter.Value, + Valid: filter.CreatedAfter.Valid, + }, + }) + if err != nil { + return nil, err + } + + var result []domain.ShopBetDetail = make([]domain.ShopBetDetail, 0, len(bets)) + for _, bet := range bets { + result = append(result, convertDBShopBetDetail(bet)) + } + + return result, nil +} + +func (s *Store) GetShopBetByID(ctx context.Context, id int64) (domain.ShopBetDetail, error) { + bet, err := s.queries.GetShopBetByID(ctx, id) + if err != nil { + return domain.ShopBetDetail{}, err + } + return convertDBShopBetDetail(bet), nil +} + +func (s *Store) GetShopBetByBetID(ctx context.Context, betID int64) (domain.ShopBetDetail, error) { + bet, err := s.queries.GetShopBetByBetID(ctx, betID) + if err != nil { + return domain.ShopBetDetail{}, err + } + return convertDBShopBetDetail(bet), nil +} +func (s *Store) GetShopBetByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopBetDetail, error) { + bet, err := s.queries.GetShopBetByShopTransactionID(ctx, shopTransactionID) + if err != nil { + return domain.ShopBetDetail{}, err + } + return convertDBShopBetDetail(bet), nil +} + +func (s *Store) UpdateShopBetCashOut(ctx context.Context, id int64, cashedOut bool) error { + err := s.queries.UpdateShopBetCashOut(ctx, dbgen.UpdateShopBetCashOutParams{ + ID: id, + CashedOut: cashedOut, + }) + if err != nil { + domain.MongoDBLogger.Error("failed to update cashout", + zap.Int64("id", id), + zap.Bool("cashed_out", cashedOut), + zap.Error(err), + ) + } + return err +} + +func (s *Store) UpdateShopBetCashoutID(ctx context.Context, id int64, cashoutID string) error { + err := s.queries.UpdateShopBetCashoutID(ctx, dbgen.UpdateShopBetCashoutIDParams{ + ID: id, + CashoutID: cashoutID, + }) + if err != nil { + domain.MongoDBLogger.Error("failed to update cashout_id", + zap.Int64("id", id), + zap.String("cashout_id", cashoutID), + zap.Error(err), + ) + } + return err +} diff --git a/internal/repository/shop_deposit.go b/internal/repository/shop_deposit.go new file mode 100644 index 0000000..abb2079 --- /dev/null +++ b/internal/repository/shop_deposit.go @@ -0,0 +1,104 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) + +func convertShopDeposit(deposit dbgen.ShopDeposit) domain.ShopDeposit { + return domain.ShopDeposit{ + ID: deposit.ID, + ShopTransactionID: deposit.ShopTransactionID, + CustomerID: deposit.CustomerID, + WalletTransferID: deposit.WalletTransferID, + } +} + +func convertShopDepositDetail(deposit dbgen.ShopDepositDetail) domain.ShopDepositDetail { + return domain.ShopDepositDetail{ + ID: deposit.ID, + ShopTransactionID: deposit.ShopTransactionID, + CustomerID: deposit.CustomerID, + WalletTransferID: deposit.WalletTransferID, + FullName: deposit.FullName, + PhoneNumber: deposit.PhoneNumber, + Amount: domain.Currency(deposit.Amount), + BranchID: deposit.BranchID, + CompanyID: deposit.CompanyID, + TransactionVerified: deposit.TransactionVerified, + UpdatedAt: deposit.UpdatedAt.Time, + CreatedAt: deposit.CreatedAt.Time, + } +} +func convertCreateShopDeposit(deposit domain.CreateShopDeposit) dbgen.CreateShopDepositParams { + return dbgen.CreateShopDepositParams{ + ShopTransactionID: deposit.ShopTransactionID, + CustomerID: deposit.CustomerID, + WalletTransferID: deposit.WalletTransferID, + } +} + +func (s *Store) CreateShopDeposit(ctx context.Context, deposit domain.CreateShopDeposit) (domain.ShopDeposit, error) { + newShopDeposit, err := s.queries.CreateShopDeposit(ctx, convertCreateShopDeposit(deposit)) + + if err != nil { + return domain.ShopDeposit{}, err + } + return convertShopDeposit(newShopDeposit), nil +} + +func (s *Store) GetAllShopDeposit(ctx context.Context, filter domain.ShopDepositFilter) ([]domain.ShopDepositDetail, error) { + deposits, err := s.queries.GetAllShopDeposit(ctx, dbgen.GetAllShopDepositParams{ + BranchID: pgtype.Int8{ + Int64: filter.BranchID.Value, + Valid: filter.BranchID.Valid, + }, + CompanyID: pgtype.Int8{ + Int64: filter.CompanyID.Value, + Valid: filter.CompanyID.Valid, + }, + Query: pgtype.Text{ + String: filter.Query.Value, + Valid: filter.Query.Valid, + }, + CreatedBefore: pgtype.Timestamp{ + Time: filter.CreatedBefore.Value, + Valid: filter.CreatedBefore.Valid, + }, + CreatedAfter: pgtype.Timestamp{ + Time: filter.CreatedAfter.Value, + Valid: filter.CreatedAfter.Valid, + }, + }) + if err != nil { + return nil, err + } + + var result []domain.ShopDepositDetail = make([]domain.ShopDepositDetail, 0, len(deposits)) + + for _, deposit := range deposits { + result = append(result, convertShopDepositDetail(deposit)) + } + return result, nil +} + +func (s *Store) GetShopDepositByID(ctx context.Context, id int64) (domain.ShopDepositDetail, error) { + deposit, err := s.queries.GetShopDepositByID(ctx, id) + if err != nil { + return domain.ShopDepositDetail{}, err + } + + return convertShopDepositDetail(deposit), err +} + +func (s *Store) GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopDepositDetail, error) { + deposit, err := s.queries.GetShopDepositByShopTransactionID(ctx, shopTransactionID) + if err != nil { + return domain.ShopDepositDetail{}, err + } + + return convertShopDepositDetail(deposit), err +} diff --git a/internal/repository/transaction.go b/internal/repository/shop_transaction.go similarity index 53% rename from internal/repository/transaction.go rename to internal/repository/shop_transaction.go index 9a1d2de..038ca16 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/shop_transaction.go @@ -11,81 +11,140 @@ import ( func convertDBShopTransaction(transaction dbgen.ShopTransaction) domain.ShopTransaction { return domain.ShopTransaction{ - ID: transaction.ID, - Amount: domain.Currency(transaction.Amount), - BranchID: transaction.BranchID, - CashierID: transaction.CashierID.Int64, - BetID: transaction.BetID.Int64, - NumberOfOutcomes: transaction.NumberOfOutcomes.Int64, - Type: domain.ShopTransactionType(transaction.Type.Int64), - PaymentOption: domain.PaymentOption(transaction.PaymentOption.Int64), - FullName: transaction.FullName.String, - PhoneNumber: transaction.PhoneNumber.String, - BankCode: transaction.BankCode.String, - BeneficiaryName: transaction.BeneficiaryName.String, - AccountName: transaction.AccountName.String, - AccountNumber: transaction.AccountNumber.String, - ReferenceNumber: transaction.ReferenceNumber.String, + ID: transaction.ID, + Amount: domain.Currency(transaction.Amount), + BranchID: transaction.BranchID, + UserID: transaction.UserID, + Type: domain.ShopTransactionType(transaction.Type), + PaymentOption: domain.PaymentOption(transaction.PaymentOption), + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: domain.ValidString{ + Value: transaction.BankCode.String, + Valid: transaction.BankCode.Valid, + }, + BeneficiaryName: domain.ValidString{ + Value: transaction.BeneficiaryName.String, + Valid: transaction.BeneficiaryName.Valid, + }, + AccountName: domain.ValidString{ + Value: transaction.AccountName.String, + Valid: transaction.AccountName.Valid, + }, + AccountNumber: domain.ValidString{ + Value: transaction.AccountName.String, + Valid: transaction.AccountNumber.Valid, + }, + ReferenceNumber: domain.ValidString{ + Value: transaction.ReferenceNumber.String, + Valid: transaction.ReferenceNumber.Valid, + }, ApprovedBy: domain.ValidInt64{ Value: transaction.ApprovedBy.Int64, Valid: transaction.ApprovedBy.Valid, }, - CreatedAt: transaction.CreatedAt.Time, - UpdatedAt: transaction.UpdatedAt.Time, - Verified: transaction.Verified, - BranchName: transaction.BranchName.String, - BranchLocation: transaction.BranchLocation.String, - CashierName: transaction.CashierName.String, - CompanyID: transaction.CompanyID.Int64, - ApproverName: domain.ValidString{ - Value: transaction.ApproverName.String, - Valid: transaction.ApprovedBy.Valid, - }, + CreatedAt: transaction.CreatedAt.Time, + UpdatedAt: transaction.UpdatedAt.Time, + Verified: transaction.Verified, + CompanyID: transaction.CompanyID, } } -func convertCreateTransaction(transaction domain.CreateShopTransaction) dbgen.CreateShopTransactionParams { +func convertDBShopTransactionDetail(transaction dbgen.ShopTransactionDetail) domain.ShopTransactionDetail { + return domain.ShopTransactionDetail{ + ID: transaction.ID, + Amount: domain.Currency(transaction.Amount), + BranchID: transaction.BranchID, + UserID: transaction.UserID, + CreatorFirstName: transaction.CreatorFirstName.String, + CreatorLastName: transaction.CreatorLastName.String, + CreatorPhoneNumber: transaction.CreatorPhoneNumber.String, + Type: domain.ShopTransactionType(transaction.Type), + PaymentOption: domain.PaymentOption(transaction.PaymentOption), + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: domain.ValidString{ + Value: transaction.BankCode.String, + Valid: transaction.BankCode.Valid, + }, + BeneficiaryName: domain.ValidString{ + Value: transaction.BeneficiaryName.String, + Valid: transaction.BeneficiaryName.Valid, + }, + AccountName: domain.ValidString{ + Value: transaction.AccountName.String, + Valid: transaction.AccountName.Valid, + }, + AccountNumber: domain.ValidString{ + Value: transaction.AccountName.String, + Valid: transaction.AccountNumber.Valid, + }, + ReferenceNumber: domain.ValidString{ + Value: transaction.ReferenceNumber.String, + Valid: transaction.ReferenceNumber.Valid, + }, + ApprovedBy: domain.ValidInt64{ + Value: transaction.ApprovedBy.Int64, + Valid: transaction.ApprovedBy.Valid, + }, + ApproverFirstName: domain.ValidString{ + Value: transaction.ApproverFirstName.String, + Valid: transaction.ApproverFirstName.Valid, + }, + ApproverLastName: domain.ValidString{ + Value: transaction.ApproverLastName.String, + Valid: transaction.ApproverLastName.Valid, + }, + ApproverPhoneNumber: domain.ValidString{ + Value: transaction.ApproverPhoneNumber.String, + Valid: transaction.ApproverPhoneNumber.Valid, + }, + CreatedAt: transaction.CreatedAt.Time, + UpdatedAt: transaction.UpdatedAt.Time, + Verified: transaction.Verified, + CompanyID: transaction.CompanyID, + BranchName: transaction.BranchName.String, + BranchLocation: transaction.BranchLocation.String, + } +} + +func convertCreateShopTransaction(transaction domain.CreateShopTransaction) dbgen.CreateShopTransactionParams { return dbgen.CreateShopTransactionParams{ - Amount: int64(transaction.Amount), - BranchID: transaction.BranchID, - CashierID: pgtype.Int8{Int64: transaction.CashierID, Valid: true}, - BetID: pgtype.Int8{Int64: transaction.BetID, Valid: true}, - Type: pgtype.Int8{Int64: int64(transaction.Type), Valid: true}, - PaymentOption: pgtype.Int8{Int64: int64(transaction.PaymentOption), Valid: true}, - FullName: pgtype.Text{String: transaction.FullName, Valid: transaction.FullName != ""}, - PhoneNumber: pgtype.Text{String: transaction.PhoneNumber, Valid: transaction.PhoneNumber != ""}, - BankCode: pgtype.Text{String: transaction.BankCode, Valid: transaction.BankCode != ""}, - BeneficiaryName: pgtype.Text{String: transaction.BeneficiaryName, Valid: transaction.BeneficiaryName != ""}, - AccountName: pgtype.Text{String: transaction.AccountName, Valid: transaction.AccountName != ""}, - AccountNumber: pgtype.Text{String: transaction.AccountNumber, Valid: transaction.AccountNumber != ""}, - ReferenceNumber: pgtype.Text{String: transaction.ReferenceNumber, Valid: transaction.ReferenceNumber != ""}, - NumberOfOutcomes: pgtype.Int8{Int64: transaction.NumberOfOutcomes, Valid: true}, - BranchName: pgtype.Text{String: transaction.BranchName, Valid: transaction.BranchName != ""}, - BranchLocation: pgtype.Text{String: transaction.BranchLocation, Valid: transaction.BranchLocation != ""}, - CashierName: pgtype.Text{String: transaction.CashierName, Valid: transaction.CashierName != ""}, - CompanyID: pgtype.Int8{Int64: transaction.CompanyID, Valid: true}, + Amount: int64(transaction.Amount), + BranchID: transaction.BranchID, + UserID: transaction.UserID, + Type: int64(transaction.Type), + PaymentOption: int64(transaction.PaymentOption), + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + CompanyID: transaction.CompanyID, + BankCode: pgtype.Text{String: transaction.BankCode.Value, Valid: transaction.BankCode.Valid}, + BeneficiaryName: pgtype.Text{String: transaction.BeneficiaryName.Value, Valid: transaction.BeneficiaryName.Valid}, + AccountName: pgtype.Text{String: transaction.AccountName.Value, Valid: transaction.AccountName.Valid}, + AccountNumber: pgtype.Text{String: transaction.AccountNumber.Value, Valid: transaction.AccountNumber.Valid}, + ReferenceNumber: pgtype.Text{String: transaction.ReferenceNumber.Value, Valid: transaction.ReferenceNumber.Valid}, } } func (s *Store) CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) { - newTransaction, err := s.queries.CreateShopTransaction(ctx, convertCreateTransaction(transaction)) + newTransaction, err := s.queries.CreateShopTransaction(ctx, convertCreateShopTransaction(transaction)) if err != nil { return domain.ShopTransaction{}, err } return convertDBShopTransaction(newTransaction), err } -func (s *Store) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) { +func (s *Store) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransactionDetail, error) { transaction, err := s.queries.GetShopTransactionByID(ctx, id) if err != nil { - return domain.ShopTransaction{}, err + return domain.ShopTransactionDetail{}, err } - return convertDBShopTransaction(transaction), nil + return convertDBShopTransactionDetail(transaction), nil } -func (s *Store) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) { +func (s *Store) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransactionDetail, error) { transaction, err := s.queries.GetAllShopTransactions(ctx, dbgen.GetAllShopTransactionsParams{ BranchID: pgtype.Int8{ Int64: filter.BranchID.Value, @@ -95,9 +154,9 @@ func (s *Store) GetAllShopTransactions(ctx context.Context, filter domain.ShopTr Int64: filter.CompanyID.Value, Valid: filter.CompanyID.Valid, }, - CashierID: pgtype.Int8{ - Int64: filter.CashierID.Value, - Valid: filter.CashierID.Valid, + UserID: pgtype.Int8{ + Int64: filter.UserID.Value, + Valid: filter.UserID.Valid, }, Query: pgtype.Text{ String: filter.Query.Value, @@ -117,22 +176,22 @@ func (s *Store) GetAllShopTransactions(ctx context.Context, filter domain.ShopTr return nil, err } - var result []domain.ShopTransaction = make([]domain.ShopTransaction, 0, len(transaction)) - for _, ticket := range transaction { - result = append(result, convertDBShopTransaction(ticket)) + var result []domain.ShopTransactionDetail = make([]domain.ShopTransactionDetail, 0, len(transaction)) + for _, tAction := range transaction { + result = append(result, convertDBShopTransactionDetail(tAction)) } return result, nil } -func (s *Store) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) { +func (s *Store) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransactionDetail, error) { transaction, err := s.queries.GetShopTransactionByBranch(ctx, id) if err != nil { return nil, err } - var result []domain.ShopTransaction = make([]domain.ShopTransaction, 0, len(transaction)) + var result []domain.ShopTransactionDetail = make([]domain.ShopTransactionDetail, 0, len(transaction)) for _, ticket := range transaction { - result = append(result, convertDBShopTransaction(ticket)) + result = append(result, convertDBShopTransactionDetail(ticket)) } return result, nil } @@ -145,14 +204,11 @@ func (s *Store) UpdateShopTransactionVerified(ctx context.Context, id int64, ver Valid: true, }, Verified: verified, - ApproverName: pgtype.Text{ - String: approverName, - Valid: true, - }, }) return err } + // GetTransactionTotals returns total deposits and withdrawals func (s *Store) GetTransactionTotals(ctx context.Context, filter domain.ReportFilter) (deposits, withdrawals domain.Currency, err error) { query := `SELECT @@ -172,7 +228,7 @@ func (s *Store) GetTransactionTotals(ctx context.Context, filter domain.ReportFi args = append(args, filter.BranchID.Value) argPos++ } else if filter.UserID.Valid { - query += fmt.Sprintf(" WHERE cashier_id = $%d", argPos) + query += fmt.Sprintf(" WHERE user_id = $%d", argPos) args = append(args, filter.UserID.Value) argPos++ } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 06554d5..9757598 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -252,7 +252,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID newBet := domain.CreateBet{ Amount: domain.ToCurrency(req.Amount), TotalOdds: totalOdds, - Status: req.Status, + Status: domain.OUTCOME_STATUS_PENDING, FullName: req.FullName, PhoneNumber: req.PhoneNumber, CashoutID: cashoutID, diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go index 9d5fe09..4b41278 100644 --- a/internal/services/transaction/port.go +++ b/internal/services/transaction/port.go @@ -8,11 +8,23 @@ import ( type TransactionStore interface { CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) - GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) - GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) - GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) + GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransactionDetail, error) + GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransactionDetail, error) + GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransactionDetail, error) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error - GetTransactionTotals(ctx context.Context, filter domain.ReportFilter) (deposits, withdrawals domain.Currency, err error) GetBranchTransactionTotals(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.BranchTransactions, error) + + CreateShopBet(ctx context.Context, bet domain.CreateShopBet) (domain.ShopBet, error) + GetAllShopBet(ctx context.Context, filter domain.ShopBetFilter) ([]domain.ShopBetDetail, error) + GetShopBetByID(ctx context.Context, id int64) (domain.ShopBetDetail, error) + GetShopBetByBetID(ctx context.Context, betID int64) (domain.ShopBetDetail, error) + GetShopBetByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopBetDetail, error) + UpdateShopBetCashOut(ctx context.Context, id int64, cashedOut bool) error + UpdateShopBetCashoutID(ctx context.Context, id int64, cashoutID string) error + + CreateShopDeposit(ctx context.Context, deposit domain.CreateShopDeposit) (domain.ShopDeposit, error) + GetAllShopDeposit(ctx context.Context, filter domain.ShopDepositFilter) ([]domain.ShopDepositDetail, error) + GetShopDepositByID(ctx context.Context, id int64) (domain.ShopDepositDetail, error) + GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopDepositDetail, error) } diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 166aaab..756fbd6 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -2,32 +2,91 @@ package transaction import ( "context" + "errors" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" +) + +var ( + ErrBranchRequiredForRole = errors.New("branch_id is required to be passed for this role") + ErrInvalidBranchID = errors.New("invalid branch id") + ErrUnauthorizedCompanyID = errors.New("unauthorized company id") + ErrUnauthorizedBranchManager = errors.New("unauthorized branch manager") + ErrCustomerRoleNotAuthorized = errors.New("customer role not authorized") ) type Service struct { transactionStore TransactionStore + branchSvc branch.Service + betSvc bet.Service + walletSvc wallet.Service } -func NewService(transactionStore TransactionStore) *Service { +func NewService(transactionStore TransactionStore, branchSvc branch.Service, betSvc bet.Service, walletSvc wallet.Service) *Service { return &Service{ transactionStore: transactionStore, + branchSvc: branchSvc, + betSvc: betSvc, + walletSvc: walletSvc, } } func (s *Service) CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) { return s.transactionStore.CreateShopTransaction(ctx, transaction) } -func (s *Service) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) { +func (s *Service) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransactionDetail, error) { return s.transactionStore.GetShopTransactionByID(ctx, id) } -func (s *Service) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) { +func (s *Service) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransactionDetail, error) { return s.transactionStore.GetAllShopTransactions(ctx, filter) } -func (s *Service) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) { +func (s *Service) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransactionDetail, error) { return s.transactionStore.GetShopTransactionByBranch(ctx, id) } func (s *Service) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error { return s.transactionStore.UpdateShopTransactionVerified(ctx, id, verified, approvedBy, approverName) } + +func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role domain.Role, userID int64, userCompanyID domain.ValidInt64) (*int64, *int64, error) { + // var branchID int64 + // var companyID int64 + + if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { + if branchID == nil { + // h.logger.Error("CashoutReq Branch ID is required for this user role") + return nil, nil, ErrBranchRequiredForRole + } + branch, err := s.branchSvc.GetBranchByID(ctx, *branchID) + if err != nil { + // h.logger.Error("CashoutReq no branches") + return nil, nil, ErrInvalidBetID + } + + // Check if the user has access to the company + if role != domain.RoleSuperAdmin { + if !userCompanyID.Valid || userCompanyID.Value != branch.CompanyID { + return nil, nil, ErrUnauthorizedCompanyID + } + } + + if role == domain.RoleBranchManager { + if branch.BranchManagerID != userID { + return nil, nil, ErrUnauthorizedBranchManager + } + } + + return &branch.ID, &branch.CompanyID, nil + } else if role == domain.RoleCashier { + branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) + if err != nil { + // h.logger.Error("CashoutReq failed, branch id invalid") + return nil, nil, ErrInvalidBranchID + } + return &branch.ID, &branch.CompanyID, nil + } else { + return nil, nil, ErrCustomerRoleNotAuthorized + } +} diff --git a/internal/services/transaction/shop_bet.go b/internal/services/transaction/shop_bet.go new file mode 100644 index 0000000..fb7ef7e --- /dev/null +++ b/internal/services/transaction/shop_bet.go @@ -0,0 +1,207 @@ +package transaction + +import ( + "context" + "crypto/rand" + "errors" + "math/big" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +var ( + ErrInvalidBetID = errors.New("invalid bet id") + ErrUserHasNotWonBet = errors.New("user has not won bet") + ErrUserHasAlreadyCashoutOut = errors.New("user has already cashout") + ErrTransactionNotVerified = errors.New("transaction hasn't been verified") +) + +func (s *Service) GenerateCashoutID() (string, error) { + const chars = "abcdefghijklmnopqrstuvwxyz0123456789" + const length int = 13 + charLen := big.NewInt(int64(len(chars))) + result := make([]byte, length) + + for i := 0; i < length; i++ { + index, err := rand.Int(rand.Reader, charLen) + if err != nil { + // s.mongoLogger.Error("failed to generate random index for cashout ID", + // zap.Int("position", i), + // zap.Error(err), + // ) + return "", err + } + result[i] = chars[index.Int64()] + } + + return string(result), nil +} + +func (s *Service) CreateShopBet(ctx context.Context, userID int64, role domain.Role, userCompanyID domain.ValidInt64, req domain.ShopBetReq) (domain.ShopBet, error) { + + branchID, companyID, err := s.GetBranchByRole(ctx, req.BranchID, role, userID, userCompanyID) + + if err != nil { + return domain.ShopBet{}, err + } + + cashoutID, err := s.GenerateCashoutID() + + if err != nil { + return domain.ShopBet{}, err + } + + newBet, err := s.betSvc.PlaceBet(ctx, domain.CreateBetReq{ + Outcomes: req.Outcomes, + Amount: req.Amount, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + BranchID: branchID, + }, userID, role) + + if err != nil { + return domain.ShopBet{}, err + } + + newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{ + Amount: domain.ToCurrency(req.Amount), + BranchID: *branchID, + CompanyID: *companyID, + UserID: userID, + Type: domain.TRANSACTION_BET, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + PaymentOption: req.PaymentOption, + BankCode: domain.ValidString{ + Value: req.BankCode, + Valid: req.BankCode != "", + }, + BeneficiaryName: domain.ValidString{ + Value: req.BeneficiaryName, + Valid: req.BeneficiaryName != "", + }, + AccountName: domain.ValidString{ + Value: req.AccountName, + Valid: req.AccountName != "", + }, + AccountNumber: domain.ValidString{ + Value: req.AccountNumber, + Valid: req.AccountNumber != "", + }, + ReferenceNumber: domain.ValidString{ + Value: req.ReferenceNumber, + Valid: req.ReferenceNumber != "", + }, + Verified: false, + ApprovedBy: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + }) + + return s.transactionStore.CreateShopBet(ctx, domain.CreateShopBet{ + ShopTransactionID: newTransaction.ID, + CashoutID: cashoutID, + BetID: newBet.ID, + NumberOfOutcomes: int64(len(req.Outcomes)), + }) +} + +// func (s *Service) CreateShopBet(ctx context.Context, bet domain.CreateShopBet) (domain.ShopBet, error) { +// return s.transactionStore.CreateShopBet(ctx, bet) +// } + +func (s *Service) CashoutBet(ctx context.Context, betID int64, userID int64, role domain.Role, req domain.CashoutReq) (domain.ShopTransaction, error) { + branchID, companyID, err := s.GetBranchByRole(ctx, req.BranchID, role, userID) + + if err != nil { + return domain.ShopTransaction{}, nil + } + + bet, err := s.GetShopBetByBetID(ctx, betID) + if err != nil { + // h.logger.Error("CashoutReq failed", "error", err) + return domain.ShopTransaction{}, ErrInvalidBetID + } + + if bet.Status != domain.OUTCOME_STATUS_WIN { + // h.logger.Error("CashoutReq failed, bet has not won") + return domain.ShopTransaction{}, ErrUserHasNotWonBet + } + + if bet.CashedOut { + // s.logger.Error(("Bet has already been cashed out")) + return domain.ShopTransaction{}, ErrUserHasAlreadyCashoutOut + } + + if !bet.TransactionVerified { + return domain.ShopTransaction{}, ErrTransactionNotVerified + } + + err = s.UpdateShopBetCashOut(ctx, bet.ID, true) + + if err != nil { + return domain.ShopTransaction{}, err + } + + return s.CreateShopTransaction(ctx, domain.CreateShopTransaction{ + Amount: bet.Amount, + BranchID: *branchID, + CompanyID: *companyID, + UserID: userID, + Type: domain.TRANSACTION_CASHOUT, + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + PaymentOption: req.PaymentOption, + BankCode: domain.ValidString{ + Value: req.BankCode, + Valid: req.BankCode != "", + }, + BeneficiaryName: domain.ValidString{ + Value: req.BeneficiaryName, + Valid: req.BeneficiaryName != "", + }, + AccountName: domain.ValidString{ + Value: req.AccountName, + Valid: req.AccountName != "", + }, + AccountNumber: domain.ValidString{ + Value: req.AccountNumber, + Valid: req.AccountNumber != "", + }, + ReferenceNumber: domain.ValidString{ + Value: req.ReferenceNumber, + Valid: req.ReferenceNumber != "", + }, + Verified: false, + ApprovedBy: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + }) + +} + +func (s *Service) GetAllShopBet(ctx context.Context, filter domain.ShopBetFilter) ([]domain.ShopBetDetail, error) { + return s.transactionStore.GetAllShopBet(ctx, filter) +} + +func (s *Service) GetShopBetByID(ctx context.Context, id int64) (domain.ShopBetDetail, error) { + return s.transactionStore.GetShopBetByBetID(ctx, id) +} + +func (s *Service) GetShopBetByBetID(ctx context.Context, betID int64) (domain.ShopBetDetail, error) { + return s.transactionStore.GetShopBetByBetID(ctx, betID) +} + +func (s *Service) GetShopBetByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopBetDetail, error) { + return s.transactionStore.GetShopBetByShopTransactionID(ctx, shopTransactionID) +} + +func (s *Service) UpdateShopBetCashOut(ctx context.Context, id int64, cashedOut bool) error { + return s.transactionStore.UpdateShopBetCashOut(ctx, id, cashedOut) +} + +func (s *Service) UpdateShopBetCashoutID(ctx context.Context, id int64, cashoutID string) error { + return s.transactionStore.UpdateShopBetCashoutID(ctx, id, cashoutID) +} diff --git a/internal/services/transaction/shop_deposit.go b/internal/services/transaction/shop_deposit.go new file mode 100644 index 0000000..7ef1016 --- /dev/null +++ b/internal/services/transaction/shop_deposit.go @@ -0,0 +1,114 @@ +package transaction + +import ( + "context" + "fmt" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role domain.Role, req domain.ShopDepositReq) (domain.ShopDeposit, error) { + var branchID int64 + var companyID int64 + var senderID int64 + if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { + if req.BranchID == nil { + // h.logger.Error("CashoutReq Branch ID is required for this user role") + return domain.ShopDeposit{}, ErrBranchRequiredForRole + } + branch, err := s.branchSvc.GetBranchByID(ctx, *req.BranchID) + if err != nil { + // h.logger.Error("CashoutReq no branches") + return domain.ShopDeposit{}, ErrInvalidBetID + } + + branchID = branch.ID + companyID = branch.CompanyID + senderID = branch.WalletID + } else if role == domain.RoleCashier { + branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) + if err != nil { + // h.logger.Error("CashoutReq failed, branch id invalid") + return domain.ShopDeposit{}, ErrInvalidBranchID + } + branchID = branch.ID + companyID = branch.CompanyID + senderID = branch.WalletID + } else { + return domain.ShopDeposit{}, ErrCustomerRoleNotAuthorized + } + + customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, req.CustomerID) + + if err != nil { + return domain.ShopDeposit{}, err + } + + transfer, err := s.walletSvc.TransferToWallet(ctx, + senderID, customerWallet.RegularID, domain.ToCurrency(req.Amount), domain.TRANSFER_DIRECT, + domain.ValidInt64{Value: userID, Valid: true}, + fmt.Sprintf("Transferred %v from customer wallet deposit", req.Amount), + ) + + newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{ + Amount: domain.Currency(req.Amount), + BranchID: branchID, + CompanyID: companyID, + UserID: userID, + Type: domain.TRANSACTION_DEPOSIT, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + PaymentOption: req.PaymentOption, + BankCode: domain.ValidString{ + Value: req.BankCode, + Valid: req.BankCode != "", + }, + BeneficiaryName: domain.ValidString{ + Value: req.BeneficiaryName, + Valid: req.BeneficiaryName != "", + }, + AccountName: domain.ValidString{ + Value: req.AccountName, + Valid: req.AccountName != "", + }, + AccountNumber: domain.ValidString{ + Value: req.AccountNumber, + Valid: req.AccountNumber != "", + }, + ReferenceNumber: domain.ValidString{ + Value: req.ReferenceNumber, + Valid: req.ReferenceNumber != "", + }, + Verified: false, + ApprovedBy: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + }) + + if err != nil { + return domain.ShopDeposit{}, err + } + + return s.transactionStore.CreateShopDeposit(ctx, domain.CreateShopDeposit{ + ShopTransactionID: newTransaction.ID, + CustomerID: req.CustomerID, + WalletTransferID: transfer.ID, + }) +} + +// func (s *Service) CreateShopDeposit(ctx context.Context, deposit domain.CreateShopDeposit) (domain.ShopDeposit, error) { +// return s.transactionStore.CreateShopDeposit(ctx, deposit) +// } + +func (s *Service) GetAllShopDeposit(ctx context.Context, filter domain.ShopDepositFilter) ([]domain.ShopDepositDetail, error) { + return s.transactionStore.GetAllShopDeposit(ctx, filter) +} + +func (s *Service) GetShopDepositByID(ctx context.Context, id int64) (domain.ShopDepositDetail, error) { + return s.transactionStore.GetShopDepositByID(ctx, id) +} + +func (s *Service) GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopDepositDetail, error) { + return s.transactionStore.GetShopDepositByShopTransactionID(ctx, shopTransactionID) +} diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index 7622424..b025392 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -115,7 +115,9 @@ func (c *Client) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain return &domain.BetResponse{}, err } - c.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) + c.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT, + fmt.Sprintf("Deducting %v from wallet for creating Veli Game Bet", req.Amount.Amount), + ) return &res, err } @@ -156,7 +158,9 @@ func (c *Client) ProcessWin(ctx context.Context, req domain.WinRequest) (*domain return &domain.WinResponse{}, err } - c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, + fmt.Sprintf("Adding %v to wallet due to winning Veli Games bet", req.Amount), + ) return &res, err } @@ -199,7 +203,9 @@ func (c *Client) ProcessCancel(ctx context.Context, req domain.CancelRequest) (* return &domain.CancelResponse{}, err } - c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.AdjustmentRefund.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.AdjustmentRefund.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, + fmt.Sprintf("Adding %v to wallet due to cancelling virtual game bet", req.AdjustmentRefund.Amount), + ) return &res, err } diff --git a/internal/web_server/handlers/shop_handler.go b/internal/web_server/handlers/shop_handler.go new file mode 100644 index 0000000..aeb17b6 --- /dev/null +++ b/internal/web_server/handlers/shop_handler.go @@ -0,0 +1,303 @@ +package handlers + +import ( + "log/slog" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + "github.com/gofiber/fiber/v2" +) + +// CreateShopBet godoc +// @Summary Create bet at branch +// @Description Create bet at branch +// @Tags transaction +// @Accept json +// @Produce json +// @Param createBet body domain.ShopBetReq true "create bet" +// @Success 200 {object} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/bet [post] +func (h *Handler) CreateShopBet(c *fiber.Ctx) error { + + userID := c.Locals("user_id").(int64) + role := c.Locals("role").(domain.Role) + company_id := c.Locals("company_id").(domain.ValidInt64) + + var req domain.ShopBetReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("CreateBetReq failed to parse request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := h.validator.Validate(c, req) + if !ok { + h.logger.Error("CreateBetReq failed v", "error", valErrs) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + shopBet, err := h.transactionSvc.CreateShopBet(c.Context(), userID, role, company_id, req) + + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to cashout bet", err, nil) + } + res := domain.ConvertShopBet(shopBet) + + return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) +} + +// CashoutBet godoc +// @Summary Cashout bet at branch +// @Description Cashout bet at branch +// @Tags transaction +// @Accept json +// @Produce json +// @Param createBet body domain.CashoutReq true "cashout bet" +// @Success 200 {object} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/bet/{id}/cashout [post] +func (h *Handler) CashoutBet(c *fiber.Ctx) error { + + betIDStr := c.Params("id") + + betID, err := strconv.ParseInt(betIDStr, 10, 64) + + if err != nil { + h.logger.Error("CashoutReq invalid bet id", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet id", err, nil) + } + + userID := c.Locals("user_id").(int64) + role := c.Locals("role").(domain.Role) + + var req domain.CashoutReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("CashoutReq failed to parse request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := h.validator.Validate(c, req) + if !ok { + h.logger.Error("CashoutReq failed v", "error", valErrs) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + transaction, err := h.transactionSvc.CashoutBet(c.Context(), betID, userID, role, req) + + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to cashout bet", err, nil) + } + res := domain.ConvertShopTransaction(transaction) + + return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) +} + +// DepositForCustomer godoc +// @Summary Shop deposit into customer wallet +// @Description Transfers money from branch wallet to customer wallet +// @Tags transaction +// @Accept json +// @Produce json +// @Param transferToWallet body domain.ShopDepositReq true "ShopDepositReq" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/deposit [post] +func (h *Handler) DepositForCustomer(c *fiber.Ctx) error { + + // Get sender ID from the cashier + userID := c.Locals("user_id").(int64) + role := c.Locals("role").(domain.Role) + + var req domain.ShopDepositReq + + if err := c.BodyParser(&req); err != nil { + h.logger.Error("CreateTransferReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := h.validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req) + + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to create shop deposit", err, nil) + } + + res := domain.ConvertShopDeposit(deposit) + + return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) + +} + +// GetAllTransactions godoc +// @Summary Gets all transactions +// @Description Gets all the transactions +// @Tags transaction +// @Accept json +// @Produce json +// @Success 200 {array} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/transaction [get] +func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { + // Get user_id from middleware + // userID := c.Locals("user_id").(int64) + // role := c.Locals("role").(domain.Role) + companyID := c.Locals("company_id").(domain.ValidInt64) + branchID := c.Locals("branch_id").(domain.ValidInt64) + + searchQuery := c.Query("query") + searchString := domain.ValidString{ + Value: searchQuery, + Valid: searchQuery != "", + } + + createdBeforeQuery := c.Query("created_before") + var createdBefore domain.ValidTime + if createdBeforeQuery != "" { + createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdBefore = domain.ValidTime{ + Value: createdBeforeParsed, + Valid: true, + } + } + + createdAfterQuery := c.Query("created_after") + var createdAfter domain.ValidTime + if createdAfterQuery != "" { + createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdAfter = domain.ValidTime{ + Value: createdAfterParsed, + Valid: true, + } + } + + // Check user role and fetch transactions accordingly + transactions, err := h.transactionSvc.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{ + CompanyID: companyID, + BranchID: branchID, + Query: searchString, + CreatedBefore: createdBefore, + CreatedAfter: createdAfter, + }) + + if err != nil { + h.logger.Error("Failed to get transactions", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) + } + + res := make([]domain.ShopTransactionRes, len(transactions)) + for i, transaction := range transactions { + res[i] = domain.ConvertShopTransactionDetail(transaction) + } + + return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) + +} + +// GetTransactionByID godoc +// @Summary Gets transaction by id +// @Description Gets a single transaction by id +// @Tags transaction +// @Accept json +// @Produce json +// @Param id path int true "Transaction ID" +// @Success 200 {object} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/transaction/{id} [get] +func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { + transactionID := c.Params("id") + id, err := strconv.ParseInt(transactionID, 10, 64) + if err != nil { + h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) + return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") + } + + transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) + if err != nil { + h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") + } + + res := domain.ConvertShopTransactionDetail(transaction) + return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) +} + +type UpdateTransactionVerifiedReq struct { + Verified bool `json:"verified" example:"true"` +} + +// UpdateTransactionVerified godoc +// @Summary Updates the verified field of a transaction +// @Description Updates the verified status of a transaction +// @Tags transaction +// @Accept json +// @Produce json +// @Param id path int true "Transaction ID" +// @Param updateVerified body UpdateTransactionVerifiedReq true "Updates Transaction Verification" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /shop/transaction/{id} [put] +func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { + transactionID := c.Params("id") + userID := c.Locals("user_id").(int64) + companyID := c.Locals("company_id").(domain.ValidInt64) + role := c.Locals("role").(domain.Role) + + id, err := strconv.ParseInt(transactionID, 10, 64) + if err != nil { + h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) + return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") + } + + var req UpdateTransactionVerifiedReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + h.logger.Info("Update Transaction Verified", slog.Bool("verified", req.Verified)) + + if valErrs, ok := h.validator.Validate(c, req); !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) + if role != domain.RoleSuperAdmin { + if !companyID.Valid || companyID.Value != transaction.CompanyID { + h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + } + + user, err := h.userSvc.GetUserById(c.Context(), userID) + if err != nil { + h.logger.Error("Invalid user ID", "userID", userID, "error", err) + return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID") + } + err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, user.FirstName+" "+user.LastName) + if err != nil { + h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") + } + + return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) +} diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go deleted file mode 100644 index 087ab30..0000000 --- a/internal/web_server/handlers/transaction_handler.go +++ /dev/null @@ -1,498 +0,0 @@ -package handlers - -import ( - "fmt" - "log/slog" - "strconv" - "time" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" - "github.com/gofiber/fiber/v2" - "go.uber.org/zap" -) - -type ShopTransactionRes struct { - ID int64 `json:"id" example:"1"` - Amount float32 `json:"amount" example:"100.0"` - BranchID int64 `json:"branch_id" example:"1"` - BranchName string `json:"branch_name" example:"Branch Name"` - BranchLocation string `json:"branch_location" example:"Branch Location"` - CompanyID int64 `json:"company_id" example:"1"` - CashierID int64 `json:"cashier_id" example:"1"` - CashierName string `json:"cashier_name" example:"John Smith"` - BetID int64 `json:"bet_id" example:"1"` - NumberOfOutcomes int64 `json:"number_of_outcomes" example:"1"` - Type int64 `json:"type" example:"1"` - PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` - FullName string `json:"full_name" example:"John Smith"` - PhoneNumber string `json:"phone_number" example:"0911111111"` - BankCode string `json:"bank_code"` - BeneficiaryName string `json:"beneficiary_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - ReferenceNumber string `json:"reference_number"` - Verified bool `json:"verified" example:"true"` - ApprovedBy *int64 `json:"approved_by" example:"1"` - ApproverName *string `json:"approver_name" example:"John Smith"` - UpdatedAt time.Time `json:"updated_at"` - CreatedAt time.Time `json:"created_at"` -} - -type CashoutReq struct { - CashoutID string `json:"cashout_id" example:"191212"` - Amount float32 `json:"amount" example:"100.0"` - BetID int64 `json:"bet_id" example:"1"` - Type int64 `json:"type" example:"1"` - PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` - FullName string `json:"full_name" example:"John Smith"` - PhoneNumber string `json:"phone_number" example:"0911111111"` - BankCode string `json:"bank_code"` - BeneficiaryName string `json:"beneficiary_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - ReferenceNumber string `json:"reference_number"` - BranchID *int64 `json:"branch_id,omitempty" example:"1"` -} - -func convertShopTransaction(transaction domain.ShopTransaction) ShopTransactionRes { - newTransaction := ShopTransactionRes{ - ID: transaction.ID, - Amount: transaction.Amount.Float32(), - BranchID: transaction.BranchID, - BranchName: transaction.BranchName, - BranchLocation: transaction.BranchLocation, - CompanyID: transaction.CompanyID, - CashierID: transaction.CashierID, - CashierName: transaction.CashierName, - BetID: transaction.BetID, - Type: int64(transaction.Type), - PaymentOption: transaction.PaymentOption, - FullName: transaction.FullName, - PhoneNumber: transaction.PhoneNumber, - BankCode: transaction.BankCode, - BeneficiaryName: transaction.BeneficiaryName, - AccountName: transaction.AccountName, - AccountNumber: transaction.AccountNumber, - ReferenceNumber: transaction.ReferenceNumber, - Verified: transaction.Verified, - NumberOfOutcomes: transaction.NumberOfOutcomes, - CreatedAt: transaction.CreatedAt, - UpdatedAt: transaction.UpdatedAt, - } - - if transaction.ApprovedBy.Valid { - newTransaction.ApprovedBy = &transaction.ApprovedBy.Value - newTransaction.ApproverName = &transaction.ApproverName.Value - - } - - return newTransaction -} - -// CashoutBet godoc -// @Summary Cashout bet at branch -// @Description Cashout bet at branch -// @Tags transaction -// @Accept json -// @Produce json -// @Param createBet body CashoutReq true "cashout bet" -// @Success 200 {object} TransactionRes -// @Failure 400 {object} response.APIResponse -// @Failure 500 {object} response.APIResponse -// @Router /shop/cashout [post] -func (h *Handler) CashoutBet(c *fiber.Ctx) error { - userID := c.Locals("user_id").(int64) - role := c.Locals("role").(domain.Role) - // user, err := h.userSvc.GetUserByID(c.Context(), userID) - - // TODO: Make a "Only Company" middleware auth and move this into that - if role == domain.RoleCustomer { - h.logger.Error("CashoutReq failed due to unauthorized access") - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ - "error": "unauthorized access", - }) - } - - var req CashoutReq - if err := c.BodyParser(&req); err != nil { - h.logger.Error("CashoutReq failed to parse request", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) - } - - valErrs, ok := h.validator.Validate(c, req) - if !ok { - h.logger.Error("CashoutReq failed v", "error", valErrs) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - } - - var branchID int64 - var branchName string - var branchLocation string - var companyID int64 - if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { - if req.BranchID == nil { - h.logger.Error("CashoutReq Branch ID is required for this user role") - return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this user role", nil, nil) - } - branch, err := h.branchSvc.GetBranchByID(c.Context(), *req.BranchID) - if err != nil { - h.logger.Error("CashoutReq no branches") - return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil) - } - - branchID = branch.ID - branchName = branch.Name - branchLocation = branch.Location - companyID = branch.CompanyID - } else { - branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) - if err != nil { - h.logger.Error("CashoutReq failed, branch id invalid") - return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil) - } - branchID = branch.ID - branchName = branch.Name - branchLocation = branch.Location - companyID = branch.CompanyID - } - - bet, err := h.betSvc.GetBetByID(c.Context(), req.BetID) - if err != nil { - h.logger.Error("CashoutReq failed", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Bet ID invalid", err, nil) - } - - // if bet.Status != domain.OUTCOME_STATUS_WIN { - // h.logger.Error("CashoutReq failed, bet has not won") - // return response.WriteJSON(c, fiber.StatusBadRequest, "User has not won bet", err, nil) - // } - - if bet.CashedOut { - h.logger.Error(("Bet has already been cashed out")) - return response.WriteJSON(c, fiber.StatusBadRequest, "This bet has already been cashed out", err, nil) - } - - user, err := h.userSvc.GetUserByID(c.Context(), userID) - if err != nil { - h.logger.Error("CashoutReq failed, user id invalid", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "User ID invalid", err, nil) - } - transaction, err := h.transactionSvc.CreateShopTransaction(c.Context(), domain.CreateShopTransaction{ - BranchID: branchID, - CashierID: userID, - Amount: domain.ToCurrency(req.Amount), - BetID: bet.ID, - NumberOfOutcomes: int64(len(bet.Outcomes)), - Type: domain.ShopTransactionType(req.Type), - PaymentOption: domain.PaymentOption(req.PaymentOption), - FullName: req.FullName, - PhoneNumber: req.PhoneNumber, - BankCode: req.BankCode, - BeneficiaryName: req.BeneficiaryName, - AccountName: req.AccountName, - AccountNumber: req.AccountNumber, - ReferenceNumber: req.ReferenceNumber, - CashierName: user.FirstName + " " + user.LastName, - BranchName: branchName, - BranchLocation: branchLocation, - CompanyID: companyID, - }) - - if err != nil { - h.logger.Error("CashoutReq failed", "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) - } - - err = h.betSvc.UpdateCashOut(c.Context(), req.BetID, true) - - if err != nil { - h.logger.Error("CashoutReq failed", "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) - } - - res := convertShopTransaction(transaction) - return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) -} - -type DepositForCustomerReq struct { - Amount float32 `json:"amount" example:"100.0"` - PaymentMethod string `json:"payment_method" example:"cash"` - BankCode string `json:"bank_code"` - BeneficiaryName string `json:"beneficiary_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - ReferenceNumber string `json:"reference_number"` - BranchID *int64 `json:"branch_id,omitempty" example:"1"` -} - -// DepositForCustomer godoc -// @Summary Shop deposit into customer wallet -// @Description Transfers money from branch wallet to customer wallet -// @Tags transaction -// @Accept json -// @Produce json -// @Param transferToWallet body DepositForCustomerReq true "DepositForCustomer" -// @Success 200 {object} TransferWalletRes -// @Failure 400 {object} response.APIResponse -// @Failure 500 {object} response.APIResponse -// @Router /shop/deposit/:id [post] - -func (h *Handler) DepositForCustomer(c *fiber.Ctx) error { - customerIDString := c.Params("id") - customerID, err := strconv.ParseInt(customerIDString, 10, 64) - - if err != nil { - h.logger.Error("Invalid customer ID", "customerID", customerID, "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) - } - - // Get sender ID from the cashier - userID := c.Locals("user_id").(int64) - role := c.Locals("role").(domain.Role) - - var req DepositForCustomerReq - - if err := c.BodyParser(&req); err != nil { - h.logger.Error("CreateTransferReq failed", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) - } - - valErrs, ok := h.validator.Validate(c, req) - if !ok { - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - } - var senderID int64 - switch role { - case domain.RoleCustomer: - h.logger.Error("Unauthorized access", "userID", userID, "role", role) - return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) - case domain.RoleAdmin, domain.RoleSuperAdmin, domain.RoleBranchManager: - if req.BranchID == nil { - h.logger.Error("CashoutReq Branch ID is required for this user role") - return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this user role", nil, nil) - } - branch, err := h.branchSvc.GetBranchByID(c.Context(), *req.BranchID) - if err != nil { - h.logger.Error("CashoutReq no branches") - return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil) - } - - senderID = branch.WalletID - case domain.RoleCashier: - cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) - if err != nil { - h.logger.Error("Failed to get branch", "user ID", userID, "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil) - } - senderID = cashierBranch.WalletID - default: - return response.WriteJSON(c, fiber.StatusInternalServerError, "Unknown Role", err, nil) - } - - customerWallet, err := h.walletSvc.GetCustomerWallet(c.Context(), customerID) - - if err != nil { - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid customer id", err, nil) - } - - transfer, err := h.walletSvc.TransferToWallet(c.Context(), - senderID, customerWallet.RegularID, domain.ToCurrency(req.Amount), domain.PaymentMethod(req.PaymentMethod), - domain.ValidInt64{Value: userID, Valid: true}, - fmt.Sprintf("Transferred %v from wallet to customer wallet", req.Amount), - ) - - if err != nil { - h.mongoLoggerSvc.Error("Failed to transfer money to wallet", zap.Error(err)) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) - } - - transaction, err := h.transactionSvc.CreateShopTransaction(c.Context(), domain.CreateShopTransaction{ - BranchID: branchID, - CashierID: userID, - Amount: domain.ToCurrency(req.Amount), - Type: domain.TRANSACTION_DEPOSIT, - PaymentOption: domain.PaymentOption(req.PaymentOption), - FullName: req.FullName, - PhoneNumber: req.PhoneNumber, - BankCode: req.BankCode, - BeneficiaryName: req.BeneficiaryName, - AccountName: req.AccountName, - AccountNumber: req.AccountNumber, - ReferenceNumber: req.ReferenceNumber, - CashierName: user.FirstName + " " + user.LastName, - BranchName: branchName, - BranchLocation: branchLocation, - CompanyID: companyID, - }) - - res := convertTransfer(transfer) - - return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) - -} - -// GetAllTransactions godoc -// @Summary Gets all transactions -// @Description Gets all the transactions -// @Tags transaction -// @Accept json -// @Produce json -// @Success 200 {array} TransactionRes -// @Failure 400 {object} response.APIResponse -// @Failure 500 {object} response.APIResponse -// @Router /shop/transaction [get] -func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { - // Get user_id from middleware - // userID := c.Locals("user_id").(int64) - // role := c.Locals("role").(domain.Role) - companyID := c.Locals("company_id").(domain.ValidInt64) - branchID := c.Locals("branch_id").(domain.ValidInt64) - - searchQuery := c.Query("query") - searchString := domain.ValidString{ - Value: searchQuery, - Valid: searchQuery != "", - } - - createdBeforeQuery := c.Query("created_before") - var createdBefore domain.ValidTime - if createdBeforeQuery != "" { - createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) - if err != nil { - h.logger.Error("invalid start_time format", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) - } - createdBefore = domain.ValidTime{ - Value: createdBeforeParsed, - Valid: true, - } - } - - createdAfterQuery := c.Query("created_after") - var createdAfter domain.ValidTime - if createdAfterQuery != "" { - createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) - if err != nil { - h.logger.Error("invalid start_time format", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) - } - createdAfter = domain.ValidTime{ - Value: createdAfterParsed, - Valid: true, - } - } - - // Check user role and fetch transactions accordingly - transactions, err := h.transactionSvc.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{ - CompanyID: companyID, - BranchID: branchID, - Query: searchString, - CreatedBefore: createdBefore, - CreatedAfter: createdAfter, - }) - - if err != nil { - h.logger.Error("Failed to get transactions", "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) - } - - res := make([]ShopTransactionRes, len(transactions)) - for i, transaction := range transactions { - res[i] = convertShopTransaction(transaction) - } - - return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) - -} - -// GetTransactionByID godoc -// @Summary Gets transaction by id -// @Description Gets a single transaction by id -// @Tags transaction -// @Accept json -// @Produce json -// @Param id path int true "Transaction ID" -// @Success 200 {object} TransactionRes -// @Failure 400 {object} response.APIResponse -// @Failure 500 {object} response.APIResponse -// @Router /shop/transaction/{id} [get] -func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { - transactionID := c.Params("id") - id, err := strconv.ParseInt(transactionID, 10, 64) - if err != nil { - h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") - } - - transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) - if err != nil { - h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") - } - - res := convertShopTransaction(transaction) - return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) -} - -type UpdateTransactionVerifiedReq struct { - Verified bool `json:"verified" example:"true"` -} - -// UpdateTransactionVerified godoc -// @Summary Updates the verified field of a transaction -// @Description Updates the verified status of a transaction -// @Tags transaction -// @Accept json -// @Produce json -// @Param id path int true "Transaction ID" -// @Param updateVerified body UpdateTransactionVerifiedReq true "Updates Transaction Verification" -// @Success 200 {object} response.APIResponse -// @Failure 400 {object} response.APIResponse -// @Failure 500 {object} response.APIResponse -// @Router /shop/transaction/{id} [put] -func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { - transactionID := c.Params("id") - userID := c.Locals("user_id").(int64) - companyID := c.Locals("company_id").(domain.ValidInt64) - role := c.Locals("role").(domain.Role) - - id, err := strconv.ParseInt(transactionID, 10, 64) - if err != nil { - h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") - } - - var req UpdateTransactionVerifiedReq - if err := c.BodyParser(&req); err != nil { - h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) - } - - h.logger.Info("Update Transaction Verified", slog.Bool("verified", req.Verified)) - - if valErrs, ok := h.validator.Validate(c, req); !ok { - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - } - - transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) - if role != domain.RoleSuperAdmin { - if !companyID.Valid || companyID.Value != transaction.CompanyID { - h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) - } - } - - user, err := h.userSvc.GetUserById(c.Context(), userID) - if err != nil { - h.logger.Error("Invalid user ID", "userID", userID, "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID") - } - err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, user.FirstName+" "+user.LastName) - if err != nil { - h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") - } - - return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) -} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 66b1423..0ac9978 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -273,7 +273,9 @@ func (a *App) initAppRoutes() { // group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) // Transactions /shop/transactions - a.fiber.Post("/shop/cashout", a.authMiddleware, h.CashoutBet) + a.fiber.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet) + a.fiber.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet) + a.fiber.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer) a.fiber.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions) a.fiber.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID) a.fiber.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) diff --git a/makefile b/makefile index e29d29e..99d1cce 100644 --- a/makefile +++ b/makefile @@ -43,7 +43,8 @@ migrations/up: postgres: @echo 'Running postgres db...' docker compose -f docker-compose.yml exec postgres psql -U root -d gh - +postgres_log: + docker logs fortunebet-backend-postgres-1 .PHONY: swagger swagger: @swag init -g cmd/main.go @@ -51,7 +52,7 @@ swagger: .PHONY: db-up db-up: @docker compose up -d postgres migrate mongo redis - + @docker logs fortunebet-backend-postgres-1 > logs/postgres.log 2>&1 & .PHONY: db-down db-down: @docker compose down diff --git a/sqlc.yaml b/sqlc.yaml index dca5591..ff84380 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -24,4 +24,7 @@ sql: go_type: type: "TicketOutcome" slice: true - + - column: "shop_bet_detail.outcomes" + go_type: + type: "BetOutcome" + slice: true