fix(order): 支持待号主交接及异常状态下客服重置交接倒计时
This commit is contained in:
@@ -254,7 +254,8 @@ func (r *Repository) AdminResetHandoff(ctx context.Context, adminID uint64, orde
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resetTargetForOrder(order model.RentalOrder) (adminResetTarget, bool) {
|
func resetTargetForOrder(order model.RentalOrder) (adminResetTarget, bool) {
|
||||||
if order.Status == orderStatusPendingHandoff && order.HandoffStatus == handoffStatusOwnerTimeout {
|
if (order.Status == orderStatusPendingHandoff || order.Status == orderStatusAbnormal) &&
|
||||||
|
(order.HandoffStatus == handoffStatusOwnerTimeout || order.HandoffStatus == handoffStatusPendingOwner) {
|
||||||
return adminResetTarget{
|
return adminResetTarget{
|
||||||
OrderStatus: orderStatusPendingHandoff,
|
OrderStatus: orderStatusPendingHandoff,
|
||||||
HandoffStatus: handoffStatusPendingOwner,
|
HandoffStatus: handoffStatusPendingOwner,
|
||||||
@@ -267,7 +268,8 @@ func resetTargetForOrder(order model.RentalOrder) (adminResetTarget, bool) {
|
|||||||
RefreshStageTime: true,
|
RefreshStageTime: true,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
if order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout {
|
if (order.Status == orderStatusPendingHandoff || order.Status == orderStatusAbnormal) &&
|
||||||
|
(order.HandoffStatus == handoffStatusRenterConfirmTimeout || order.HandoffStatus == handoffStatusPendingRenterConfirm) {
|
||||||
return adminResetTarget{
|
return adminResetTarget{
|
||||||
OrderStatus: orderStatusPendingHandoff,
|
OrderStatus: orderStatusPendingHandoff,
|
||||||
HandoffStatus: handoffStatusPendingRenterConfirm,
|
HandoffStatus: handoffStatusPendingRenterConfirm,
|
||||||
@@ -280,9 +282,10 @@ func resetTargetForOrder(order model.RentalOrder) (adminResetTarget, bool) {
|
|||||||
RefreshStageTime: true,
|
RefreshStageTime: true,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
if order.Status == orderStatusAbnormal &&
|
if (order.Status == orderStatusPendingCheckoutConfirm || order.Status == orderStatusAbnormal) &&
|
||||||
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
||||||
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout) {
|
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusPendingOwnerCheckout) {
|
||||||
return adminResetTarget{
|
return adminResetTarget{
|
||||||
OrderStatus: orderStatusPendingCheckoutConfirm,
|
OrderStatus: orderStatusPendingCheckoutConfirm,
|
||||||
HandoffStatus: handoffStatusPendingOwnerCheckout,
|
HandoffStatus: handoffStatusPendingOwnerCheckout,
|
||||||
@@ -295,7 +298,8 @@ func resetTargetForOrder(order model.RentalOrder) (adminResetTarget, bool) {
|
|||||||
RefreshStageTime: true,
|
RefreshStageTime: true,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
if order.Status == orderStatusOverdue && order.HandoffStatus == handoffStatusReturnOverdue {
|
if (order.Status == orderStatusOverdue || order.Status == orderStatusRenting || order.Status == orderStatusAbnormal) &&
|
||||||
|
order.HandoffStatus == handoffStatusReturnOverdue {
|
||||||
return adminResetTarget{
|
return adminResetTarget{
|
||||||
OrderStatus: orderStatusRenting,
|
OrderStatus: orderStatusRenting,
|
||||||
HandoffStatus: handoffStatusReceived,
|
HandoffStatus: handoffStatusReceived,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func writeOrderError(c *gin.Context, err error) {
|
|||||||
case errors.Is(err, ErrOrderCannotForceHandoff):
|
case errors.Is(err, ErrOrderCannotForceHandoff):
|
||||||
response.Error(c, http.StatusConflict, "order_cannot_force_handoff", "当前订单不能客服一键交接")
|
response.Error(c, http.StatusConflict, "order_cannot_force_handoff", "当前订单不能客服一键交接")
|
||||||
case errors.Is(err, ErrOrderCannotResetHandoff):
|
case errors.Is(err, ErrOrderCannotResetHandoff):
|
||||||
response.Error(c, http.StatusConflict, "order_cannot_reset_handoff", "仅超时卡住的订单可重置到对应待办阶段")
|
response.Error(c, http.StatusConflict, "order_cannot_reset_handoff", "当前订单状态不可重置待办")
|
||||||
case errors.Is(err, ErrOrderCannotReceive):
|
case errors.Is(err, ErrOrderCannotReceive):
|
||||||
response.Error(c, http.StatusConflict, "order_cannot_receive", "当前订单不能确认收号")
|
response.Error(c, http.StatusConflict, "order_cannot_receive", "当前订单不能确认收号")
|
||||||
case errors.Is(err, ErrOrderCannotReturn):
|
case errors.Is(err, ErrOrderCannotReturn):
|
||||||
|
|||||||
@@ -1026,6 +1026,101 @@ func TestCancelDoesNotReleaseAssetsWhenSiblingOrderActive(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminResetOwnerHandoff(t *testing.T) {
|
||||||
|
db := setupOrderTestDB(t)
|
||||||
|
repo := NewRepository(db)
|
||||||
|
|
||||||
|
owner := model.User{Phone: "13800003009"}
|
||||||
|
renter := model.User{Phone: "13900003009"}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 测试在待号主交接(pending_owner)状态下重置交接
|
||||||
|
oldTime := time.Now().Add(-1 * time.Hour)
|
||||||
|
order := model.RentalOrder{
|
||||||
|
OrderNo: "ORD-RESET-OWNER-001",
|
||||||
|
ListingID: 1,
|
||||||
|
AccountID: 1,
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
RenterID: renter.ID,
|
||||||
|
Status: orderStatusPendingHandoff,
|
||||||
|
HandoffStatus: handoffStatusPendingOwner,
|
||||||
|
HandoffStartedAt: &oldTime,
|
||||||
|
SettlementStatus: settlementStatusPending,
|
||||||
|
}
|
||||||
|
if err := db.Create(&order).Error; err != nil {
|
||||||
|
t.Fatalf("create order failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := adminActionsForOrder(order)
|
||||||
|
if actions == nil || actions.ResetHandoff == nil || !actions.ResetHandoff.Enabled {
|
||||||
|
t.Fatalf("expected ResetHandoff action to be enabled for pending_owner, got %+v", actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.AdminResetHandoff(t.Context(), 1, order.ID, AdminActionRequest{Reason: "客服协调重置交接时限"}, AuditMeta{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AdminResetHandoff() 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 != orderStatusPendingHandoff {
|
||||||
|
t.Fatalf("status = %q, want %q", saved.Status, orderStatusPendingHandoff)
|
||||||
|
}
|
||||||
|
if saved.HandoffStatus != handoffStatusPendingOwner {
|
||||||
|
t.Fatalf("handoff status = %q, want %q", saved.HandoffStatus, handoffStatusPendingOwner)
|
||||||
|
}
|
||||||
|
if saved.HandoffStartedAt == nil || !saved.HandoffStartedAt.After(oldTime) {
|
||||||
|
t.Fatal("handoff started at should be refreshed to current time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 测试在号主交接超时(owner_timeout)状态下重置交接
|
||||||
|
orderTimeout := model.RentalOrder{
|
||||||
|
OrderNo: "ORD-RESET-OWNER-002",
|
||||||
|
ListingID: 1,
|
||||||
|
AccountID: 1,
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
RenterID: renter.ID,
|
||||||
|
Status: orderStatusPendingHandoff,
|
||||||
|
HandoffStatus: handoffStatusOwnerTimeout,
|
||||||
|
HandoffStartedAt: &oldTime,
|
||||||
|
SettlementStatus: settlementStatusPending,
|
||||||
|
}
|
||||||
|
if err := db.Create(&orderTimeout).Error; err != nil {
|
||||||
|
t.Fatalf("create order failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsTimeout := adminActionsForOrder(orderTimeout)
|
||||||
|
if actionsTimeout == nil || actionsTimeout.ResetHandoff == nil || !actionsTimeout.ResetHandoff.Enabled {
|
||||||
|
t.Fatalf("expected ResetHandoff action to be enabled for owner_timeout, got %+v", actionsTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.AdminResetHandoff(t.Context(), 1, orderTimeout.ID, AdminActionRequest{Reason: "超时后人工重置交接"}, AuditMeta{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AdminResetHandoff() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var savedTimeout model.RentalOrder
|
||||||
|
if err := db.First(&savedTimeout, orderTimeout.ID).Error; err != nil {
|
||||||
|
t.Fatalf("load order failed: %v", err)
|
||||||
|
}
|
||||||
|
if savedTimeout.Status != orderStatusPendingHandoff {
|
||||||
|
t.Fatalf("status = %q, want %q", savedTimeout.Status, orderStatusPendingHandoff)
|
||||||
|
}
|
||||||
|
if savedTimeout.HandoffStatus != handoffStatusPendingOwner {
|
||||||
|
t.Fatalf("handoff status = %q, want %q", savedTimeout.HandoffStatus, handoffStatusPendingOwner)
|
||||||
|
}
|
||||||
|
if savedTimeout.HandoffStartedAt == nil || !savedTimeout.HandoffStartedAt.After(oldTime) {
|
||||||
|
t.Fatal("handoff started at should be refreshed to current time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminResetCheckoutConfirmTimeout(t *testing.T) {
|
func TestAdminResetCheckoutConfirmTimeout(t *testing.T) {
|
||||||
db := setupOrderTestDB(t)
|
db := setupOrderTestDB(t)
|
||||||
repo := NewRepository(db)
|
repo := NewRepository(db)
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ const actionTitle = computed(() => {
|
|||||||
return '标记订单异常'
|
return '标记订单异常'
|
||||||
})
|
})
|
||||||
const actionConfirmButtonType = computed(() => {
|
const actionConfirmButtonType = computed(() => {
|
||||||
|
if (actionType.value === 'reset') return 'success'
|
||||||
if (['deposit_release', 'platform_checkout_confirm'].includes(actionType.value)) return 'primary'
|
if (['deposit_release', 'platform_checkout_confirm'].includes(actionType.value)) return 'primary'
|
||||||
if (actionType.value === 'platform_checkout_dispute') return 'warning'
|
if (actionType.value === 'platform_checkout_dispute') return 'warning'
|
||||||
return 'danger'
|
return 'danger'
|
||||||
@@ -1488,6 +1489,14 @@ function formatRatioNumber(value: number) {
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
/>
|
/>
|
||||||
|
<el-alert
|
||||||
|
v-if="actionType === 'reset'"
|
||||||
|
title="重置阶段交接倒计时"
|
||||||
|
description="重置后将重新计算交接时限,恢复到待办状态,并重新向号主和租客推送交接通知。"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="reason"
|
v-model="reason"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
|
|||||||
Reference in New Issue
Block a user