refactor(admin): 成本中心路由按域拆分
cost-center.ts 从 898 行、48 个端点拆成 4 个子路由 + 54 行入口: - cost-center-catalog 商品主数据、渠道映射、模板/自动发货/接单平台回填 - cost-center-cost 成本项、成本版本、快照、异常 - cost-center-manual 人工履约 SKU 配置 - cost-center-credits 客服平台积分钱包 入口只保留角色校验、overview/options 与挂载顺序;子路由路径仍是完整路径, 不需要再加前缀。 验证: - 静态核对:拆分前后 (method, path) 集合完全一致,48 → 48; - 运行时核对:遍历 Express router stack,仍注册 48 条 /cost-center 路由; - 端点正文逐字保留,只有 import 与文件边界变化。
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* 成本中心 · 商品与映射路由。
|
||||
*
|
||||
* 覆盖:接单模板初始化、自动发货/接单平台商品同步与回填、人工商品归一、
|
||||
* 标准商品 CRUD、渠道映射 CRUD、未归一队列、归一数量回填。
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
|
||||
import { normalizeManualProducts } from '../../services/cost-center/manual-product-normalization-service.js'
|
||||
import {
|
||||
backfillAdminNormalizedQuantities,
|
||||
backfillAdminProductMappings,
|
||||
createAdminChannelMapping,
|
||||
createAdminProduct,
|
||||
deleteAdminChannelMapping,
|
||||
deleteAdminProduct,
|
||||
getAdminChannelMappingDiagnostics,
|
||||
listAdminChannelMappings,
|
||||
listAdminProducts,
|
||||
listAdminUnmappedMappingGroups,
|
||||
updateAdminChannelMapping,
|
||||
updateAdminProduct,
|
||||
} from '../../services/cost-center/product-catalog-service.js'
|
||||
import {
|
||||
backfillAdminAffiliateDashSkus,
|
||||
syncAdminAffiliateDashProducts,
|
||||
} from '../../services/cost-center/affiliate-product-service.js'
|
||||
import {
|
||||
applyAdminTemplateProductBootstrap,
|
||||
previewAdminTemplateProductBootstrap,
|
||||
} from '../../services/cost-center/template-bootstrap-service.js'
|
||||
import {
|
||||
backfillAdminWorkOrderProductSkus,
|
||||
syncAdminWorkOrderTemplateCostVersions,
|
||||
} from '../../services/cost-center/work-order-product-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { bodyOf, idOf, sessionOf } from './request-helpers.js'
|
||||
import { createJsonHandler, requireAdminPermission } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get(
|
||||
'/cost-center/template-bootstrap/preview',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler(() => previewAdminTemplateProductBootstrap(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取接单模板初始化预览失败',
|
||||
scope: '[admin/cost-center/template-bootstrap/preview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/template-bootstrap/apply',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => applyAdminTemplateProductBootstrap(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '接单模板初始化完成',
|
||||
errorMessage: '接单模板初始化失败',
|
||||
scope: '[admin/cost-center/template-bootstrap/apply]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_template_bootstrap_applied',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/affiliate-products/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => syncAdminAffiliateDashProducts(), {
|
||||
successMessage: '自动发货商品已同步',
|
||||
errorMessage: '同步自动发货商品失败',
|
||||
scope: '[admin/cost-center/affiliate-products/sync]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_affiliate_products_synced',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/affiliate-products/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => backfillAdminAffiliateDashSkus(), {
|
||||
successMessage: '自动发货 SKU 已回填',
|
||||
errorMessage: '回填自动发货 SKU 失败',
|
||||
scope: '[admin/cost-center/affiliate-products/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_affiliate_skus_backfilled',
|
||||
targetType: 'order_items',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/work-order-products/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => backfillAdminWorkOrderProductSkus(), {
|
||||
successMessage: '接单平台商品已回填',
|
||||
errorMessage: '回填接单平台商品失败',
|
||||
scope: '[admin/cost-center/work-order-products/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_work_order_products_backfilled',
|
||||
targetType: 'order_items',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/work-order-template-costs/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => syncAdminWorkOrderTemplateCostVersions(), {
|
||||
successMessage: '接单模板发单金额已同步',
|
||||
errorMessage: '同步接单模板发单金额失败',
|
||||
scope: '[admin/cost-center/work-order-template-costs/sync]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_work_order_template_costs_synced',
|
||||
targetType: 'cost_versions',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/manual-products/normalize',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
normalizeManualProducts({
|
||||
dateFrom: bodyOf(req).dateFrom as string | undefined,
|
||||
dateTo: bodyOf(req).dateTo as string | undefined,
|
||||
dryRun: bodyOf(req).dryRun === true,
|
||||
}),
|
||||
{
|
||||
successMessage: '人工商品归一完成',
|
||||
errorMessage: '归一人工商品失败',
|
||||
scope: '[admin/cost-center/manual-products/normalize]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_manual_products_normalized',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/products',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminProducts(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取标准商品失败',
|
||||
scope: '[admin/cost-center/products]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/products',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminProduct(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '标准商品已创建',
|
||||
errorMessage: '创建标准商品失败',
|
||||
scope: '[admin/cost-center/products]',
|
||||
audit: (_req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_created',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/products/:productId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminProduct(idOf(req, 'productId'), bodyOf(req)), {
|
||||
successMessage: '标准商品已更新',
|
||||
errorMessage: '更新标准商品失败',
|
||||
scope: '[admin/cost-center/products/:productId]',
|
||||
audit: (req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_updated',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || req.params.productId || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/products/:productId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminProduct(idOf(req, 'productId')), {
|
||||
successMessage: '标准商品已删除',
|
||||
errorMessage: '删除标准商品失败',
|
||||
scope: '[admin/cost-center/products/:productId]',
|
||||
audit: (req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_deleted',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || req.params.productId || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminChannelMappings(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings/diagnostics',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => getAdminChannelMappingDiagnostics(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取渠道映射诊断失败',
|
||||
scope: '[admin/cost-center/mappings/diagnostics]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings/unmapped',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminUnmappedMappingGroups(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取未归一队列失败',
|
||||
scope: '[admin/cost-center/mappings/unmapped]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/mappings',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminChannelMapping(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '渠道映射已创建',
|
||||
errorMessage: '创建渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings]',
|
||||
audit: (_req, data) => {
|
||||
const mapping = (
|
||||
data as { mapping?: { id?: number; provider?: string; platformSkuCode?: string } }
|
||||
)?.mapping
|
||||
return {
|
||||
action: 'cost_mapping_created',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mapping?.id || ''),
|
||||
data: {
|
||||
provider: mapping?.provider || '',
|
||||
platformSkuCode: mapping?.platformSkuCode || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/mappings/:mappingId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminChannelMapping(idOf(req, 'mappingId'), bodyOf(req)), {
|
||||
successMessage: '渠道映射已更新',
|
||||
errorMessage: '更新渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings/:mappingId]',
|
||||
audit: (req, data) => {
|
||||
const mapping = (
|
||||
data as { mapping?: { id?: number; provider?: string; platformSkuCode?: string } }
|
||||
)?.mapping
|
||||
return {
|
||||
action: 'cost_mapping_updated',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mapping?.id || req.params.mappingId || ''),
|
||||
data: {
|
||||
provider: mapping?.provider || '',
|
||||
platformSkuCode: mapping?.platformSkuCode || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/mappings/:mappingId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminChannelMapping(idOf(req, 'mappingId')), {
|
||||
successMessage: '渠道映射已删除',
|
||||
errorMessage: '删除渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings/:mappingId]',
|
||||
audit: (req, data) => {
|
||||
const mappingId = (data as { mappingId?: number })?.mappingId
|
||||
return {
|
||||
action: 'cost_mapping_deleted',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mappingId || req.params.mappingId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/mappings/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => backfillAdminProductMappings(bodyOf(req)), {
|
||||
successMessage: '商品映射回填完成',
|
||||
errorMessage: '回填商品映射失败',
|
||||
scope: '[admin/cost-center/mappings/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_mapping_backfilled',
|
||||
targetType: 'order_item',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/normalized-quantities/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => backfillAdminNormalizedQuantities(bodyOf(req)), {
|
||||
successMessage: '归一数量回填完成',
|
||||
errorMessage: '回填归一数量失败',
|
||||
scope: '[admin/cost-center/normalized-quantities/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'order_item_quantity_backfilled',
|
||||
targetType: 'order_item',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* 成本中心 · 成本项/版本/快照/异常路由。
|
||||
*
|
||||
* 改价只走 cost_versions;订单只保存快照;匹配不到成本进异常队列。
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
import {
|
||||
createAdminCostItem,
|
||||
createAdminCostVersion,
|
||||
deleteAdminCostItem,
|
||||
deleteAdminCostVersion,
|
||||
listAdminCostExceptions,
|
||||
listAdminCostItems,
|
||||
listAdminCostVersions,
|
||||
resolveAdminCostException,
|
||||
retireAdminCostVersion,
|
||||
summarizeAdminCostExceptions,
|
||||
updateAdminCostItem,
|
||||
} from '../../services/cost-center/cost-definition-service.js'
|
||||
import {
|
||||
createAdminCostSnapshot,
|
||||
previewAdminCost,
|
||||
} from '../../services/cost-center/cost-resolution-service.js'
|
||||
import {
|
||||
syncActualWorkOrderCostSnapshots,
|
||||
syncEstimatedOrderItemCostSnapshots,
|
||||
} from '../../services/cost-center/cost-snapshot-sync-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { bodyOf, idOf, sessionOf } from './request-helpers.js'
|
||||
import { createJsonHandler, requireAdminPermission } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get(
|
||||
'/cost-center/cost-items',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostItems(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/cost-items',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostItem(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本项目已创建',
|
||||
errorMessage: '创建成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items]',
|
||||
audit: (_req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_created',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/cost-items/:costItemId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminCostItem(idOf(req, 'costItemId'), bodyOf(req)), {
|
||||
successMessage: '成本项目已更新',
|
||||
errorMessage: '更新成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items/:costItemId]',
|
||||
audit: (req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_updated',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || req.params.costItemId || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/cost-items/:costItemId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminCostItem(idOf(req, 'costItemId')), {
|
||||
successMessage: '成本项目已删除',
|
||||
errorMessage: '删除成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items/:costItemId]',
|
||||
audit: (req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_deleted',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || req.params.costItemId || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/cost-versions',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostVersions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/cost-versions',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostVersion(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本版本已创建',
|
||||
errorMessage: '创建成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions]',
|
||||
audit: (_req, data) => {
|
||||
const version = (data as { version?: { id?: number; costItemId?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_created',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || ''),
|
||||
data: { costItemId: version?.costItemId || 0 },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/cost-versions/:versionId/retire',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => retireAdminCostVersion(idOf(req, 'versionId'), bodyOf(req)), {
|
||||
successMessage: '成本版本已停用',
|
||||
errorMessage: '停用成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions/:versionId/retire]',
|
||||
audit: (req, data) => {
|
||||
const version = (data as { version?: { id?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_retired',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || req.params.versionId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/cost-versions/:versionId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminCostVersion(idOf(req, 'versionId')), {
|
||||
successMessage: '成本版本已删除',
|
||||
errorMessage: '删除成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions/:versionId]',
|
||||
audit: (req, data) => {
|
||||
const version = (data as { version?: { id?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_deleted',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || req.params.versionId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/exceptions',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostExceptions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本异常失败',
|
||||
scope: '[admin/cost-center/exceptions]',
|
||||
}),
|
||||
)
|
||||
|
||||
// 必须注册在 /cost-center/exceptions/:exceptionId 之前,避免 summary 被当成 ID。
|
||||
router.get(
|
||||
'/cost-center/exceptions/summary',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => summarizeAdminCostExceptions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本异常汇总失败',
|
||||
scope: '[admin/cost-center/exceptions/summary]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/exceptions/:exceptionId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
(req) => resolveAdminCostException(idOf(req, 'exceptionId'), bodyOf(req), sessionOf(req)),
|
||||
{
|
||||
successMessage: '成本异常已处理',
|
||||
errorMessage: '处理成本异常失败',
|
||||
scope: '[admin/cost-center/exceptions/:exceptionId]',
|
||||
audit: (req, data) => {
|
||||
const exception = (data as { exception?: { id?: number; status?: string } })?.exception
|
||||
return {
|
||||
action: 'cost_exception_resolved',
|
||||
targetType: 'cost_exception',
|
||||
targetId: String(exception?.id || req.params.exceptionId || ''),
|
||||
data: { status: exception?.status || '' },
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/preview',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => previewAdminCost(bodyOf(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '成本预览失败',
|
||||
scope: '[admin/cost-center/preview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/snapshots',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostSnapshot(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本快照已生成',
|
||||
errorMessage: '生成成本快照失败',
|
||||
scope: '[admin/cost-center/snapshots]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
createdCount?: number
|
||||
exceptionCreatedCount?: number
|
||||
preview?: { context?: { orderItemId?: number } }
|
||||
}
|
||||
return {
|
||||
action: 'cost_snapshot_created',
|
||||
targetType: 'order_item',
|
||||
targetId: String(result.preview?.context?.orderItemId || req.body?.orderItemId || ''),
|
||||
data: {
|
||||
createdCount: result.createdCount || 0,
|
||||
exceptionCreatedCount: result.exceptionCreatedCount || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/snapshots/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
async (req) => {
|
||||
const body = bodyOf(req)
|
||||
const mode = String(body.mode || 'both').trim()
|
||||
const estimated = mode === 'actual' ? null : await syncEstimatedOrderItemCostSnapshots(body)
|
||||
const actual = mode === 'estimated' ? null : await syncActualWorkOrderCostSnapshots(body)
|
||||
return { mode, estimated, actual }
|
||||
},
|
||||
{
|
||||
successMessage: '成本快照回填完成',
|
||||
errorMessage: '回填成本快照失败',
|
||||
scope: '[admin/cost-center/snapshots/sync]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
mode?: string
|
||||
estimated?: { createdCount?: number; exceptionCreatedCount?: number } | null
|
||||
actual?: {
|
||||
createdCount?: number
|
||||
directRewardCount?: number
|
||||
upgradedCount?: number
|
||||
estimatedRewardCount?: number
|
||||
} | null
|
||||
}
|
||||
return {
|
||||
action: 'cost_snapshot_synced',
|
||||
targetType: 'cost_snapshot',
|
||||
targetId: '',
|
||||
data: {
|
||||
mode: result.mode || '',
|
||||
estimatedCreated: result.estimated?.createdCount || 0,
|
||||
actualCreated:
|
||||
(result.actual?.directRewardCount || 0) + (result.actual?.upgradedCount || 0),
|
||||
estimatedRewardCreated: result.actual?.estimatedRewardCount || 0,
|
||||
exceptions: result.estimated?.exceptionCreatedCount || 0,
|
||||
dateFrom: req.body?.dateFrom || '',
|
||||
dateTo: req.body?.dateTo || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 成本中心 · 客服平台积分钱包路由(南航 / 乐玩 / ROCK)。
|
||||
*
|
||||
* 积分只能通过充值/调整流水变更,账户余额由流水推导。
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
import {
|
||||
adjustPlatformCredit,
|
||||
grantPlatformCredit,
|
||||
listPlatformCreditAccounts,
|
||||
listPlatformCreditEntries,
|
||||
listPlatformCreditOwners,
|
||||
} from '../../services/cost-center/platform-credit-service.js'
|
||||
import { bodyOf, sessionOf } from './request-helpers.js'
|
||||
import { createJsonHandler, requireAdminPermission, requireAnyAdminPermission } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 客服平台积分钱包(南航 / 乐玩 / ROCK)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/accounts',
|
||||
requireAnyAdminPermission(['fund.view', 'finance.view']),
|
||||
createJsonHandler((req) => listPlatformCreditAccounts(sessionOf(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取积分钱包失败',
|
||||
scope: '[admin/cost-center/platform-credits/accounts]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/entries',
|
||||
requireAnyAdminPermission(['fund.view', 'finance.view']),
|
||||
createJsonHandler(
|
||||
(req) => listPlatformCreditEntries(req.query as Record<string, unknown>, sessionOf(req)),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取积分流水失败',
|
||||
scope: '[admin/cost-center/platform-credits/entries]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/owners',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => listPlatformCreditOwners(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取客服列表失败',
|
||||
scope: '[admin/cost-center/platform-credits/owners]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/platform-credits/grant',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => grantPlatformCredit(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '积分已充值',
|
||||
errorMessage: '积分充值失败',
|
||||
scope: '[admin/cost-center/platform-credits/grant]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
platformKey?: string
|
||||
quantity?: number
|
||||
amountFen?: number
|
||||
}
|
||||
return {
|
||||
action: 'platform_credit_granted',
|
||||
targetType: 'platform_credit_account',
|
||||
targetId: String(req.body?.ownerAdminUserId || ''),
|
||||
data: {
|
||||
platformKey: result.platformKey || '',
|
||||
quantity: result.quantity || 0,
|
||||
amountFen: result.amountFen || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/platform-credits/adjust',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => adjustPlatformCredit(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '积分已调整',
|
||||
errorMessage: '积分调整失败',
|
||||
scope: '[admin/cost-center/platform-credits/adjust]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
platformKey?: string
|
||||
quantity?: number
|
||||
amountFen?: number
|
||||
}
|
||||
return {
|
||||
action: 'platform_credit_adjusted',
|
||||
targetType: 'platform_credit_account',
|
||||
targetId: String(req.body?.ownerAdminUserId || ''),
|
||||
data: {
|
||||
platformKey: result.platformKey || '',
|
||||
quantity: result.quantity || 0,
|
||||
amountFen: result.amountFen || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 成本中心 · 人工履约 SKU 配置路由。
|
||||
*
|
||||
* 链路:扫描内部 SKU → 配置履约渠道与消耗积分 → 重新匹配历史订单项。
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
import {
|
||||
deleteManualFulfillmentConfig,
|
||||
getManualFulfillmentConfig,
|
||||
getManualSkuOverview,
|
||||
listManualPlatformOptions,
|
||||
listManualSkus,
|
||||
rematchManualFulfillments,
|
||||
saveManualFulfillmentConfig,
|
||||
scanManualSkus,
|
||||
} from '../../services/cost-center/manual-fulfillment-config-service.js'
|
||||
import { bodyOf } from './request-helpers.js'
|
||||
import { createJsonHandler, requireAdminPermission } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 人工履约 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
|
||||
@@ -1,77 +1,26 @@
|
||||
import type { Request } from 'express'
|
||||
/**
|
||||
* 成本中心 API 入口。
|
||||
*
|
||||
* 48 个端点按域拆到 4 个子路由,本文件只负责角色校验与挂载顺序:
|
||||
* - cost-center-catalog 商品主数据、渠道映射、模板/自动发货/接单平台回填
|
||||
* - cost-center-cost 成本项、成本版本、成本快照、成本异常
|
||||
* - cost-center-manual 人工履约 SKU 配置(扫描 → 配置 → 重新匹配)
|
||||
* - cost-center-credits 客服平台积分钱包
|
||||
*
|
||||
* 子路由里的路径仍是完整路径(/cost-center/...),因此挂载时不需要再加前缀;
|
||||
* 角色校验放在最前面,子路由只需要各自声明 finance.view / finance.config 权限。
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
|
||||
import {
|
||||
getAdminCostCenterOptions,
|
||||
getAdminCostCenterOverview,
|
||||
} from '../../services/cost-center/cost-center-overview-service.js'
|
||||
import {
|
||||
createAdminCostItem,
|
||||
createAdminCostSnapshot,
|
||||
createAdminCostVersion,
|
||||
deleteAdminCostItem,
|
||||
deleteAdminCostVersion,
|
||||
listAdminCostExceptions,
|
||||
listAdminCostItems,
|
||||
listAdminCostVersions,
|
||||
previewAdminCost,
|
||||
resolveAdminCostException,
|
||||
retireAdminCostVersion,
|
||||
summarizeAdminCostExceptions,
|
||||
updateAdminCostItem,
|
||||
} from '../../services/cost-center/cost-definition-service.js'
|
||||
import {
|
||||
backfillAdminNormalizedQuantities,
|
||||
backfillAdminProductMappings,
|
||||
createAdminChannelMapping,
|
||||
createAdminProduct,
|
||||
deleteAdminChannelMapping,
|
||||
deleteAdminProduct,
|
||||
getAdminChannelMappingDiagnostics,
|
||||
listAdminChannelMappings,
|
||||
listAdminProducts,
|
||||
listAdminUnmappedMappingGroups,
|
||||
updateAdminChannelMapping,
|
||||
updateAdminProduct,
|
||||
} from '../../services/cost-center/product-catalog-service.js'
|
||||
import {
|
||||
adjustPlatformCredit,
|
||||
grantPlatformCredit,
|
||||
listPlatformCreditAccounts,
|
||||
listPlatformCreditEntries,
|
||||
listPlatformCreditOwners,
|
||||
} from '../../services/cost-center/platform-credit-service.js'
|
||||
import {
|
||||
deleteManualFulfillmentConfig,
|
||||
getManualFulfillmentConfig,
|
||||
getManualSkuOverview,
|
||||
listManualPlatformOptions,
|
||||
listManualSkus,
|
||||
rematchManualFulfillments,
|
||||
saveManualFulfillmentConfig,
|
||||
scanManualSkus,
|
||||
} from '../../services/cost-center/manual-fulfillment-config-service.js'
|
||||
import {
|
||||
applyAdminTemplateProductBootstrap,
|
||||
backfillAdminAffiliateDashSkus,
|
||||
backfillAdminWorkOrderProductSkus,
|
||||
previewAdminTemplateProductBootstrap,
|
||||
syncAdminAffiliateDashProducts,
|
||||
syncAdminWorkOrderTemplateCostVersions,
|
||||
} from '../../services/cost-center/template-bootstrap-service.js'
|
||||
import { normalizeManualProducts } from '../../services/cost-center/manual-product-normalization-service.js'
|
||||
import {
|
||||
syncActualWorkOrderCostSnapshots,
|
||||
syncEstimatedOrderItemCostSnapshots,
|
||||
} from '../../services/cost-center/cost-snapshot-sync-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
createJsonHandler,
|
||||
requireAdminPermission,
|
||||
requireAdminRoles,
|
||||
requireAnyAdminPermission,
|
||||
} from './session.js'
|
||||
import { bodyOf, idOf, sessionOf } from './request-helpers.js'
|
||||
import catalogRoutes from './cost-center-catalog.js'
|
||||
import costRoutes from './cost-center-cost.js'
|
||||
import creditRoutes from './cost-center-credits.js'
|
||||
import manualRoutes from './cost-center-manual.js'
|
||||
import { createJsonHandler, requireAdminPermission, requireAdminRoles } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -97,802 +46,9 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/template-bootstrap/preview',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler(() => previewAdminTemplateProductBootstrap(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取接单模板初始化预览失败',
|
||||
scope: '[admin/cost-center/template-bootstrap/preview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/template-bootstrap/apply',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => applyAdminTemplateProductBootstrap(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '接单模板初始化完成',
|
||||
errorMessage: '接单模板初始化失败',
|
||||
scope: '[admin/cost-center/template-bootstrap/apply]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_template_bootstrap_applied',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/affiliate-products/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => syncAdminAffiliateDashProducts(), {
|
||||
successMessage: '自动发货商品已同步',
|
||||
errorMessage: '同步自动发货商品失败',
|
||||
scope: '[admin/cost-center/affiliate-products/sync]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_affiliate_products_synced',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/affiliate-products/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => backfillAdminAffiliateDashSkus(), {
|
||||
successMessage: '自动发货 SKU 已回填',
|
||||
errorMessage: '回填自动发货 SKU 失败',
|
||||
scope: '[admin/cost-center/affiliate-products/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_affiliate_skus_backfilled',
|
||||
targetType: 'order_items',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/work-order-products/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => backfillAdminWorkOrderProductSkus(), {
|
||||
successMessage: '接单平台商品已回填',
|
||||
errorMessage: '回填接单平台商品失败',
|
||||
scope: '[admin/cost-center/work-order-products/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_work_order_products_backfilled',
|
||||
targetType: 'order_items',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/work-order-template-costs/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => syncAdminWorkOrderTemplateCostVersions(), {
|
||||
successMessage: '接单模板发单金额已同步',
|
||||
errorMessage: '同步接单模板发单金额失败',
|
||||
scope: '[admin/cost-center/work-order-template-costs/sync]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_work_order_template_costs_synced',
|
||||
targetType: 'cost_versions',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/manual-products/normalize',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
normalizeManualProducts({
|
||||
dateFrom: bodyOf(req).dateFrom as string | undefined,
|
||||
dateTo: bodyOf(req).dateTo as string | undefined,
|
||||
dryRun: bodyOf(req).dryRun === true,
|
||||
}),
|
||||
{
|
||||
successMessage: '人工商品归一完成',
|
||||
errorMessage: '归一人工商品失败',
|
||||
scope: '[admin/cost-center/manual-products/normalize]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_manual_products_normalized',
|
||||
targetType: 'product_catalog',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/products',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminProducts(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取标准商品失败',
|
||||
scope: '[admin/cost-center/products]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/products',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminProduct(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '标准商品已创建',
|
||||
errorMessage: '创建标准商品失败',
|
||||
scope: '[admin/cost-center/products]',
|
||||
audit: (_req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_created',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/products/:productId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminProduct(idOf(req, 'productId'), bodyOf(req)), {
|
||||
successMessage: '标准商品已更新',
|
||||
errorMessage: '更新标准商品失败',
|
||||
scope: '[admin/cost-center/products/:productId]',
|
||||
audit: (req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_updated',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || req.params.productId || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/products/:productId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminProduct(idOf(req, 'productId')), {
|
||||
successMessage: '标准商品已删除',
|
||||
errorMessage: '删除标准商品失败',
|
||||
scope: '[admin/cost-center/products/:productId]',
|
||||
audit: (req, data) => {
|
||||
const product = (data as { product?: { id?: number; canonicalSku?: string } })?.product
|
||||
return {
|
||||
action: 'cost_product_deleted',
|
||||
targetType: 'product_catalog',
|
||||
targetId: String(product?.id || req.params.productId || ''),
|
||||
data: { canonicalSku: product?.canonicalSku || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminChannelMappings(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings/diagnostics',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => getAdminChannelMappingDiagnostics(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取渠道映射诊断失败',
|
||||
scope: '[admin/cost-center/mappings/diagnostics]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/mappings/unmapped',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminUnmappedMappingGroups(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取未归一队列失败',
|
||||
scope: '[admin/cost-center/mappings/unmapped]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/mappings',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminChannelMapping(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '渠道映射已创建',
|
||||
errorMessage: '创建渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings]',
|
||||
audit: (_req, data) => {
|
||||
const mapping = (
|
||||
data as { mapping?: { id?: number; provider?: string; platformSkuCode?: string } }
|
||||
)?.mapping
|
||||
return {
|
||||
action: 'cost_mapping_created',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mapping?.id || ''),
|
||||
data: {
|
||||
provider: mapping?.provider || '',
|
||||
platformSkuCode: mapping?.platformSkuCode || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/mappings/:mappingId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminChannelMapping(idOf(req, 'mappingId'), bodyOf(req)), {
|
||||
successMessage: '渠道映射已更新',
|
||||
errorMessage: '更新渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings/:mappingId]',
|
||||
audit: (req, data) => {
|
||||
const mapping = (
|
||||
data as { mapping?: { id?: number; provider?: string; platformSkuCode?: string } }
|
||||
)?.mapping
|
||||
return {
|
||||
action: 'cost_mapping_updated',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mapping?.id || req.params.mappingId || ''),
|
||||
data: {
|
||||
provider: mapping?.provider || '',
|
||||
platformSkuCode: mapping?.platformSkuCode || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/mappings/:mappingId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminChannelMapping(idOf(req, 'mappingId')), {
|
||||
successMessage: '渠道映射已删除',
|
||||
errorMessage: '删除渠道映射失败',
|
||||
scope: '[admin/cost-center/mappings/:mappingId]',
|
||||
audit: (req, data) => {
|
||||
const mappingId = (data as { mappingId?: number })?.mappingId
|
||||
return {
|
||||
action: 'cost_mapping_deleted',
|
||||
targetType: 'channel_product_mapping',
|
||||
targetId: String(mappingId || req.params.mappingId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/mappings/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => backfillAdminProductMappings(bodyOf(req)), {
|
||||
successMessage: '商品映射回填完成',
|
||||
errorMessage: '回填商品映射失败',
|
||||
scope: '[admin/cost-center/mappings/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'cost_mapping_backfilled',
|
||||
targetType: 'order_item',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/normalized-quantities/backfill',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => backfillAdminNormalizedQuantities(bodyOf(req)), {
|
||||
successMessage: '归一数量回填完成',
|
||||
errorMessage: '回填归一数量失败',
|
||||
scope: '[admin/cost-center/normalized-quantities/backfill]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'order_item_quantity_backfilled',
|
||||
targetType: 'order_item',
|
||||
targetId: '',
|
||||
data: data as Record<string, unknown>,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/cost-items',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostItems(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/cost-items',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostItem(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本项目已创建',
|
||||
errorMessage: '创建成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items]',
|
||||
audit: (_req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_created',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/cost-items/:costItemId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => updateAdminCostItem(idOf(req, 'costItemId'), bodyOf(req)), {
|
||||
successMessage: '成本项目已更新',
|
||||
errorMessage: '更新成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items/:costItemId]',
|
||||
audit: (req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_updated',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || req.params.costItemId || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/cost-items/:costItemId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminCostItem(idOf(req, 'costItemId')), {
|
||||
successMessage: '成本项目已删除',
|
||||
errorMessage: '删除成本项目失败',
|
||||
scope: '[admin/cost-center/cost-items/:costItemId]',
|
||||
audit: (req, data) => {
|
||||
const item = (data as { costItem?: { id?: number; code?: string } })?.costItem
|
||||
return {
|
||||
action: 'cost_item_deleted',
|
||||
targetType: 'cost_item',
|
||||
targetId: String(item?.id || req.params.costItemId || ''),
|
||||
data: { code: item?.code || '' },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/cost-versions',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostVersions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/cost-versions',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostVersion(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本版本已创建',
|
||||
errorMessage: '创建成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions]',
|
||||
audit: (_req, data) => {
|
||||
const version = (data as { version?: { id?: number; costItemId?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_created',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || ''),
|
||||
data: { costItemId: version?.costItemId || 0 },
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/cost-versions/:versionId/retire',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => retireAdminCostVersion(idOf(req, 'versionId'), bodyOf(req)), {
|
||||
successMessage: '成本版本已停用',
|
||||
errorMessage: '停用成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions/:versionId/retire]',
|
||||
audit: (req, data) => {
|
||||
const version = (data as { version?: { id?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_retired',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || req.params.versionId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/cost-center/cost-versions/:versionId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => deleteAdminCostVersion(idOf(req, 'versionId')), {
|
||||
successMessage: '成本版本已删除',
|
||||
errorMessage: '删除成本版本失败',
|
||||
scope: '[admin/cost-center/cost-versions/:versionId]',
|
||||
audit: (req, data) => {
|
||||
const version = (data as { version?: { id?: number } })?.version
|
||||
return {
|
||||
action: 'cost_version_deleted',
|
||||
targetType: 'cost_version',
|
||||
targetId: String(version?.id || req.params.versionId || ''),
|
||||
data: {},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/exceptions',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listAdminCostExceptions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本异常失败',
|
||||
scope: '[admin/cost-center/exceptions]',
|
||||
}),
|
||||
)
|
||||
|
||||
// 必须注册在 /cost-center/exceptions/:exceptionId 之前,避免 summary 被当成 ID。
|
||||
router.get(
|
||||
'/cost-center/exceptions/summary',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => summarizeAdminCostExceptions(req.query as JsonObject), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取成本异常汇总失败',
|
||||
scope: '[admin/cost-center/exceptions/summary]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/cost-center/exceptions/:exceptionId',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
(req) => resolveAdminCostException(idOf(req, 'exceptionId'), bodyOf(req), sessionOf(req)),
|
||||
{
|
||||
successMessage: '成本异常已处理',
|
||||
errorMessage: '处理成本异常失败',
|
||||
scope: '[admin/cost-center/exceptions/:exceptionId]',
|
||||
audit: (req, data) => {
|
||||
const exception = (data as { exception?: { id?: number; status?: string } })?.exception
|
||||
return {
|
||||
action: 'cost_exception_resolved',
|
||||
targetType: 'cost_exception',
|
||||
targetId: String(exception?.id || req.params.exceptionId || ''),
|
||||
data: { status: exception?.status || '' },
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/preview',
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => previewAdminCost(bodyOf(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '成本预览失败',
|
||||
scope: '[admin/cost-center/preview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/snapshots',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => createAdminCostSnapshot(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '成本快照已生成',
|
||||
errorMessage: '生成成本快照失败',
|
||||
scope: '[admin/cost-center/snapshots]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
createdCount?: number
|
||||
exceptionCreatedCount?: number
|
||||
preview?: { context?: { orderItemId?: number } }
|
||||
}
|
||||
return {
|
||||
action: 'cost_snapshot_created',
|
||||
targetType: 'order_item',
|
||||
targetId: String(result.preview?.context?.orderItemId || req.body?.orderItemId || ''),
|
||||
data: {
|
||||
createdCount: result.createdCount || 0,
|
||||
exceptionCreatedCount: result.exceptionCreatedCount || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/snapshots/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
async (req) => {
|
||||
const body = bodyOf(req)
|
||||
const mode = String(body.mode || 'both').trim()
|
||||
const estimated = mode === 'actual' ? null : await syncEstimatedOrderItemCostSnapshots(body)
|
||||
const actual = mode === 'estimated' ? null : await syncActualWorkOrderCostSnapshots(body)
|
||||
return { mode, estimated, actual }
|
||||
},
|
||||
{
|
||||
successMessage: '成本快照回填完成',
|
||||
errorMessage: '回填成本快照失败',
|
||||
scope: '[admin/cost-center/snapshots/sync]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
mode?: string
|
||||
estimated?: { createdCount?: number; exceptionCreatedCount?: number } | null
|
||||
actual?: {
|
||||
createdCount?: number
|
||||
directRewardCount?: number
|
||||
upgradedCount?: number
|
||||
estimatedRewardCount?: number
|
||||
} | null
|
||||
}
|
||||
return {
|
||||
action: 'cost_snapshot_synced',
|
||||
targetType: 'cost_snapshot',
|
||||
targetId: '',
|
||||
data: {
|
||||
mode: result.mode || '',
|
||||
estimatedCreated: result.estimated?.createdCount || 0,
|
||||
actualCreated:
|
||||
(result.actual?.directRewardCount || 0) + (result.actual?.upgradedCount || 0),
|
||||
estimatedRewardCreated: result.actual?.estimatedRewardCount || 0,
|
||||
exceptions: result.estimated?.exceptionCreatedCount || 0,
|
||||
dateFrom: req.body?.dateFrom || '',
|
||||
dateTo: req.body?.dateTo || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 人工履约 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 || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 客服平台积分钱包(南航 / 乐玩 / ROCK)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/accounts',
|
||||
requireAnyAdminPermission(['fund.view', 'finance.view']),
|
||||
createJsonHandler((req) => listPlatformCreditAccounts(sessionOf(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取积分钱包失败',
|
||||
scope: '[admin/cost-center/platform-credits/accounts]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/entries',
|
||||
requireAnyAdminPermission(['fund.view', 'finance.view']),
|
||||
createJsonHandler(
|
||||
(req) => listPlatformCreditEntries(req.query as Record<string, unknown>, sessionOf(req)),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取积分流水失败',
|
||||
scope: '[admin/cost-center/platform-credits/entries]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/cost-center/platform-credits/owners',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(() => listPlatformCreditOwners(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取客服列表失败',
|
||||
scope: '[admin/cost-center/platform-credits/owners]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/platform-credits/grant',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => grantPlatformCredit(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '积分已充值',
|
||||
errorMessage: '积分充值失败',
|
||||
scope: '[admin/cost-center/platform-credits/grant]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
platformKey?: string
|
||||
quantity?: number
|
||||
amountFen?: number
|
||||
}
|
||||
return {
|
||||
action: 'platform_credit_granted',
|
||||
targetType: 'platform_credit_account',
|
||||
targetId: String(req.body?.ownerAdminUserId || ''),
|
||||
data: {
|
||||
platformKey: result.platformKey || '',
|
||||
quantity: result.quantity || 0,
|
||||
amountFen: result.amountFen || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/platform-credits/adjust',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler((req) => adjustPlatformCredit(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '积分已调整',
|
||||
errorMessage: '积分调整失败',
|
||||
scope: '[admin/cost-center/platform-credits/adjust]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
platformKey?: string
|
||||
quantity?: number
|
||||
amountFen?: number
|
||||
}
|
||||
return {
|
||||
action: 'platform_credit_adjusted',
|
||||
targetType: 'platform_credit_account',
|
||||
targetId: String(req.body?.ownerAdminUserId || ''),
|
||||
data: {
|
||||
platformKey: result.platformKey || '',
|
||||
quantity: result.quantity || 0,
|
||||
amountFen: result.amountFen || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
router.use(catalogRoutes)
|
||||
router.use(costRoutes)
|
||||
router.use(manualRoutes)
|
||||
router.use(creditRoutes)
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user