feat(claim): 支持固定H5订单号领取与订单兑换有效性后台控制

- 数据库新增 099_order_claim_status.sql 迁移,扩展 claim_status、claim_status_reason 等字段
- 后端新增 /claims/h5/:orderNo、UID提交、分销提交接口,保留原有 token 接口双轨兼容
- 增加兑换有效性强拦截:暂停/关闭/退款/取消订单精准阻断并返回友好提示
- 后台新增 POST /admin/orders/:orderId/claim-status 接口与审计日志记录
- 前端管理后台(订单列表、详情页)支持 H5 兑换状态展示、一键复制链接及暂停/关闭/启用弹窗
- 前端领取页面适配 /h5/:orderNo 路由及暂停/关闭等状态的异常展示
- 验证方式:运行前后端类型检查、全量单元测试(后端 674、前端 132)及全量构建编译均通过
This commit is contained in:
yml2213
2026-10-10 18:05:46 +08:00
parent 2d727a6797
commit 412d219cca
21 changed files with 788 additions and 50 deletions
@@ -96,6 +96,9 @@ export function mapAdminOrderListItem(
customerStatus: fulfillmentProgress.customerStatus,
preparedTaskCount: fulfillmentProgress.preparedTaskCount,
completedTaskCount: fulfillmentProgress.completedTaskCount,
claimStatus: item.claim_status || 'active',
claimStatusReason: item.claim_status_reason || '',
claimStatusUpdatedAt: item.claim_status_updated_at || null,
}
}
@@ -200,6 +200,9 @@ export async function getAdminOrderDetail(
? order.raw_payload_json
: JSON.parse(String(order.raw_payload_json || '{}')),
fulfillmentProgress: buildOrderFulfillmentProgress(tasks),
claimStatus: order.claim_status || 'active',
claimStatusReason: order.claim_status_reason || '',
claimStatusUpdatedAt: order.claim_status_updated_at || null,
},
items: items.map((item) => ({
orderItemId: item.id,
@@ -0,0 +1,64 @@
import { getOrderById, updateOrderClaimStatus } from '../../../repositories/order-repo.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import type { AdminViewerSessionInput } from '../../../types/admin/read-inputs.js'
export type UpdateOrderClaimStatusPayload = {
status: 'active' | 'paused' | 'closed'
reason?: string
}
export async function updateAdminOrderClaimStatus(
orderId: number | string,
payload: UpdateOrderClaimStatusPayload,
session: AdminViewerSessionInput | null = null,
) {
const normalizedOrderId = Number(orderId)
if (!Number.isFinite(normalizedOrderId) || normalizedOrderId <= 0) {
throw createHttpError('无效的订单 ID', {
statusCode: 400,
errorCode: 'invalid_order_id',
})
}
const validStatuses = new Set(['active', 'paused', 'closed'])
if (!payload || !validStatuses.has(payload.status)) {
throw createHttpError('无效的兑换有效性状态,仅支持 active, paused, closed', {
statusCode: 400,
errorCode: 'invalid_claim_status',
})
}
const order = await getOrderById(normalizedOrderId)
if (!order) {
throw createHttpError('订单不存在', {
statusCode: 404,
errorCode: 'admin_order_not_found',
})
}
const reason = String(payload.reason || '').trim()
const now = nowIso()
const updated = await updateOrderClaimStatus(order.id, {
claimStatus: payload.status,
reason,
updatedAt: now,
})
if (!updated) {
throw createHttpError('更新订单兑换状态失败', {
statusCode: 500,
errorCode: 'update_claim_status_failed',
})
}
return {
orderId: updated.id,
platformOrderId: updated.platform_order_id,
claimStatus: updated.claim_status || payload.status,
claimStatusReason: updated.claim_status_reason || reason,
claimStatusUpdatedAt: updated.claim_status_updated_at || now,
operator: session ? { userId: session.userId, username: session.username } : null,
}
}