增加零号主收入结算确认
This commit is contained in:
@@ -134,6 +134,7 @@ type CounterCheckoutRequest struct {
|
|||||||
|
|
||||||
type AdminActionRequest struct {
|
type AdminActionRequest struct {
|
||||||
Reason string `json:"reason" binding:"required"`
|
Reason string `json:"reason" binding:"required"`
|
||||||
|
ConfirmZeroOwnerIncome bool `json:"confirm_zero_owner_income"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlatformHandoffRequest struct {
|
type PlatformHandoffRequest struct {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ func writeOrderError(c *gin.Context, err error) {
|
|||||||
response.Error(c, http.StatusConflict, "checkout_cannot_submit", "当前订单不能发起结账")
|
response.Error(c, http.StatusConflict, "checkout_cannot_submit", "当前订单不能发起结账")
|
||||||
case errors.Is(err, ErrCheckoutCannotConfirm):
|
case errors.Is(err, ErrCheckoutCannotConfirm):
|
||||||
response.Error(c, http.StatusConflict, "checkout_cannot_confirm", "当前结账不能确认")
|
response.Error(c, http.StatusConflict, "checkout_cannot_confirm", "当前结账不能确认")
|
||||||
|
case errors.Is(err, ErrZeroOwnerIncomeConfirmation):
|
||||||
|
response.Error(c, http.StatusConflict, "zero_owner_income_confirmation_required", "号主收入为 0,请确认后再结算")
|
||||||
case errors.Is(err, ErrCheckoutCannotCounter):
|
case errors.Is(err, ErrCheckoutCannotCounter):
|
||||||
response.Error(c, http.StatusConflict, "checkout_cannot_counter", "当前结账不能修改")
|
response.Error(c, http.StatusConflict, "checkout_cannot_counter", "当前结账不能修改")
|
||||||
case errors.Is(err, ErrCheckoutMaxRounds):
|
case errors.Is(err, ErrCheckoutMaxRounds):
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ func (r *Repository) AdminPlatformCheckoutConfirm(ctx context.Context, adminID u
|
|||||||
if normalizeCheckoutTurn(checkout) != checkoutTurnOwner {
|
if normalizeCheckoutTurn(checkout) != checkoutTurnOwner {
|
||||||
return ErrCheckoutCannotConfirm
|
return ErrCheckoutCannotConfirm
|
||||||
}
|
}
|
||||||
|
if checkout.OwnerIncomeAmountCent == 0 && !req.ConfirmZeroOwnerIncome {
|
||||||
|
return ErrZeroOwnerIncomeConfirmation
|
||||||
|
}
|
||||||
if err := ensureCheckoutCompletable(order, checkout); err != nil {
|
if err := ensureCheckoutCompletable(order, checkout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -196,6 +199,7 @@ func (r *Repository) AdminPlatformCheckoutConfirm(ctx context.Context, adminID u
|
|||||||
"after_settlement_status": order.SettlementStatus,
|
"after_settlement_status": order.SettlementStatus,
|
||||||
"offline_settlement_status": order.OfflineSettlementStatus,
|
"offline_settlement_status": order.OfflineSettlementStatus,
|
||||||
"offline_settlement_amount_cent": order.OfflineSettlementAmountCent,
|
"offline_settlement_amount_cent": order.OfflineSettlementAmountCent,
|
||||||
|
"confirm_zero_owner_income": req.ConfirmZeroOwnerIncome,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package order
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -1794,3 +1795,48 @@ func createOpenPlatformCheckout(t *testing.T, db *gorm.DB, order model.RentalOrd
|
|||||||
}
|
}
|
||||||
return checkout
|
return checkout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminPlatformCheckoutConfirmRequiresExplicitConfirmationForZeroOwnerIncome(t *testing.T) {
|
||||||
|
db := setupOrderTestDB(t)
|
||||||
|
repo := NewRepository(db, Dependencies{
|
||||||
|
RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
||||||
|
return "pending", nil
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
const adminID uint64 = 7001
|
||||||
|
_, renter, order := createPlatformManagedCheckoutOrder(t, db, adminID)
|
||||||
|
checkout := createOpenPlatformCheckout(t, db, order, renter.ID)
|
||||||
|
checkout.OwnerIncomeAmountCent = 0
|
||||||
|
if err := db.Save(&checkout).Error; err != nil {
|
||||||
|
t.Fatalf("将号主收入调整为零失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.AdminPlatformCheckoutConfirm(t.Context(), *order.ManagedAdminID, order.ID, AdminActionRequest{
|
||||||
|
Reason: "客服核对结算",
|
||||||
|
}, AuditMeta{})
|
||||||
|
if !errors.Is(err, ErrZeroOwnerIncomeConfirmation) {
|
||||||
|
t.Fatalf("未确认零收入时 error = %v, want %v", err, ErrZeroOwnerIncomeConfirmation)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pending model.RentalOrder
|
||||||
|
if err := db.First(&pending, order.ID).Error; err != nil {
|
||||||
|
t.Fatalf("读取订单失败: %v", err)
|
||||||
|
}
|
||||||
|
if pending.Status != orderStatusPendingCheckoutConfirm {
|
||||||
|
t.Fatalf("未确认零收入后订单状态 = %s, want %s", pending.Status, orderStatusPendingCheckoutConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.AdminPlatformCheckoutConfirm(t.Context(), *order.ManagedAdminID, order.ID, AdminActionRequest{
|
||||||
|
Reason: "已确认号主收入为零",
|
||||||
|
ConfirmZeroOwnerIncome: true,
|
||||||
|
}, AuditMeta{}); err != nil {
|
||||||
|
t.Fatalf("确认零收入后结算失败: %v", err)
|
||||||
|
}
|
||||||
|
var completed model.RentalOrder
|
||||||
|
if err := db.First(&completed, order.ID).Error; err != nil {
|
||||||
|
t.Fatalf("读取完成订单失败: %v", err)
|
||||||
|
}
|
||||||
|
if completed.Status != orderStatusCompleted {
|
||||||
|
t.Fatalf("确认零收入后订单状态 = %s, want %s", completed.Status, orderStatusCompleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ var (
|
|||||||
ErrOrderCannotComplete = errors.New("order cannot complete")
|
ErrOrderCannotComplete = errors.New("order cannot complete")
|
||||||
ErrCheckoutCannotSubmit = errors.New("checkout cannot submit")
|
ErrCheckoutCannotSubmit = errors.New("checkout cannot submit")
|
||||||
ErrCheckoutCannotConfirm = errors.New("checkout cannot confirm")
|
ErrCheckoutCannotConfirm = errors.New("checkout cannot confirm")
|
||||||
|
ErrZeroOwnerIncomeConfirmation = errors.New("zero owner income confirmation required")
|
||||||
ErrCheckoutCannotCounter = errors.New("checkout cannot counter")
|
ErrCheckoutCannotCounter = errors.New("checkout cannot counter")
|
||||||
ErrCheckoutMaxRounds = errors.New("checkout max rounds reached")
|
ErrCheckoutMaxRounds = errors.New("checkout max rounds reached")
|
||||||
ErrCheckoutDepositShortfall = errors.New("checkout deposit shortfall")
|
ErrCheckoutDepositShortfall = errors.New("checkout deposit shortfall")
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type OrderActionType =
|
|||||||
| ''
|
| ''
|
||||||
const actionType = ref<OrderActionType>('')
|
const actionType = ref<OrderActionType>('')
|
||||||
const reason = ref('')
|
const reason = ref('')
|
||||||
|
const zeroOwnerIncomeConfirmed = ref(false)
|
||||||
const refundStatus = ref<RefundStatus | null>(null)
|
const refundStatus = ref<RefundStatus | null>(null)
|
||||||
const platformHandoffVisible = ref(false)
|
const platformHandoffVisible = ref(false)
|
||||||
const platformHandoffContent = ref('')
|
const platformHandoffContent = ref('')
|
||||||
@@ -476,7 +477,11 @@ async function submitAction() {
|
|||||||
await adminResetHandoff(order.value.id, reason.value)
|
await adminResetHandoff(order.value.id, reason.value)
|
||||||
ElMessage.success(`${resetActionLabel.value}成功`)
|
ElMessage.success(`${resetActionLabel.value}成功`)
|
||||||
} else if (actionType.value === 'platform_checkout_confirm') {
|
} else if (actionType.value === 'platform_checkout_confirm') {
|
||||||
await adminPlatformCheckoutConfirm(order.value.id, reason.value)
|
await adminPlatformCheckoutConfirm(
|
||||||
|
order.value.id,
|
||||||
|
reason.value,
|
||||||
|
zeroOwnerIncomeConfirmed.value
|
||||||
|
)
|
||||||
ElMessage.success('结账已确认')
|
ElMessage.success('结账已确认')
|
||||||
} else if (actionType.value === 'platform_checkout_dispute') {
|
} else if (actionType.value === 'platform_checkout_dispute') {
|
||||||
await adminPlatformCheckoutDispute(order.value.id, reason.value)
|
await adminPlatformCheckoutDispute(order.value.id, reason.value)
|
||||||
@@ -500,6 +505,28 @@ async function submitAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function confirmPlatformCheckout() {
|
||||||
|
if (!order.value) return
|
||||||
|
zeroOwnerIncomeConfirmed.value = false
|
||||||
|
if (Number(order.value.checkout?.owner_income_amount_cent ?? 0) === 0) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'本次结算的号主收入为 ¥0.0。确认后将完成结算,请再次确认是否继续。',
|
||||||
|
'号主收入为零',
|
||||||
|
{
|
||||||
|
confirmButtonText: '仍确认结算',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zeroOwnerIncomeConfirmed.value = true
|
||||||
|
}
|
||||||
|
openAction('platform_checkout_confirm')
|
||||||
|
}
|
||||||
|
|
||||||
function openPlatformHandoff() {
|
function openPlatformHandoff() {
|
||||||
platformHandoffContent.value = ''
|
platformHandoffContent.value = ''
|
||||||
platformHandoffReason.value = ''
|
platformHandoffReason.value = ''
|
||||||
@@ -952,7 +979,7 @@ function paymentPaidAt(record: AdminPayment) {
|
|||||||
<el-button
|
<el-button
|
||||||
v-if="canPlatformCheckoutConfirm"
|
v-if="canPlatformCheckoutConfirm"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openAction('platform_checkout_confirm')"
|
@click="confirmPlatformCheckout"
|
||||||
>
|
>
|
||||||
{{ platformCheckoutConfirmAction?.label || '客服确认结账' }}
|
{{ platformCheckoutConfirmAction?.label || '客服确认结账' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -1500,6 +1527,17 @@ function paymentPaidAt(record: AdminPayment) {
|
|||||||
<p>
|
<p>
|
||||||
<strong>{{ order.order_no }}</strong> · 商品编号 {{ listingCode }} · {{ order.title }}
|
<strong>{{ order.order_no }}</strong> · 商品编号 {{ listingCode }} · {{ order.title }}
|
||||||
</p>
|
</p>
|
||||||
|
<el-alert
|
||||||
|
v-if="
|
||||||
|
actionType === 'platform_checkout_confirm' &&
|
||||||
|
Number(order.checkout?.owner_income_amount_cent ?? 0) === 0
|
||||||
|
"
|
||||||
|
title="号主收入为零"
|
||||||
|
description="本次结算将按号主收入 ¥0.0 完成,请确认无误后再继续。"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="reason"
|
v-model="reason"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
|
|||||||
@@ -458,10 +458,14 @@ export async function adminForceHandoff(id: number, content: string, reason: str
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function adminPlatformCheckoutConfirm(id: number, reason: string) {
|
export async function adminPlatformCheckoutConfirm(
|
||||||
|
id: number,
|
||||||
|
reason: string,
|
||||||
|
confirmZeroOwnerIncome = false
|
||||||
|
) {
|
||||||
const { data } = await apiClient.post<ApiResponse<{ confirmed: boolean }>>(
|
const { data } = await apiClient.post<ApiResponse<{ confirmed: boolean }>>(
|
||||||
`/admin/orders/${id}/platform-checkout/confirm`,
|
`/admin/orders/${id}/platform-checkout/confirm`,
|
||||||
{ reason }
|
{ reason, confirm_zero_owner_income: confirmZeroOwnerIncome }
|
||||||
)
|
)
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user