chore(cost-center): 下线会与管道冲突的旧商品入口
- 移除自动发货商品同步、SKU 回填、按工单回填、人工商品归一等旧接口与按钮 - 商品目录只保留「从接单模板初始化」与手工新建 - 自动发货面板移除已失效的「重算成本快照」入口
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user