优化券码操作类型, 增加手动销毁

This commit is contained in:
yml2213
2026-07-12 18:46:30 +08:00
parent 1b8cdf16d3
commit 3d760f987c
16 changed files with 481 additions and 29 deletions
@@ -1,5 +1,6 @@
import {
CheckCircleOutlined,
DeleteOutlined,
ReloadOutlined,
RollbackOutlined,
SearchOutlined,
@@ -33,6 +34,7 @@ import {
approveAdminKuaishouIndustryRefund,
checkAdminKuaishouIndustryVoucherAvailable,
consumeAdminKuaishouIndustryVoucher,
destroyAdminKuaishouIndustryVoucher,
disagreeAdminKuaishouIndustryRefund,
fetchAdminKuaishouIndustryShops,
fetchAdminKuaishouIndustryVouchers,
@@ -64,6 +66,7 @@ type IndustryActionScope = 'vouchers' | 'refunds'
type IndustryActionKind =
| 'check'
| 'consume'
| 'destroy'
| 'reverse'
| 'resend'
| 'refund-list'
@@ -107,8 +110,6 @@ type RefundListState = {
negotiateStatus: string
}
const DEFAULT_ETICKET_TYPE = 'DINING_OPEN_TICKET'
export default function AdminKuaishouIndustryPage() {
const isSupportOnly = getAdminRole() === 'support'
const canManageRefund = hasAdminRole('operator')
@@ -130,8 +131,9 @@ export default function AdminKuaishouIndustryPage() {
pageSize: 50,
})
const [toolForm, setToolForm] = useState<AdminKuaishouIndustryVoucherToolPayload>({
eticketType: DEFAULT_ETICKET_TYPE,
eticketType: '',
consumeType: 'consume',
reason: 'SYS_ADMIN_DESTROY',
})
const [refundDateRange, setRefundDateRange] = useState<DateRangeValue>(() => [
dayjs().subtract(1, 'day'),
@@ -255,13 +257,20 @@ export default function AdminKuaishouIndustryPage() {
},
{
title: '状态',
width: 118,
width: 148,
render: (_, row) => (
<div className="cell-stack">
<Tag color={getVoucherStatusColor(row.status)}>{getVoucherStatusLabel(row.status)}</Tag>
<Tag color={row.sendCallbackStatus === 'success' ? 'green' : 'orange'}>
{row.sendCallbackStatus || '-'}
</Tag>
{row.eticketType ? (
<Typography.Text type="secondary" ellipsis={{ tooltip: row.eticketType }} className="muted">
{row.eticketType}
</Typography.Text>
) : (
<span className="muted"></span>
)}
</div>
),
},
@@ -425,7 +434,9 @@ export default function AdminKuaishouIndustryPage() {
orderId: row.oid,
taskId: row.taskId || '',
voucherCode: row.voucherCode,
eticketType: row.eticketType || current.eticketType || '',
serialNum: row.consumeSerialNum || current.serialNum,
reason: current.reason || 'SYS_ADMIN_DESTROY',
}))
showSuccess('已填入券码操作区')
}
@@ -440,7 +451,7 @@ export default function AdminKuaishouIndustryPage() {
await runOpenApiAction('reverse', '电子凭证冲正回调', 'reverse', () =>
reverseAdminKuaishouIndustryVoucher({
...buildVoucherToolPayload(),
reason: toolForm.reason || '后台手动冲正',
reason: '后台手动冲正',
}),
)
await loadVouchers()
@@ -484,7 +495,7 @@ export default function AdminKuaishouIndustryPage() {
orderId: row.oid,
taskId: row.taskId || '',
voucherCode: row.voucherCode,
eticketType: DEFAULT_ETICKET_TYPE,
eticketType: row.eticketType || undefined,
consumeType: 'consume',
serialNum: row.consumeSerialNum || undefined,
etickets: [{ id: row.voucherCode, code: row.voucherCode, num: 1 }],
@@ -540,6 +551,36 @@ export default function AdminKuaishouIndustryPage() {
}
}
async function runDestroy() {
setActionLoading('destroy')
try {
const response = await destroyAdminKuaishouIndustryVoucher({
...buildVoucherToolPayload(),
reason: toolForm.reason || 'SYS_ADMIN_DESTROY',
})
publishActionResult(
buildIndustryActionResultView('destroy', '手动销毁', response.data),
)
showSuccess(
response.data.alreadyDestroyed
? `券码已是销毁状态,销毁回调已重试(${response.data.reason || 'SYS_ADMIN_DESTROY'}`
: `券码已销毁(${response.data.reason || 'SYS_ADMIN_DESTROY'}`,
)
await loadVouchers()
} catch (error) {
const message = error instanceof Error ? error.message : '手动销毁失败'
publishActionResult(
buildIndustryActionResultView('destroy', '手动销毁', {
success: false,
error: message,
}),
)
showError(message)
} finally {
setActionLoading('')
}
}
async function runRefundList() {
const [begin, end] = refundDateRange || []
const sellerId = resolveRefundSellerId(refundListForm.sellerId)
@@ -888,7 +929,7 @@ export default function AdminKuaishouIndustryPage() {
onChange={(event) => updateToolForm({ voucherCode: event.target.value })}
/>
<Input
placeholder="电子凭证类型"
placeholder="电子凭证类型(选中券码自动填入)"
value={toolForm.eticketType}
onChange={(event) => updateToolForm({ eticketType: event.target.value })}
/>
@@ -902,10 +943,18 @@ export default function AdminKuaishouIndustryPage() {
value={toolForm.consumeType}
onChange={(event) => updateToolForm({ consumeType: event.target.value })}
/>
<Input
placeholder="冲正原因"
value={toolForm.reason}
onChange={(event) => updateToolForm({ reason: event.target.value })}
<Select
allowClear
placeholder="销毁/冲正原因"
value={toolForm.reason || undefined}
onChange={(value) => updateToolForm({ reason: value || '' })}
options={[
{ label: 'SYS_ADMIN_DESTROY(后台作废)', value: 'SYS_ADMIN_DESTROY' },
{ label: 'SUPPLY_DESTROY(供应商作废)', value: 'SUPPLY_DESTROY' },
{ label: 'USER_APPLY_REFUND(用户退款)', value: 'USER_APPLY_REFUND' },
{ label: 'ETICKET_EXPIRED(凭证过期)', value: 'ETICKET_EXPIRED' },
]}
style={{ minWidth: 220 }}
/>
<Input
placeholder="门店名称"
@@ -942,6 +991,23 @@ export default function AdminKuaishouIndustryPage() {
>
</Button>
<Popconfirm
title="确认销毁该券码?"
description="将向快手发起销毁回调,并关闭关联任务。已销毁券会重试回调。"
okText="销毁"
cancelText="取消"
onConfirm={() => runDestroy()}
>
<Button
danger
type="primary"
icon={<DeleteOutlined />}
loading={actionLoading === 'destroy'}
disabled={!canManageRefund}
>
</Button>
</Popconfirm>
<Button
icon={<SendOutlined />}
loading={actionLoading === 'resend'}
@@ -1416,19 +1482,44 @@ function buildIndustryActionResultView(
}
if (kind === 'check') {
const etickets = extractAvailableEtickets(responseData)
pushHighlight(highlights, '可核销券数', String(etickets.length || 0))
const etickets = extractAvailableEtickets(response)
pushHighlight(highlights, '返回券数', String(etickets.length || 0))
const resolvedType = pickFirstString([
record.eticketType,
voucher?.eticketType,
asRecord(openApi.request)?.eticketType,
asRecord(asRecord(openApi.request)?.param)?.eticketType,
])
if (resolvedType) {
pushHighlight(highlights, '请求类型', resolvedType)
}
if (etickets[0]) {
pushHighlight(
highlights,
'首张券码',
pickFirstString([etickets[0].code, etickets[0].id]) || '-',
)
pushHighlight(highlights, '首张数量', String(etickets[0].num || 1))
pushHighlight(
highlights,
'展示状态',
pickFirstString([etickets[0].displayStatus, etickets[0].status]) || '-',
)
pushHighlight(
highlights,
'平台类型',
pickFirstString([etickets[0].eticketType]) || '-',
)
pushHighlight(highlights, '剩余数量', String(etickets[0].leftNum ?? etickets[0].num ?? 1))
}
}
if (kind === 'consume' || kind === 'reverse' || kind === 'resend' || voucher) {
if (
kind === 'consume' ||
kind === 'destroy' ||
kind === 'reverse' ||
kind === 'resend' ||
voucher
) {
if (voucher) {
pushHighlight(highlights, '券码', String(voucher.voucherCode || '-'))
pushHighlight(
@@ -1436,6 +1527,15 @@ function buildIndustryActionResultView(
'券状态',
getVoucherStatusLabel(String(voucher.status || '')),
)
if (voucher.eticketType) {
pushHighlight(highlights, '凭证类型', String(voucher.eticketType))
}
if (kind === 'destroy' && record.reason) {
pushHighlight(highlights, '销毁原因', String(record.reason))
}
if (kind === 'destroy' && typeof record.alreadyDestroyed === 'boolean') {
pushHighlight(highlights, '已是销毁态', record.alreadyDestroyed ? '是(重试回调)' : '否')
}
if (voucher.consumeSerialNum) {
pushHighlight(highlights, '核销序列号', String(voucher.consumeSerialNum))
}
@@ -1520,7 +1620,7 @@ function buildActionSummary(
if (kind === 'check') {
return success
? context.platformMessage || '平台返回券码有效,可继续核销。'
? context.platformMessage || '检查成功,请结合展示状态(如 frozen)判断是否可核销。'
: context.platformMessage || '检查失败,请核对卖家、券码与电子凭证类型。'
}
@@ -1530,6 +1630,12 @@ function buildActionSummary(
: context.platformMessage || '核销失败。'
}
if (kind === 'destroy') {
return success
? `销毁成功${context.voucherStatus ? `,券状态:${context.voucherStatus}` : ''}`
: context.platformMessage || '销毁失败。'
}
if (kind === 'reverse') {
return success
? `冲正成功${context.voucherStatus ? `,券状态已回写为 ${context.voucherStatus}` : ''}`
@@ -1575,7 +1681,7 @@ function resolveActionSuccess(
if (typeof record.success === 'boolean') {
return record.success
}
if (kind === 'consume') {
if (kind === 'consume' || kind === 'destroy') {
return Boolean(record.voucher)
}
return openApi.success
@@ -6,6 +6,7 @@ import type {
AdminKuaishouIndustryRefundListPayload,
AdminKuaishouIndustryShopListResult,
AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherDestroyResult,
AdminKuaishouIndustryVoucherListResult,
AdminKuaishouIndustryVoucherResendResult,
AdminKuaishouIndustryVoucherToolPayload,
@@ -84,3 +85,12 @@ export function resendAdminKuaishouIndustryVoucherCode(
payload,
)
}
export function destroyAdminKuaishouIndustryVoucher(
payload: AdminKuaishouIndustryVoucherToolPayload,
) {
return apiPost<AdminKuaishouIndustryVoucherDestroyResult>(
'/api/v1/admin/kuaishou-industry/vouchers/destroy',
payload,
)
}
+1
View File
@@ -38,6 +38,7 @@ export type {
AdminKuaishouIndustryShopOption,
AdminKuaishouIndustryShopListResult,
AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherDestroyResult,
AdminKuaishouIndustryVoucherResendResult,
AdminKuaishouIndustryRefundListPayload,
AdminKuaishouIndustryRefundApprovePayload,
@@ -28,6 +28,8 @@ export interface AdminKuaishouIndustryVoucher {
/** 关联订单商品名称(列表 join) */
skuName?: string
tokenMasked: string
/** 快手电子凭证类型,如 GAME_OPEN_TICKET_CONSUME */
eticketType: string
status: string
validStartTime: number
validEndTime: number
@@ -72,6 +74,19 @@ export interface AdminKuaishouIndustryVoucherConsumeResult {
}
}
export interface AdminKuaishouIndustryVoucherDestroyResult {
success: boolean
alreadyDestroyed: boolean
reason: string
voucher: AdminKuaishouIndustryVoucher
task: null | {
taskId: number
taskNo: string
status: string
deliveryStatus: string
}
}
export interface AdminKuaishouIndustryVoucherResendResult {
success: boolean
response: Record<string, unknown> | null