diff --git a/internal/service/task_center/service.go b/internal/service/task_center/service.go index 320981a..8bb349b 100755 --- a/internal/service/task_center/service.go +++ b/internal/service/task_center/service.go @@ -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 diff --git a/internal/service/task_center/service_test.go b/internal/service/task_center/service_test.go index cc6d174..6c653ce 100644 --- a/internal/service/task_center/service_test.go +++ b/internal/service/task_center/service_test.go @@ -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 { diff --git a/internal/service/task_center/task_center_test.go b/internal/service/task_center/task_center_test.go index ccf685b..d604cef 100755 --- a/internal/service/task_center/task_center_test.go +++ b/internal/service/task_center/task_center_test.go @@ -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