chore(cost-center): 下线会与管道冲突的旧商品入口

- 移除自动发货商品同步、SKU 回填、按工单回填、人工商品归一等旧接口与按钮
- 商品目录只保留「从接单模板初始化」与手工新建
- 自动发货面板移除已失效的「重算成本快照」入口
This commit is contained in:
yml2213
2026-10-31 20:17:39 +08:00
parent d979ef50d6
commit 3f13b8a5f7
3 changed files with 7 additions and 326 deletions
@@ -23,19 +23,14 @@ import {
updateAdminProduct,
} from '../../services/cost-center/product-catalog-service.js'
import {
backfillAdminAffiliateDashSkus,
importAdminAffiliateDashPricesBaseline,
setAdminAffiliateProductPrice,
syncAdminAffiliateDashProducts,
} from '../../services/cost-center/affiliate-product-service.js'
import {
applyAdminTemplateProductBootstrap,
previewAdminTemplateProductBootstrap,
} from '../../services/cost-center/template-bootstrap-service.js'
import {
backfillAdminWorkOrderProductSkus,
syncAdminWorkOrderTemplateCostVersions,
} from '../../services/cost-center/work-order-product-service.js'
import { syncAdminWorkOrderTemplateCostVersions } from '../../services/cost-center/work-order-product-service.js'
import type { JsonObject } from '../../types/json.js'
import { BATCH_STATEMENT_TIMEOUT_MS, runWithStatementTimeout } from '../../db/client.js'
import { bodyOf, idOf, sessionOf } from './request-helpers.js'
@@ -84,38 +79,6 @@ router.post(
),
)
router.post(
'/cost-center/affiliate-products/sync',
requireAdminPermission('finance.config'),
createJsonHandler(() => syncAdminAffiliateDashProducts(), {
successMessage: '自动发货商品已同步',
errorMessage: '同步自动发货商品失败',
scope: '[admin/cost-center/affiliate-products/sync]',
audit: (_req, data) => ({
action: 'cost_affiliate_products_synced',
targetType: 'product_catalog',
targetId: '',
data: data as Record<string, unknown>,
}),
}),
)
router.post(
'/cost-center/affiliate-products/backfill',
requireAdminPermission('finance.config'),
createJsonHandler(() => backfillAdminAffiliateDashSkus(), {
successMessage: '自动发货 SKU 已回填',
errorMessage: '回填自动发货 SKU 失败',
scope: '[admin/cost-center/affiliate-products/backfill]',
audit: (_req, data) => ({
action: 'cost_affiliate_skus_backfilled',
targetType: 'order_items',
targetId: '',
data: data as Record<string, unknown>,
}),
}),
)
router.post(
'/cost-center/affiliate-products/price-baseline',
requireAdminPermission('finance.config'),
@@ -151,25 +114,6 @@ router.post(
),
)
router.post(
'/cost-center/work-order-products/backfill',
requireAdminPermission('finance.config'),
createJsonHandler(
(req) => withBatchTimeout(() => backfillAdminWorkOrderProductSkus(bodyOf(req))),
{
successMessage: '接单平台商品已回填',
errorMessage: '回填接单平台商品失败',
scope: '[admin/cost-center/work-order-products/backfill]',
audit: (_req, data) => ({
action: 'cost_work_order_products_backfilled',
targetType: 'order_items',
targetId: '',
data: data as Record<string, unknown>,
}),
},
),
)
router.post(
'/cost-center/work-order-template-costs/sync',
requireAdminPermission('finance.config'),
@@ -28,12 +28,9 @@ import dayjs from 'dayjs'
import { useState } from 'react'
import {
backfillAdminAffiliateDashSkus,
fetchAdminProductCatalog,
importAdminAffiliateDashPricesBaseline,
setAdminAffiliateProductPrice,
syncAdminAffiliateDashCostSnapshots,
syncAdminAffiliateDashProducts,
updateAdminProductCatalog,
} from '@/services/admin'
import type { ProductCatalogItem } from '@/types/admin'
@@ -57,10 +54,7 @@ export function AffiliateSkuPanel() {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [statusUpdatingId, setStatusUpdatingId] = useState<number | null>(null)
const [syncing, setSyncing] = useState(false)
const [backfilling, setBackfilling] = useState(false)
const [importingBaseline, setImportingBaseline] = useState(false)
const [syncingSnapshots, setSyncingSnapshots] = useState(false)
// 价格弹窗状态
const [priceModalOpen, setPriceModalOpen] = useState(false)
@@ -81,44 +75,6 @@ export function AffiliateSkuPanel() {
const products = productsQuery.data?.data.items || []
const total = productsQuery.data?.data.pagination.total || 0
async function syncProducts() {
setSyncing(true)
try {
const result = await syncAdminAffiliateDashProducts()
message.success(
`已同步 ${result.data.productCount} 个自动发货商品(新增 ${result.data.createdCount},更新 ${result.data.updatedCount})`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '同步自动发货商品失败')
} finally {
setSyncing(false)
}
}
async function backfillSkus() {
setBackfilling(true)
try {
const response = await backfillAdminAffiliateDashSkus()
const result = response.data
const factsText = result.factsRebuild
? `;已重算 ${result.factsRebuild.fromDate} ~ ${result.factsRebuild.toDate} 的销售事实`
: ''
const remainingText =
result.remainingCount > 0
? `;仍有 ${result.remainingCount} 个订单项缺少可匹配的自动发货 SKU`
: ''
message.success(
`已回填 ${result.updatedCount} 个订单项,${result.skippedCount} 个已是归一状态${factsText}${remainingText}`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '回填自动发货 SKU 失败')
} finally {
setBackfilling(false)
}
}
function handleImportBaseline() {
modal.confirm({
title: '导入上游采购成本基线',
@@ -143,31 +99,6 @@ export function AffiliateSkuPanel() {
})
}
function handleSyncSnapshots() {
modal.confirm({
title: '重算自动发货预估成本快照',
content:
'将为最近一个月的自动发货订单重新计算预估成本快照,自动消除因缺少采购价导致的 cost_missing 异常,并重算对应周期的销售事实。确定执行?',
okText: '确认重算',
cancelText: '取消',
onOk: async () => {
setSyncingSnapshots(true)
try {
const res = await syncAdminAffiliateDashCostSnapshots()
const snap = res.data.snapshotResult
message.success(
`重算完成!已生成成本快照 ${snap.createdCount} 条,已消除/解决异常 ${snap.resolvedExceptionCount} 条`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '重算成本快照失败')
} finally {
setSyncingSnapshots(false)
}
},
})
}
function openPriceModal(product: ProductCatalogItem) {
setSelectedProduct(product)
const currentYuan =
@@ -255,23 +186,6 @@ export function AffiliateSkuPanel() {
>
导入基线价格
</Button>
<Button
icon={<SyncOutlined />}
loading={syncingSnapshots}
onClick={handleSyncSnapshots}
>
重算成本快照
</Button>
<Button icon={<SyncOutlined />} loading={syncing} onClick={() => void syncProducts()}>
同步自动发货商品
</Button>
<Button
icon={<SyncOutlined />}
loading={backfilling}
onClick={() => void backfillSkus()}
>
回填自动发货 SKU
</Button>
<Button
icon={<ReloadOutlined />}
loading={productsQuery.isFetching}
@@ -285,7 +199,7 @@ export function AffiliateSkuPanel() {
type="info"
showIcon
style={{ marginBottom: 12 }}
message="自动发货订单直接按 aff:<skuCode> 归一,不走渠道映射。点击右侧「配置成本」可录入采购单价;已配置的商品可通过「重算成本快照」消除历史缺失异常。"
message="自动发货订单直接按 aff:<skuCode> 归一,不走渠道映射。点击「配置成本」录入采购单价;缺少成本价时由成本快照管道自动标记并补齐。"
/>
<PercentTable
tableLayout="fixed"
@@ -41,15 +41,11 @@ import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import dayjs from '@/lib/dayjs'
import {
applyAdminTemplateProductBootstrap,
backfillAdminAffiliateDashSkus,
backfillAdminWorkOrderProductSkus,
createAdminProductCatalog,
deleteAdminProductCatalog,
fetchAdminProductCatalog,
normalizeAdminManualProducts,
previewAdminTemplateProductBootstrap,
setAdminProductPrice,
syncAdminAffiliateDashProducts,
syncAdminProductPrice,
updateAdminProductCatalog,
} from '@/services/admin'
@@ -110,10 +106,6 @@ export function ProductCatalogPanel() {
const [bootstrapPreviewing, setBootstrapPreviewing] = useState(false)
const [bootstrapApplying, setBootstrapApplying] = useState(false)
const bootstrapSyncOptionsRef = useRef<BootstrapSyncOptions>({ includeDisabledTemplates: false })
const [backfillingWorkOrderSkus, setBackfillingWorkOrderSkus] = useState(false)
const [normalizingManualProducts, setNormalizingManualProducts] = useState(false)
const [syncingAffiliate, setSyncingAffiliate] = useState(false)
const [backfillingAffiliate, setBackfillingAffiliate] = useState(false)
const [productPage, setProductPage] = useState(1)
const [productPageSize, setProductPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [productModalOpen, setProductModalOpen] = useState(false)
@@ -299,117 +291,6 @@ export function ProductCatalogPanel() {
}
}
async function backfillWorkOrderProducts() {
setBackfillingWorkOrderSkus(true)
try {
const response = await backfillAdminWorkOrderProductSkus(backfillPayload())
const result = response.data
const factsText = result.factsRebuild
? `;已重算 ${result.factsRebuild.fromDate} ~ ${result.factsRebuild.toDate} 的销售事实`
: ''
const remainingText =
result.remainingCount > 0
? `;仍有 ${result.remainingCount} 个订单项没有可用的工单模板`
: ''
const tailText =
result.tailUpdatedCount > 0
? `,其中长尾商品 ${result.tailUpdatedCount} 个(新建商品 ${result.tailProductCount}、映射 ${result.tailMappingCount})`
: ''
message.success(
`已按工单模板回填 ${result.updatedCount} 个订单项${tailText},${result.skippedCount} 个已一致${factsText}${remainingText}`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '回填接单平台商品失败')
} finally {
setBackfillingWorkOrderSkus(false)
}
}
async function normalizeManualProductCatalog() {
setNormalizingManualProducts(true)
try {
const preview = await normalizeAdminManualProducts({ dryRun: true, ...backfillPayload() })
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(backfillPayload())
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)
}
}
async function syncAffiliateProducts() {
setSyncingAffiliate(true)
try {
const result = await syncAdminAffiliateDashProducts()
message.success(
`已同步 ${result.data.productCount} 个自动发货商品(新增 ${result.data.createdCount},更新 ${result.data.updatedCount})`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '同步自动发货商品失败')
} finally {
setSyncingAffiliate(false)
}
}
async function backfillAffiliateSkus() {
setBackfillingAffiliate(true)
try {
const response = await backfillAdminAffiliateDashSkus()
const result = response.data
const factsText = result.factsRebuild
? `;已重算 ${result.factsRebuild.fromDate} ~ ${result.factsRebuild.toDate} 的销售事实`
: ''
const remainingText =
result.remainingCount > 0
? `;仍有 ${result.remainingCount} 个订单项缺少可匹配的自动发货 SKU`
: ''
message.success(
`已回填 ${result.updatedCount} 个订单项,${result.skippedCount} 个已是归一状态${factsText}${remainingText}`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '回填自动发货 SKU 失败')
} finally {
setBackfillingAffiliate(false)
}
}
function openCreateProduct() {
setEditingProduct(null)
productForm.resetFields()
@@ -795,73 +676,15 @@ export function ProductCatalogPanel() {
allowClear={false}
/>
) : null}
{productSourceFilter === 'affiliate_dash' ? (
<>
<Button
icon={<SyncOutlined />}
loading={syncingAffiliate}
onClick={() => void syncAffiliateProducts()}
>
同步自动发货商品
</Button>
<Button
icon={<SyncOutlined />}
loading={backfillingAffiliate}
onClick={() => void backfillAffiliateSkus()}
>
回填自动发货 SKU
</Button>
</>
) : productSourceFilter === 'manual' ? (
{productSourceFilter !== 'affiliate_dash' ? (
<Button
icon={<SyncOutlined />}
loading={normalizingManualProducts}
onClick={() => void normalizeManualProductCatalog()}
loading={bootstrapPreviewing || bootstrapApplying}
onClick={() => void runTemplateBootstrap()}
>
归一人工商品
从接单模板初始化
</Button>
) : productSourceFilter === 'work_order' ? (
<>
<Button
icon={<SyncOutlined />}
loading={bootstrapPreviewing || bootstrapApplying}
onClick={() => void runTemplateBootstrap()}
>
从接单模板初始化
</Button>
<Button
icon={<SyncOutlined />}
loading={backfillingWorkOrderSkus}
onClick={() => void backfillWorkOrderProducts()}
>
按工单回填商品
</Button>
</>
) : (
<>
<Button
icon={<SyncOutlined />}
loading={bootstrapPreviewing || bootstrapApplying}
onClick={() => void runTemplateBootstrap()}
>
从接单模板初始化
</Button>
<Button
icon={<SyncOutlined />}
loading={backfillingWorkOrderSkus}
onClick={() => void backfillWorkOrderProducts()}
>
按工单回填商品
</Button>
<Button
icon={<SyncOutlined />}
loading={normalizingManualProducts}
onClick={() => void normalizeManualProductCatalog()}
>
归一人工商品
</Button>
</>
)}
) : null}
<Button
icon={<ReloadOutlined />}
loading={productsQuery.isFetching}