From cc974f954fd3b5bfaff371b2366a697f06e9c399 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 20 Aug 2026 16:43:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B9=B3=E5=8F=B0=E4=BB=A3?= =?UTF-8?q?=E7=AE=A1=E5=AE=A2=E6=9C=8D=E4=B8=80=E9=94=AE=E4=BA=A4=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/order/dto.go | 2 +- .../internal/modules/order/force_handoff.go | 19 +++++- .../order/repository_integration_test.go | 68 ++++++++++++++++++- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/backend/internal/modules/order/dto.go b/backend/internal/modules/order/dto.go index 067deff..344ecd3 100644 --- a/backend/internal/modules/order/dto.go +++ b/backend/internal/modules/order/dto.go @@ -139,7 +139,7 @@ type PlatformHandoffRequest struct { Reason string `json:"reason" binding:"required"` } -// ForceHandoffRequest 客服确认普通号主订单已完成线下交接。 +// ForceHandoffRequest 客服确认订单已完成交接。 type ForceHandoffRequest struct { Content string `json:"content"` Reason string `json:"reason" binding:"required"` diff --git a/backend/internal/modules/order/force_handoff.go b/backend/internal/modules/order/force_handoff.go index 1fb08a5..9cd1d7d 100644 --- a/backend/internal/modules/order/force_handoff.go +++ b/backend/internal/modules/order/force_handoff.go @@ -13,7 +13,13 @@ import ( ) func canAdminForceHandoff(order model.RentalOrder) bool { - if isPlatformHandoffOrder(order) || isPlatformSettlementOrder(order) { + if isPlatformHandoffOrder(order) { + if order.Status == orderStatusPendingHandoff { + return order.HandoffStatus == handoffStatusPendingRenterConfirm + } + return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout + } + if isPlatformSettlementOrder(order) { return false } if order.Status == orderStatusPendingHandoff { @@ -24,11 +30,18 @@ func canAdminForceHandoff(order model.RentalOrder) bool { return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout } +func forceHandoffRecordType(order model.RentalOrder) string { + if isPlatformHandoffOrder(order) { + return "platform_handoff" + } + return "owner_handoff" +} + func forceHandoffNeedsContent(order model.RentalOrder) bool { return order.HandoffStatus == handoffStatusPendingOwner || order.HandoffStatus == handoffStatusOwnerTimeout } -// AdminForceHandoff 由客服确认普通号主订单已完成线下交接,并立即开始租期。 +// AdminForceHandoff 由客服确认订单已完成交接,并立即开始租期。 func (r *Repository) AdminForceHandoff(ctx context.Context, adminID uint64, orderID uint64, req ForceHandoffRequest, meta AuditMeta) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder @@ -60,7 +73,7 @@ func (r *Repository) AdminForceHandoff(ctx context.Context, adminID uint64, orde if !needsContent { var handoffCount int64 if err := tx.Model(&model.HandoffRecord{}). - Where("order_id = ? AND type = ?", order.ID, "owner_handoff"). + Where("order_id = ? AND type = ?", order.ID, forceHandoffRecordType(order)). Count(&handoffCount).Error; err != nil { return err } diff --git a/backend/internal/modules/order/repository_integration_test.go b/backend/internal/modules/order/repository_integration_test.go index 506d14d..a91b03e 100644 --- a/backend/internal/modules/order/repository_integration_test.go +++ b/backend/internal/modules/order/repository_integration_test.go @@ -1079,7 +1079,7 @@ func TestAdminForceHandoffStartsNormalOwnerOrder(t *testing.T) { } } -func TestAdminForceHandoffUsesExistingOwnerRecordAndRejectsPlatformOrder(t *testing.T) { +func TestAdminForceHandoffUsesExistingOwnerRecordAndRejectsPlatformOrderBeforeHandoff(t *testing.T) { db := setupOrderTestDB(t) if err := db.AutoMigrate(&model.Dispute{}); err != nil { t.Fatalf("migrate disputes failed: %v", err) @@ -1160,6 +1160,72 @@ func TestAdminForceHandoffUsesExistingOwnerRecordAndRejectsPlatformOrder(t *test } } +func TestAdminForceHandoffStartsPlatformManagedOrderAfterPlatformHandoff(t *testing.T) { + db := setupOrderTestDB(t) + if err := db.AutoMigrate(&model.Dispute{}); err != nil { + t.Fatalf("migrate disputes failed: %v", err) + } + repo := NewRepository(db) + + owner := model.User{Phone: "admin:force-handoff"} + renter := model.User{Phone: "13900003008"} + if err := db.Create(&owner).Error; err != nil { + t.Fatalf("create owner failed: %v", err) + } + if err := db.Create(&renter).Error; err != nil { + t.Fatalf("create renter failed: %v", err) + } + + order := model.RentalOrder{ + OrderNo: "ORD-FORCE-HANDOFF-PLATFORM-001", + ListingID: 4, + AccountID: 4, + OwnerID: owner.ID, + RenterID: renter.ID, + Status: orderStatusPendingHandoff, + HandoffStatus: handoffStatusPendingRenterConfirm, + HandoffMode: handoffModePlatform, + SettlementMode: settlementModePlatformManaged, + AccountSnapshot: datatypes.JSON([]byte(`{ + "haf_coin_amount": 100000000, + "daily_loss_m": 50 + }`)), + } + if err := db.Create(&order).Error; err != nil { + t.Fatalf("create order failed: %v", err) + } + if err := db.Create(&model.HandoffRecord{ + OrderID: order.ID, + FromUserID: 99, + ToUserID: renter.ID, + Type: "platform_handoff", + Content: "客服已提交账号和登录说明", + }).Error; err != nil { + t.Fatalf("create platform handoff record failed: %v", err) + } + + if err := repo.AdminForceHandoff(t.Context(), 99, order.ID, ForceHandoffRequest{ + Reason: "租客未确认,客服已核实账号可正常使用", + }, AuditMeta{}); err != nil { + t.Fatalf("AdminForceHandoff() error = %v", err) + } + + var saved model.RentalOrder + if err := db.First(&saved, order.ID).Error; err != nil { + t.Fatalf("load order failed: %v", err) + } + if saved.Status != orderStatusRenting || saved.HandoffStatus != handoffStatusReceived { + t.Fatalf("status = %s/%s, want renting/received", saved.Status, saved.HandoffStatus) + } + var record model.HandoffRecord + if err := db.Where("order_id = ? AND type = ?", order.ID, "admin_force_handoff").First(&record).Error; err != nil { + t.Fatalf("find force handoff record failed: %v", err) + } + if record.Content != "客服已确认双方完成交接,订单已进入使用中。" { + t.Fatalf("record content = %q", record.Content) + } +} + func TestSubmitCheckoutRefreshesStageTime(t *testing.T) { db := setupOrderTestDB(t) repo := NewRepository(db)