feat(douyin-mashida): 新增抖音券核销后台界面(列表/核销/冲正/同步/对账/作废)
抖音订单并非纯自动发货:内部 SKU 命中 AFFILIATE_DASH_SKU_MAPPING 才走自动通道, 未命中显式回落人工,且核销依赖买家自己到 /redeem 输订单号。此前核销侧在后台完全 不可见:自动核销是 void .catch(warn) 只落日志,cancelVerify 出网方法已就绪但没有入口。 照快手电子凭证的骨架做一套抖音版(两张表两套状态机,不复用): - 券码状态、平台侧状态、同步错误全部可视化,支持只看同步失败 - 核销/冲正/重新同步/平台对账/重发券码/作废,每个写操作都带审计 - 券码列显示「核销实际会用的券码」(platform_cert_no 优先),平台改过名时额外标注 - 冲正先打平台 cancelVerify 再回退本地,避免本地与平台状态漂移
This commit is contained in:
@@ -4,6 +4,7 @@ import authRouter from './admin/auth.js'
|
||||
import auditLogsRouter from './admin/audit-logs.js'
|
||||
import dashboardRouter from './admin/dashboard.js'
|
||||
import devMockRouter from './admin/dev-mock.js'
|
||||
import douyinMashidaRouter from './admin/douyin-mashida.js'
|
||||
import financeRouter from './admin/finance.js'
|
||||
import filesRouter from './admin/files.js'
|
||||
import kuaishouIndustryRouter from './admin/kuaishou-industry.js'
|
||||
@@ -30,6 +31,7 @@ router.use(rbacRouter)
|
||||
router.use(auditLogsRouter)
|
||||
router.use(platformConfigRouter)
|
||||
router.use(kuaishouIndustryRouter)
|
||||
router.use(douyinMashidaRouter)
|
||||
router.use(ordersRouter)
|
||||
router.use(notificationsRouter)
|
||||
router.use(realtimeRouter)
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { Router } from 'express'
|
||||
|
||||
import {
|
||||
cancelAdminDouyinMashidaCertificateVerify,
|
||||
checkAdminDouyinMashidaCertificate,
|
||||
fetchAdminDouyinMashidaOrderDetail,
|
||||
invalidateAdminDouyinMashidaCertificate,
|
||||
listAdminDouyinMashidaCertificates,
|
||||
resendAdminDouyinMashidaCertificate,
|
||||
syncAdminDouyinMashidaCertificate,
|
||||
verifyAdminDouyinMashidaCertificate,
|
||||
} from '../../services/admin/douyin-mashida-certificate-service.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||
import type {
|
||||
AdminDouyinMashidaCertificateActionRouteBody,
|
||||
AdminDouyinMashidaCertificateListRouteQuery,
|
||||
} from '../../types/admin/route-inputs.js'
|
||||
|
||||
/**
|
||||
* 后台「抖音券核销」:与 `admin/kuaishou-industry.ts` 同型的运营工具路由,
|
||||
* 操作对象换成我们自发的兑换券(`redeem_certificates`,provider=douyin_mashida)。
|
||||
*
|
||||
* 这里每个写操作都带审计:**<|hy_place▁holder▁no▁813|>码/核销/冲正会直接改变平台侧资金相关状态**,
|
||||
* 没有留痕等于出事没法追溯。
|
||||
*/
|
||||
const router = Router()
|
||||
|
||||
const MANAGE_ROLES = ['admin', 'operator', 'support']
|
||||
|
||||
router.get(
|
||||
'/douyin-mashida/certificates',
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
listAdminDouyinMashidaCertificates(req.query as AdminDouyinMashidaCertificateListRouteQuery),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '查询抖音兑换券失败',
|
||||
scope: '[admin/douyin-mashida/certificates]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/verify',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
verifyAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||||
{
|
||||
successMessage: '券码已核销',
|
||||
errorMessage: '手动核销券码失败',
|
||||
scope: '[admin/douyin-mashida/certificates/verify]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_verify', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/cancel-verify',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
cancelAdminDouyinMashidaCertificateVerify(
|
||||
req.body as AdminDouyinMashidaCertificateActionRouteBody,
|
||||
),
|
||||
{
|
||||
successMessage: '券码已冲正',
|
||||
errorMessage: '券码冲正失败',
|
||||
scope: '[admin/douyin-mashida/certificates/cancel-verify]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_cancel_verify', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/sync',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
syncAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||||
{
|
||||
successMessage: '券码已重新同步',
|
||||
errorMessage: '重新同步券码失败',
|
||||
scope: '[admin/douyin-mashida/certificates/sync]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_sync', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/check',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
checkAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||||
{
|
||||
successMessage: '平台券码已查询',
|
||||
errorMessage: '查询平台券码失败',
|
||||
scope: '[admin/douyin-mashida/certificates/check]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_check', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/resend',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
resendAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||||
{
|
||||
successMessage: '券码已重发',
|
||||
errorMessage: '重发券码失败',
|
||||
scope: '[admin/douyin-mashida/certificates/resend]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_resend', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/certificates/invalidate',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
invalidateAdminDouyinMashidaCertificate(
|
||||
req.body as AdminDouyinMashidaCertificateActionRouteBody,
|
||||
),
|
||||
{
|
||||
successMessage: '券码已作废',
|
||||
errorMessage: '作废券码失败',
|
||||
scope: '[admin/douyin-mashida/certificates/invalidate]',
|
||||
audit: (req, data) =>
|
||||
buildCertificateAudit('douyin_mashida_certificate_invalidate', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/douyin-mashida/order-detail',
|
||||
requireAdminRoles(MANAGE_ROLES),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
fetchAdminDouyinMashidaOrderDetail(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||||
{
|
||||
successMessage: '平台订单详情已查询',
|
||||
errorMessage: '查询平台订单详情失败',
|
||||
scope: '[admin/douyin-mashida/order-detail]',
|
||||
audit: (req, data) => buildCertificateAudit('douyin_mashida_order_detail', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/** 审计只记「操作了哪张券 + 结果摘要」,不整包落 rawPayload(里面有商品快照)。 */
|
||||
function buildCertificateAudit(action: string, body: unknown, data: unknown) {
|
||||
const payload = body && typeof body === 'object' ? (body as Record<string, unknown>) : {}
|
||||
const result = data && typeof data === 'object' ? (data as Record<string, unknown>) : {}
|
||||
const certificate = result.certificate as Record<string, unknown> | undefined
|
||||
|
||||
return {
|
||||
action,
|
||||
targetType: 'douyin_mashida_certificate',
|
||||
targetId: resolveAuditTargetId(payload, certificate),
|
||||
data: {
|
||||
certNo: String(certificate?.certNo || payload.certNo || '').trim(),
|
||||
orderId: String(
|
||||
certificate?.orderId || payload.orderId || payload.platformOrderId || '',
|
||||
).trim(),
|
||||
reason: String(payload.reason || '').trim(),
|
||||
success: result.success !== false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAuditTargetId(
|
||||
payload: Record<string, unknown>,
|
||||
certificate: Record<string, unknown> | undefined,
|
||||
): string {
|
||||
return String(
|
||||
certificate?.certNo || payload.certNo || payload.orderId || payload.platformOrderId || '-',
|
||||
)
|
||||
}
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user