379 lines
12 KiB
Go
379 lines
12 KiB
Go
package rechargereward
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type rechargeRewardClaimRecordRow struct {
|
|
UserID int64 `gorm:"column:user_id"`
|
|
RechargeAmountCents int64 `gorm:"column:recharge_amount_cents"`
|
|
LastRechargeTime string `gorm:"column:last_recharge_time"`
|
|
LastClaimTime string `gorm:"column:last_claim_time"`
|
|
Account string `gorm:"column:account"`
|
|
UserAvatar string `gorm:"column:user_avatar"`
|
|
UserNickname string `gorm:"column:user_nickname"`
|
|
CountryCode string `gorm:"column:country_code"`
|
|
CountryName string `gorm:"column:country_name"`
|
|
}
|
|
|
|
type rechargeRewardClaimDetailRow struct {
|
|
UserID int64 `gorm:"column:user_id"`
|
|
ActivityID int64 `gorm:"column:activity_id"`
|
|
Level int `gorm:"column:level"`
|
|
ResourceGroupID *int64 `gorm:"column:resource_group_id"`
|
|
RewardGroupName string `gorm:"column:reward_group_name"`
|
|
RechargeThresholdCents int64 `gorm:"column:recharge_threshold_cents"`
|
|
RewardGold int64 `gorm:"column:reward_gold"`
|
|
ClaimTime string `gorm:"column:claim_time"`
|
|
}
|
|
|
|
// PageClaimRecords 分页返回真实领取过累计充值奖励的用户。
|
|
func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, month string, userID int64, cursor int, limit int) (*RechargeRewardClaimRecordPageResponse, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
cursor, limit = NormalizePage(cursor, limit)
|
|
|
|
bundle, err := s.loadConfigBundle(ctx, sysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
location := s.location
|
|
if bundle.Config != nil {
|
|
location = resolveLocation(bundle.Config.Timezone, s.location)
|
|
}
|
|
rechargeDate, displayMonth, err := normalizeRechargeRewardRecordMonth(month, time.Now().In(location))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var total int64
|
|
countSQL := `
|
|
SELECT COUNT(DISTINCT receive.user_id)
|
|
FROM recharge_reward_grant_record AS receive
|
|
WHERE receive.recharge_date = ?
|
|
AND receive.sys_origin = ?
|
|
AND receive.status = 'SUCCESS'
|
|
AND (? = 0 OR receive.user_id = ?)`
|
|
if err := s.db.WithContext(ctx).Raw(
|
|
countSQL,
|
|
rechargeDate,
|
|
sysOrigin,
|
|
userID,
|
|
userID,
|
|
).Scan(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records := make([]RechargeRewardClaimRecordView, 0)
|
|
if total > 0 {
|
|
var rows []rechargeRewardClaimRecordRow
|
|
listSQL := `
|
|
SELECT
|
|
claims.user_id,
|
|
claims.recharge_amount_cents,
|
|
claims.last_recharge_time,
|
|
claims.last_claim_time,
|
|
claims.account,
|
|
claims.user_avatar,
|
|
claims.user_nickname,
|
|
claims.country_code,
|
|
claims.country_name
|
|
FROM (
|
|
SELECT
|
|
receive.user_id,
|
|
MAX(receive.recharge_amount_cents) AS recharge_amount_cents,
|
|
COALESCE(MAX(receive.sent_at), MAX(receive.update_time), '') AS last_recharge_time,
|
|
COALESCE(MAX(receive.sent_at), MAX(receive.create_time), '') AS last_claim_time,
|
|
MAX(receive.account) AS account,
|
|
MAX(receive.user_avatar) AS user_avatar,
|
|
MAX(receive.user_nickname) AS user_nickname,
|
|
MAX(receive.country_code) AS country_code,
|
|
MAX(receive.country_name) AS country_name
|
|
FROM recharge_reward_grant_record AS receive
|
|
WHERE receive.recharge_date = ?
|
|
AND receive.sys_origin = ?
|
|
AND receive.status = 'SUCCESS'
|
|
AND (? = 0 OR receive.user_id = ?)
|
|
GROUP BY receive.user_id
|
|
ORDER BY COALESCE(MAX(receive.sent_at), MAX(receive.create_time)) DESC, receive.user_id ASC
|
|
LIMIT ? OFFSET ?
|
|
) AS claims
|
|
ORDER BY claims.last_claim_time DESC, claims.user_id ASC`
|
|
if err := s.db.WithContext(ctx).Raw(
|
|
listSQL,
|
|
rechargeDate,
|
|
sysOrigin,
|
|
userID,
|
|
userID,
|
|
limit,
|
|
(cursor-1)*limit,
|
|
).Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
details, err := s.loadClaimDetails(ctx, sysOrigin, rechargeDate, claimRecordUserIDs(rows))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rewardItems := s.loadRewardItemsMap(ctx, sysOrigin, claimDetailLevels(details))
|
|
records = make([]RechargeRewardClaimRecordView, 0, len(rows))
|
|
for _, row := range rows {
|
|
records = append(records, rechargeRewardClaimRecordView(row, displayMonth, rechargeDate, details[row.UserID], rewardItems))
|
|
}
|
|
}
|
|
|
|
return &RechargeRewardClaimRecordPageResponse{
|
|
Month: displayMonth,
|
|
RechargeDate: rechargeDate,
|
|
Timezone: location.String(),
|
|
Records: records,
|
|
Total: total,
|
|
Current: cursor,
|
|
Size: limit,
|
|
}, nil
|
|
}
|
|
|
|
func emptyRechargeRewardClaimRecordPage(month string, rechargeDate int, timezone string, cursor int, limit int) *RechargeRewardClaimRecordPageResponse {
|
|
return &RechargeRewardClaimRecordPageResponse{
|
|
Month: month,
|
|
RechargeDate: rechargeDate,
|
|
Timezone: timezone,
|
|
Records: []RechargeRewardClaimRecordView{},
|
|
Total: 0,
|
|
Current: cursor,
|
|
Size: limit,
|
|
}
|
|
}
|
|
|
|
func normalizeRechargeRewardRecordMonth(raw string, now time.Time) (int, string, error) {
|
|
value := strings.TrimSpace(raw)
|
|
if value == "" {
|
|
value = now.Format("2006-01")
|
|
}
|
|
if len(value) == 6 && !strings.Contains(value, "-") {
|
|
value = value[:4] + "-" + value[4:]
|
|
}
|
|
parsed, err := time.Parse("2006-01", value)
|
|
if err != nil {
|
|
return 0, "", NewAppError(http.StatusBadRequest, "invalid_month", "month must be YYYY-MM")
|
|
}
|
|
rechargeDate := parsed.Year()*100 + int(parsed.Month())
|
|
return rechargeDate, parsed.Format("2006-01"), nil
|
|
}
|
|
|
|
func enabledRechargeRewardLevels(levels []rechargeRewardLevelSnapshot) []rechargeRewardLevelSnapshot {
|
|
result := make([]rechargeRewardLevelSnapshot, 0, len(levels))
|
|
for _, level := range levels {
|
|
if !level.Enabled || level.RechargeAmountCents <= 0 {
|
|
continue
|
|
}
|
|
result = append(result, level)
|
|
}
|
|
sortLevels(result)
|
|
return result
|
|
}
|
|
|
|
func rechargeRewardClaimRecordView(
|
|
row rechargeRewardClaimRecordRow,
|
|
month string,
|
|
rechargeDate int,
|
|
details []rechargeRewardClaimDetailRow,
|
|
rewardItems map[int64][]RechargeRewardRewardItem,
|
|
) RechargeRewardClaimRecordView {
|
|
reached := claimDetailPayloads(details, rewardItems)
|
|
return RechargeRewardClaimRecordView{
|
|
RecordKey: fmt.Sprintf("%d-%d", row.UserID, rechargeDate),
|
|
UserID: row.UserID,
|
|
Account: strings.TrimSpace(row.Account),
|
|
UserAvatar: strings.TrimSpace(row.UserAvatar),
|
|
UserNickname: strings.TrimSpace(row.UserNickname),
|
|
CountryCode: strings.TrimSpace(row.CountryCode),
|
|
CountryName: strings.TrimSpace(row.CountryName),
|
|
RechargeMonth: month,
|
|
RechargeDate: rechargeDate,
|
|
RechargeAmount: formatAmountCents(row.RechargeAmountCents),
|
|
RechargeAmountCents: row.RechargeAmountCents,
|
|
ReachedLevels: reached,
|
|
ClaimedRewards: rechargeRewardClaimedRewards(reached),
|
|
LastRechargeTime: formatRechargeRewardRecordTime(row.LastRechargeTime),
|
|
}
|
|
}
|
|
|
|
func (s *Service) loadClaimDetails(ctx context.Context, sysOrigin string, rechargeDate int, userIDs []int64) (map[int64][]rechargeRewardClaimDetailRow, error) {
|
|
if len(userIDs) == 0 {
|
|
return map[int64][]rechargeRewardClaimDetailRow{}, nil
|
|
}
|
|
var rows []rechargeRewardClaimDetailRow
|
|
detailSQL := `
|
|
SELECT
|
|
receive.user_id,
|
|
receive.level_id AS activity_id,
|
|
receive.level,
|
|
receive.reward_group_id AS resource_group_id,
|
|
receive.reward_group_name,
|
|
receive.recharge_threshold_cents,
|
|
receive.reward_gold,
|
|
COALESCE(receive.sent_at, receive.create_time) AS claim_time
|
|
FROM recharge_reward_grant_record AS receive
|
|
WHERE receive.recharge_date = ?
|
|
AND receive.sys_origin = ?
|
|
AND receive.status = 'SUCCESS'
|
|
AND receive.user_id IN ?
|
|
ORDER BY receive.user_id ASC, receive.level ASC, receive.create_time ASC`
|
|
if err := s.db.WithContext(ctx).Raw(detailSQL, rechargeDate, sysOrigin, userIDs).Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[int64][]rechargeRewardClaimDetailRow, len(userIDs))
|
|
for _, row := range rows {
|
|
result[row.UserID] = append(result[row.UserID], row)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func claimRecordUserIDs(rows []rechargeRewardClaimRecordRow) []int64 {
|
|
result := make([]int64, 0, len(rows))
|
|
seen := make(map[int64]struct{}, len(rows))
|
|
for _, row := range rows {
|
|
if row.UserID <= 0 {
|
|
continue
|
|
}
|
|
if _, exists := seen[row.UserID]; exists {
|
|
continue
|
|
}
|
|
seen[row.UserID] = struct{}{}
|
|
result = append(result, row.UserID)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func claimDetailLevels(details map[int64][]rechargeRewardClaimDetailRow) []rechargeRewardLevelSnapshot {
|
|
levels := make([]rechargeRewardLevelSnapshot, 0)
|
|
seen := make(map[int64]struct{})
|
|
for _, rows := range details {
|
|
for _, row := range rows {
|
|
if row.ResourceGroupID == nil || *row.ResourceGroupID <= 0 {
|
|
continue
|
|
}
|
|
if _, exists := seen[*row.ResourceGroupID]; exists {
|
|
continue
|
|
}
|
|
seen[*row.ResourceGroupID] = struct{}{}
|
|
levels = append(levels, rechargeRewardLevelSnapshot{
|
|
RewardGroupID: row.ResourceGroupID,
|
|
Enabled: true,
|
|
})
|
|
}
|
|
}
|
|
return levels
|
|
}
|
|
|
|
func claimDetailPayloads(
|
|
details []rechargeRewardClaimDetailRow,
|
|
rewardItems map[int64][]RechargeRewardRewardItem,
|
|
) []RechargeRewardLevelPayload {
|
|
result := make([]RechargeRewardLevelPayload, 0, len(details))
|
|
for index, detail := range details {
|
|
level := detail.Level
|
|
if level <= 0 {
|
|
level = index + 1
|
|
}
|
|
var items []RechargeRewardRewardItem
|
|
if detail.ResourceGroupID != nil {
|
|
items = rewardItems[*detail.ResourceGroupID]
|
|
}
|
|
if items == nil {
|
|
items = []RechargeRewardRewardItem{}
|
|
}
|
|
result = append(result, RechargeRewardLevelPayload{
|
|
ID: detail.ActivityID,
|
|
Level: level,
|
|
RechargeAmount: formatAmountCents(detail.RechargeThresholdCents),
|
|
RechargeAmountCents: detail.RechargeThresholdCents,
|
|
RewardGold: detail.RewardGold,
|
|
RewardGroupID: detail.ResourceGroupID,
|
|
RewardGroupName: strings.TrimSpace(detail.RewardGroupName),
|
|
RewardItems: items,
|
|
Enabled: true,
|
|
Reached: true,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func reachedRechargeRewardLevels(
|
|
levels []rechargeRewardLevelSnapshot,
|
|
rewardItems map[int64][]RechargeRewardRewardItem,
|
|
rechargeCents int64,
|
|
) []RechargeRewardLevelPayload {
|
|
result := make([]RechargeRewardLevelPayload, 0)
|
|
for _, level := range levels {
|
|
if rechargeCents < level.RechargeAmountCents {
|
|
continue
|
|
}
|
|
var items []RechargeRewardRewardItem
|
|
if level.RewardGroupID != nil {
|
|
items = rewardItems[*level.RewardGroupID]
|
|
}
|
|
if items == nil {
|
|
items = []RechargeRewardRewardItem{}
|
|
}
|
|
result = append(result, RechargeRewardLevelPayload{
|
|
ID: level.ID,
|
|
Level: level.Level,
|
|
RechargeAmount: formatAmountCents(level.RechargeAmountCents),
|
|
RechargeAmountCents: level.RechargeAmountCents,
|
|
RewardGold: level.RewardGold,
|
|
RewardGroupID: level.RewardGroupID,
|
|
RewardGroupName: level.RewardGroupName,
|
|
RewardItems: items,
|
|
Enabled: level.Enabled,
|
|
Reached: true,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func rechargeRewardClaimedRewards(levels []RechargeRewardLevelPayload) []RechargeRewardClaimedRewardPayload {
|
|
result := make([]RechargeRewardClaimedRewardPayload, 0, len(levels))
|
|
for _, level := range levels {
|
|
items := level.RewardItems
|
|
if items == nil {
|
|
items = []RechargeRewardRewardItem{}
|
|
}
|
|
result = append(result, RechargeRewardClaimedRewardPayload{
|
|
Level: level.Level,
|
|
RechargeAmount: level.RechargeAmount,
|
|
RechargeAmountCents: level.RechargeAmountCents,
|
|
RewardGold: level.RewardGold,
|
|
RewardGroupID: level.RewardGroupID,
|
|
RewardGroupName: level.RewardGroupName,
|
|
RewardItems: items,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func formatRechargeRewardRecordTime(raw string) string {
|
|
value := strings.TrimSpace(raw)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
for _, layout := range []string{
|
|
"2006-01-02 15:04:05.999999999-07:00",
|
|
"2006-01-02 15:04:05.999999999",
|
|
"2006-01-02 15:04:05",
|
|
time.RFC3339Nano,
|
|
time.RFC3339,
|
|
} {
|
|
if parsed, err := time.Parse(layout, value); err == nil {
|
|
return formatDateTime(parsed)
|
|
}
|
|
}
|
|
if len(value) > len("2006-01-02 15:04:05") {
|
|
return value[:len("2006-01-02 15:04:05")]
|
|
}
|
|
return value
|
|
}
|