hyapp-server/server/admin/internal/modules/dashboard/external_coin_seller_bills.go
2026-07-06 12:43:40 +08:00

124 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dashboard
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
)
// ListCoinSellerRechargeBills 把 dashboard-cdc-worker 的 dealer_recharge 聚合事实暴露给 finance 充值流水。
// Yumi 当前只有按天/国家沉淀的币商充值金额,没有单笔订单号和币商用户 ID这里生成稳定 synthetic id
// 让列表、导出和区域筛选至少与总览/Databi 的金额口径完全一致。
func (s *MySQLExternalDashboardSource) ListCoinSellerRechargeBills(ctx context.Context, query CoinSellerRechargeBillQuery) ([]CoinSellerRechargeBill, int64, error) {
if s == nil || s.db == nil {
return nil, 0, fmt.Errorf("external dashboard source is not configured")
}
countryFilter, err := s.countryFilter(ctx, StatisticsQuery{RegionID: query.RegionID})
if err != nil {
return nil, 0, err
}
if countryFilter.Applied && len(countryFilter.Codes) == 0 {
return []CoinSellerRechargeBill{}, 0, nil
}
requestTimezone := strings.TrimSpace(query.StatTZ)
if requestTimezone == "" {
requestTimezone = s.statTimezone
}
requestLocation, err := time.LoadLocation(requestTimezone)
if err != nil {
return nil, 0, fmt.Errorf("load external dashboard bill request timezone %s: %w", requestTimezone, err)
}
storageTimezone := s.statTimezone
if _, err := time.LoadLocation(storageTimezone); err != nil {
return nil, 0, fmt.Errorf("load external dashboard bill storage timezone %s: %w", storageTimezone, err)
}
startDate, endDate, _, _ := externalDashboardDateRange(StatisticsQuery{StartMS: query.StartMS, EndMS: query.EndMS}, requestLocation)
whereSQL, args := s.periodMetricWhere(storageTimezone, startDate, endDate, countryFilter)
whereSQL, args = s.appendCoinSellerBillKeyword(whereSQL, args, query.Keyword)
page := query.Page
if page < 1 {
page = 1
}
pageSize := query.PageSize
if pageSize < 1 {
pageSize = 10
}
queryCtx, cancel := context.WithTimeout(ctx, s.requestTimeout)
defer cancel()
var total int64
if err := s.db.QueryRowContext(queryCtx, `
SELECT COUNT(*) FROM (
SELECT t.period_key, t.country_code
FROM country_dashboard_period_metric t
WHERE `+whereSQL+`
GROUP BY t.period_key, t.country_code
HAVING COALESCE(SUM(t.dealer_recharge), 0) <> 0
) bills`, args...).Scan(&total); err != nil {
return nil, 0, fmt.Errorf("count external coin seller recharge bills for %s: %w", s.appCode, err)
}
if total == 0 {
return []CoinSellerRechargeBill{}, 0, nil
}
rows, err := s.db.QueryContext(queryCtx, `
SELECT t.period_key, UPPER(t.country_code), MAX(t.country_name), CAST(COALESCE(SUM(t.dealer_recharge), 0) AS CHAR)
FROM country_dashboard_period_metric t
WHERE `+whereSQL+`
GROUP BY t.period_key, t.country_code
HAVING COALESCE(SUM(t.dealer_recharge), 0) <> 0
ORDER BY t.period_key DESC, t.country_code
LIMIT ? OFFSET ?`, append(args, pageSize, (page-1)*pageSize)...)
if err != nil {
return nil, 0, fmt.Errorf("query external coin seller recharge bills for %s: %w", s.appCode, err)
}
defer rows.Close()
items := make([]CoinSellerRechargeBill, 0, pageSize)
for rows.Next() {
var periodKey string
var countryCode string
var countryName sql.NullString
var amountText string
if err := rows.Scan(&periodKey, &countryCode, &countryName, &amountText); err != nil {
return nil, 0, fmt.Errorf("scan external coin seller recharge bill for %s: %w", s.appCode, err)
}
amount, err := decimalTextToMinor(amountText)
if err != nil {
return nil, 0, fmt.Errorf("parse external coin seller recharge amount for %s/%s/%s: %w", s.appCode, periodKey, countryCode, err)
}
createdAt, err := time.ParseInLocation("2006-01-02", periodKey, requestLocation)
if err != nil {
return nil, 0, fmt.Errorf("parse external coin seller recharge day for %s/%s/%s: %w", s.appCode, periodKey, countryCode, err)
}
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
items = append(items, CoinSellerRechargeBill{
TransactionID: fmt.Sprintf("%s-dealer-recharge-%s-%s", s.appCode, periodKey, countryCode),
Source: "dealer_recharge",
CountryCode: countryCode,
USDMinorAmount: amount,
CreatedAtMS: createdAt.UnixMilli(),
})
}
if err := rows.Err(); err != nil {
return nil, 0, fmt.Errorf("iterate external coin seller recharge bills for %s: %w", s.appCode, err)
}
return items, total, nil
}
func (s *MySQLExternalDashboardSource) appendCoinSellerBillKeyword(whereSQL string, args []any, rawKeyword string) (string, []any) {
keyword := strings.TrimSpace(rawKeyword)
if keyword == "" {
return whereSQL, args
}
upperKeyword := strings.ToUpper(keyword)
return whereSQL + `
AND (CONCAT(?, '-dealer-recharge-', t.period_key, '-', UPPER(t.country_code)) = ?
OR UPPER(t.country_code) = ?
OR t.country_name LIKE ?)`, append(args, s.appCode, keyword, upperKeyword, "%"+keyword+"%")
}