diff --git a/backend/internal/modules/adminfinance/helper.go b/backend/internal/modules/adminfinance/helper.go index c43e012..8c30ac4 100644 --- a/backend/internal/modules/adminfinance/helper.go +++ b/backend/internal/modules/adminfinance/helper.go @@ -53,7 +53,7 @@ func normalRefundBizTypes() []string { // settledSettlementStatuses 是视为"已完成结算"的结算状态集合。 // 预计收入只统计尚未结算的订单,这些状态需从预计口径中排除。 func settledSettlementStatuses() []string { - return []string{"settled", "closed"} + return []string{"settled", "closed", "arbitrated"} } // nonBillableOrderStatuses 是不产生平台收入的订单状态集合(待支付、已取消、已关闭)。 diff --git a/backend/internal/modules/adminfinance/helper_test.go b/backend/internal/modules/adminfinance/helper_test.go index 3fcedea..adc850f 100644 --- a/backend/internal/modules/adminfinance/helper_test.go +++ b/backend/internal/modules/adminfinance/helper_test.go @@ -45,3 +45,15 @@ func TestRefundBizTypesIncludeMohongRefund(t *testing.T) { t.Fatal("refundBizTypes missing mohong_refund") } } + +func TestSettledSettlementStatusesIncludeArbitrated(t *testing.T) { + values := map[string]bool{} + for _, item := range settledSettlementStatuses() { + values[item] = true + } + for _, want := range []string{"settled", "closed", "arbitrated"} { + if !values[want] { + t.Fatalf("settledSettlementStatuses missing %s", want) + } + } +} diff --git a/backend/internal/modules/order/constants.go b/backend/internal/modules/order/constants.go index 29bae02..cab53be 100644 --- a/backend/internal/modules/order/constants.go +++ b/backend/internal/modules/order/constants.go @@ -32,11 +32,12 @@ const ( handoffStatusAdminClosed = "admin_closed" handoffStatusAdminAbnormal = "admin_abnormal" - settlementStatusUnsettled = "unsettled" - settlementStatusPending = "pending" - settlementStatusSettled = "settled" - settlementStatusClosed = "closed" - settlementStatusDisputed = "disputed" + settlementStatusUnsettled = "unsettled" + settlementStatusPending = "pending" + settlementStatusSettled = "settled" + settlementStatusClosed = "closed" + settlementStatusDisputed = "disputed" + settlementStatusArbitrated = "arbitrated" handoffModeOwner = "owner" handoffModePlatform = "platform" diff --git a/backend/internal/modules/order/platform_managed.go b/backend/internal/modules/order/platform_managed.go index c7bffbb..2c0b07b 100644 --- a/backend/internal/modules/order/platform_managed.go +++ b/backend/internal/modules/order/platform_managed.go @@ -66,7 +66,7 @@ func canAdminPlatformCheckoutConfirm(order model.RentalOrder) bool { func canAdminMarkOfflineSettlement(order model.RentalOrder) bool { return isPlatformSettlementOrder(order) && order.Status == orderStatusCompleted && - order.SettlementStatus == settlementStatusSettled && + (order.SettlementStatus == settlementStatusSettled || order.SettlementStatus == settlementStatusArbitrated) && effectiveOfflineSettlementStatus(order) == offlineSettlementStatusPending && order.OfflineSettlementAmountCent > 0 } diff --git a/backend/internal/modules/order/repository_integration_test.go b/backend/internal/modules/order/repository_integration_test.go index 835e1b8..c939399 100644 --- a/backend/internal/modules/order/repository_integration_test.go +++ b/backend/internal/modules/order/repository_integration_test.go @@ -1580,6 +1580,46 @@ func TestAdminPlatformCheckoutCounterCanBeAcceptedByRenter(t *testing.T) { } } +func TestAdminMarkOfflineSettlementAllowsArbitratedPlatformOrder(t *testing.T) { + db := setupOrderTestDB(t) + repo := NewRepository(db) + adminID := uint64(79) + order := model.RentalOrder{ + OrderNo: "ORD-OFFLINE-ARBITRATED-001", + ListingID: 1, + AccountID: 1, + OwnerID: 1, + RenterID: 2, + Status: orderStatusCompleted, + SettlementStatus: settlementStatusArbitrated, + HandoffMode: handoffModePlatform, + SettlementMode: settlementModePlatformManaged, + OfflineSettlementStatus: offlineSettlementStatusPending, + OfflineSettlementAmountCent: 8000, + } + if err := db.Create(&order).Error; err != nil { + t.Fatalf("create order failed: %v", err) + } + + if !canAdminMarkOfflineSettlement(order) { + t.Fatal("arbitrated platform order should allow offline settlement") + } + if err := repo.AdminMarkOfflineSettlement(t.Context(), adminID, order.ID, OfflineSettlementRequest{Remark: "仲裁后线下转账"}, AuditMeta{}); err != nil { + t.Fatalf("AdminMarkOfflineSettlement() 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.OfflineSettlementStatus != offlineSettlementStatusSettled { + t.Fatalf("offline settlement status = %q, want %q", saved.OfflineSettlementStatus, offlineSettlementStatusSettled) + } + if saved.OfflineSettledBy == nil || *saved.OfflineSettledBy != adminID { + t.Fatalf("offline settled by = %#v, want %d", saved.OfflineSettledBy, adminID) + } +} + func TestPlatformManagedCheckoutIncludesConsumableAndDepositCompensation(t *testing.T) { db := setupOrderTestDB(t) repo := NewRepository(db)