feat(finance): P1 人工商品归一,按内部 SKU 生成 man 商品并回填
- 新服务 manual-product-normalization-service:skuNick → outSkuSn → sku_code 兜底,按内部 SKU 生成 man:<sku> 商品 - 订单项按 id 分批(2 万/批)回填,避免 15s 语句超时;同步补齐人工成本快照的商品维度 - 新增 POST /cost-center/manual-products/normalize(支持 dryRun/日期窗口),成本中心「标准商品」加「归一人工商品」按钮 - 每日 cost_finance_sync 接入最近 N 天的增量归一 - 渠道映射未归一队列增加人工单提示:人工单不能用平台 sku_code 建映射 - 文档同步:P1 标记完成,补充归一接口契约与销售事实重算结果
This commit is contained in:
@@ -733,6 +733,15 @@ export function ChannelMappingPanel() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{unmappedGroups.some((row) => row.fulfillmentMode === 'manual') ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="人工客服的未归一建议按内部 SKU 批量归一"
|
||||
description="人工单的内部 SKU(快手 skuNick / 拼多多 outSkuSn)和平台 sku_code 不是同一个值,手建渠道映射通常抓不到历史单。请到「标准商品 → 归一人工商品」,按内部 SKU 生成 man: 商品并回填。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Space wrap>
|
||||
<Input
|
||||
allowClear
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
createAdminProductCatalog,
|
||||
deleteAdminProductCatalog,
|
||||
fetchAdminProductCatalog,
|
||||
normalizeAdminManualProducts,
|
||||
previewAdminTemplateProductBootstrap,
|
||||
updateAdminProductCatalog,
|
||||
} from '@/services/admin'
|
||||
@@ -66,6 +67,7 @@ export function ProductCatalogPanel() {
|
||||
const [bootstrapPreviewing, setBootstrapPreviewing] = useState(false)
|
||||
const [bootstrapApplying, setBootstrapApplying] = useState(false)
|
||||
const [backfillingWorkOrderSkus, setBackfillingWorkOrderSkus] = useState(false)
|
||||
const [normalizingManualProducts, setNormalizingManualProducts] = useState(false)
|
||||
const [productPage, setProductPage] = useState(1)
|
||||
const [productPageSize, setProductPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
|
||||
const [productModalOpen, setProductModalOpen] = useState(false)
|
||||
@@ -227,6 +229,52 @@ export function ProductCatalogPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
async function normalizeManualProductCatalog() {
|
||||
setNormalizingManualProducts(true)
|
||||
try {
|
||||
const preview = await normalizeAdminManualProducts({ dryRun: true })
|
||||
const data = preview.data
|
||||
if (data.distinctSkuCount === 0) {
|
||||
message.success('没有待归一的人工订单项')
|
||||
return
|
||||
}
|
||||
modal.confirm({
|
||||
title: '归一人工商品',
|
||||
content: (
|
||||
<Space direction="vertical" size={8}>
|
||||
<span>
|
||||
按内部 SKU(快手 skuNick / 拼多多 outSkuSn)新建最多{' '}
|
||||
<strong>{data.distinctSkuCount}</strong> 个 <code>man:</code> 标准商品。
|
||||
</span>
|
||||
<span>
|
||||
把 <strong>{data.pendingItemCount}</strong>{' '}
|
||||
个人工订单项归一到对应商品,并补齐已有成本快照的商品维度。
|
||||
</span>
|
||||
<span className="muted">已归一过的订单项不会改动;重复执行是幂等的。</span>
|
||||
</Space>
|
||||
),
|
||||
okText: '开始归一',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const response = await normalizeAdminManualProducts({})
|
||||
const result = response.data
|
||||
message.success(
|
||||
`已新建 ${result.createdProductCount} 个商品、归一 ${result.updatedItemCount} 个订单项${
|
||||
result.resolvedExceptionCount > 0
|
||||
? `、收口 ${result.resolvedExceptionCount} 条异常`
|
||||
: ''
|
||||
},剩余 ${result.remainingItemCount} 个未归一`,
|
||||
)
|
||||
refreshAll()
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '归一人工商品失败')
|
||||
} finally {
|
||||
setNormalizingManualProducts(false)
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateProduct() {
|
||||
setEditingProduct(null)
|
||||
productForm.resetFields()
|
||||
@@ -463,6 +511,13 @@ export function ProductCatalogPanel() {
|
||||
>
|
||||
按工单回填商品
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={normalizingManualProducts}
|
||||
onClick={() => void normalizeManualProductCatalog()}
|
||||
>
|
||||
归一人工商品
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={productsQuery.isFetching}
|
||||
|
||||
@@ -132,6 +132,20 @@ export function fetchAdminChannelProductMappings(params?: Record<string, unknown
|
||||
return apiGet<ChannelProductMappingListResult>('/api/v1/admin/cost-center/mappings', params)
|
||||
}
|
||||
|
||||
export function normalizeAdminManualProducts(payload: Record<string, unknown>) {
|
||||
return apiPost<{
|
||||
dryRun: boolean
|
||||
range: { fromDate: string; toDate: string } | null
|
||||
distinctSkuCount: number
|
||||
pendingItemCount: number
|
||||
createdProductCount: number
|
||||
updatedItemCount: number
|
||||
updatedSnapshotCount: number
|
||||
resolvedExceptionCount: number
|
||||
remainingItemCount: number
|
||||
}>('/api/v1/admin/cost-center/manual-products/normalize', payload)
|
||||
}
|
||||
|
||||
export function fetchAdminChannelMappingDiagnostics(params?: Record<string, unknown>) {
|
||||
return apiGet<ChannelMappingDiagnostics>('/api/v1/admin/cost-center/mappings/diagnostics', params)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user