feat(cost-center): 接单模板商品全量同步,改价默认立即生效
This commit is contained in:
@@ -34,7 +34,7 @@ import {
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import PercentTable from '@/components/common/PercentTable'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
||||
@@ -65,6 +65,33 @@ import {
|
||||
type ProductFormValues,
|
||||
} from './shared'
|
||||
|
||||
/** 全量同步选项:停用模板默认不建商品,勾选后建成「归档」商品。 */
|
||||
type BootstrapSyncOptions = { includeDisabledTemplates: boolean }
|
||||
|
||||
function DisabledTemplateSyncOption({
|
||||
initialValue,
|
||||
onChange,
|
||||
}: {
|
||||
initialValue: boolean
|
||||
onChange: (value: boolean) => void
|
||||
}) {
|
||||
const [checked, setChecked] = useState(initialValue)
|
||||
return (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={(event) => {
|
||||
setChecked(event.target.checked)
|
||||
onChange(event.target.checked)
|
||||
}}
|
||||
>
|
||||
<strong>同时为停用模板建商品(归档状态,不参与成本统计)</strong>
|
||||
<div style={{ color: '#64748b', fontSize: 12, marginTop: 2 }}>
|
||||
默认只同步启用模板;停用模板一般是历史遗留,建出来的商品会以「归档」出现在商品列表里。
|
||||
</div>
|
||||
</Checkbox>
|
||||
)
|
||||
}
|
||||
|
||||
export function ProductCatalogPanel() {
|
||||
const { message, modal } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -82,6 +109,7 @@ export function ProductCatalogPanel() {
|
||||
const [productStatusUpdatingId, setProductStatusUpdatingId] = useState<number | null>(null)
|
||||
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)
|
||||
@@ -152,36 +180,51 @@ export function ProductCatalogPanel() {
|
||||
try {
|
||||
const response = await previewAdminTemplateProductBootstrap(backfillPayload())
|
||||
const data = response.data
|
||||
bootstrapSyncOptionsRef.current.includeDisabledTemplates = false
|
||||
modal.confirm({
|
||||
title: '从接单模板初始化商品与映射',
|
||||
title: '从接单模板全量同步商品与映射',
|
||||
width: 720,
|
||||
content: (
|
||||
<Space direction="vertical" size={8}>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message={`统计范围:${data.dateFrom} ~ ${data.dateTo}(默认最近 30 天)`}
|
||||
message={`统计范围:${data.dateFrom} ~ ${data.dateTo}(商品全量同步,不依赖订单;映射证据按该范围)`}
|
||||
/>
|
||||
<span>
|
||||
启用接单模板 <strong>{data.templateCount}</strong> 个:将新建{' '}
|
||||
<strong>{data.productCreateCount}</strong> 个标准商品,复用{' '}
|
||||
<strong>{data.productExistingCount}</strong> 个已有商品。
|
||||
接单模板共 <strong>{data.totalTemplateCount}</strong> 个(启用{' '}
|
||||
<strong>{data.templateCount}</strong> / 停用{' '}
|
||||
<strong>{data.disabledTemplateCount}</strong>)。
|
||||
</span>
|
||||
<span>
|
||||
根据所选时间范围内工单命中的模板,将建立 <strong>{data.mappingCreateCount}</strong>{' '}
|
||||
条渠道映射:编码精确 <strong>{data.mappingCreateByMatchType.sku_code}</strong>{' '}
|
||||
条、编码 + 名称系列 <strong>{data.mappingCreateByMatchType.sku_code_series}</strong>{' '}
|
||||
条、 编码 + 精确名称 <strong>{data.mappingCreateByMatchType.sku_code_name}</strong>{' '}
|
||||
条、名称系列兜底 <strong>{data.mappingCreateByMatchType.sku_name_series}</strong> 条。
|
||||
启用模板全部建标准商品:将新建 <strong>{data.productCreateCount}</strong> 个、复用{' '}
|
||||
<strong>{data.productExistingCount}</strong>{' '}
|
||||
个(已有模板商品会同步刷新名称、分类、发单金额)。
|
||||
</span>
|
||||
<span>
|
||||
其中 <strong>{data.evidenceTemplateCount}</strong> 个模板在范围内有工单证据,将建立{' '}
|
||||
<strong>{data.mappingCreateCount}</strong> 条渠道映射:编码精确{' '}
|
||||
<strong>{data.mappingCreateByMatchType.sku_code}</strong> 条、编码 + 名称系列{' '}
|
||||
<strong>{data.mappingCreateByMatchType.sku_code_series}</strong> 条、 编码 + 精确名称{' '}
|
||||
<strong>{data.mappingCreateByMatchType.sku_code_name}</strong> 条、名称系列兜底{' '}
|
||||
<strong>{data.mappingCreateByMatchType.sku_name_series}</strong> 条。
|
||||
</span>
|
||||
<span>
|
||||
<strong>{data.templatesWithoutWorkOrders}</strong> 个启用模板在范围内没有工单证据:
|
||||
只建商品(可在「商品与成本中心」直接改价),不建渠道映射。
|
||||
</span>
|
||||
<span>
|
||||
预计可归一 <strong>{data.orderItemCoverCount}</strong> / {data.evidenceItemCount}{' '}
|
||||
个范围内有工单证据的订单项。
|
||||
</span>
|
||||
<span>
|
||||
<strong>{data.templatesWithoutWorkOrders}</strong>{' '}
|
||||
个启用模板在范围内没有工单证据,本次不会创建。
|
||||
</span>
|
||||
{data.disabledTemplateCount > 0 ? (
|
||||
<DisabledTemplateSyncOption
|
||||
initialValue={false}
|
||||
onChange={(value) => {
|
||||
bootstrapSyncOptionsRef.current = { includeDisabledTemplates: value }
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{data.mappingConflictCount > 0 ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
@@ -215,12 +258,15 @@ export function ProductCatalogPanel() {
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
),
|
||||
okText: '开始初始化并修复旧导入',
|
||||
okText: '开始全量同步',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
setBootstrapApplying(true)
|
||||
try {
|
||||
const result = await applyAdminTemplateProductBootstrap(backfillPayload())
|
||||
const result = await applyAdminTemplateProductBootstrap({
|
||||
...backfillPayload(),
|
||||
includeDisabledTemplates: bootstrapSyncOptionsRef.current.includeDisabledTemplates,
|
||||
})
|
||||
const applied = result.data
|
||||
const rebuildText =
|
||||
applied.deletedImportedMappingCount > 0
|
||||
@@ -230,8 +276,12 @@ export function ProductCatalogPanel() {
|
||||
applied.mappingConflictCount > 0
|
||||
? `;保留 ${applied.mappingConflictCount} 组冲突待人工处理`
|
||||
: ''
|
||||
const disabledText =
|
||||
applied.disabledTemplateCount > 0
|
||||
? `(含停用模板 ${applied.disabledTemplateCount} 个,已建为归档商品)`
|
||||
: ''
|
||||
message.success(
|
||||
`已新建 ${applied.createdProductCount} 个商品、${applied.createdMappingCount} 条映射,同步 ${applied.templateCostVersionCount} 条模板发单金额,回填 ${applied.mappedOrderItemCount} 个订单项${rebuildText}${conflictText}`,
|
||||
`已同步 ${applied.templateCount} 个模板商品:新建 ${applied.createdProductCount} 个、刷新 ${applied.updatedProductCount} 个、复用 ${applied.reusedProductCount} 个${disabledText};新增 ${applied.createdMappingCount} 条映射,同步 ${applied.templateCostVersionCount} 条模板发单金额,回填 ${applied.mappedOrderItemCount} 个订单项${rebuildText}${conflictText}`,
|
||||
)
|
||||
refreshAll()
|
||||
} catch (error) {
|
||||
@@ -411,7 +461,7 @@ export function ProductCatalogPanel() {
|
||||
item.activeCostAmountFen !== null && item.activeCostAmountFen !== undefined
|
||||
? (item.activeCostAmountFen / 100).toFixed(2)
|
||||
: undefined,
|
||||
effectiveScope: 'retroactive',
|
||||
effectiveScope: 'immediate',
|
||||
changeReason: '在商品与成本中心统一调整价格',
|
||||
syncOpenOrders: true,
|
||||
syncSnapshots: true,
|
||||
@@ -1065,16 +1115,16 @@ export function ProductCatalogPanel() {
|
||||
rules={[{ required: true, message: '请选择生效时间范围' }]}
|
||||
>
|
||||
<Radio.Group style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<Radio value="retroactive">
|
||||
<strong>追溯至历史订单(推荐)</strong>
|
||||
<Radio value="immediate">
|
||||
<strong>自当前时间立即生效(推荐,仅对后续订单生效)</strong>
|
||||
<div style={{ color: '#64748b', fontSize: 12, marginTop: 2 }}>
|
||||
自近一个月业务起点(30 天前)生效,直接更新基线并消除近一个月 cost_missing 异常
|
||||
保留旧版本历史,自新的价格版本创建时刻起计算后续履约结算
|
||||
</div>
|
||||
</Radio>
|
||||
<Radio value="immediate">
|
||||
<strong>自当前时间立即生效(仅对后续订单生效)</strong>
|
||||
<Radio value="retroactive">
|
||||
<strong>追溯至历史订单</strong>
|
||||
<div style={{ color: '#64748b', fontSize: 12, marginTop: 2 }}>
|
||||
保留旧版本历史,自新版本创建时刻起计算后续履约结算
|
||||
自近一个月业务起点(30 天前)生效,直接更新基线并消除近一个月 cost_missing 异常
|
||||
</div>
|
||||
</Radio>
|
||||
<Radio value="custom">
|
||||
|
||||
@@ -55,6 +55,7 @@ export function previewAdminTemplateProductBootstrap(payload?: {
|
||||
export function applyAdminTemplateProductBootstrap(payload?: {
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
includeDisabledTemplates?: boolean
|
||||
}) {
|
||||
return apiPost<TemplateProductBootstrapResult>(
|
||||
'/api/v1/admin/cost-center/template-bootstrap/apply',
|
||||
|
||||
@@ -175,7 +175,10 @@ export interface CostCenterOptions {
|
||||
export interface TemplateProductBootstrapPreview {
|
||||
dateFrom: string
|
||||
dateTo: string
|
||||
totalTemplateCount: number
|
||||
templateCount: number
|
||||
disabledTemplateCount: number
|
||||
evidenceTemplateCount: number
|
||||
productExistingCount: number
|
||||
productCreateCount: number
|
||||
mappingCandidateCount: number
|
||||
@@ -215,7 +218,11 @@ export interface TemplateProductBootstrapPreview {
|
||||
|
||||
export interface TemplateProductBootstrapResult {
|
||||
templateCount: number
|
||||
enabledTemplateCount: number
|
||||
disabledTemplateCount: number
|
||||
evidenceTemplateCount: number
|
||||
createdProductCount: number
|
||||
updatedProductCount: number
|
||||
reusedProductCount: number
|
||||
createdMappingCount: number
|
||||
skippedMappingCount: number
|
||||
|
||||
Reference in New Issue
Block a user