hyapp-server/server/admin/internal/modules/payment/recharge_bill_overview.go
2026-07-03 17:35:47 +08:00

135 lines
4.9 KiB
Go

package payment
import (
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
walletv1 "hyapp.local/api/proto/wallet/v1"
"github.com/gin-gonic/gin"
)
type rechargeBillDailyBucketDTO struct {
Date string `json:"date"`
GoogleUsdMinor int64 `json:"googleUsdMinor"`
ThirdPartyUsdMinor int64 `json:"thirdPartyUsdMinor"`
CoinSellerUsdMinor int64 `json:"coinSellerUsdMinor"`
GoogleCoinAmount int64 `json:"googleCoinAmount"`
ThirdPartyCoinAmount int64 `json:"thirdPartyCoinAmount"`
CoinSellerCoinAmount int64 `json:"coinSellerCoinAmount"`
}
type rechargeBillRegionBucketDTO struct {
RegionID int64 `json:"regionId"`
Name string `json:"name"`
BillCount int64 `json:"billCount"`
UsdMinorAmount int64 `json:"usdMinorAmount"`
}
type rechargeBillGooglePaidStatsDTO struct {
GoogleBillCount int64 `json:"googleBillCount"`
SyncedCount int64 `json:"syncedCount"`
UnsyncedCount int64 `json:"unsyncedCount"`
CoveredUsdMinor int64 `json:"coveredUsdMinor"`
EstFeeUsdMinor int64 `json:"estFeeUsdMinor"`
EstTaxUsdMinor int64 `json:"estTaxUsdMinor"`
EstNetUsdMinor int64 `json:"estNetUsdMinor"`
}
type rechargeBillOverviewDTO struct {
Daily []rechargeBillDailyBucketDTO `json:"daily"`
Regions []rechargeBillRegionBucketDTO `json:"regions"`
GooglePaid rechargeBillGooglePaidStatsDTO `json:"googlePaid"`
}
// GetRechargeBillOverview 返回财务概览聚合:按日趋势、区域分布与谷歌实付覆盖度;筛选口径与账单列表一致。
func (h *Handler) GetRechargeBillOverview(c *gin.Context) {
options := shared.ListOptions(c)
appCode := appctx.FromContext(c.Request.Context())
tzOffsetMinutes := int32(queryInt64(c, "tz_offset_minutes", "tzOffsetMinutes"))
if tzOffsetMinutes == 0 {
// 财务系统统一按中国时区切日边界。
tzOffsetMinutes = 480
}
if source, ok := h.billSources[appCode]; ok {
overview, err := source.Overview(c.Request.Context(), legacyRechargeBillQuery{
Keyword: options.Keyword,
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
RegionID: queryInt64(c, "region_id", "regionId"),
StartAtMS: queryInt64(c, "start_at_ms", "startAtMs"),
EndAtMS: queryInt64(c, "end_at_ms", "endAtMs"),
}, tzOffsetMinutes)
if err != nil {
response.ServerError(c, "获取财务概览失败")
return
}
response.OK(c, overview)
return
}
resp, err := h.wallet.GetRechargeBillOverview(c.Request.Context(), &walletv1.GetRechargeBillOverviewRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appCode,
RegionId: queryInt64(c, "region_id", "regionId"),
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
Status: options.Status,
StartAtMs: queryInt64(c, "start_at_ms", "startAtMs"),
EndAtMs: queryInt64(c, "end_at_ms", "endAtMs"),
TzOffsetMinutes: tzOffsetMinutes,
})
if err != nil {
writeWalletError(c, err, "获取财务概览失败")
return
}
overview := rechargeBillOverviewDTO{
Daily: make([]rechargeBillDailyBucketDTO, 0, len(resp.GetDaily())),
Regions: make([]rechargeBillRegionBucketDTO, 0, len(resp.GetRegions())),
}
for _, bucket := range resp.GetDaily() {
overview.Daily = append(overview.Daily, rechargeBillDailyBucketDTO{
Date: bucket.GetDate(),
GoogleUsdMinor: bucket.GetGoogleUsdMinor(),
ThirdPartyUsdMinor: bucket.GetThirdPartyUsdMinor(),
CoinSellerUsdMinor: bucket.GetCoinSellerUsdMinor(),
GoogleCoinAmount: bucket.GetGoogleCoinAmount(),
ThirdPartyCoinAmount: bucket.GetThirdPartyCoinAmount(),
CoinSellerCoinAmount: bucket.GetCoinSellerCoinAmount(),
})
}
// 区域名从后台区域目录补齐;目录缺失时回退区域 ID 文本,避免概览页出现空白区块。
regionNames := map[int64]string{}
if regions, err := h.listRechargeBillRegions(c.Request.Context(), appCode); err == nil {
for _, region := range regions {
name := region.Name
if name == "" {
name = region.RegionCode
}
regionNames[region.RegionID] = name
}
}
for _, bucket := range resp.GetRegions() {
overview.Regions = append(overview.Regions, rechargeBillRegionBucketDTO{
RegionID: bucket.GetRegionId(),
Name: regionNames[bucket.GetRegionId()],
BillCount: bucket.GetBillCount(),
UsdMinorAmount: bucket.GetUsdMinorAmount(),
})
}
if stats := resp.GetGooglePaid(); stats != nil {
overview.GooglePaid = rechargeBillGooglePaidStatsDTO{
GoogleBillCount: stats.GetGoogleBillCount(),
SyncedCount: stats.GetSyncedCount(),
UnsyncedCount: stats.GetUnsyncedCount(),
CoveredUsdMinor: stats.GetCoveredUsdMinor(),
EstFeeUsdMinor: stats.GetEstFeeUsdMinor(),
EstTaxUsdMinor: stats.GetEstTaxUsdMinor(),
EstNetUsdMinor: stats.GetEstNetUsdMinor(),
}
}
response.OK(c, overview)
}