fix(task-center): 统一任务消费统计口径

修复任务中心全局消费任务只统计抽奖链路订单的问题,改为按真实已支付订单的实付金额计算,并统一使用支付时间优先的时间口径。补充全局消费任务测试并更新 SQLite 测试表结构,确保消费判断与福利活动口径一致。
This commit is contained in:
Zuncle 2026-05-13 00:34:39 +08:00
parent e3007c4e0d
commit cf82660a45
3 changed files with 95 additions and 11 deletions

View File

@ -226,23 +226,37 @@ func tierFingerprint(metric string, threshold int64, activityID int64, window st
return fmt.Sprintf("%s-%d-%d-%s", metric, threshold, activityID, window)
}
func paidTimeExpr() string {
return "COALESCE(NULLIF(orders.paid_at, '1970-01-01 00:00:00'), orders.created_at)"
}
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.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.actual_amount")
Select("orders.id AS order_id, 0 AS activity_id, 0 AS draw_count, 0 AS ticket_price, orders.actual_amount").
Where("orders.user_id = ? AND orders.status = 2", userID)
if len(activityIDs) > 0 {
query = query.Where("activity_issues.activity_id IN ?", activityIDs)
}
if start != nil {
query = query.Where("orders.created_at >= ?", *start)
query = query.Where(paidTimeExpr()+" >= ?", *start)
}
if end != nil {
query = query.Where("orders.created_at <= ?", *end)
query = query.Where(paidTimeExpr()+" <= ?", *end)
}
if len(activityIDs) > 0 {
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.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).
Where("activity_issues.activity_id IN ?", activityIDs).
Group("orders.id, activity_issues.activity_id, activities.price_draw, orders.actual_amount")
if start != nil {
query = query.Where(paidTimeExpr()+" >= ?", *start)
}
if end != nil {
query = query.Where(paidTimeExpr()+" <= ?", *end)
}
}
var rows []orderMetricRow

View File

@ -25,6 +25,7 @@ func ensureExtraTablesForServiceTest(t *testing.T, db *gorm.DB) {
total_amount INTEGER NOT NULL DEFAULT 0,
actual_amount INTEGER NOT NULL DEFAULT 0,
remark TEXT,
paid_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME
@ -345,6 +346,74 @@ func TestGetUserProgress_UsesActualAmount(t *testing.T) {
}
}
func TestOrderAmount_GlobalTaskUsesAllPaidOrders(t *testing.T) {
repo, err := mysql.NewSQLiteRepoForTest()
if err != nil {
t.Fatalf("创建 repo 失败: %v", err)
}
db := repo.GetDbW()
initTestTables(t, db)
ensureExtraTablesForServiceTest(t, db)
svc := New(nil, repo, nil, nil, nil)
now := time.Now()
taskStart := now.Add(-24 * time.Hour)
taskEnd := now.Add(24 * time.Hour)
task := &tcmodel.Task{
Name: "全局消费任务",
Status: 1,
Visibility: 1,
StartTime: &taskStart,
EndTime: &taskEnd,
}
if err := db.Create(task).Error; err != nil {
t.Fatalf("创建任务失败: %v", err)
}
tier := &tcmodel.TaskTier{
TaskID: task.ID,
Metric: MetricOrderAmount,
Operator: OperatorGTE,
Threshold: 1000,
Window: WindowLifetime,
ActivityID: 0,
}
if err := db.Create(tier).Error; err != nil {
t.Fatalf("创建档位失败: %v", err)
}
userID := int64(8888)
inside := now.Format(time.DateTime)
// 普通实付订单:以前不会进 activity_draw_logs 链路,现在全局任务应计入
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (801, ?, 2, 4, 4550, 4550, ?)", userID, inside)
// 抽奖订单:也应计入
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (802, ?, 2, 2, 2000, 2000, ?)", userID, inside)
db.Exec("INSERT INTO activities (id, price_draw) VALUES (901, 500)")
db.Exec("INSERT INTO activity_issues (id, activity_id) VALUES (902, 901)")
db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (802, 902)")
// 零实付订单金额应为0但订单数仍按现有逻辑计入已支付订单
db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, actual_amount, created_at) VALUES (803, ?, 2, 4, 1300, 0, ?)", userID, inside)
progress, err := svc.GetUserProgress(context.Background(), userID, task.ID)
if err != nil {
t.Fatalf("获取进度失败: %v", err)
}
if progress.OrderAmount != 6550 {
t.Fatalf("全局任务金额统计错误,期望 6550 实际 %d", progress.OrderAmount)
}
if progress.OrderCount != 3 {
t.Fatalf("全局任务订单数错误,期望 3 实际 %d", progress.OrderCount)
}
tierProgress := progress.TierProgressMap[tier.ID]
if tierProgress.OrderAmount != 6550 {
t.Fatalf("全局任务档位金额错误,期望 6550 实际 %d", tierProgress.OrderAmount)
}
}
func TestTimeWindow_ActivityPeriod(t *testing.T) {
repo, err := mysql.NewSQLiteRepoForTest()
if err != nil {

View File

@ -682,6 +682,7 @@ func TestGetUserProgress_ActivityFilter_Integration(t *testing.T) {
total_amount INTEGER NOT NULL DEFAULT 0,
actual_amount INTEGER NOT NULL DEFAULT 0,
remark TEXT,
paid_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME