feat(cost-center): 卡券规则批量生成与异常标签补齐
- 新增「批量生成渠道规则」,为 tpv: 商品一次性补 third_party_voucher 规则 - 异常原因补「缺少履约载体」,渠道标签补卡券/待分类/归档/未分类
This commit is contained in:
@@ -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',
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user