fix(order): 修复超时与异常过渡态下的状态机流转及权限断层
This commit is contained in:
@@ -32,7 +32,8 @@ func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64,
|
|||||||
if order.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" {
|
if order.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" {
|
||||||
return ErrInvalidDispute
|
return ErrInvalidDispute
|
||||||
}
|
}
|
||||||
isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept"
|
isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept" ||
|
||||||
|
(order.Status == "abnormal" && (order.HandoffStatus == "owner_checkout_confirm_timeout" || order.HandoffStatus == "owner_return_confirm_timeout" || order.HandoffStatus == "pending_owner_checkout"))
|
||||||
var count int64
|
var count int64
|
||||||
if err := tx.Model(&model.Dispute{}).
|
if err := tx.Model(&model.Dispute{}).
|
||||||
Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}).
|
Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}).
|
||||||
|
|||||||
@@ -112,7 +112,15 @@ func (r *Repository) ConfirmCheckout(ctx context.Context, userID uint64, orderID
|
|||||||
if order.OwnerID != userID {
|
if order.OwnerID != userID {
|
||||||
return ErrPermissionDenied
|
return ErrPermissionDenied
|
||||||
}
|
}
|
||||||
if order.Status != orderStatusPendingCheckoutConfirm || order.HandoffStatus != handoffStatusPendingOwnerCheckout {
|
isOwnerPending := order.Status == orderStatusPendingCheckoutConfirm ||
|
||||||
|
(order.Status == orderStatusAbnormal &&
|
||||||
|
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusPendingOwnerCheckout))
|
||||||
|
if !isOwnerPending ||
|
||||||
|
(order.HandoffStatus != handoffStatusPendingOwnerCheckout &&
|
||||||
|
order.HandoffStatus != handoffStatusOwnerCheckoutConfirmTimeout &&
|
||||||
|
order.HandoffStatus != handoffStatusOwnerReturnConfirmTimeout) {
|
||||||
return ErrCheckoutCannotConfirm
|
return ErrCheckoutCannotConfirm
|
||||||
}
|
}
|
||||||
before := orderState(order)
|
before := orderState(order)
|
||||||
@@ -162,7 +170,13 @@ func (r *Repository) CounterCheckout(ctx context.Context, userID uint64, orderID
|
|||||||
if !isOwner && !isRenter {
|
if !isOwner && !isRenter {
|
||||||
return ErrPermissionDenied
|
return ErrPermissionDenied
|
||||||
}
|
}
|
||||||
if order.Status != orderStatusPendingCheckoutConfirm && order.Status != orderStatusPendingCheckoutAccept {
|
isOwnerPending := order.Status == orderStatusPendingCheckoutConfirm ||
|
||||||
|
(order.Status == orderStatusAbnormal &&
|
||||||
|
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusPendingOwnerCheckout))
|
||||||
|
isRenterPending := order.Status == orderStatusPendingCheckoutAccept
|
||||||
|
if !isOwnerPending && !isRenterPending {
|
||||||
return ErrCheckoutCannotCounter
|
return ErrCheckoutCannotCounter
|
||||||
}
|
}
|
||||||
before := orderState(order)
|
before := orderState(order)
|
||||||
@@ -171,10 +185,10 @@ func (r *Repository) CounterCheckout(ctx context.Context, userID uint64, orderID
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
turn := normalizeCheckoutTurn(checkout)
|
turn := normalizeCheckoutTurn(checkout)
|
||||||
if isOwner && (turn != checkoutTurnOwner || order.Status != orderStatusPendingCheckoutConfirm) {
|
if isOwner && (turn != checkoutTurnOwner || !isOwnerPending) {
|
||||||
return ErrCheckoutCannotCounter
|
return ErrCheckoutCannotCounter
|
||||||
}
|
}
|
||||||
if isRenter && (turn != checkoutTurnRenter || order.Status != orderStatusPendingCheckoutAccept) {
|
if isRenter && (turn != checkoutTurnRenter || !isRenterPending) {
|
||||||
return ErrCheckoutCannotCounter
|
return ErrCheckoutCannotCounter
|
||||||
}
|
}
|
||||||
round := checkout.RoundCount
|
round := checkout.RoundCount
|
||||||
|
|||||||
@@ -15,20 +15,22 @@ import (
|
|||||||
|
|
||||||
func canAdminForceHandoff(order model.RentalOrder) bool {
|
func canAdminForceHandoff(order model.RentalOrder) bool {
|
||||||
if isPlatformHandoffOrder(order) {
|
if isPlatformHandoffOrder(order) {
|
||||||
if order.Status == orderStatusPendingHandoff {
|
if order.Status == orderStatusPendingHandoff || order.Status == orderStatusAbnormal {
|
||||||
return order.HandoffStatus == handoffStatusPendingRenterConfirm
|
return order.HandoffStatus == handoffStatusPendingRenterConfirm ||
|
||||||
|
order.HandoffStatus == handoffStatusRenterConfirmTimeout
|
||||||
}
|
}
|
||||||
return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout
|
return false
|
||||||
}
|
}
|
||||||
if isPlatformSettlementOrder(order) {
|
if isPlatformSettlementOrder(order) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if order.Status == orderStatusPendingHandoff {
|
if order.Status == orderStatusPendingHandoff || order.Status == orderStatusAbnormal {
|
||||||
return order.HandoffStatus == handoffStatusPendingOwner ||
|
return order.HandoffStatus == handoffStatusPendingOwner ||
|
||||||
order.HandoffStatus == handoffStatusOwnerTimeout ||
|
order.HandoffStatus == handoffStatusOwnerTimeout ||
|
||||||
order.HandoffStatus == handoffStatusPendingRenterConfirm
|
order.HandoffStatus == handoffStatusPendingRenterConfirm ||
|
||||||
|
order.HandoffStatus == handoffStatusRenterConfirmTimeout
|
||||||
}
|
}
|
||||||
return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func forceHandoffRecordType(order model.RentalOrder) string {
|
func forceHandoffRecordType(order model.RentalOrder) string {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func canAdminPlatformHandoff(order model.RentalOrder) bool {
|
|||||||
if !isPlatformHandoffOrder(order) {
|
if !isPlatformHandoffOrder(order) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if order.Status != orderStatusPendingHandoff {
|
if order.Status != orderStatusPendingHandoff && order.Status != orderStatusAbnormal {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return order.HandoffStatus == handoffStatusPendingOwner ||
|
return order.HandoffStatus == handoffStatusPendingOwner ||
|
||||||
@@ -61,7 +61,9 @@ func canAdminPlatformCheckoutConfirm(order model.RentalOrder) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return order.Status == orderStatusAbnormal &&
|
return order.Status == orderStatusAbnormal &&
|
||||||
order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout
|
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusPendingOwnerCheckout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func canAdminMarkOfflineSettlement(order model.RentalOrder) bool {
|
func canAdminMarkOfflineSettlement(order model.RentalOrder) bool {
|
||||||
@@ -98,6 +100,7 @@ func (r *Repository) AdminPlatformHandoff(ctx context.Context, adminID uint64, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
order.Status = orderStatusPendingHandoff
|
||||||
order.HandoffStatus = handoffStatusPendingRenterConfirm
|
order.HandoffStatus = handoffStatusPendingRenterConfirm
|
||||||
order.HandoffStartedAt = &now
|
order.HandoffStartedAt = &now
|
||||||
if err := appendOrderEvent(tx, order, "handoff", "platform_handoff_submitted", processlog.ActorAdmin, adminID, processlog.ActorUser, &order.RenterID, req.Content, req.Reason, nil, before, nil); err != nil {
|
if err := appendOrderEvent(tx, order, "handoff", "platform_handoff_submitted", processlog.ActorAdmin, adminID, processlog.ActorUser, &order.RenterID, req.Content, req.Reason, nil, before, nil); err != nil {
|
||||||
|
|||||||
@@ -374,7 +374,13 @@ func applyCheckoutActionFlags(dto *CheckoutDTO, checkout model.OrderCheckout, or
|
|||||||
if dto == nil {
|
if dto == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
negotiating := order.Status == orderStatusPendingCheckoutConfirm || order.Status == orderStatusPendingCheckoutAccept
|
isOwnerPending := order.Status == orderStatusPendingCheckoutConfirm ||
|
||||||
|
(order.Status == orderStatusAbnormal &&
|
||||||
|
(order.HandoffStatus == handoffStatusOwnerCheckoutConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusOwnerReturnConfirmTimeout ||
|
||||||
|
order.HandoffStatus == handoffStatusPendingOwnerCheckout))
|
||||||
|
isRenterPending := order.Status == orderStatusPendingCheckoutAccept
|
||||||
|
negotiating := isOwnerPending || isRenterPending
|
||||||
if !negotiating || (checkout.Status != checkoutStatusSubmitted && checkout.Status != checkoutStatusCountered) {
|
if !negotiating || (checkout.Status != checkoutStatusSubmitted && checkout.Status != checkoutStatusCountered) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -382,11 +388,11 @@ func applyCheckoutActionFlags(dto *CheckoutDTO, checkout model.OrderCheckout, or
|
|||||||
round := dto.RoundCount
|
round := dto.RoundCount
|
||||||
isOwner := userID == order.OwnerID
|
isOwner := userID == order.OwnerID
|
||||||
isRenter := userID == order.RenterID
|
isRenter := userID == order.RenterID
|
||||||
if isOwner && turn == checkoutTurnOwner && order.Status == orderStatusPendingCheckoutConfirm {
|
if isOwner && turn == checkoutTurnOwner && isOwnerPending {
|
||||||
dto.CanAccept = dto.ShortfallCent <= 0
|
dto.CanAccept = dto.ShortfallCent <= 0
|
||||||
dto.CanCounter = round < checkoutMaxRounds
|
dto.CanCounter = round < checkoutMaxRounds
|
||||||
}
|
}
|
||||||
if isRenter && turn == checkoutTurnRenter && order.Status == orderStatusPendingCheckoutAccept {
|
if isRenter && turn == checkoutTurnRenter && isRenterPending {
|
||||||
dto.CanAccept = dto.ShortfallCent <= 0
|
dto.CanAccept = dto.ShortfallCent <= 0
|
||||||
dto.CanCounter = round < checkoutMaxRounds
|
dto.CanCounter = round < checkoutMaxRounds
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1385,6 +1385,145 @@ func TestAdminForceHandoffStartsNormalOwnerOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminForceHandoffAbnormalOwnerTimeout(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: "13800003051"}
|
||||||
|
renter := model.User{Phone: "13900003051"}
|
||||||
|
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-ABNORMAL-001",
|
||||||
|
ListingID: 1,
|
||||||
|
AccountID: 1,
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
RenterID: renter.ID,
|
||||||
|
Status: orderStatusAbnormal,
|
||||||
|
HandoffStatus: handoffStatusOwnerTimeout,
|
||||||
|
HandoffMode: handoffModeOwner,
|
||||||
|
}
|
||||||
|
if err := db.Create(&order).Error; err != nil {
|
||||||
|
t.Fatalf("create order failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canAdminForceHandoff(order) {
|
||||||
|
t.Fatal("expected canAdminForceHandoff to be true for abnormal owner_timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.AdminForceHandoff(t.Context(), 99, order.ID, ForceHandoffRequest{
|
||||||
|
Content: "客服核实账号已给租客",
|
||||||
|
Reason: "超时后人工介入交接",
|
||||||
|
}, AuditMeta{})
|
||||||
|
if 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOwnerConfirmCheckoutAfterTimeout(t *testing.T) {
|
||||||
|
db := setupOrderTestDB(t)
|
||||||
|
repo := NewRepository(db)
|
||||||
|
|
||||||
|
owner := model.User{Phone: "13800003052"}
|
||||||
|
renter := model.User{Phone: "13900003052"}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
account := model.GameAccount{
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
ServerRegion: "国服",
|
||||||
|
LoginPlatform: "steam",
|
||||||
|
Title: "测试超时结账账号",
|
||||||
|
}
|
||||||
|
if err := db.Create(&account).Error; err != nil {
|
||||||
|
t.Fatalf("create account failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listing := model.RentalListing{
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
AccountID: account.ID,
|
||||||
|
Status: listingStatusRented,
|
||||||
|
ReviewStatus: listingReviewStatusApproved,
|
||||||
|
PriceCent: 1000,
|
||||||
|
}
|
||||||
|
if err := db.Create(&listing).Error; err != nil {
|
||||||
|
t.Fatalf("create listing failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
order := model.RentalOrder{
|
||||||
|
OrderNo: "ORD-CHECKOUT-TIMEOUT-001",
|
||||||
|
ListingID: listing.ID,
|
||||||
|
AccountID: account.ID,
|
||||||
|
OwnerID: owner.ID,
|
||||||
|
RenterID: renter.ID,
|
||||||
|
Status: orderStatusAbnormal,
|
||||||
|
HandoffStatus: handoffStatusOwnerCheckoutConfirmTimeout,
|
||||||
|
SettlementStatus: settlementStatusPending,
|
||||||
|
RentAmountCent: 1000,
|
||||||
|
DepositAmountCent: 0,
|
||||||
|
}
|
||||||
|
if err := db.Create(&order).Error; err != nil {
|
||||||
|
t.Fatalf("create order failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkout := model.OrderCheckout{
|
||||||
|
OrderID: order.ID,
|
||||||
|
InitiatedBy: renter.ID,
|
||||||
|
ProposedBy: renter.ID,
|
||||||
|
Turn: checkoutTurnOwner,
|
||||||
|
RoundCount: 1,
|
||||||
|
Status: checkoutStatusSubmitted,
|
||||||
|
DepositAmountCent: 0,
|
||||||
|
RentAmountCent: 1000,
|
||||||
|
OwnerIncomeAmountCent: 1000,
|
||||||
|
RenterRefundAmountCent: 0,
|
||||||
|
ShortfallCent: 0,
|
||||||
|
}
|
||||||
|
if err := db.Create(&checkout).Error; err != nil {
|
||||||
|
t.Fatalf("create checkout failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证前端 flags 在超时状态下仍正确开放
|
||||||
|
checkoutDTO := toCheckoutDTOForUser(checkout, owner.ID, order)
|
||||||
|
if !checkoutDTO.CanAccept {
|
||||||
|
t.Fatal("expected CanAccept to be true for owner after timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证号主超时后补确认结账
|
||||||
|
err := repo.ConfirmCheckout(t.Context(), owner.ID, order.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ConfirmCheckout() 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 != orderStatusCompleted {
|
||||||
|
t.Fatalf("order status = %s, want completed", saved.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminForceHandoffUsesExistingOwnerRecordAndRejectsPlatformOrderBeforeHandoff(t *testing.T) {
|
func TestAdminForceHandoffUsesExistingOwnerRecordAndRejectsPlatformOrderBeforeHandoff(t *testing.T) {
|
||||||
db := setupOrderTestDB(t)
|
db := setupOrderTestDB(t)
|
||||||
if err := db.AutoMigrate(&model.Dispute{}); err != nil {
|
if err := db.AutoMigrate(&model.Dispute{}); err != nil {
|
||||||
|
|||||||
@@ -189,13 +189,13 @@ export function useOrderActions(options: UseOrderActionsOptions) {
|
|||||||
)
|
)
|
||||||
const canOpenDispute = computed(() => {
|
const canOpenDispute = computed(() => {
|
||||||
if (!order.value || (!isOwner.value && !isRenter.value)) return false
|
if (!order.value || (!isOwner.value && !isRenter.value)) return false
|
||||||
|
if (order.value.active_dispute) return false
|
||||||
return ![
|
return ![
|
||||||
'completed',
|
'completed',
|
||||||
'cancelled',
|
'cancelled',
|
||||||
'closed',
|
'closed',
|
||||||
'disputing',
|
'disputing',
|
||||||
'checkout_disputing',
|
'checkout_disputing',
|
||||||
'abnormal',
|
|
||||||
].includes(order.value.status)
|
].includes(order.value.status)
|
||||||
})
|
})
|
||||||
const canCounterCheckout = computed(() => {
|
const canCounterCheckout = computed(() => {
|
||||||
@@ -208,8 +208,15 @@ export function useOrderActions(options: UseOrderActionsOptions) {
|
|||||||
if (typeof order.value.checkout.can_accept === 'boolean') {
|
if (typeof order.value.checkout.can_accept === 'boolean') {
|
||||||
return order.value.checkout.can_accept
|
return order.value.checkout.can_accept
|
||||||
}
|
}
|
||||||
|
const isOwnerPending =
|
||||||
|
isOwner.value &&
|
||||||
|
(order.value.status === 'pending_checkout_confirm' ||
|
||||||
|
(order.value.status === 'abnormal' &&
|
||||||
|
(order.value.handoff_status === 'owner_checkout_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'owner_return_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'pending_owner_checkout')))
|
||||||
return (
|
return (
|
||||||
(isOwner.value && order.value.status === 'pending_checkout_confirm') ||
|
isOwnerPending ||
|
||||||
(isRenter.value && order.value.status === 'pending_checkout_accept')
|
(isRenter.value && order.value.status === 'pending_checkout_accept')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -121,11 +121,21 @@ const {
|
|||||||
const showDisputePopup = ref(false)
|
const showDisputePopup = ref(false)
|
||||||
const showCounterPopup = ref(false)
|
const showCounterPopup = ref(false)
|
||||||
const showRejectPopup = ref(false)
|
const showRejectPopup = ref(false)
|
||||||
|
const isOwnerCheckoutPending = computed(() => {
|
||||||
|
if (!order.value || !isOwner.value) return false
|
||||||
|
return (
|
||||||
|
order.value.status === 'pending_checkout_confirm' ||
|
||||||
|
(order.value.status === 'abnormal' &&
|
||||||
|
(order.value.handoff_status === 'owner_checkout_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'owner_return_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'pending_owner_checkout'))
|
||||||
|
)
|
||||||
|
})
|
||||||
const showCheckoutCounterAction = computed(
|
const showCheckoutCounterAction = computed(
|
||||||
() =>
|
() =>
|
||||||
!!order.value &&
|
!!order.value &&
|
||||||
canCounterCheckout.value &&
|
canCounterCheckout.value &&
|
||||||
((isOwner.value && order.value.status === 'pending_checkout_confirm') ||
|
(isOwnerCheckoutPending.value ||
|
||||||
(isRenter.value && order.value.status === 'pending_checkout_accept'))
|
(isRenter.value && order.value.status === 'pending_checkout_accept'))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -263,12 +273,21 @@ async function copyListingCode() {
|
|||||||
<!-- Handoff Panel: Owner Handoff Info Submission -->
|
<!-- Handoff Panel: Owner Handoff Info Submission -->
|
||||||
<section
|
<section
|
||||||
v-if="
|
v-if="
|
||||||
isOwner && order.status === 'pending_handoff' && order.handoff_status === 'pending_owner'
|
isOwner &&
|
||||||
|
(order.status === 'pending_handoff' || order.status === 'abnormal') &&
|
||||||
|
(order.handoff_status === 'pending_owner' || order.handoff_status === 'owner_timeout')
|
||||||
"
|
"
|
||||||
class="card-section action-card"
|
class="card-section action-card"
|
||||||
>
|
>
|
||||||
<h3 class="section-title">提交交接说明</h3>
|
<h3 class="section-title">提交交接说明</h3>
|
||||||
<p class="action-hint">提交登录所需的账号密码、密保注意事项或扫码交接联系方式。</p>
|
<p
|
||||||
|
v-if="order.handoff_status === 'owner_timeout'"
|
||||||
|
class="action-hint"
|
||||||
|
style="color: #e6a23c; font-weight: 500"
|
||||||
|
>
|
||||||
|
当前交接已超时,请尽快补提交交接说明避免订单被关闭。
|
||||||
|
</p>
|
||||||
|
<p v-else class="action-hint">提交登录所需的账号密码、密保注意事项或扫码交接联系方式。</p>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="handoffContent"
|
v-model="handoffContent"
|
||||||
rows="3"
|
rows="3"
|
||||||
@@ -354,12 +373,14 @@ async function copyListingCode() {
|
|||||||
<OrderCheckoutSummary
|
<OrderCheckoutSummary
|
||||||
v-if="
|
v-if="
|
||||||
order.checkout &&
|
order.checkout &&
|
||||||
[
|
(
|
||||||
'pending_checkout_confirm',
|
[
|
||||||
'pending_checkout_accept',
|
'pending_checkout_confirm',
|
||||||
'checkout_disputing',
|
'pending_checkout_accept',
|
||||||
'completed',
|
'checkout_disputing',
|
||||||
].includes(order.status)
|
'completed',
|
||||||
|
].includes(order.status) || order.status === 'abnormal'
|
||||||
|
)
|
||||||
"
|
"
|
||||||
variant="mobile"
|
variant="mobile"
|
||||||
:order="order"
|
:order="order"
|
||||||
@@ -369,11 +390,18 @@ async function copyListingCode() {
|
|||||||
|
|
||||||
<!-- Owner Action: Confirm Checkout / Make Counter Adjustment Proposal -->
|
<!-- Owner Action: Confirm Checkout / Make Counter Adjustment Proposal -->
|
||||||
<section
|
<section
|
||||||
v-if="isOwner && order.status === 'pending_checkout_confirm'"
|
v-if="isOwnerCheckoutPending"
|
||||||
class="card-section action-card"
|
class="card-section action-card"
|
||||||
>
|
>
|
||||||
<h3 class="section-title">结账处理</h3>
|
<h3 class="section-title">结账处理</h3>
|
||||||
<p class="action-hint">
|
<p
|
||||||
|
v-if="order.handoff_status === 'owner_checkout_confirm_timeout'"
|
||||||
|
class="action-hint"
|
||||||
|
style="color: #e6a23c; font-weight: 500"
|
||||||
|
>
|
||||||
|
结账确认已超时,你可以直接同意当前方案完结,或修改方案交对方确认。
|
||||||
|
</p>
|
||||||
|
<p v-else class="action-hint">
|
||||||
{{
|
{{
|
||||||
checkoutRoundLabel || '请核对消耗与扣款'
|
checkoutRoundLabel || '请核对消耗与扣款'
|
||||||
}}。确认无误后同意完结;也可修改方案交对方确认(最多 6
|
}}。确认无误后同意完结;也可修改方案交对方确认(最多 6
|
||||||
|
|||||||
@@ -127,11 +127,20 @@ const qrGenerating = cashier.qrGenerating
|
|||||||
const checkingPayment = cashier.checkingPayment
|
const checkingPayment = cashier.checkingPayment
|
||||||
const paymentPayURL = cashier.payURL
|
const paymentPayURL = cashier.payURL
|
||||||
const activePayWayLabel = computedPayWayLabel(activePayment)
|
const activePayWayLabel = computedPayWayLabel(activePayment)
|
||||||
const handoffFocused = ref(false)
|
const isOwnerCheckoutPending = computed(() => {
|
||||||
|
if (!order.value || !isOwner.value) return false
|
||||||
|
return (
|
||||||
|
order.value.status === 'pending_checkout_confirm' ||
|
||||||
|
(order.value.status === 'abnormal' &&
|
||||||
|
(order.value.handoff_status === 'owner_checkout_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'owner_return_confirm_timeout' ||
|
||||||
|
order.value.handoff_status === 'pending_owner_checkout'))
|
||||||
|
)
|
||||||
|
})
|
||||||
const hasSidebarActions = computed(() => {
|
const hasSidebarActions = computed(() => {
|
||||||
if (!order.value) return false
|
if (!order.value) return false
|
||||||
return (
|
return (
|
||||||
(isOwner.value && order.value.status === 'pending_checkout_confirm') ||
|
isOwnerCheckoutPending.value ||
|
||||||
(isRenter.value && order.value.status === 'pending_checkout_accept')
|
(isRenter.value && order.value.status === 'pending_checkout_accept')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -139,7 +148,7 @@ const showCheckoutCounterForm = computed(
|
|||||||
() =>
|
() =>
|
||||||
!!order.value &&
|
!!order.value &&
|
||||||
canCounterCheckout.value &&
|
canCounterCheckout.value &&
|
||||||
((isOwner.value && order.value.status === 'pending_checkout_confirm') ||
|
(isOwnerCheckoutPending.value ||
|
||||||
(isRenter.value && order.value.status === 'pending_checkout_accept'))
|
(isRenter.value && order.value.status === 'pending_checkout_accept'))
|
||||||
)
|
)
|
||||||
const disputeSupportTitle = computed(() => {
|
const disputeSupportTitle = computed(() => {
|
||||||
@@ -325,14 +334,20 @@ watch([() => route.query.focus, order, loading], () => {
|
|||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
isOwner &&
|
isOwner &&
|
||||||
order.status === 'pending_handoff' &&
|
(order.status === 'pending_handoff' || order.status === 'abnormal') &&
|
||||||
order.handoff_status === 'pending_owner'
|
(order.handoff_status === 'pending_owner' || order.handoff_status === 'owner_timeout')
|
||||||
"
|
"
|
||||||
class="form-section handoff-action-section"
|
class="form-section handoff-action-section"
|
||||||
>
|
>
|
||||||
<div class="section-header compact">
|
<div class="section-header compact">
|
||||||
<h2>当前待办:提交交接说明</h2>
|
<h2>当前待办:提交交接说明</h2>
|
||||||
<span>填写后租客即可确认收号</span>
|
<span
|
||||||
|
v-if="order.handoff_status === 'owner_timeout'"
|
||||||
|
style="color: #e6a23c; font-weight: 500"
|
||||||
|
>
|
||||||
|
当前交接已超时,请尽快补提交交接说明避免订单被关闭
|
||||||
|
</span>
|
||||||
|
<span v-else>填写后租客即可确认收号</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="handoff-action-card">
|
<div class="handoff-action-card">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -502,12 +517,14 @@ watch([() => route.query.focus, order, loading], () => {
|
|||||||
v-if="
|
v-if="
|
||||||
order &&
|
order &&
|
||||||
order.checkout &&
|
order.checkout &&
|
||||||
[
|
(
|
||||||
'pending_checkout_confirm',
|
[
|
||||||
'pending_checkout_accept',
|
'pending_checkout_confirm',
|
||||||
'checkout_disputing',
|
'pending_checkout_accept',
|
||||||
'completed',
|
'checkout_disputing',
|
||||||
].includes(order.status)
|
'completed',
|
||||||
|
].includes(order.status) || order.status === 'abnormal'
|
||||||
|
)
|
||||||
"
|
"
|
||||||
:order="order"
|
:order="order"
|
||||||
:is-owner="isOwner"
|
:is-owner="isOwner"
|
||||||
@@ -618,9 +635,16 @@ watch([() => route.query.focus, order, loading], () => {
|
|||||||
<!-- 右侧悬浮操作区 -->
|
<!-- 右侧悬浮操作区 -->
|
||||||
<div v-if="order && hasSidebarActions" class="action-sidebar">
|
<div v-if="order && hasSidebarActions" class="action-sidebar">
|
||||||
<!-- 确认结账 -->
|
<!-- 确认结账 -->
|
||||||
<div v-if="isOwner && order.status === 'pending_checkout_confirm'" class="sidebar-card">
|
<div v-if="isOwnerCheckoutPending" class="sidebar-card">
|
||||||
<h3 class="sidebar-title">确认结账</h3>
|
<h3 class="sidebar-title">确认结账</h3>
|
||||||
<p v-if="checkoutRoundLabel" class="sidebar-hint">{{ checkoutRoundLabel }}</p>
|
<p
|
||||||
|
v-if="order.handoff_status === 'owner_checkout_confirm_timeout'"
|
||||||
|
class="sidebar-hint"
|
||||||
|
style="color: #e6a23c; font-weight: 500"
|
||||||
|
>
|
||||||
|
结账确认已超时,你可以直接同意当前方案完结,或修改方案交对方确认。
|
||||||
|
</p>
|
||||||
|
<p v-else-if="checkoutRoundLabel" class="sidebar-hint">{{ checkoutRoundLabel }}</p>
|
||||||
<p v-if="checkoutShortfallCent > 0" class="sidebar-hint" style="color: #dc2626">
|
<p v-if="checkoutShortfallCent > 0" class="sidebar-hint" style="color: #dc2626">
|
||||||
押金不足,无法同意完结
|
押金不足,无法同意完结
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user