修复平台代管订单流程

This commit is contained in:
yml2213
2026-07-25 19:17:19 +08:00
parent e0cb15f408
commit 4bc25b87d8
35 changed files with 2216 additions and 284 deletions
+69 -20
View File
@@ -10,6 +10,7 @@ import (
"hfb_sys/backend/internal/listingstatus"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/adminnotification"
"hfb_sys/backend/internal/modules/notification"
"hfb_sys/backend/internal/modules/wallet"
"hfb_sys/backend/pkg/money"
@@ -62,7 +63,12 @@ func (r *Repository) Arbitrate(ctx context.Context, adminID uint64, id uint64, r
order.Status = arbitrateOrderStatus(req.Result)
order.SettlementStatus = "arbitrated"
order.SettledAt = &now
order.OwnerSettledAt = &now
if isPlatformManagedOrder(order) {
order.OwnerSettledAt = nil
applyPlatformManagedSettlement(&order, &settlement)
} else {
order.OwnerSettledAt = &now
}
if order.HandoffStatus != "cancelled" && order.HandoffStatus != "returned" {
order.HandoffStatus = "arbitrated"
}
@@ -147,25 +153,32 @@ func (r *Repository) Arbitrate(ctx context.Context, adminID uint64, id uint64, r
}); err != nil {
return err
}
if err := notification.Append(tx,
notification.Entry{
UserID: order.RenterID,
Type: "arbitration",
Title: "申诉仲裁已完成",
Content: "客服已给出仲裁结果,请在订单和申诉记录中查看处理说明。",
BizType: "dispute",
BizID: &disputeID,
},
notification.Entry{
UserID: order.OwnerID,
Type: "arbitration",
Title: "申诉仲裁已完成",
Content: "客服已给出仲裁结果,请在订单和申诉记录中查看处理说明。",
BizType: "dispute",
BizID: &disputeID,
},
); err != nil {
return err
renterNotification := notification.Entry{
UserID: order.RenterID,
Type: "arbitration",
Title: "申诉仲裁已完成",
Content: "客服已给出仲裁结果,请在订单和申诉记录中查看处理说明。",
BizType: "dispute",
BizID: &disputeID,
}
if isPlatformManagedOrder(order) {
if err := notification.Append(tx, renterNotification); err != nil {
return err
}
} else {
if err := notification.Append(tx,
renterNotification,
notification.Entry{
UserID: order.OwnerID,
Type: "arbitration",
Title: "申诉仲裁已完成",
Content: "客服已给出仲裁结果,请在订单和申诉记录中查看处理说明。",
BizType: "dispute",
BizID: &disputeID,
},
); err != nil {
return err
}
}
return nil
})
@@ -181,6 +194,42 @@ func (r *Repository) Arbitrate(ctx context.Context, adminID uint64, id uint64, r
return &dto, nil
}
func isPlatformManagedOrder(order model.RentalOrder) bool {
return order.SettlementMode == "platform_managed" || order.HandoffMode == "platform"
}
func appendPlatformManagedAdminNotification(tx *gorm.DB, order model.RentalOrder, typ string, title string, content string) error {
if order.ManagedAdminID == nil || *order.ManagedAdminID == 0 || title == "" {
return nil
}
return adminnotification.Append(tx, adminnotification.Entry{
AdminUserID: *order.ManagedAdminID,
Type: typ,
Title: title,
Content: content,
})
}
func applyPlatformManagedSettlement(order *model.RentalOrder, settlement *arbitrationSettlement) {
if order == nil || settlement == nil {
return
}
filtered := settlement.Entries[:0]
for _, entry := range settlement.Entries {
if entry.UserID == order.OwnerID && entry.Direction == "in" && entry.BizType == "arbitration_owner_income" {
continue
}
filtered = append(filtered, entry)
}
settlement.Entries = filtered
order.OfflineSettlementAmountCent = settlement.OwnerIncomeAmountCent
if settlement.OwnerIncomeAmountCent > 0 {
order.OfflineSettlementStatus = "pending"
} else {
order.OfflineSettlementStatus = "none"
}
}
type arbitrationSettlement struct {
Entries []wallet.Entry
RenterRefundAmountCent int64
+29 -19
View File
@@ -115,25 +115,35 @@ func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64,
title = "订单进入结账争议"
content = "对方已发起结账争议,请等待客服仲裁或补充结账证据。"
}
if err := notification.Append(tx,
notification.Entry{
UserID: targetID,
Type: "dispute",
Title: title,
Content: content,
BizType: "dispute",
BizID: &disputeID,
},
notification.Entry{
UserID: userID,
Type: "dispute",
Title: "申诉已提交",
Content: "申诉已进入待处理状态,客服仲裁后会通知双方。",
BizType: "dispute",
BizID: &disputeID,
},
); err != nil {
return err
initiatorNotification := notification.Entry{
UserID: userID,
Type: "dispute",
Title: "申诉已提交",
Content: "申诉已进入待处理状态,客服仲裁后会通知双方。",
BizType: "dispute",
BizID: &disputeID,
}
if isPlatformManagedOrder(order) {
if err := appendPlatformManagedAdminNotification(tx, order, "dispute", title, content); err != nil {
return err
}
if err := notification.Append(tx, initiatorNotification); err != nil {
return err
}
} else {
if err := notification.Append(tx,
notification.Entry{
UserID: targetID,
Type: "dispute",
Title: title,
Content: content,
BizType: "dispute",
BizID: &disputeID,
},
initiatorNotification,
); err != nil {
return err
}
}
createdID = row.ID
return nil
@@ -169,6 +169,76 @@ func TestRepositoryCancelRestoresCheckoutDisputeStatus(t *testing.T) {
}
}
func TestPlatformManagedArbitrationUsesOfflineSettlement(t *testing.T) {
db := setupDisputeTestDB(t)
repo := NewRepository(db)
adminID := uint64(77)
owner, renter, order := createDisputeOrderFixture(t, db, model.RentalOrder{
Status: "renting",
HandoffStatus: "received",
SettlementStatus: "unsettled",
HandoffMode: "platform",
SettlementMode: "platform_managed",
ManagedAdminID: &adminID,
RentAmountCent: 10000,
OwnerRentAmountCent: 8000,
PlatformFeeCent: 2000,
OfflineSettlementStatus: "none",
OfflineSettlementAmountCent: 0,
})
created, err := repo.Create(t.Context(), renter.ID, order.ID, CreateRequest{
Type: "cannot_login",
Description: "外部上传账号需要客服处理",
})
if err != nil {
t.Fatalf("创建申诉失败: %v", err)
}
var ownerNotificationCount int64
if err := db.Model(&model.Notification{}).Where("user_id = ?", owner.ID).Count(&ownerNotificationCount).Error; err != nil {
t.Fatalf("统计号主通知失败: %v", err)
}
if ownerNotificationCount != 0 {
t.Fatalf("owner notification count = %d, want 0", ownerNotificationCount)
}
var adminNotificationCount int64
if err := db.Model(&model.AdminNotification{}).Where("admin_user_id = ?", adminID).Count(&adminNotificationCount).Error; err != nil {
t.Fatalf("统计客服通知失败: %v", err)
}
if adminNotificationCount == 0 {
t.Fatal("platform managed dispute should notify managed admin")
}
if _, err := repo.Arbitrate(t.Context(), adminID, created.ID, ArbitrateRequest{
Result: "release_deposit",
Remark: "确认租金给卖家,线下结算",
}, AuditMeta{}); err != nil {
t.Fatalf("仲裁失败: %v", err)
}
var saved model.RentalOrder
if err := db.First(&saved, order.ID).Error; err != nil {
t.Fatalf("读取订单失败: %v", err)
}
if saved.OfflineSettlementStatus != "pending" {
t.Fatalf("offline settlement status = %q, want pending", saved.OfflineSettlementStatus)
}
if saved.OfflineSettlementAmountCent != 8000 {
t.Fatalf("offline settlement amount = %d, want 8000", saved.OfflineSettlementAmountCent)
}
if saved.OwnerSettledAt != nil {
t.Fatalf("owner settled at = %#v, want nil before offline settlement", saved.OwnerSettledAt)
}
var ownerLedgerCount int64
if err := db.Model(&model.WalletLedger{}).Where("user_id = ?", owner.ID).Count(&ownerLedgerCount).Error; err != nil {
t.Fatalf("统计钱包流水失败: %v", err)
}
if ownerLedgerCount != 0 {
t.Fatalf("owner wallet ledger count = %d, want 0", ownerLedgerCount)
}
}
func TestBuildArbitrationSettlementSkipsFrozenReleaseWhenNoFrozenBalance(t *testing.T) {
order := model.RentalOrder{
ID: 11,