feat(supply-chain): SABC 不考核原因改用档案产品类型,并纳入每日回填

This commit is contained in:
居居 2026-09-29 11:19:17 +08:00
parent 9ae9423742
commit 4c518b054e
3 changed files with 29 additions and 21 deletions

View File

@ -3,8 +3,9 @@
// 1) 聚水潭产品档案全量同步
// 2) 聚水潭销售日数据增量同步(最近 7 天)
// 3) 以正式库产品全量档案对齐(生产状态 + 创建时间)
// 4) 以正式库类目档位表同步本地类目配置
// 5) 重算指定月份(默认上个月)SABC 并写库
// 4) 回填产品全量档案「产品类型」(定厂优先,聚水潭 vc_name 兜底)
// 5) 以正式库类目档位表同步本地类目配置
// 6) 重算指定月份(默认上个月)SABC 并写库
// 用法:
// node scripts/local-sync-archive.mjs [--month=2026-08] [--dry-run] [--skip-sabc]
import { spawn } from 'node:child_process'
@ -135,9 +136,9 @@ async function main() {
}
if (apply) {
console.log(`==> 1/5 同步聚水潭产品档案(${env.JST_REPORT_DB_HOST}/${env.JST_REPORT_DB_NAME} → 本地)`)
console.log(`==> 1/6 同步聚水潭产品档案(${env.JST_REPORT_DB_HOST}/${env.JST_REPORT_DB_NAME} → 本地)`)
} else {
console.log('==> 1/5 预览聚水潭产品档案差异(dry-run,不写库)')
console.log('==> 1/6 预览聚水潭产品档案差异(dry-run,不写库)')
}
const syncArgs = ['scripts/sync-jst-sku-product-archive.ts', '--all', '--batch-size=500', '--concurrency=8']
if (apply) {
@ -153,7 +154,7 @@ async function main() {
printSyncSummary(await run(syncArgs, { capture: true }))
if (apply) {
console.log('==> 2/5 同步聚水潭销售日数据(最近 7 天)')
console.log('==> 2/6 同步聚水潭销售日数据(最近 7 天)')
await run([
'scripts/sync-supply-chain-product-archive-sales-performance-daily.ts',
'--apply',
@ -163,32 +164,40 @@ async function main() {
'--lookback-days=7',
])
} else {
console.log('==> 2/5 已跳过销售日数据同步(dry-run)')
console.log('==> 2/6 已跳过销售日数据同步(dry-run)')
}
if (apply) {
console.log('==> 3/5 以正式库产品全量档案对齐(状态 + 创建时间)')
console.log('==> 3/6 以正式库产品全量档案对齐(状态 + 创建时间)')
await run(['scripts/reconcile-archive-from-prod.ts', '--apply', '--confirm=reconcile-archive-from-prod'])
} else {
console.log('==> 3/5 预览正式库对齐差异(dry-run,不写库)')
console.log('==> 3/6 预览正式库对齐差异(dry-run,不写库)')
await run(['scripts/reconcile-archive-from-prod.ts'])
}
if (apply) {
console.log('==> 4/5 同步正式库类目档位配置')
console.log('==> 4/6 回填产品类型(定厂优先,聚水潭 vc_name 兜底)')
await run(['scripts/backfill-product-archive-production-type.ts', '--apply', '--batch-size=200'])
} else {
console.log('==> 4/6 预览产品类型差异(dry-run,不写库)')
await run(['scripts/backfill-product-archive-production-type.ts', '--batch-size=200'])
}
if (apply) {
console.log('==> 5/6 同步正式库类目档位配置')
await run(['scripts/sync-product-category-config.ts', '--apply', '--confirm=sync-product-category-config'])
} else {
console.log('==> 4/5 预览类目档位差异(dry-run,不写库)')
console.log('==> 5/6 预览类目档位差异(dry-run,不写库)')
await run(['scripts/sync-product-category-config.ts'])
}
if (apply && !skipSabc) {
const year = month.split('-')[0]
const monthNumber = String(Number(month.split('-')[1]))
console.log(`==> 5/5 重算 ${year}-${monthNumber} SABC 并写库(APPLY)`)
console.log(`==> 6/6 重算 ${year}-${monthNumber} SABC 并写库(APPLY)`)
await run(['scripts/run-product-sabc.ts', `--year=${year}`, `--month=${monthNumber}`, '--mode=APPLY', '--confirm=run-product-sabc'])
} else {
console.log('==> 5/5 已跳过 SABC 重算')
console.log('==> 6/6 已跳过 SABC 重算')
}
console.log('==> 完成。SABC 页面:http://localhost:3000/supply-chain/management/blank-pages/blank-page-3')

View File

@ -35,6 +35,7 @@ const archiveSelect = {
productName: true,
productStatus: true,
productAttribute: true,
productionType: true,
brand: true,
productTag: true,
sabcRating: true,
@ -312,7 +313,8 @@ function createBlankRow(target: ArchiveTarget, periodEnd: Date, configuration: S
grossMargin: '',
afterSaleRatio: '',
assessmentStatus: '',
noAssessmentReason: '',
// 「不考核原因」取产品全量档案的「产品类型」(OEM/ODM 等)。
noAssessmentReason: target.productionType ?? '',
sameProductCode: '无',
eligible: isSabcEligibleProduct(target),
}
@ -323,12 +325,7 @@ function applyValues(target: ArchiveTarget, periodEnd: Date, values: MonthlyValu
if (row.eligible) applyRawMetrics(row, values, periodEnd.getUTCDate())
// 零业绩(真实业绩为 0 且发货件数为 0)按人工表口径固定为 异常/滞销款/五级/D,不依赖类目阈值配置。
const zeroPerformance = row.eligible && values.realPerformance.isZero() && values.shipQty === BigInt(0)
if (!row.eligible || (!configuration?.rules.length && !zeroPerformance)) {
row.noAssessmentReason = row.eligible
? '类目未配置 SABC 阈值'
: ''
return row
}
if (!row.eligible || (!configuration?.rules.length && !zeroPerformance)) return row
const daysInPeriod = periodEnd.getUTCDate()
const classification = zeroPerformance
? {

View File

@ -225,6 +225,7 @@ describe('supply-chain-product-sabc rules', () => {
productName: '休闲椅',
productStatus: '销售',
productAttribute: '成品',
productionType: 'ODM',
brand: '品牌',
productTag: '',
sabcRating: '',
@ -271,7 +272,7 @@ describe('supply-chain-product-sabc rules', () => {
grossMargin: '0.79674797',
afterSaleRatio: '0.02439024',
sabcRating: '',
noAssessmentReason: '类目未配置 SABC 阈值',
noAssessmentReason: 'ODM',
}))
})
@ -288,6 +289,7 @@ describe('supply-chain-product-sabc rules', () => {
productName: '飘窗柜',
productStatus: '销售',
productAttribute: '成品',
productionType: 'OEM',
brand: '其他品牌-山东专用',
productTag: '',
sabcRating: '',
@ -314,7 +316,7 @@ describe('supply-chain-product-sabc rules', () => {
sabcRating: 'D',
movingStatus: '不动销',
assessmentStatus: '考核',
noAssessmentReason: '',
noAssessmentReason: 'OEM',
}))
})