feat(finance): 人工履约 SKU 配置清单 + 四钱包(南航/乐玩/ROCK积分 + 备用金现金)
客服人工履约有四个钱包,成本与渠道在后台配置好,客服确认履约时零输入。 口径(2026-10-24 确认): - 四个钱包:南航积分 / 乐玩积分 / ROCK 积分 / 备用金现金。三平台积分互不通用, 每客服每平台一个积分账户;现金复用现有 fund_accounts / fund_entries。 - 积分换算率按平台不同:南航 1 积分 = 1 分,乐玩 1 积分 = 1 分, ROCK 1 积分 = 1 元(100 分)。真源是履约档案 config_json.rechargeUnitPriceFen。 - 积分是货币,幸运币是商品;消耗积分与成本金额按换算率一一对应。 - 配置主键是**内部 SKU**(快手 spec_json.skuNick / 拼多多 spec_json.outSkuSn), 不是商品标题(标题是引流)。同名 SKU 算同一商品,跨渠道共用一份配置。 - 配置链路没有匹配引擎、没有打分:扫描 → 配置 → 重新匹配。 10 月口径下人工单 17,469 单 / 357 个内部 SKU,Top 50 覆盖 88% 单量。 更早的老单没配置意义,不纳入统计口径。 - 消耗积分填 0 = 成本不固定,客服在确认履约时手填实际成本; 渠道始终提前配好,客服不选。 迁移 110: - manual_sku_registry:扫描到的内部 SKU 登记表(统计 + 配置状态)。 - manual_fulfillment_configs:内部 SKU → 渠道 + 消耗积分 + 成本模式。 - platform_credit_accounts / platform_credit_entries:三平台积分钱包与流水。 - order_items.fulfillment_config_id:订单项挂配置,重新匹配时回填。 - 4 条 manual_* 履约档案(manual_dispatch 执行器 + config_json 区分平台)。 不复用 kuaishou_ct_assisted / kuaishou_feifei——那是已下线的历史自动发货通道。 性能:spec_json->>'skuNick' 无索引时扫描 2.4s / 回填 5.4s; 新增 idx_order_items_manual_internal_sku 表达式索引后回填降到 1.7s。 后端:manual-fulfillment-config-service.ts(扫描/清单/配置CRUD/重新匹配), 成本中心挂 7 个接口。 前端:成本中心新增「人工 SKU 配置」Tab(扫描、清单、配置弹窗、重新匹配)。 校验:后端 765 项 / 0 失败(含 check:sql 迁移护栏),前端 143 项全通过; E2E 实测扫描 357 个 SKU、配置四平台换算正确、重新匹配回填 7,604 个订单项; 本地库迁移 110 执行后数据量与改造前一致。 Co-Authored-By: Claude Opus 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
fb679d76f6
commit
4bcb4e6b57
@@ -31,6 +31,16 @@ import {
|
||||
updateAdminChannelMapping,
|
||||
updateAdminProduct,
|
||||
} from '../../services/cost-center/product-catalog-service.js'
|
||||
import {
|
||||
deleteManualFulfillmentConfig,
|
||||
getManualFulfillmentConfig,
|
||||
getManualSkuOverview,
|
||||
listManualPlatformOptions,
|
||||
listManualSkus,
|
||||
rematchManualFulfillments,
|
||||
saveManualFulfillmentConfig,
|
||||
scanManualSkus,
|
||||
} from '../../services/cost-center/manual-fulfillment-config-service.js'
|
||||
import {
|
||||
applyAdminTemplateProductBootstrap,
|
||||
backfillAdminAffiliateDashSkus,
|
||||
@@ -597,4 +607,142 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 人工履约 SKU 配置:扫描 → 配置 → 重新匹配
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/cost-center/manual-skus',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listManualSkus(req.query as Record<string, unknown>), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工 SKU 清单失败',
|
||||
scope: '[admin/cost-center/manual-skus]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/manual-skus/overview',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler(() => getManualSkuOverview(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工 SKU 概览失败',
|
||||
scope: '[admin/cost-center/manual-skus/overview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/manual-platforms',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler(() => listManualPlatformOptions(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工履约渠道失败',
|
||||
scope: '[admin/cost-center/manual-platforms]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/manual-configs/:skuKey',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => getManualFulfillmentConfig(String(req.params.skuKey || '')), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工履约配置失败',
|
||||
scope: '[admin/cost-center/manual-configs/:skuKey]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/manual-skus/scan',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => scanManualSkus(bodyOf(req) as never), {
|
||||
successMessage: '扫描完成',
|
||||
errorMessage: '扫描人工 SKU 失败',
|
||||
scope: '[admin/cost-center/manual-skus/scan]',
|
||||
audit: (req, data) => {
|
||||
const result = data as { scanned?: number; fromDate?: string; toDate?: string }
|
||||
return {
|
||||
action: 'manual_skus_scanned',
|
||||
targetType: 'manual_sku_registry',
|
||||
targetId: '',
|
||||
data: {
|
||||
scanned: result.scanned || 0,
|
||||
fromDate: result.fromDate || '',
|
||||
toDate: result.toDate || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/manual-configs',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => saveManualFulfillmentConfig(bodyOf(req)), {
|
||||
successMessage: '人工履约配置已保存',
|
||||
errorMessage: '保存人工履约配置失败',
|
||||
scope: '[admin/cost-center/manual-configs]',
|
||||
audit: (req, data) => {
|
||||
const config = data as {
|
||||
id?: number
|
||||
skuKey?: string
|
||||
platformKey?: string
|
||||
creditQuantity?: number
|
||||
costFen?: number
|
||||
}
|
||||
return {
|
||||
action: 'manual_fulfillment_config_saved',
|
||||
targetType: 'manual_fulfillment_config',
|
||||
targetId: String(config.id || ''),
|
||||
data: {
|
||||
skuKey: config.skuKey || '',
|
||||
platformKey: config.platformKey || '',
|
||||
creditQuantity: config.creditQuantity || 0,
|
||||
costFen: config.costFen || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/manual-configs/:skuKey',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteManualFulfillmentConfig(String(req.params.skuKey || '')), {
|
||||
successMessage: '人工履约配置已删除',
|
||||
errorMessage: '删除人工履约配置失败',
|
||||
scope: '[admin/cost-center/manual-configs/:skuKey]',
|
||||
audit: (req, data) => {
|
||||
const result = data as { skuKey?: string; deleted?: number }
|
||||
return {
|
||||
action: 'manual_fulfillment_config_deleted',
|
||||
targetType: 'manual_fulfillment_config',
|
||||
targetId: String(req.params.skuKey || ''),
|
||||
data: { skuKey: result.skuKey || '', deleted: result.deleted || 0 },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/manual-configs/rematch',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => rematchManualFulfillments(bodyOf(req)), {
|
||||
successMessage: '重新匹配完成',
|
||||
errorMessage: '重新匹配失败',
|
||||
scope: '[admin/cost-center/manual-configs/rematch]',
|
||||
audit: (req, data) => {
|
||||
const result = data as { updatedCount?: number; skuKey?: string }
|
||||
return {
|
||||
action: 'manual_fulfillment_rematched',
|
||||
targetType: 'order_items',
|
||||
targetId: '',
|
||||
data: {
|
||||
updatedCount: result.updatedCount || 0,
|
||||
skuKey: result.skuKey || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user