feat(cost-center): 卡券规则批量生成与异常标签补齐

- 新增「批量生成渠道规则」,为 tpv: 商品一次性补 third_party_voucher 规则
- 异常原因补「缺少履约载体」,渠道标签补卡券/待分类/归档/未分类
This commit is contained in:
yml2213
2026-10-31 20:41:50 +08:00
parent a62f6e1ec8
commit 7fab610159
4 changed files with 75 additions and 7 deletions
@@ -6,6 +6,8 @@
*/
import { Router } from 'express'
import { ensureVoucherChannelRules } from '../../services/fulfillment/channel-assign-service.js'
import {
backfillAdminNormalizedQuantities,
backfillAdminProductMappings,
@@ -364,4 +366,20 @@ router.post(
),
)
router.post(
'/cost-center/voucher-products/ensure-rules',
requireAdminPermission('finance.config'),
createJsonHandler(() => ensureVoucherChannelRules(), {
successMessage: '卡券渠道规则已补齐',
errorMessage: '补齐卡券渠道规则失败',
scope: '[admin/cost-center/voucher-products/ensure-rules]',
audit: (_req, data) => ({
action: 'voucher_channel_rules_ensured',
targetType: 'sku_channel_rules',
targetId: '',
data: data as Record<string, unknown>,
}),
}),
)
export default router
@@ -47,8 +47,11 @@ const FULFILLMENT_MODE_TAG_COLORS: Record<string, string> = {
const FULFILLMENT_MODE_LABELS: Record<string, string> = {
work_order: '接单平台',
affiliate_dash: '自动发货',
legacy_platform: '历史平台',
third_party_voucher: '第三方卡券',
manual: '人工客服',
pending: '待分类',
archived: '历史归档',
unclassified: '未分类',
}
const REASON_LABELS: Record<string, string> = {
@@ -58,6 +61,7 @@ const REASON_LABELS: Record<string, string> = {
cost_missing: '成本缺失',
manual_config_missing: '人工 SKU 未配置',
manual_cost_missing: '人工成本未记录',
carrier_missing: '缺少履约载体',
}
const RANGE_OPTIONS = [
@@ -4,13 +4,28 @@
* 商品编码固定为 `tpv:<sku>`;成本写入 `sys_third_party_voucher_cost` 的
* 商品级版本,由成本快照管道消费。
*/
import { ReloadOutlined } from '@ant-design/icons'
import { Button, Card, Form, Input, InputNumber, Modal, Tag, Typography, message } from 'antd'
import { ReloadOutlined, SyncOutlined } from '@ant-design/icons'
import {
Button,
Card,
Form,
Input,
InputNumber,
Modal,
Space,
Tag,
Typography,
message,
} from 'antd'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import PercentTable from '@/components/common/PercentTable'
import { fetchAdminProductCatalog, setAdminProductPrice } from '@/services/admin'
import {
ensureVoucherChannelRules,
fetchAdminProductCatalog,
setAdminProductPrice,
} from '@/services/admin'
import type { ProductCatalogItem } from '@/types/admin'
import { useCostCenter } from './CostCenterContext'
@@ -21,6 +36,7 @@ export default function VoucherSkuPanel() {
const { canConfig, refreshAll } = useCostCenter()
const [page] = useState(1)
const [selected, setSelected] = useState<ProductCatalogItem | null>(null)
const [ensuringRules, setEnsuringRules] = useState(false)
const [form] = Form.useForm<{ amountFen: number; changeReason?: string }>()
const query = useQuery({
@@ -49,6 +65,20 @@ export default function VoucherSkuPanel() {
onError: (error) => message.error(error instanceof Error ? error.message : '保存失败'),
})
async function handleEnsureRules() {
setEnsuringRules(true)
try {
const result = await ensureVoucherChannelRules()
message.success(
`卡券商品 ${result.data.productCount} 个,已补齐渠道规则 ${result.data.created} 条`,
)
} catch (error) {
message.error(error instanceof Error ? error.message : '补齐卡券渠道规则失败')
} finally {
setEnsuringRules(false)
}
}
const items = query.data?.data.items || []
const total = query.data?.data.pagination.total || 0
@@ -99,9 +129,19 @@ export default function VoucherSkuPanel() {
<Card
title={`卡券商品采购价(${total})`}
extra={
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新
</Button>
<Space size={8}>
<Button
icon={<SyncOutlined />}
loading={ensuringRules}
disabled={!canConfig}
onClick={() => void handleEnsureRules()}
>
批量生成渠道规则
</Button>
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新
</Button>
</Space>
}
>
<Typography.Paragraph type="secondary">
@@ -519,3 +519,9 @@ export function syncAdminProductPrice(productId: number) {
message: string
}>(`/api/v1/admin/cost-center/products/${productId}/sync-price`, {})
}
export function ensureVoucherChannelRules() {
return apiPost<{ created: number; productCount: number }>(
'/api/v1/admin/cost-center/voucher-products/ensure-rules',
)
}