修复超时订单重置流程
This commit is contained in:
@@ -347,6 +347,8 @@ func (j *Job) handleRenterConfirmTimeout(ctx context.Context, now time.Time, cfg
|
||||
if cfg.RenterConfirmTimeoutMinutes <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
deadline := now.Add(-time.Duration(cfg.RenterConfirmTimeoutMinutes) * time.Minute)
|
||||
|
||||
var rows []model.RentalOrder
|
||||
err := j.db.WithContext(ctx).
|
||||
Where("status = ? AND handoff_status = ?", "pending_handoff", "pending_renter_confirm").
|
||||
@@ -356,15 +358,42 @@ func (j *Job) handleRenterConfirmTimeout(ctx context.Context, now time.Time, cfg
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 批量查询所有订单的最新交接记录,减少 N+1 查询
|
||||
orderIDs := make([]uint64, len(rows))
|
||||
for i, row := range rows {
|
||||
orderIDs[i] = row.ID
|
||||
}
|
||||
var handoffs []model.HandoffRecord
|
||||
if err := j.db.WithContext(ctx).
|
||||
Where("order_id IN ? AND type = ?", orderIDs, "owner_handoff").
|
||||
Order("order_id ASC, id DESC").
|
||||
Find(&handoffs).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// 构建 order_id -> 最新交接记录的映射
|
||||
latestHandoff := make(map[uint64]model.HandoffRecord, len(handoffs))
|
||||
for _, h := range handoffs {
|
||||
if _, exists := latestHandoff[h.OrderID]; !exists {
|
||||
latestHandoff[h.OrderID] = h
|
||||
}
|
||||
}
|
||||
|
||||
count := 0
|
||||
deadline := now.Add(-time.Duration(cfg.RenterConfirmTimeoutMinutes) * time.Minute)
|
||||
for _, row := range rows {
|
||||
var handoff model.HandoffRecord
|
||||
err := j.db.WithContext(ctx).
|
||||
Where("order_id = ? AND type = ?", row.ID, "owner_handoff").
|
||||
Order("id DESC").
|
||||
First(&handoff).Error
|
||||
if err != nil || handoff.CreatedAt.After(deadline) {
|
||||
handoff, exists := latestHandoff[row.ID]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
// 判断阶段开始时间:优先使用 HandoffStartedAt(管理员重置后刷新的时间)
|
||||
stageStart := handoff.CreatedAt
|
||||
if row.HandoffStartedAt != nil && row.HandoffStartedAt.After(handoff.CreatedAt) {
|
||||
stageStart = *row.HandoffStartedAt
|
||||
}
|
||||
if stageStart.After(deadline) {
|
||||
continue
|
||||
}
|
||||
if err := j.updateOrder(ctx, row.ID, "order.timeout.renter_confirm", func(tx *gorm.DB, order *model.RentalOrder) (string, error) {
|
||||
@@ -460,7 +489,7 @@ func (j *Job) handleOwnerReturnConfirmTimeout(ctx context.Context, now time.Time
|
||||
}
|
||||
var rows []model.RentalOrder
|
||||
err := j.db.WithContext(ctx).
|
||||
Where("status = ? AND handoff_status = ? AND updated_at <= ?", "pending_checkout_confirm", "pending_owner_checkout", now.Add(-time.Duration(cfg.OwnerReturnConfirmTimeoutMinutes)*time.Minute)).
|
||||
Where("status = ? AND handoff_status = ? AND COALESCE(handoff_started_at, updated_at) <= ?", "pending_checkout_confirm", "pending_owner_checkout", now.Add(-time.Duration(cfg.OwnerReturnConfirmTimeoutMinutes)*time.Minute)).
|
||||
Order("id ASC").
|
||||
Limit(100).
|
||||
Find(&rows).Error
|
||||
|
||||
@@ -23,6 +23,8 @@ func setupOrderTimeoutTestDB(t *testing.T) *gorm.DB {
|
||||
}
|
||||
if err := db.AutoMigrate(
|
||||
&model.RentalOrder{},
|
||||
&model.HandoffRecord{},
|
||||
&model.Notification{},
|
||||
&model.ChatConversation{},
|
||||
&model.ChatParticipant{},
|
||||
&model.ChatMessage{},
|
||||
@@ -152,6 +154,101 @@ func TestHandleEndedOrderRenterRetention(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRenterConfirmTimeoutUsesRefreshedStageTime(t *testing.T) {
|
||||
db := setupOrderTimeoutTestDB(t)
|
||||
job := New(db, nil, zap.NewNop())
|
||||
now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC)
|
||||
oldHandoffAt := now.Add(-2 * time.Hour)
|
||||
refreshedAt := now.Add(-5 * time.Minute)
|
||||
|
||||
order := model.RentalOrder{
|
||||
OrderNo: "ORDER-RENTER-CONFIRM-RESET",
|
||||
ListingID: 101,
|
||||
AccountID: 201,
|
||||
OwnerID: 301,
|
||||
RenterID: 401,
|
||||
Status: "pending_handoff",
|
||||
HandoffStatus: "pending_renter_confirm",
|
||||
HandoffStartedAt: &refreshedAt,
|
||||
}
|
||||
if err := db.Create(&order).Error; err != nil {
|
||||
t.Fatalf("创建订单失败: %v", err)
|
||||
}
|
||||
record := model.HandoffRecord{
|
||||
OrderID: order.ID,
|
||||
FromUserID: order.OwnerID,
|
||||
ToUserID: order.RenterID,
|
||||
Type: "owner_handoff",
|
||||
Content: "交接说明",
|
||||
CreatedAt: oldHandoffAt,
|
||||
}
|
||||
if err := db.Create(&record).Error; err != nil {
|
||||
t.Fatalf("创建交接记录失败: %v", err)
|
||||
}
|
||||
|
||||
count, err := job.handleRenterConfirmTimeout(t.Context(), now, thresholds{
|
||||
RenterConfirmTimeoutMinutes: 30,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("确认收号超时任务失败: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("处理数量 = %d, want 0", count)
|
||||
}
|
||||
|
||||
var saved model.RentalOrder
|
||||
if err := db.First(&saved, order.ID).Error; err != nil {
|
||||
t.Fatalf("读取订单失败: %v", err)
|
||||
}
|
||||
if saved.Status != "pending_handoff" || saved.HandoffStatus != "pending_renter_confirm" {
|
||||
t.Fatalf("订单状态 = %s/%s, want pending_handoff/pending_renter_confirm", saved.Status, saved.HandoffStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleOwnerReturnConfirmTimeoutUsesStageTime(t *testing.T) {
|
||||
db := setupOrderTimeoutTestDB(t)
|
||||
job := New(db, nil, zap.NewNop())
|
||||
now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC)
|
||||
oldUpdatedAt := now.Add(-2 * time.Hour)
|
||||
refreshedAt := now.Add(-5 * time.Minute)
|
||||
|
||||
order := model.RentalOrder{
|
||||
OrderNo: "ORDER-CHECKOUT-CONFIRM-RESET",
|
||||
ListingID: 102,
|
||||
AccountID: 202,
|
||||
OwnerID: 302,
|
||||
RenterID: 402,
|
||||
Status: "pending_checkout_confirm",
|
||||
HandoffStatus: "pending_owner_checkout",
|
||||
HandoffStartedAt: &refreshedAt,
|
||||
UpdatedAt: oldUpdatedAt,
|
||||
}
|
||||
if err := db.Create(&order).Error; err != nil {
|
||||
t.Fatalf("创建订单失败: %v", err)
|
||||
}
|
||||
if err := db.Model(&model.RentalOrder{}).Where("id = ?", order.ID).Update("updated_at", oldUpdatedAt).Error; err != nil {
|
||||
t.Fatalf("回写旧更新时间失败: %v", err)
|
||||
}
|
||||
|
||||
count, err := job.handleOwnerReturnConfirmTimeout(t.Context(), now, thresholds{
|
||||
OwnerReturnConfirmTimeoutMinutes: 30,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("号主确认结账超时任务失败: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("处理数量 = %d, want 0", count)
|
||||
}
|
||||
|
||||
var saved model.RentalOrder
|
||||
if err := db.First(&saved, order.ID).Error; err != nil {
|
||||
t.Fatalf("读取订单失败: %v", err)
|
||||
}
|
||||
if saved.Status != "pending_checkout_confirm" || saved.HandoffStatus != "pending_owner_checkout" {
|
||||
t.Fatalf("订单状态 = %s/%s, want pending_checkout_confirm/pending_owner_checkout", saved.Status, saved.HandoffStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClampRenterRetentionDays(t *testing.T) {
|
||||
tests := []struct {
|
||||
value int
|
||||
|
||||
Reference in New Issue
Block a user