fix(task-center): 改为按实付金额统计任务进度与领奖判断

任务中心的消费金额口径从订单总额统一调整为实付金额,确保优惠券抵扣部分不再计入任务达标、领奖判断和进度展示;同时同步修正相关测试与中文说明,避免新旧口径混用。
This commit is contained in:
Zuncle 2026-05-11 01:39:08 +08:00
parent 3390d0e24a
commit e3007c4e0d
4 changed files with 37 additions and 47 deletions

View File

@ -23,6 +23,7 @@ func TestInviteLogicSymmetry(t *testing.T) {
status INTEGER NOT NULL DEFAULT 1,
source_type INTEGER NOT NULL DEFAULT 0,
total_amount INTEGER NOT NULL DEFAULT 0,
actual_amount INTEGER NOT NULL DEFAULT 0,
remark TEXT,
deleted_at DATETIME
);`)
@ -49,7 +50,7 @@ func TestInviteLogicSymmetry(t *testing.T) {
// 只有 101 在活动 77 中下过单并开奖
db.Exec("INSERT INTO activity_issues (id, activity_id) VALUES (1, 77)")
db.Exec("INSERT INTO activities (id, price_draw) VALUES (77, 100)")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type) VALUES (10, 101, 2, 100, 0)")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type) VALUES (10, 101, 2, 100, 100, 0)")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (10, 1)")
// === 场景 1全局任务 (ActivityID = 0) ===

View File

@ -186,11 +186,11 @@ type TaskRewardItem struct {
}
type orderMetricRow struct {
OrderID int64
ActivityID int64
DrawCount int64
TicketPrice int64
TotalAmount int64
OrderID int64
ActivityID int64
DrawCount int64
TicketPrice int64
ActualAmount int64
}
var allowedWindows = map[string]struct{}{
@ -228,12 +228,12 @@ func tierFingerprint(metric string, threshold int64, activityID int64, window st
func (s *service) fetchOrderMetricRows(ctx context.Context, userID int64, activityIDs []int64, start, end *time.Time) ([]orderMetricRow, error) {
query := s.repo.GetDbR().WithContext(ctx).Table(model.TableNameOrders).
Select("orders.id AS order_id, activity_issues.activity_id AS activity_id, COUNT(activity_draw_logs.id) AS draw_count, COALESCE(activities.price_draw, 0) AS ticket_price, orders.total_amount").
Select("orders.id AS order_id, activity_issues.activity_id AS activity_id, COUNT(activity_draw_logs.id) AS draw_count, COALESCE(activities.price_draw, 0) AS ticket_price, orders.actual_amount").
Joins("JOIN activity_draw_logs ON activity_draw_logs.order_id = orders.id").
Joins("JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id").
Joins("LEFT JOIN activities ON activities.id = activity_issues.activity_id").
Where("orders.user_id = ? AND orders.status = 2 AND orders.source_type != 1", userID).
Group("orders.id, activity_issues.activity_id, activities.price_draw, orders.total_amount")
Group("orders.id, activity_issues.activity_id, activities.price_draw, orders.actual_amount")
if len(activityIDs) > 0 {
query = query.Where("activity_issues.activity_id IN ?", activityIDs)
@ -253,18 +253,7 @@ func (s *service) fetchOrderMetricRows(ctx context.Context, userID int64, activi
}
func (s *service) calculateEffectiveAmount(row orderMetricRow) int64 {
if row.TicketPrice > 0 && row.DrawCount > 0 {
return row.TicketPrice * row.DrawCount
}
if row.TotalAmount > 0 {
if s.logger != nil && row.TicketPrice == 0 {
s.logger.Warn("task center: missing ticket price snapshot, fallback to order amount",
zap.Int64("order_id", row.OrderID),
zap.Int64("activity_id", row.ActivityID))
}
return row.TotalAmount
}
return 0
return row.ActualAmount
}
func (s *service) aggregateOrderMetrics(rows []orderMetricRow, perActivity bool) (count int64, amount int64) {

View File

@ -130,17 +130,17 @@ func TestGetUserProgress_TimeWindow_Integration(t *testing.T) {
// 插入三笔订单与邀请,处于不同时间段
o1Time := now.Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (101, ?, 2, 0, 100, ?)", userID, o1Time)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (101, ?, 2, 0, 100, 100, ?)", userID, o1Time)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (101, 1)")
db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 901, ?)", userID, o1Time)
o2Time := now.AddDate(0, -2, 0).Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (102, ?, 2, 0, 100, ?)", userID, o2Time)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (102, ?, 2, 0, 100, 100, ?)", userID, o2Time)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (102, 1)")
db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 902, ?)", userID, o2Time)
o3Time := now.AddDate(-1, 0, 0).Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (103, ?, 2, 0, 100, ?)", userID, o3Time)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (103, ?, 2, 0, 100, 100, ?)", userID, o3Time)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (103, 1)")
db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 903, ?)", userID, o3Time)
@ -260,7 +260,7 @@ func TestUpsertTaskRewards_AllowsMultipleRewardsSameType(t *testing.T) {
}
}
func TestGetUserProgress_UsesEffectiveAmount(t *testing.T) {
func TestGetUserProgress_UsesActualAmount(t *testing.T) {
repo, err := mysql.NewSQLiteRepoForTest()
if err != nil {
t.Fatalf("创建 repo 失败: %v", err)
@ -313,13 +313,13 @@ func TestGetUserProgress_UsesEffectiveAmount(t *testing.T) {
now := time.Now()
inside := now.Format(time.DateTime)
// 次卡订单:total_amount=0但 price_draw>0, draw_count=2
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (401, ?, 2, 0, 0, ?)", userID, inside)
// 次卡订单:actual_amount=0按纯实付口径不计入消费金额
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (401, ?, 2, 0, 0, 0, ?)", userID, inside)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (401, 301)")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (401, 301)")
// 现金订单:price_draw=0需回退 total_amount
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (402, ?, 2, 0, 1500, ?)", userID, inside)
// 现金订单:按 actual_amount 统计
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (402, ?, 2, 0, 1500, 1500, ?)", userID, inside)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (402, 302)")
progress, err := svc.GetUserProgress(context.Background(), userID, task.ID)
@ -327,8 +327,8 @@ func TestGetUserProgress_UsesEffectiveAmount(t *testing.T) {
t.Fatalf("获取进度失败: %v", err)
}
if progress.OrderAmount != 3500 {
t.Fatalf("订单金额统计错误,期望 3500 实际 %d", progress.OrderAmount)
if progress.OrderAmount != 1500 {
t.Fatalf("订单金额统计错误,期望 1500 实际 %d", progress.OrderAmount)
}
if progress.OrderCount != 2 {
t.Fatalf("订单数量统计错误,期望 2 实际 %d", progress.OrderCount)
@ -337,8 +337,8 @@ func TestGetUserProgress_UsesEffectiveAmount(t *testing.T) {
if !ok {
t.Fatalf("未找到档位进度")
}
if tierProgress.OrderAmount != 2000 {
t.Fatalf("档位金额错误,期望 2000 实际 %d", tierProgress.OrderAmount)
if tierProgress.OrderAmount != 0 {
t.Fatalf("档位金额错误,期望 0 实际 %d", tierProgress.OrderAmount)
}
if tierProgress.OrderCount != 1 {
t.Fatalf("档位订单数错误,期望 1 实际 %d", tierProgress.OrderCount)
@ -388,10 +388,10 @@ func TestTimeWindow_ActivityPeriod(t *testing.T) {
inside := start.Add(24 * time.Hour).Format(time.DateTime)
outside := end.Add(24 * time.Hour).Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (701, ?, 2, 0, 0, ?)", userID, inside)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (701, ?, 2, 0, 0, 0, ?)", userID, inside)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (701, 601)")
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (702, ?, 2, 0, 0, ?)", userID, outside)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (702, ?, 2, 0, 0, 0, ?)", userID, outside)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (702, 601)")
progress, err := svc.GetUserProgress(context.Background(), userID, task.ID)
@ -569,14 +569,14 @@ func TestLifetimeWindow_RespectsTaskStartTime(t *testing.T) {
// 插入历史订单(任务开始之前)
historicalOrder := taskStart.Add(-10 * 24 * time.Hour).Format(time.DateTime)
for i := int64(101); i <= 105; i++ {
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (?, ?, 2, 0, 100, ?)", i, userID, historicalOrder)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (?, ?, 2, 0, 100, 100, ?)", i, userID, historicalOrder)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (?, 1)", i)
}
// 插入新订单(任务开始之后)
recentOrder := now.Add(-1 * 24 * time.Hour).Format(time.DateTime)
for i := int64(201); i <= 202; i++ {
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (?, ?, 2, 0, 100, ?)", i, userID, recentOrder)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (?, ?, 2, 0, 100, 100, ?)", i, userID, recentOrder)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (?, 1)", i)
}
@ -650,12 +650,12 @@ func TestEmptyWindow_RespectsTaskStartTime(t *testing.T) {
// 历史订单(任务开始前)
oldTime := taskStart.Add(-24 * time.Hour).Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (301, ?, 2, 0, 100, ?)", userID, oldTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (301, ?, 2, 0, 100, 100, ?)", userID, oldTime)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (301, 1)")
// 新订单(任务开始后)
newTime := now.Add(-1 * time.Hour).Format(time.DateTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (302, ?, 2, 0, 100, ?)", userID, newTime)
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (302, ?, 2, 0, 100, 100, ?)", userID, newTime)
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (302, 1)")
progress, err := svc.GetUserProgress(context.Background(), userID, task.ID)

View File

@ -378,12 +378,12 @@ func TestOrderCountMetric(t *testing.T) {
t.Logf("订单数量指标测试通过: 当前订单数=%d", p.OrderCount)
}
// TestOrderAmountMetric 测试消费金额指标
// TestOrderAmountMetric 测试实付金额指标
func TestOrderAmountMetric(t *testing.T) {
db := CreateTestDB(t)
combo := TaskCombination{
Name: "消费金额测试任务",
Name: "实付金额测试任务",
Metric: MetricOrderAmount,
Operator: OperatorGTE,
Threshold: 10000, // 100元
@ -411,10 +411,10 @@ func TestOrderAmountMetric(t *testing.T) {
var p tcmodel.UserTaskProgress
db.Where("user_id = ? AND task_id = ?", userID, taskID).First(&p)
if p.OrderAmount < 10000 {
t.Error("消费金额未达到阈值")
t.Error("实付金额未达到阈值")
}
t.Logf("消费金额指标测试通过: 当前消费=%d分", p.OrderAmount)
t.Logf("实付金额指标测试通过: 当前实付=%d分", p.OrderAmount)
}
// TestInviteCountMetric 测试邀请人数指标
@ -747,25 +747,25 @@ func TestGetUserProgress_ActivityFilter_Integration(t *testing.T) {
db.Exec("INSERT INTO activity_issues (id, activity_id) VALUES (20, 200)")
// 订单 A: 匹配活动 100
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (1, ?, 2, 100, 0, ?)", userID, "activity:100|count:1")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type, remark) VALUES (1, ?, 2, 100, 100, 0, ?)", userID, "activity:100|count:1")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (1, 10)")
// 订单 B: 匹配活动 200 (不应被统计,因为任务关联的是 100)
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (2, ?, 2, 200, 0, ?)", userID, "activity:200|count:1")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type, remark) VALUES (2, ?, 2, 200, 200, 0, ?)", userID, "activity:200|count:1")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (2, 20)")
// 订单 C: 普通订单 (不应被统计,因为没有关联活动 100)
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (3, ?, 2, 300, 0, ?)", userID, "normal_order")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type, remark) VALUES (3, ?, 2, 300, 300, 0, ?)", userID, "normal_order")
// 订单 D: 匹配活动 100 但未支付 (不应被统计)
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (4, ?, 1, 100, 0, ?)", userID, "activity:100|count:1")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type, remark) VALUES (4, ?, 1, 100, 100, 0, ?)", userID, "activity:100|count:1")
// 3. 插入邀请记录
db.Exec("INSERT INTO user_invites (inviter_id, invitee_id) VALUES (?, 1001)", userID)
db.Exec("INSERT INTO user_invites (inviter_id, invitee_id) VALUES (?, 1002)", userID)
// 4. 让其中一个被邀请人(1001)在活动 100 中产生有效订单(使其成为"有效邀请”)
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (10, 1001, 2, 50, 0, ?)", "activity:100|count:1")
db.Exec("INSERT INTO orders (id, user_id, status, total_amount, actual_amount, source_type, remark) VALUES (10, 1001, 2, 50, 50, 0, ?)", "activity:100|count:1")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (10, 10)")
// 5. 调用 GetUserProgress