fix(recharge-reward): show actual claim records only
This commit is contained in:
parent
d0007b3319
commit
25aab0d6a2
@ -2,7 +2,9 @@ package rechargereward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -26,6 +28,7 @@ type rechargeRewardClaimRecordRow struct {
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
RechargeAmount string `gorm:"column:recharge_amount"`
|
||||
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"`
|
||||
@ -33,7 +36,17 @@ type rechargeRewardClaimRecordRow struct {
|
||||
CountryName string `gorm:"column:country_name"`
|
||||
}
|
||||
|
||||
// PageClaimRecords 分页返回当前配置下已达到充值奖励档位的用户。
|
||||
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"`
|
||||
RuleDescription string `gorm:"column:rule_description"`
|
||||
JSONData string `gorm:"column:json_data"`
|
||||
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)
|
||||
@ -51,35 +64,22 @@ func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, month
|
||||
return nil, err
|
||||
}
|
||||
|
||||
levels := enabledRechargeRewardLevels(bundle.Levels)
|
||||
if len(levels) == 0 {
|
||||
return emptyRechargeRewardClaimRecordPage(displayMonth, rechargeDate, location.String(), cursor, limit), nil
|
||||
}
|
||||
minThresholdCents := levels[0].RechargeAmountCents
|
||||
minThresholdAmount := float64(minThresholdCents) / 100
|
||||
|
||||
var total int64
|
||||
countSQL := `
|
||||
SELECT COUNT(1)
|
||||
FROM (
|
||||
SELECT recharge.user_id
|
||||
FROM user_monthly_recharge_v2 AS recharge
|
||||
INNER JOIN user_base_info AS user ON user.id = recharge.user_id
|
||||
WHERE recharge.recharge_date = ?
|
||||
AND recharge.type IN ?
|
||||
AND user.origin_sys = ?
|
||||
AND (? = 0 OR recharge.user_id = ?)
|
||||
GROUP BY recharge.user_id
|
||||
HAVING SUM(recharge.amount) >= ?
|
||||
) AS qualified`
|
||||
SELECT COUNT(DISTINCT receive.user_id)
|
||||
FROM user_activity_reward_receive_record AS receive
|
||||
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||
WHERE receive.recharge_date = ?
|
||||
AND rule.sys_origin = ?
|
||||
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||
AND COALESCE(rule.is_del, 0) = 0
|
||||
AND (? = 0 OR receive.user_id = ?)`
|
||||
if err := s.db.WithContext(ctx).Raw(
|
||||
countSQL,
|
||||
rechargeDate,
|
||||
personalMonthlyRechargeTypes,
|
||||
sysOrigin,
|
||||
userID,
|
||||
userID,
|
||||
minThresholdAmount,
|
||||
).Scan(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -89,41 +89,63 @@ FROM (
|
||||
var rows []rechargeRewardClaimRecordRow
|
||||
listSQL := `
|
||||
SELECT
|
||||
recharge.user_id,
|
||||
SUM(recharge.amount) AS recharge_amount,
|
||||
MAX(recharge.update_time) AS last_recharge_time,
|
||||
claims.user_id,
|
||||
COALESCE(recharge.recharge_amount, 0) AS recharge_amount,
|
||||
COALESCE(recharge.last_recharge_time, '') AS last_recharge_time,
|
||||
claims.last_claim_time,
|
||||
user.account,
|
||||
user.user_avatar,
|
||||
user.user_nickname,
|
||||
user.country_code,
|
||||
user.country_name
|
||||
FROM user_monthly_recharge_v2 AS recharge
|
||||
INNER JOIN user_base_info AS user ON user.id = recharge.user_id
|
||||
WHERE recharge.recharge_date = ?
|
||||
AND recharge.type IN ?
|
||||
AND user.origin_sys = ?
|
||||
AND (? = 0 OR recharge.user_id = ?)
|
||||
GROUP BY recharge.user_id, user.account, user.user_avatar, user.user_nickname, user.country_code, user.country_name
|
||||
HAVING SUM(recharge.amount) >= ?
|
||||
ORDER BY SUM(recharge.amount) DESC, recharge.user_id ASC
|
||||
LIMIT ? OFFSET ?`
|
||||
FROM (
|
||||
SELECT
|
||||
receive.user_id,
|
||||
MAX(receive.create_time) AS last_claim_time
|
||||
FROM user_activity_reward_receive_record AS receive
|
||||
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||
WHERE receive.recharge_date = ?
|
||||
AND rule.sys_origin = ?
|
||||
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||
AND COALESCE(rule.is_del, 0) = 0
|
||||
AND (? = 0 OR receive.user_id = ?)
|
||||
GROUP BY receive.user_id
|
||||
ORDER BY MAX(receive.create_time) DESC, receive.user_id ASC
|
||||
LIMIT ? OFFSET ?
|
||||
) AS claims
|
||||
INNER JOIN user_base_info AS user ON user.id = claims.user_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
recharge.user_id,
|
||||
SUM(recharge.amount) AS recharge_amount,
|
||||
MAX(recharge.update_time) AS last_recharge_time
|
||||
FROM user_monthly_recharge_v2 AS recharge
|
||||
WHERE recharge.recharge_date = ?
|
||||
AND recharge.type IN ?
|
||||
GROUP BY recharge.user_id
|
||||
) AS recharge ON recharge.user_id = claims.user_id
|
||||
ORDER BY claims.last_claim_time DESC, claims.user_id ASC`
|
||||
if err := s.db.WithContext(ctx).Raw(
|
||||
listSQL,
|
||||
rechargeDate,
|
||||
personalMonthlyRechargeTypes,
|
||||
sysOrigin,
|
||||
userID,
|
||||
userID,
|
||||
minThresholdAmount,
|
||||
limit,
|
||||
(cursor-1)*limit,
|
||||
rechargeDate,
|
||||
personalMonthlyRechargeTypes,
|
||||
).Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rewardItems := s.loadRewardItemsMap(ctx, levels)
|
||||
details, err := s.loadClaimDetails(ctx, sysOrigin, rechargeDate, claimRecordUserIDs(rows))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rewardItems := s.loadRewardItemsMap(ctx, claimDetailLevels(details))
|
||||
records = make([]RechargeRewardClaimRecordView, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
records = append(records, rechargeRewardClaimRecordView(row, displayMonth, rechargeDate, levels, rewardItems))
|
||||
records = append(records, rechargeRewardClaimRecordView(row, displayMonth, rechargeDate, details[row.UserID], rewardItems))
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,11 +204,11 @@ func rechargeRewardClaimRecordView(
|
||||
row rechargeRewardClaimRecordRow,
|
||||
month string,
|
||||
rechargeDate int,
|
||||
levels []rechargeRewardLevelSnapshot,
|
||||
details []rechargeRewardClaimDetailRow,
|
||||
rewardItems map[int64][]RechargeRewardRewardItem,
|
||||
) RechargeRewardClaimRecordView {
|
||||
rechargeCents := parseIntegrationAmountCents(integrationAmount(row.RechargeAmount))
|
||||
reached := reachedRechargeRewardLevels(levels, rewardItems, rechargeCents)
|
||||
reached := claimDetailPayloads(details, rewardItems)
|
||||
return RechargeRewardClaimRecordView{
|
||||
RecordKey: fmt.Sprintf("%d-%d", row.UserID, rechargeDate),
|
||||
UserID: row.UserID,
|
||||
@ -205,6 +227,132 @@ func rechargeRewardClaimRecordView(
|
||||
}
|
||||
}
|
||||
|
||||
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.activity_id,
|
||||
COALESCE(rule.sort, 0) AS level,
|
||||
rule.resource_group_id,
|
||||
rule.rule_description,
|
||||
rule.json_data,
|
||||
receive.create_time AS claim_time
|
||||
FROM user_activity_reward_receive_record AS receive
|
||||
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||
WHERE receive.recharge_date = ?
|
||||
AND rule.sys_origin = ?
|
||||
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||
AND COALESCE(rule.is_del, 0) = 0
|
||||
AND receive.user_id IN ?
|
||||
ORDER BY receive.user_id ASC, COALESCE(rule.sort, 0) 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]
|
||||
}
|
||||
thresholdCents := rechargeRewardRuleQuantityCents(detail.JSONData)
|
||||
result = append(result, RechargeRewardLevelPayload{
|
||||
ID: detail.ActivityID,
|
||||
Level: level,
|
||||
RechargeAmount: formatAmountCents(thresholdCents),
|
||||
RechargeAmountCents: thresholdCents,
|
||||
RewardGroupID: detail.ResourceGroupID,
|
||||
RewardGroupName: strings.TrimSpace(detail.RuleDescription),
|
||||
RewardItems: items,
|
||||
Enabled: true,
|
||||
Reached: true,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func rechargeRewardRuleQuantityCents(raw string) int64 {
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
|
||||
return 0
|
||||
}
|
||||
value, ok := payload["quantity"]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return int64(math.Round(typed * 100))
|
||||
case string:
|
||||
cents, err := parseAmountCents(typed)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return cents
|
||||
default:
|
||||
cents, err := parseAmountCents(fmt.Sprint(typed))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return cents
|
||||
}
|
||||
}
|
||||
|
||||
func reachedRechargeRewardLevels(
|
||||
levels []rechargeRewardLevelSnapshot,
|
||||
rewardItems map[int64][]RechargeRewardRewardItem,
|
||||
|
||||
@ -161,7 +161,7 @@ func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPageClaimRecordsUsesMonthlyPersonalRechargeAndReachedLevels(t *testing.T) {
|
||||
func TestPageClaimRecordsUsesActualReceiveRecords(t *testing.T) {
|
||||
groupID := int64(9001)
|
||||
service, db := newTestService(t, rechargeRewardTestGateway{
|
||||
groups: map[int64]integration.RewardGroupDetail{
|
||||
@ -184,6 +184,29 @@ CREATE TABLE user_monthly_recharge_v2 (
|
||||
)`).Error; err != nil {
|
||||
t.Fatalf("create monthly recharge table: %v", err)
|
||||
}
|
||||
if err := db.Exec(`
|
||||
CREATE TABLE user_activity_reward_receive_record (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
recharge_date INTEGER NOT NULL,
|
||||
activity_id INTEGER NOT NULL,
|
||||
create_time DATETIME NOT NULL
|
||||
)`).Error; err != nil {
|
||||
t.Fatalf("create receive record table: %v", err)
|
||||
}
|
||||
if err := db.Exec(`
|
||||
CREATE TABLE props_activity_rule_config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
sys_origin TEXT NOT NULL,
|
||||
activity_type TEXT NOT NULL,
|
||||
resource_group_id INTEGER NOT NULL,
|
||||
json_data TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL,
|
||||
rule_description TEXT NOT NULL,
|
||||
is_del INTEGER NOT NULL
|
||||
)`).Error; err != nil {
|
||||
t.Fatalf("create activity rule table: %v", err)
|
||||
}
|
||||
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
|
||||
SysOrigin: "LIKEI",
|
||||
Enabled: true,
|
||||
@ -208,10 +231,23 @@ CREATE TABLE user_monthly_recharge_v2 (
|
||||
if err := db.Exec(`
|
||||
INSERT INTO user_monthly_recharge_v2 (user_id, type, amount, recharge_date, update_time) VALUES
|
||||
(42, 'GOOGLE', 150.50, 202605, '2026-05-07 10:00:00'),
|
||||
(42, 'MIFA_PAY', 70.00, 202605, '2026-05-07 11:00:00')
|
||||
(42, 'MIFA_PAY', 70.00, 202605, '2026-05-07 11:00:00'),
|
||||
(43, 'GOOGLE', 300.00, 202605, '2026-05-07 12:00:00')
|
||||
`).Error; err != nil {
|
||||
t.Fatalf("insert monthly recharge: %v", err)
|
||||
}
|
||||
if err := db.Exec(`
|
||||
INSERT INTO props_activity_rule_config (id, sys_origin, activity_type, resource_group_id, json_data, sort, rule_description, is_del) VALUES
|
||||
(7001, 'LIKEI', 'CUMULATIVE_RECHARGE', 9001, '{"quantity":100}', 1, 'Gift Box', 0)
|
||||
`).Error; err != nil {
|
||||
t.Fatalf("insert activity rule: %v", err)
|
||||
}
|
||||
if err := db.Exec(`
|
||||
INSERT INTO user_activity_reward_receive_record (id, user_id, recharge_date, activity_id, create_time) VALUES
|
||||
(8001, 42, 202605, 7001, '2026-05-07 10:30:00')
|
||||
`).Error; err != nil {
|
||||
t.Fatalf("insert receive record: %v", err)
|
||||
}
|
||||
|
||||
resp, err := service.PageClaimRecords(context.Background(), "LIKEI", "2026-05", 0, 1, 20)
|
||||
if err != nil {
|
||||
@ -227,6 +263,9 @@ INSERT INTO user_monthly_recharge_v2 (user_id, type, amount, recharge_date, upda
|
||||
if len(record.ReachedLevels) != 1 || record.ReachedLevels[0].Level != 1 {
|
||||
t.Fatalf("ReachedLevels = %+v, want only level 1", record.ReachedLevels)
|
||||
}
|
||||
if record.ReachedLevels[0].RechargeAmount != "100.00" {
|
||||
t.Fatalf("ReachedLevels[0].RechargeAmount = %q, want 100.00", record.ReachedLevels[0].RechargeAmount)
|
||||
}
|
||||
if len(record.ClaimedRewards) != 1 || len(record.ClaimedRewards[0].RewardItems) != 1 {
|
||||
t.Fatalf("ClaimedRewards = %+v, want level reward item", record.ClaimedRewards)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user