124 lines
4.7 KiB
Go
124 lines
4.7 KiB
Go
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+"%")
|
||
}
|