- 人工配置保存/删除同步渠道规则,人工确认写 channel/status - 按 SKU 指定渠道默认同时落规则 - 管道补充:载体缺失、人工成本未记录、事实与总账按水位追赶
239 lines
6.9 KiB
TypeScript
239 lines
6.9 KiB
TypeScript
/**
|
|
* 履约渠道 v2 路由:待分类队列、运营指定渠道、渠道规则、管道触发。
|
|
*
|
|
* 与成本中心的分工:
|
|
* · 这里只处理「渠道」;
|
|
* · 商品归一、成本版本、异常处置仍在成本中心的既有页面里。
|
|
*/
|
|
import { Router } from 'express'
|
|
|
|
import {
|
|
assignChannelBySku,
|
|
assignChannelToOrderItems,
|
|
deleteChannelRule,
|
|
listChannelRules,
|
|
listPendingChannelGroups,
|
|
unlockChannel,
|
|
upsertChannelRule,
|
|
} from '../../services/fulfillment/channel-assign-service.js'
|
|
import { listExceptionQueue } from '../../services/cost-center/cost-exception-service.js'
|
|
import {
|
|
runFinancePipelineOnce,
|
|
runSingleFinancePipeline,
|
|
} from '../../services/scheduler/finance-pipeline-jobs.js'
|
|
import {
|
|
getFinancePipelineStatus,
|
|
isFinancePipelineName,
|
|
} from '../../services/scheduler/finance-pipeline-status.js'
|
|
import { bodyOf } from './request-helpers.js'
|
|
import { createJsonHandler, requireAdminPermission, requireAdminRoles } from './session.js'
|
|
|
|
const router = Router()
|
|
|
|
router.use('/fulfillment', requireAdminRoles(['admin', 'operator', 'finance', 'support']))
|
|
|
|
router.get(
|
|
'/fulfillment/pending',
|
|
requireAdminPermission('finance.view'),
|
|
createJsonHandler(
|
|
(req) =>
|
|
listPendingChannelGroups({
|
|
page: Number(req.query.page),
|
|
pageSize: Number(req.query.pageSize),
|
|
keyword: String(req.query.keyword || ''),
|
|
}),
|
|
{
|
|
successMessage: 'ok',
|
|
errorMessage: '读取待分类队列失败',
|
|
scope: '[admin/fulfillment/pending]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.get(
|
|
'/fulfillment/exceptions',
|
|
requireAdminPermission('finance.view'),
|
|
createJsonHandler(
|
|
(req) =>
|
|
listExceptionQueue({
|
|
reason: String(req.query.reason || ''),
|
|
status: String(req.query.status || 'open'),
|
|
page: Number(req.query.page),
|
|
pageSize: Number(req.query.pageSize),
|
|
}),
|
|
{
|
|
successMessage: 'ok',
|
|
errorMessage: '读取问题队列失败',
|
|
scope: '[admin/fulfillment/exceptions]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.post(
|
|
'/fulfillment/assign',
|
|
requireAdminPermission('finance.config'),
|
|
createJsonHandler(
|
|
async (req) => {
|
|
const body = bodyOf(req)
|
|
const channel = String(body.channel || '').trim()
|
|
const adminUserId = req.adminSession?.userId ?? null
|
|
const note = String(body.note || '')
|
|
|
|
if (body.skuCode) {
|
|
return assignChannelBySku({
|
|
provider: String(body.provider || ''),
|
|
shopId: String(body.shopId || ''),
|
|
skuCode: String(body.skuCode),
|
|
channel,
|
|
adminUserId,
|
|
note,
|
|
createRule: body.createRule !== false,
|
|
})
|
|
}
|
|
|
|
const ids = Array.isArray(body.orderItemIds)
|
|
? body.orderItemIds.map((value: unknown) => Number(value))
|
|
: [Number(body.orderItemId)].filter((value) => Number.isInteger(value) && value > 0)
|
|
|
|
return assignChannelToOrderItems({ orderItemIds: ids, channel, adminUserId, note })
|
|
},
|
|
{
|
|
successMessage: '已指定渠道',
|
|
errorMessage: '指定渠道失败',
|
|
scope: '[admin/fulfillment/assign]',
|
|
audit: (req, data) => ({
|
|
action: 'fulfillment.assign_channel',
|
|
targetType: 'order_item',
|
|
targetId: String(bodyOf(req).skuCode || bodyOf(req).orderItemId || ''),
|
|
data: { payload: bodyOf(req), result: data as Record<string, unknown> },
|
|
}),
|
|
},
|
|
),
|
|
)
|
|
|
|
router.post(
|
|
'/fulfillment/unlock',
|
|
requireAdminPermission('finance.config'),
|
|
createJsonHandler(
|
|
async (req) => {
|
|
const body = bodyOf(req)
|
|
const ids = Array.isArray(body.orderItemIds)
|
|
? body.orderItemIds.map((value: unknown) => Number(value))
|
|
: []
|
|
return unlockChannel(ids)
|
|
},
|
|
{
|
|
successMessage: '已解锁',
|
|
errorMessage: '解锁失败',
|
|
scope: '[admin/fulfillment/unlock]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.get(
|
|
'/fulfillment/rules',
|
|
requireAdminPermission('finance.view'),
|
|
createJsonHandler(
|
|
(req) =>
|
|
listChannelRules({
|
|
channel: String(req.query.channel || ''),
|
|
...(req.query.enabled === undefined
|
|
? {}
|
|
: { enabled: String(req.query.enabled) !== 'false' }),
|
|
}),
|
|
{
|
|
successMessage: 'ok',
|
|
errorMessage: '读取渠道规则失败',
|
|
scope: '[admin/fulfillment/rules]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.post(
|
|
'/fulfillment/rules',
|
|
requireAdminPermission('finance.config'),
|
|
createJsonHandler(
|
|
async (req) => {
|
|
const body = bodyOf(req)
|
|
return upsertChannelRule({
|
|
...(body.id ? { id: Number(body.id) } : {}),
|
|
provider: String(body.provider || ''),
|
|
shopId: String(body.shopId || ''),
|
|
matchKeyType: String(body.matchKeyType || 'sku_code'),
|
|
matchKey: String(body.matchKey || ''),
|
|
channel: String(body.channel || ''),
|
|
priority: Number(body.priority),
|
|
source: String(body.source || 'operator'),
|
|
enabled: body.enabled !== false,
|
|
note: String(body.note || ''),
|
|
adminUserId: req.adminSession?.userId ?? null,
|
|
})
|
|
},
|
|
{
|
|
successMessage: '规则已保存',
|
|
errorMessage: '保存渠道规则失败',
|
|
scope: '[admin/fulfillment/rules]',
|
|
audit: (req, data) => ({
|
|
action: 'fulfillment.upsert_rule',
|
|
targetType: 'sku_channel_rule',
|
|
targetId: String((data as { id?: number })?.id || ''),
|
|
data: { payload: bodyOf(req) },
|
|
}),
|
|
},
|
|
),
|
|
)
|
|
|
|
router.delete(
|
|
'/fulfillment/rules/:id',
|
|
requireAdminPermission('finance.config'),
|
|
createJsonHandler((req) => deleteChannelRule(Number(req.params.id)), {
|
|
successMessage: '规则已删除',
|
|
errorMessage: '删除渠道规则失败',
|
|
scope: '[admin/fulfillment/rules]',
|
|
}),
|
|
)
|
|
|
|
router.get(
|
|
'/fulfillment/pipeline/status',
|
|
requireAdminPermission('finance.view'),
|
|
createJsonHandler(() => getFinancePipelineStatus(), {
|
|
successMessage: 'ok',
|
|
errorMessage: '读取管道状态失败',
|
|
scope: '[admin/fulfillment/pipeline/status]',
|
|
}),
|
|
)
|
|
|
|
router.post(
|
|
'/fulfillment/pipeline/run',
|
|
requireAdminPermission('finance.config'),
|
|
createJsonHandler(
|
|
async (req) => {
|
|
const body = bodyOf(req)
|
|
const days = Number(body.days)
|
|
// 指定 pipeline 时只跑单步,便于排障与补跑。
|
|
if (body.pipeline) {
|
|
if (!isFinancePipelineName(body.pipeline)) {
|
|
throw new Error(`未知管道:${String(body.pipeline)}`)
|
|
}
|
|
return runSingleFinancePipeline(body.pipeline, {
|
|
config: Number.isFinite(days) ? { days } : {},
|
|
})
|
|
}
|
|
return runFinancePipelineOnce({ days: Number.isFinite(days) ? days : 3 })
|
|
},
|
|
{
|
|
successMessage: '管道已执行',
|
|
errorMessage: '执行管道失败',
|
|
scope: '[admin/fulfillment/pipeline]',
|
|
audit: (req, data) => ({
|
|
action: 'fulfillment.run_pipeline',
|
|
targetType: 'pipeline',
|
|
targetId: 'finance',
|
|
data: { payload: bodyOf(req), result: data as Record<string, unknown> },
|
|
}),
|
|
},
|
|
),
|
|
)
|
|
|
|
export default router
|