feat(kuaishou): 增加历史电子券补发并核销

This commit is contained in:
yml2213
2026-11-06 10:38:26 +08:00
parent 1bf1280877
commit 398eafa911
10 changed files with 607 additions and 1 deletions
@@ -6,15 +6,18 @@ import {
SearchOutlined,
SendOutlined,
SyncOutlined,
ThunderboltOutlined,
} from '@ant-design/icons'
import {
Alert,
Button,
Card,
Checkbox,
Collapse,
Descriptions,
Input,
InputNumber,
Modal,
Popconfirm,
Select,
Space,
@@ -39,6 +42,7 @@ import {
fetchAdminKuaishouIndustryShops,
fetchAdminKuaishouIndustryVouchers,
listAdminKuaishouIndustryRefunds,
reissueAndConsumeAdminKuaishouIndustryVoucher,
resendAdminKuaishouIndustryVoucherCode,
reverseAdminKuaishouIndustryVoucher,
} from '@/services/admin'
@@ -69,6 +73,7 @@ type IndustryActionKind =
| 'destroy'
| 'reverse'
| 'resend'
| 'reissue-consume'
| 'refund-list'
| 'refund-approve'
| 'refund-disagree'
@@ -120,6 +125,11 @@ export default function AdminKuaishouIndustryPage() {
const [actionLoading, setActionLoading] = useState('')
const [consumingVoucherId, setConsumingVoucherId] = useState<number | null>(null)
const [reversingVoucherId, setReversingVoucherId] = useState<number | null>(null)
const [reissueConsumeOpen, setReissueConsumeOpen] = useState(false)
const [reissueConsumeMinutes, setReissueConsumeMinutes] = useState<number>(10)
const [reissueConsumeReason, setReissueConsumeReason] =
useState('历史发码失败补偿,补发后立即核销')
const [reissueConsumeConfirmed, setReissueConsumeConfirmed] = useState(false)
const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([])
const [selectedVoucher, setSelectedVoucher] = useState<AdminKuaishouIndustryVoucher | null>(null)
const [voucherTotal, setVoucherTotal] = useState(0)
@@ -646,6 +656,64 @@ export default function AdminKuaishouIndustryPage() {
}
}
function openReissueConsumeModal() {
if (!selectedVoucher && !String(toolForm.voucherCode || '').trim()) {
showError('请先选择或填写本地券码')
return
}
if (selectedVoucher) {
const sendSucceeded = selectedVoucher.sendCallbackStatus === 'success'
const stillValid = Number(selectedVoucher.validEndTime || 0) > Date.now()
if (selectedVoucher.status !== 'UNUSED') {
showError('仅未使用电子凭证可以补发并核销')
return
}
if (sendSucceeded && stillValid) {
showError('该券已发码成功且仍在有效期,请直接执行手动核销')
return
}
}
setReissueConsumeConfirmed(false)
setReissueConsumeOpen(true)
}
async function runReissueConsume() {
if (!reissueConsumeConfirmed) {
showError('请先确认已核实订单仍需履约')
return
}
setActionLoading('reissue-consume')
try {
const response = await reissueAndConsumeAdminKuaishouIndustryVoucher({
...buildVoucherToolPayload(),
validMinutes: reissueConsumeMinutes,
reason: reissueConsumeReason,
})
publishActionResult(
buildIndustryActionResultView('reissue-consume', '历史券补发并核销', response.data),
)
if (response.data.success) {
showSuccess('原券码已同步到快手并完成核销')
setReissueConsumeOpen(false)
} else {
showError(response.data.error || '历史券补发并核销失败')
}
await loadVouchers()
} catch (error) {
const message = error instanceof Error ? error.message : '历史券补发并核销失败'
publishActionResult(
buildIndustryActionResultView('reissue-consume', '历史券补发并核销', {
success: false,
error: message,
}),
)
showError(message)
} finally {
setActionLoading('')
}
}
async function runDestroy() {
setActionLoading('destroy')
try {
@@ -870,6 +938,98 @@ export default function AdminKuaishouIndustryPage() {
onChange={(key) => setActiveTab(key as IndustryTab)}
items={tabItems}
/>
<Modal
title="历史券补发并核销"
open={reissueConsumeOpen}
okText="补发并核销"
cancelText="取消"
confirmLoading={actionLoading === 'reissue-consume'}
okButtonProps={{
disabled:
!reissueConsumeConfirmed ||
!String(reissueConsumeReason || '').trim() ||
reissueConsumeMinutes < 1 ||
reissueConsumeMinutes > 60,
}}
onOk={runReissueConsume}
onCancel={() => setReissueConsumeOpen(false)}
destroyOnHidden
>
<Space direction="vertical" size={16} className="full-width">
<Alert
type="warning"
showIcon
message="该操作会重发原券码并立即核销"
description="仅用于已确认订单仍需履约、但历史发码失败或凭证已过期的补偿场景。操作会先临时延长本地券有效期,发码成功后立即核销。"
/>
<Descriptions
size="small"
bordered
column={1}
items={[
{
key: 'voucherCode',
label: '本地券码',
children:
selectedVoucher?.voucherCode || String(toolForm.voucherCode || '').trim() || '-',
},
{
key: 'oid',
label: '平台订单',
children:
selectedVoucher?.oid ||
String(toolForm.oid || toolForm.orderId || '').trim() ||
'-',
},
{
key: 'validEndTime',
label: '原有效期至',
children: selectedVoucher
? formatAdminDateTime(
new Date(Number(selectedVoucher.validEndTime || 0)).toISOString(),
)
: '-',
},
{
key: 'sendError',
label: '最近发码错误',
children: selectedVoucher?.sendCallbackLastError || '-',
},
]}
/>
<label className="kuaishou-industry-tool-field">
<span>临时有效期(分钟)</span>
<InputNumber
min={1}
max={60}
precision={0}
value={reissueConsumeMinutes}
onChange={(value) => setReissueConsumeMinutes(Number(value || 10))}
style={{ width: '100%' }}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span>补发原因</span>
<Input
maxLength={200}
showCount
value={reissueConsumeReason}
onChange={(event) => setReissueConsumeReason(event.target.value)}
/>
</label>
<Checkbox
checked={reissueConsumeConfirmed}
onChange={(event) => setReissueConsumeConfirmed(event.target.checked)}
>
我已核实该订单仍需履约,并确认补发后立即核销
</Checkbox>
</Space>
</Modal>
</div>
)
@@ -1210,6 +1370,15 @@ export default function AdminKuaishouIndustryPage() {
>
手动核销
</Button>
<Button
danger
icon={<ThunderboltOutlined />}
loading={actionLoading === 'reissue-consume'}
disabled={!hasVoucherTarget}
onClick={openReissueConsumeModal}
>
历史补发并核销
</Button>
<Button
danger
icon={<RollbackOutlined />}
@@ -1677,6 +1846,7 @@ function buildIndustryActionResultView(
const responseData = asRecord(response?.data)
const success = resolveActionSuccess(kind, record, openApi)
const platformMessage = pickFirstString([
...(kind === 'reissue-consume' ? [record.error] : []),
response?.error_msg,
response?.msg,
response?.sub_msg,
@@ -1788,6 +1958,24 @@ function buildIndustryActionResultView(
}
}
if (kind === 'reissue-consume') {
pushHighlight(highlights, '执行阶段', String(record.stage || '-'))
if (record.validMinutes) {
pushHighlight(highlights, '临时有效期', `${String(record.validMinutes)} 分钟`)
}
for (const [label, value] of [
['发码成功', record.sendSuccess],
['核销成功', record.consumeSuccess],
] as const) {
if (typeof value === 'boolean') {
pushHighlight(highlights, label, value ? '是' : '否')
}
}
if (record.reason) {
pushHighlight(highlights, '补发原因', String(record.reason))
}
}
if (platformMessage) {
pushHighlight(highlights, '平台消息', platformMessage)
}
@@ -1855,6 +2043,12 @@ function buildActionSummary(
: context.platformMessage || '重发发码失败。'
}
if (kind === 'reissue-consume') {
return success
? '原券码已同步到快手并完成核销。'
: context.platformMessage || '历史券补发并核销失败。'
}
if (kind === 'refund-list') {
return success
? `查询成功,共 ${context.refundCount ?? 0} 条售后单,已填充列表。`
@@ -8,6 +8,8 @@ import type {
AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherDestroyResult,
AdminKuaishouIndustryVoucherListResult,
AdminKuaishouIndustryVoucherReissueConsumePayload,
AdminKuaishouIndustryVoucherReissueConsumeResult,
AdminKuaishouIndustryVoucherResendResult,
AdminKuaishouIndustryVoucherToolPayload,
} from '@/types/admin'
@@ -84,6 +86,15 @@ export function resendAdminKuaishouIndustryVoucherCode(
)
}
export function reissueAndConsumeAdminKuaishouIndustryVoucher(
payload: AdminKuaishouIndustryVoucherReissueConsumePayload,
) {
return apiPost<AdminKuaishouIndustryVoucherReissueConsumeResult>(
'/api/v1/admin/kuaishou-industry/vouchers/reissue-consume',
payload,
)
}
export function destroyAdminKuaishouIndustryVoucher(
payload: AdminKuaishouIndustryVoucherToolPayload,
) {
+2
View File
@@ -81,6 +81,8 @@ export type {
AdminKuaishouIndustryShopListResult,
AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherDestroyResult,
AdminKuaishouIndustryVoucherReissueConsumeResult,
AdminKuaishouIndustryVoucherReissueConsumePayload,
AdminKuaishouIndustryVoucherResendResult,
AdminKuaishouIndustryRefundListPayload,
AdminKuaishouIndustryRefundApprovePayload,
@@ -94,6 +94,26 @@ export interface AdminKuaishouIndustryVoucherResendResult {
voucher: AdminKuaishouIndustryVoucher | null
}
export interface AdminKuaishouIndustryVoucherReissueConsumeResult {
success: boolean
stage: 'send' | 'consume' | 'completed' | string
reason: string
validMinutes: number
originalValidEndTime: number
validEndTime: number
sendSuccess: boolean
consumeSuccess: boolean
response: Record<string, unknown> | null
error: string
voucher: AdminKuaishouIndustryVoucher | null
task: null | {
taskId: number
taskNo: string
status: string
deliveryStatus: string
}
}
export interface AdminKuaishouIndustryRefundListPayload {
sellerId?: string
beginTime?: number | string
@@ -149,3 +169,8 @@ export interface AdminKuaishouIndustryVoucherToolPayload {
expressNo?: string
etickets?: Array<{ id?: string; code?: string; num?: number | string }>
}
export interface AdminKuaishouIndustryVoucherReissueConsumePayload extends AdminKuaishouIndustryVoucherToolPayload {
validMinutes: number | string
reason: string
}