fix(cost-center): 归档已被其他渠道接管的人工商品

This commit is contained in:
yml2213
2026-11-02 17:07:29 +08:00
parent 290890e848
commit 31b6d2d65f
11 changed files with 361 additions and 16 deletions
@@ -43,6 +43,7 @@ import dayjs from '@/lib/dayjs'
import {
applyAdminTemplateProductBootstrap,
archiveAdminHistoricalManualProducts,
archiveAdminSupersededManualProducts,
createAdminProductCatalog,
deleteAdminProductCatalog,
fetchAdminProductCatalog,
@@ -56,6 +57,7 @@ import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admi
import { useCostCenter } from './CostCenterContext'
import {
FULFILLMENT_MODE_LABELS,
PRODUCT_SOURCE_OPTIONS,
PRODUCT_SOURCE_TAG_COLORS,
labelOf,
@@ -108,6 +110,7 @@ export function ProductCatalogPanel() {
const [bootstrapPreviewing, setBootstrapPreviewing] = useState(false)
const [bootstrapApplying, setBootstrapApplying] = useState(false)
const [archivingHistoricalManuals, setArchivingHistoricalManuals] = useState(false)
const [archivingSupersededManuals, setArchivingSupersededManuals] = useState(false)
const bootstrapSyncOptionsRef = useRef<BootstrapSyncOptions>({ includeDisabledTemplates: false })
const [productPage, setProductPage] = useState(1)
const [productPageSize, setProductPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
@@ -487,6 +490,41 @@ export function ProductCatalogPanel() {
}
}
async function archiveSupersededManuals() {
setArchivingSupersededManuals(true)
try {
const preview = (await archiveAdminSupersededManualProducts()).data
if (preview.candidateCount === 0) {
message.info('没有已被其他渠道接管的人工归一商品')
return
}
modal.confirm({
title: '归档跨渠道重复的人工归一商品',
content: `发现 ${preview.candidateCount} 个 man: 商品已有自动发货、接单平台或卡券主渠道。将归档这些旧商品,并把仍指向它们的人工兜底改指当前主商品;不会删除商品行、订单、订单项、成本版本或成本快照。`,
okText: '确认归档',
cancelText: '取消',
onOk: async () => {
setArchivingSupersededManuals(true)
try {
const result = (await archiveAdminSupersededManualProducts({ execute: true })).data
message.success(
`已归档 ${result.archivedCount} 个重复商品,修正 ${result.repointedChannelCount} 条人工兜底`,
)
refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '归档跨渠道重复商品失败')
} finally {
setArchivingSupersededManuals(false)
}
},
})
} catch (error) {
message.error(error instanceof Error ? error.message : '读取跨渠道重复商品失败')
} finally {
setArchivingSupersededManuals(false)
}
}
function deleteProduct(item: ProductCatalogItem) {
const affiliateHint =
item.source === 'affiliate_dash'
@@ -554,6 +592,12 @@ export function ProductCatalogPanel() {
{row.templatePricingMode === 'unit' ? '按件计价' : '固定金额'}
</Tag>
) : null}
{row.activePrimaryChannel && row.activePrimaryChannel !== row.source ? (
<Tag color="purple" style={{ marginInlineEnd: 0 }}>
当前主渠道:
{FULFILLMENT_MODE_LABELS[row.activePrimaryChannel] || row.activePrimaryChannel}
</Tag>
) : null}
</Space>
</div>
),
@@ -716,13 +760,22 @@ export function ProductCatalogPanel() {
canConfig ? (
<Space size={6} wrap>
{productSourceFilter === 'manual' ? (
<Button
icon={<ClearOutlined />}
loading={archivingHistoricalManuals}
onClick={() => void archiveHistoricalManuals()}
>
归档历史人工商品
</Button>
<>
<Button
icon={<ClearOutlined />}
loading={archivingSupersededManuals}
onClick={() => void archiveSupersededManuals()}
>
归档跨渠道重复商品
</Button>
<Button
icon={<ClearOutlined />}
loading={archivingHistoricalManuals}
onClick={() => void archiveHistoricalManuals()}
>
归档纯历史人工商品
</Button>
</>
) : null}
{productSourceFilter === 'work_order' ? (
<>
@@ -105,6 +105,23 @@ export function archiveAdminHistoricalManualProducts(payload: { execute?: boolea
}>('/api/v1/admin/cost-center/products/archive-historical-manual', payload)
}
export function archiveAdminSupersededManualProducts(payload: { execute?: boolean } = {}) {
return apiPost<{
dryRun: boolean
candidateCount: number
archivedCount: number
repointedChannelCount: number
sample: Array<{
id: number
canonical_sku: string
canonical_name: string
primary_channel: string
target_product_id: number
target_product_sku: string
}>
}>('/api/v1/admin/cost-center/products/archive-superseded-manual', payload)
}
export function syncAdminAffiliateDashProducts() {
return apiPost<{ createdCount: number; updatedCount: number; productCount: number }>(
'/api/v1/admin/cost-center/affiliate-products/sync',
@@ -54,6 +54,7 @@ export interface ProductCatalogItem {
templatePricingMode?: 'fixed' | 'unit' | null
templateEnabled?: boolean | null
templateSharingEnabled?: boolean | null
activePrimaryChannel?: FulfillmentChannel | null
/** 自动发货履约任务数,实时取自 fulfillment_tasks。 */
affiliateTaskCount: number
affiliateLastFulfilledAt: string | null