feat(finance): 自动发货订单项按上游 SKU 直接归一
自动发货只有 affiliate-dash 单一上游渠道,订单项直接按 fulfillment_tasks.context_json.skuCode 归一为 aff:<skuCode>,不写 channel_product_mappings。\n\n新增 /cost-center/affiliate-products/backfill:按 order_item_id 取最新任务 SKU 回填 canonical_product_id,只更新有变化的订单项,并自动重算受影响日期的销售事实;返回 updatedCount/skippedCount/remainingCount。\n\n自动发货 SKU 列表改为实时统计履约任务数与最近履约时间,不读会被价格导入覆盖的 attributes_json。\n\n本地实测 41,676 个订单项全部归一,自动发货未归一收入 5,089,712.26 元降至 0;整体未归一占比 79.67% 降至 60.47%。
This commit is contained in:
@@ -40,6 +40,7 @@ import { usePersistedState } from '@/hooks/usePersistedState'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import {
|
||||
applyAdminTemplateProductBootstrap,
|
||||
backfillAdminAffiliateDashSkus,
|
||||
backfillAdminNormalizedQuantities,
|
||||
backfillAdminProductMappings,
|
||||
createAdminChannelProductMapping,
|
||||
@@ -366,6 +367,7 @@ export default function AdminCostCenterPage() {
|
||||
const [bootstrapPreviewing, setBootstrapPreviewing] = useState(false)
|
||||
const [bootstrapApplying, setBootstrapApplying] = useState(false)
|
||||
const [syncingAffiliate, setSyncingAffiliate] = useState(false)
|
||||
const [backfillingAffiliateSkus, setBackfillingAffiliateSkus] = useState(false)
|
||||
const versionScopeType = Form.useWatch('scopeType', versionForm)
|
||||
const versionCostItemId = Form.useWatch('costItemId', versionForm)
|
||||
|
||||
@@ -669,6 +671,29 @@ export default function AdminCostCenterPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function backfillAffiliateSkus() {
|
||||
setBackfillingAffiliateSkus(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 {
|
||||
setBackfillingAffiliateSkus(false)
|
||||
}
|
||||
}
|
||||
|
||||
function openEditProduct(item: ProductCatalogItem) {
|
||||
setEditingProduct(item)
|
||||
productForm.setFieldsValue({
|
||||
@@ -1970,6 +1995,13 @@ export default function AdminCostCenterPage() {
|
||||
>
|
||||
同步自动发货商品
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={backfillingAffiliateSkus}
|
||||
onClick={() => void backfillAffiliateSkus()}
|
||||
>
|
||||
回填自动发货 SKU
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={productsQuery.isFetching}
|
||||
@@ -1986,7 +2018,7 @@ export default function AdminCostCenterPage() {
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="自动发货只按上游货源 SKU 配采购成本:这里不维护游戏分类、渠道映射和商品上下架,那些只属于接单平台。"
|
||||
message="自动发货只按上游货源 SKU 配采购成本:订单项直接按 aff:<skuCode> 归一,不走渠道映射;这里不维护游戏分类和商品上下架,那些只属于接单平台。"
|
||||
/>
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
@@ -2031,14 +2063,14 @@ export default function AdminCostCenterPage() {
|
||||
{
|
||||
title: '履约任务',
|
||||
width: 110,
|
||||
render: (_, row) => `${Number(row.attributes?.taskCount || 0)} 单`,
|
||||
render: (_, row) => `${row.affiliateTaskCount} 单`,
|
||||
},
|
||||
{
|
||||
title: '最近履约',
|
||||
width: 180,
|
||||
render: (_, row) =>
|
||||
row.attributes?.lastFulfilledAt
|
||||
? formatAdminDateTime(String(row.attributes.lastFulfilledAt))
|
||||
row.affiliateLastFulfilledAt
|
||||
? formatAdminDateTime(row.affiliateLastFulfilledAt)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,6 +52,23 @@ export function syncAdminAffiliateDashProducts() {
|
||||
)
|
||||
}
|
||||
|
||||
export function backfillAdminAffiliateDashSkus() {
|
||||
return apiPost<{
|
||||
updatedCount: number
|
||||
skippedCount: number
|
||||
remainingCount: number
|
||||
affiliateItemCount: number
|
||||
matchedCount: number
|
||||
factsRebuild: {
|
||||
fromDate: string
|
||||
toDate: string
|
||||
deletedCount: number
|
||||
insertedCount: number
|
||||
batchCount: number
|
||||
} | null
|
||||
}>('/api/v1/admin/cost-center/affiliate-products/backfill')
|
||||
}
|
||||
|
||||
export function updateAdminProductCatalog(productId: number, payload: Record<string, unknown>) {
|
||||
return apiPatch<{ product: ProductCatalogItem }>(
|
||||
`/api/v1/admin/cost-center/products/${productId}`,
|
||||
|
||||
@@ -27,6 +27,9 @@ export interface ProductCatalogItem {
|
||||
attributes: Record<string, unknown>
|
||||
mappingCount: number
|
||||
costVersionCount: number
|
||||
/** 自动发货履约任务数,实时取自 fulfillment_tasks。 */
|
||||
affiliateTaskCount: number
|
||||
affiliateLastFulfilledAt: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user