feat(pickup): support platform managed offline settlement
This commit is contained in:
@@ -2,6 +2,8 @@ import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
|
||||
|
||||
export type PickupAccountSource = 'internal' | 'external_platform_managed'
|
||||
export type PickupSettlementMode = 'owner_wallet' | 'platform_managed'
|
||||
export type PickupOfflineSettlementStatus = 'none' | 'pending' | 'settled'
|
||||
export type AvailableListingSourceType = '' | 'external' | 'internal'
|
||||
|
||||
export interface AdminPickup {
|
||||
@@ -20,6 +22,7 @@ export interface AdminPickup {
|
||||
shop_name: string
|
||||
account_source: PickupAccountSource
|
||||
source_channel: string
|
||||
settlement_mode: PickupSettlementMode
|
||||
listing_price_cent: number
|
||||
owner_price_cent: number
|
||||
website_profit_cent: number
|
||||
@@ -29,6 +32,10 @@ export interface AdminPickup {
|
||||
account_snapshot?: Record<string, unknown>
|
||||
settle_amount_cent: number
|
||||
status: string
|
||||
offline_settlement_status: PickupOfflineSettlementStatus
|
||||
offline_settlement_remark: string
|
||||
offline_settled_by?: number
|
||||
offline_settled_at?: string
|
||||
remark: string
|
||||
complete_remark: string
|
||||
created_at: string
|
||||
@@ -79,6 +86,10 @@ export interface AdminPickupProfitUpdateRequest {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface AdminPickupOfflineSettlementRequest {
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface AdminPickupListQuery {
|
||||
page?: number
|
||||
page_size?: number
|
||||
@@ -150,6 +161,17 @@ export async function completeAdminPickup(id: number, req: AdminPickupCompleteRe
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function confirmAdminPickupOfflineSettlement(
|
||||
id: number,
|
||||
req: AdminPickupOfflineSettlementRequest = {}
|
||||
) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminPickup>>(
|
||||
`/admin/pickups/${id}/offline-settlement`,
|
||||
req
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateAdminPickupProfit(id: number, req: AdminPickupProfitUpdateRequest) {
|
||||
const { data } = await apiClient.put<ApiResponse<AdminPickup>>(`/admin/pickups/${id}/profit`, req)
|
||||
return data.data
|
||||
|
||||
@@ -157,6 +157,17 @@ function pickupSourceTagType(source: AdminPickup['account_source']) {
|
||||
return source === 'external_platform_managed' ? 'primary' : 'info'
|
||||
}
|
||||
|
||||
function settlementModeLabel(row: AdminPickup) {
|
||||
return row.settlement_mode === 'platform_managed' ? '平台线下结算' : '号主钱包结算'
|
||||
}
|
||||
|
||||
function offlineSettlementLabel(row: AdminPickup) {
|
||||
if (row.settlement_mode !== 'platform_managed') return '钱包已结算'
|
||||
if (row.offline_settlement_status === 'pending') return '待线下结算'
|
||||
if (row.offline_settlement_status === 'settled') return '已线下结算'
|
||||
return '待完成提号'
|
||||
}
|
||||
|
||||
function ratioText(value: number) {
|
||||
const ratio = Number(value || 0)
|
||||
if (ratio <= 0) return '-'
|
||||
@@ -317,6 +328,18 @@ function readSnapshotResources(summary: Record<string, unknown> | null): Snapsho
|
||||
<span v-if="pickup.source_channel">{{ pickup.source_channel }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>结算方式</dt>
|
||||
<dd>{{ settlementModeLabel(pickup) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>结算状态</dt>
|
||||
<dd>{{ offlineSettlementLabel(pickup) }}</dd>
|
||||
</div>
|
||||
<div v-if="pickup.settlement_mode === 'platform_managed'">
|
||||
<dt>线下结算时间</dt>
|
||||
<dd>{{ formatDateTime(pickup.offline_settled_at, '待确认') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>交易渠道</dt>
|
||||
<dd>{{ pickup.platform || '-' }}</dd>
|
||||
@@ -371,6 +394,10 @@ function readSnapshotResources(summary: Record<string, unknown> | null): Snapsho
|
||||
<dt>完成备注</dt>
|
||||
<dd>{{ pickup.complete_remark || '-' }}</dd>
|
||||
</div>
|
||||
<div v-if="pickup.settlement_mode === 'platform_managed'" class="wide">
|
||||
<dt>线下结算备注</dt>
|
||||
<dd>{{ pickup.offline_settlement_remark || '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { EditPen, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
cancelAdminPickup,
|
||||
confirmAdminPickupOfflineSettlement,
|
||||
completeAdminPickup,
|
||||
createAdminPickup,
|
||||
fetchAdminPickups,
|
||||
@@ -36,6 +37,7 @@ const createDialogVisible = ref(false)
|
||||
const completeDialogVisible = ref(false)
|
||||
const profitDialogVisible = ref(false)
|
||||
const activePickupId = ref(0)
|
||||
const activePickup = ref<AdminPickup | null>(null)
|
||||
const profitSaving = ref(false)
|
||||
|
||||
const createForm = reactive({
|
||||
@@ -184,12 +186,41 @@ async function handleCreate() {
|
||||
|
||||
function openCompleteDialog(row: AdminPickup) {
|
||||
activePickupId.value = row.id
|
||||
activePickup.value = row
|
||||
completeForm.settle_amount = 0
|
||||
completeForm.profit_amount = centToYuan(row.profit_amount_cent)
|
||||
completeForm.complete_remark = ''
|
||||
completeDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleConfirmOfflineSettlement(row: AdminPickup) {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt(
|
||||
`确认已向卖家线下支付 ${formatCentWithSymbol(row.settle_amount_cent)}?`,
|
||||
'确认线下结算',
|
||||
{
|
||||
confirmButtonText: '确认已打款',
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'textarea',
|
||||
inputPlaceholder: '结算备注(可选)',
|
||||
inputValidator: input => input.length <= 255 || '备注不能超过 255 个字符',
|
||||
}
|
||||
)
|
||||
loading.value = true
|
||||
try {
|
||||
await confirmAdminPickupOfflineSettlement(row.id, { remark: value })
|
||||
ElMessage.success('已确认线下结算')
|
||||
loadList()
|
||||
} catch (e: unknown) {
|
||||
ElMessage.error(errorMessage(e) || '确认失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
} catch {
|
||||
/* 用户放弃 */
|
||||
}
|
||||
}
|
||||
|
||||
function openProfitDialog(row: AdminPickup) {
|
||||
activePickupId.value = row.id
|
||||
profitForm.profit_amount = centToYuan(row.profit_amount_cent)
|
||||
@@ -213,7 +244,11 @@ async function handleComplete() {
|
||||
profit_amount_cent: yuanToCent(completeForm.profit_amount),
|
||||
complete_remark: completeForm.complete_remark,
|
||||
})
|
||||
ElMessage.success('提号已完成,已给卖家结算')
|
||||
ElMessage.success(
|
||||
activePickup.value?.settlement_mode === 'platform_managed'
|
||||
? '提号已完成,待线下结算'
|
||||
: '提号已完成,已给卖家结算'
|
||||
)
|
||||
completeDialogVisible.value = false
|
||||
loadList()
|
||||
} catch (e: unknown) {
|
||||
@@ -310,6 +345,24 @@ function pickupSourceTagType(source: PickupAccountSource) {
|
||||
return isExternalSource(source) ? 'primary' : 'info'
|
||||
}
|
||||
|
||||
function isPlatformManaged(row: AdminPickup) {
|
||||
return row.settlement_mode === 'platform_managed'
|
||||
}
|
||||
|
||||
function offlineSettlementLabel(row: AdminPickup) {
|
||||
if (!isPlatformManaged(row)) return '钱包已结算'
|
||||
if (row.offline_settlement_status === 'pending') return '待线下结算'
|
||||
if (row.offline_settlement_status === 'settled') return '已线下结算'
|
||||
return '待完成提号'
|
||||
}
|
||||
|
||||
function offlineSettlementTagType(row: AdminPickup) {
|
||||
if (!isPlatformManaged(row)) return 'success'
|
||||
if (row.offline_settlement_status === 'pending') return 'warning'
|
||||
if (row.offline_settlement_status === 'settled') return 'success'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function listingSourceText(item: AvailableListing) {
|
||||
const label = pickupSourceLabel(item.account_source)
|
||||
return item.source_channel ? `${label} · ${item.source_channel}` : label
|
||||
@@ -326,7 +379,7 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Admin Pickup</p>
|
||||
<h1>线下提号</h1>
|
||||
<p>管理员线下提号,跳过支付与交接流程,完成后给卖家结算余额。</p>
|
||||
<p>管理员线下提号,站内上传结算到钱包,平台代管通过线下结算确认打款。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button type="primary" :icon="Plus" @click="openCreateDialog">创建提号</el-button>
|
||||
@@ -416,10 +469,17 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算状态" width="125">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="offlineSettlementTagType(row)" effect="light">
|
||||
{{ offlineSettlementLabel(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<el-table-column label="操作" width="340" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<RouterLink :to="adminPath(`pickup/${row.id}`)">
|
||||
@@ -441,7 +501,16 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
size="small"
|
||||
@click="openCompleteDialog(row)"
|
||||
>
|
||||
完成结算
|
||||
完成提号
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'completed' && row.offline_settlement_status === 'pending'"
|
||||
type="primary"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleConfirmOfflineSettlement(row)"
|
||||
>
|
||||
确认线下结算
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'picking_up'"
|
||||
@@ -599,8 +668,8 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 完成结算 -->
|
||||
<el-dialog v-model="completeDialogVisible" title="完成提号结算" width="480px">
|
||||
<!-- 完成提号 -->
|
||||
<el-dialog v-model="completeDialogVisible" title="完成提号" width="480px">
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="结算金额(元)" required>
|
||||
<el-input-number
|
||||
@@ -611,7 +680,13 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div class="form-hint">给卖家钱包增加的可用余额</div>
|
||||
<div class="form-hint">
|
||||
{{
|
||||
activePickup?.settlement_mode === 'platform_managed'
|
||||
? '记录待线下支付的结算金额'
|
||||
: '给卖家钱包增加的可用余额'
|
||||
}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="线下利润(元)">
|
||||
<el-input-number
|
||||
@@ -629,16 +704,16 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
v-model="completeForm.complete_remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="结算说明(可选)"
|
||||
placeholder="完成说明(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-alert type="warning" :closable="false">
|
||||
完成后提号记录置为"已完成",卖家钱包立即入账,账号下架不可再上架。操作不可撤销。
|
||||
完成后账号下架不可再上架。站内上传会立即入账卖家钱包;平台代管会转为待线下结算,实际打款后需单独确认。
|
||||
</el-alert>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="completeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="handleComplete">确认结算</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="handleComplete">确认完成</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ export const AUDIT_ACTION_OPTIONS: AuditOption[] = [
|
||||
{ value: 'chat.renter_retention.remove', label: '到期移除租客会话', group: '系统任务' },
|
||||
// 提号
|
||||
{ value: 'pickup_create', label: '创建提号', group: '提号' },
|
||||
{ value: 'pickup_complete', label: '完成提号结算', group: '提号', highRisk: true },
|
||||
{ value: 'pickup_complete', label: '完成提号结算', group: '提号', highRisk: true },
|
||||
{ value: 'pickup_offline_settlement', label: '确认提号线下结算', group: '提号', highRisk: true },
|
||||
{ value: 'pickup_profit_update', label: '修改提号利润', group: '提号', highRisk: true },
|
||||
{ value: 'pickup_cancel', label: '取消提号', group: '提号' },
|
||||
// 申诉
|
||||
|
||||
Reference in New Issue
Block a user