优化快手电子凭证发码和手动处理
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Router } from 'express'
|
||||
|
||||
import { getAdminOrderDetail, getAdminOrders } from '../../services/admin/admin-read-service.js'
|
||||
import { createJsonHandler } from './session.js'
|
||||
import { resendAdminOrderKuaishouIndustryVoucherCodes } from '../../services/admin/write/kuaishou-cloud-actions.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||
import type {
|
||||
AdminOrderRouteParams,
|
||||
AdminOrderRouteQuery,
|
||||
@@ -19,7 +20,11 @@ router.get('/orders', createJsonHandler(
|
||||
))
|
||||
|
||||
router.get('/orders/:orderId', createJsonHandler(
|
||||
(req) => getAdminOrderDetail(String((req.params as AdminOrderRouteParams).orderId || '')),
|
||||
(req) =>
|
||||
getAdminOrderDetail(
|
||||
String((req.params as AdminOrderRouteParams).orderId || ''),
|
||||
req.adminSession || null,
|
||||
),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取订单详情失败',
|
||||
@@ -27,4 +32,27 @@ router.get('/orders/:orderId', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.post(
|
||||
'/orders/:orderId/kuaishou-industry/resend-code',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
resendAdminOrderKuaishouIndustryVoucherCodes(
|
||||
String((req.params as AdminOrderRouteParams).orderId || ''),
|
||||
req.adminSession || null,
|
||||
),
|
||||
{
|
||||
successMessage: '电子凭证发码回调已重发',
|
||||
errorMessage: '重发电子凭证发码回调失败',
|
||||
scope: '[admin/orders/:orderId/kuaishou-industry/resend-code]',
|
||||
audit: (req, data) => ({
|
||||
action: 'order_kuaishou_industry_resend_code',
|
||||
targetType: 'order',
|
||||
targetId: String((req.params as AdminOrderRouteParams).orderId || ''),
|
||||
data: data && typeof data === 'object' ? data as Record<string, unknown> : {},
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -4,15 +4,18 @@ import { Router } from 'express'
|
||||
import { getAdminTaskDetail, getAdminTasks } from '../../services/admin/admin-read-service.js'
|
||||
import { closeAdminTask } from '../../services/admin/write/task-actions.js'
|
||||
import {
|
||||
consumeAdminTaskKuaishouIndustryVoucher,
|
||||
dispatchAdminTaskKuaishouCloudFulfillment,
|
||||
prepareAdminTaskKuaishouCloudFulfillment,
|
||||
rebindAdminTaskKuaishouCloudRole,
|
||||
refreshAdminTaskKuaishouCloudRoleInfo,
|
||||
resendAdminTaskKuaishouIndustryVoucherCode,
|
||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||
} from '../../services/admin/write/kuaishou-cloud-actions.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||
import type {
|
||||
AdminTaskKuaishouCloudDispatchRouteBody,
|
||||
AdminTaskKuaishouIndustryConsumeRouteBody,
|
||||
AdminTaskRouteParams,
|
||||
AdminTaskRouteQuery,
|
||||
} from '../../types/admin/route-inputs.js'
|
||||
@@ -148,6 +151,40 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/tasks/:taskId/kuaishou-industry/resend-code',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
resendAdminTaskKuaishouIndustryVoucherCode(getTaskId(req), req.adminSession || null),
|
||||
{
|
||||
successMessage: '电子凭证发码回调已重发',
|
||||
errorMessage: '重发电子凭证发码回调失败',
|
||||
scope: '[admin/tasks/:taskId/kuaishou-industry/resend-code]',
|
||||
audit: (req, data) => buildTaskAudit('task_kuaishou_industry_resend_code', req, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/tasks/:taskId/kuaishou-industry/consume',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
consumeAdminTaskKuaishouIndustryVoucher(
|
||||
getTaskId(req),
|
||||
req.body as AdminTaskKuaishouIndustryConsumeRouteBody,
|
||||
req.adminSession || null,
|
||||
),
|
||||
{
|
||||
successMessage: '电子凭证已执行核销',
|
||||
errorMessage: '执行电子凭证核销失败',
|
||||
scope: '[admin/tasks/:taskId/kuaishou-industry/consume]',
|
||||
audit: (req, data) => buildTaskAudit('task_kuaishou_industry_consume', req, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
function buildTaskAudit(action: string, req: Request, data: unknown) {
|
||||
const result = data as AdminTaskActionResponse
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
|
||||
import { getOrderById, listOrders } from '../../repositories/order-repo.js'
|
||||
import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { listTaskEventsByTaskId } from '../../repositories/task-event-repo.js'
|
||||
import {
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
listKuaishouIndustryVouchersByTaskId,
|
||||
} from '../../repositories/kuaishou-industry-voucher-repo.js'
|
||||
import { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js'
|
||||
import { resolveKuaishouFeifeiClaimUrl } from '../fulfillment/kuaishou-feifei/index.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
@@ -83,7 +87,10 @@ export async function getAdminOrders(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<JsonRecord> {
|
||||
export async function getAdminOrderDetail(
|
||||
orderId: AdminEntityIdInput,
|
||||
session: AdminViewerSessionInput | null = null,
|
||||
): Promise<JsonRecord> {
|
||||
const order = await getOrderById(Number(orderId))
|
||||
|
||||
if (!order) {
|
||||
@@ -97,7 +104,9 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<
|
||||
listOrderItemsByOrderId(order.id),
|
||||
listTasksByOrderId(order.id),
|
||||
])
|
||||
const kuaishouIndustryVouchers = await listKuaishouIndustryVouchersByOid(order.platform_order_id)
|
||||
const itemSummary = summarizeOrderItems(items)
|
||||
const viewerContext = createAdminViewerContext(session)
|
||||
|
||||
return {
|
||||
order: {
|
||||
@@ -138,6 +147,37 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<
|
||||
: JSON.parse(String(item.spec_json || '{}')),
|
||||
})),
|
||||
tasks: tasks.map((task) => mapAdminTaskSummary(task)),
|
||||
kuaishouIndustryVouchers: kuaishouIndustryVouchers.map(mapAdminKuaishouIndustryVoucher),
|
||||
operations: {
|
||||
canResendKuaishouIndustryVoucherCode:
|
||||
viewerContext.canManageTaskLifecycle && kuaishouIndustryVouchers.length > 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function mapAdminKuaishouIndustryVoucher(voucher: {
|
||||
voucher_code?: unknown
|
||||
oid?: unknown
|
||||
unit_index?: unknown
|
||||
status?: unknown
|
||||
send_callback_status?: unknown
|
||||
send_callback_attempt_count?: unknown
|
||||
send_callback_last_error?: unknown
|
||||
send_callback_sent_at?: unknown
|
||||
consumed_at?: unknown
|
||||
destroyed_at?: unknown
|
||||
}) {
|
||||
return {
|
||||
oid: String(voucher.oid || '').trim(),
|
||||
voucherCode: String(voucher.voucher_code || '').trim(),
|
||||
unitIndex: Number(voucher.unit_index || 0) || 0,
|
||||
status: String(voucher.status || '').trim(),
|
||||
sendCallbackStatus: String(voucher.send_callback_status || '').trim(),
|
||||
sendCallbackAttemptCount: Number(voucher.send_callback_attempt_count || 0) || 0,
|
||||
sendCallbackLastError: String(voucher.send_callback_last_error || '').trim(),
|
||||
sendCallbackSentAt: voucher.send_callback_sent_at || null,
|
||||
consumedAt: voucher.consumed_at || null,
|
||||
destroyedAt: voucher.destroyed_at || null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +220,11 @@ export async function getAdminTaskDetail(
|
||||
}
|
||||
|
||||
const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
|
||||
const [order, claimToken, taskEvents] = await Promise.all([
|
||||
const [order, claimToken, taskEvents, taskKuaishouIndustryVouchers] = await Promise.all([
|
||||
getOrderById(task.order_id),
|
||||
primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null),
|
||||
listTaskEventsByTaskId(task.id),
|
||||
listKuaishouIndustryVouchersByTaskId(task.id),
|
||||
])
|
||||
const orderItems = order ? await listOrderItemsByOrderId(order.id) : []
|
||||
const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null
|
||||
@@ -200,6 +241,18 @@ export async function getAdminTaskDetail(
|
||||
mapKuaishouCloudFulfillmentContext(taskContext.kuaishouCloudFulfillment, {
|
||||
cloudSourceLabelMap,
|
||||
}) || mapKuaishouCloudTaskStateProjection(task, { cloudSourceLabelMap })
|
||||
const firstKuaishouIndustryVoucher = taskKuaishouIndustryVouchers[0] || null
|
||||
const kuaishouIndustryVoucher =
|
||||
firstKuaishouIndustryVoucher
|
||||
? mapAdminKuaishouIndustryVoucher(firstKuaishouIndustryVoucher)
|
||||
: mapKuaishouIndustryVoucherContext(taskContext.kuaishouIndustryVoucher)
|
||||
const hasKuaishouIndustryVoucher = Boolean(
|
||||
kuaishouIndustryVoucher && String(kuaishouIndustryVoucher.voucherCode || '').trim(),
|
||||
)
|
||||
const kuaishouIndustryVoucherStatus =
|
||||
String(kuaishouIndustryVoucher?.status || '').trim().toUpperCase()
|
||||
const kuaishouIndustryVoucherSendCallbackStatus =
|
||||
String(kuaishouIndustryVoucher?.sendCallbackStatus || '').trim().toLowerCase()
|
||||
|
||||
return {
|
||||
task: mapAdminTaskListItem(
|
||||
@@ -250,6 +303,7 @@ export async function getAdminTaskDetail(
|
||||
},
|
||||
redeemResolution: mapRedeemResolutionContext(taskContext.redeemResolution),
|
||||
kuaishouCloudFulfillment,
|
||||
kuaishouIndustryVoucher,
|
||||
manualDispatch: mapManualDispatchContext(taskContext.manualDispatch, viewerContext),
|
||||
events: taskEvents.map(mapAdminTaskEvent),
|
||||
operations: {
|
||||
@@ -289,11 +343,38 @@ export async function getAdminTaskDetail(
|
||||
viewerContext.canManageTaskLifecycle &&
|
||||
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' &&
|
||||
canReturnKuaishouCloudFulfillmentStatus(task.task_status),
|
||||
canResendKuaishouIndustryVoucherCode:
|
||||
viewerContext.canManageTaskLifecycle && hasKuaishouIndustryVoucher,
|
||||
canConsumeKuaishouIndustryVoucher:
|
||||
viewerContext.canManageTaskLifecycle &&
|
||||
hasKuaishouIndustryVoucher &&
|
||||
kuaishouIndustryVoucherStatus !== 'CONSUMED' &&
|
||||
kuaishouIndustryVoucherSendCallbackStatus === 'success',
|
||||
canViewSensitiveTaskData: viewerContext.canViewSensitiveTaskData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function mapKuaishouIndustryVoucherContext(value: unknown): JsonRecord | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const record = value as JsonRecord
|
||||
return {
|
||||
oid: String(record.oid || '').trim(),
|
||||
voucherCode: String(record.voucherCode || record.eticketId || '').trim(),
|
||||
unitIndex: Number(record.unitIndex || 0) || 0,
|
||||
status: String(record.status || '').trim(),
|
||||
sendCallbackStatus: String(record.sendCallbackStatus || '').trim(),
|
||||
sendCallbackLastError: String(record.sendCallbackLastError || '').trim(),
|
||||
sendCallbackAttemptCount: Number(record.sendCallbackAttemptCount || 0) || 0,
|
||||
sendCallbackSentAt: record.sendCallbackSentAt || null,
|
||||
consumedAt: record.consumedAt || null,
|
||||
destroyedAt: record.destroyedAt || null,
|
||||
}
|
||||
}
|
||||
|
||||
function buildCloudSourceLabelMap() {
|
||||
const labelMap = new Map<string, string>()
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { getOrderById } from '../../../repositories/order-repo.js'
|
||||
import { updateTask } from '../../../repositories/task-repo.js'
|
||||
import { listTasksByOrderId, updateTask } from '../../../repositories/task-repo.js'
|
||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByOid,
|
||||
listKuaishouIndustryVouchersByTaskId,
|
||||
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
||||
import {
|
||||
buyCloudtentaclesSku,
|
||||
getCloudtentaclesAsset,
|
||||
@@ -14,6 +19,12 @@ import {
|
||||
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
||||
import { consumeKuaishouEticket } from '../../platforms/kuaishou-eticket/consume-service.js'
|
||||
import { isKuaishouEticketMockTicketCode } from '../../platforms/kuaishou-eticket/mock-ticket-service.js'
|
||||
import { resendKuaishouIndustryVoucherSendCallback } from '../../platforms/kuaishou-industry/send-code-service.js'
|
||||
import { consumeKuaishouIndustryVoucher } from '../../platforms/kuaishou-industry/voucher-service.js'
|
||||
import {
|
||||
attachKuaishouIndustryVoucherToTask,
|
||||
bindKuaishouIndustryVouchersToOrderTasks,
|
||||
} from '../../platforms/kuaishou-industry/voucher-binding-service.js'
|
||||
import {
|
||||
getKuaishouEticketSourceConfig,
|
||||
resolveKuaishouEticketShopConfig,
|
||||
@@ -46,8 +57,12 @@ import type {
|
||||
AdminEntityIdInput,
|
||||
AdminViewerSessionInput,
|
||||
} from '../../../types/admin/read-inputs.js'
|
||||
import type { AdminTaskKuaishouCloudDispatchInput } from '../../../types/admin/write-inputs.js'
|
||||
import type {
|
||||
AdminTaskKuaishouCloudDispatchInput,
|
||||
AdminTaskKuaishouIndustryConsumeInput,
|
||||
} from '../../../types/admin/write-inputs.js'
|
||||
import type { AdminTaskActionResponse } from '../../../types/admin/write-models.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
export async function prepareAdminTaskKuaishouCloudFulfillment(
|
||||
taskId: AdminEntityIdInput,
|
||||
@@ -675,3 +690,306 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(
|
||||
task: mapTaskActionPayload(updatedTask),
|
||||
}
|
||||
}
|
||||
|
||||
export async function resendAdminTaskKuaishouIndustryVoucherCode(
|
||||
taskId: AdminEntityIdInput,
|
||||
session: AdminViewerSessionInput | null = null,
|
||||
): Promise<AdminTaskActionResponse> {
|
||||
const task = await getRequiredTask(taskId)
|
||||
const now = nowIso()
|
||||
const viewerContext = createAdminViewerContext(session)
|
||||
|
||||
if (!viewerContext.canManageTaskLifecycle) {
|
||||
throw createHttpError('当前账号没有权限重发电子凭证发码回调', {
|
||||
statusCode: 403,
|
||||
errorCode: 'admin_task_kuaishou_industry_resend_forbidden',
|
||||
})
|
||||
}
|
||||
|
||||
const voucher = await getRequiredIndustryVoucherForTask(task)
|
||||
const order = await getOrderById(task.order_id)
|
||||
const result = await resendKuaishouIndustryVoucherSendCallback({
|
||||
voucherCode: voucher.voucher_code,
|
||||
oid: voucher.oid,
|
||||
preferredTotalGoodsValue: Number(order?.total_amount || 0) || 0,
|
||||
})
|
||||
const updatedVoucher = result.voucher || voucher
|
||||
|
||||
if (!result.success) {
|
||||
const message = String(result.error || '电子凭证发码回调重发失败').trim()
|
||||
const failedTask = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.MANUAL_REVIEW,
|
||||
result_code: 'kuaishou_industry_send_callback_resend_failed',
|
||||
result_message: message,
|
||||
last_error: message,
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_industry_send_callback_resend_failed',
|
||||
{
|
||||
oid: voucher.oid,
|
||||
voucherCode: voucher.voucher_code,
|
||||
errorMessage: message,
|
||||
actor: buildAdminActionActor(session),
|
||||
},
|
||||
now,
|
||||
)
|
||||
void failedTask
|
||||
throw createHttpError(message, {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_industry_resend_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const attachedTask = await attachKuaishouIndustryVoucherToTask(task, updatedVoucher, {
|
||||
source: 'admin_resend_kuaishou_industry_code',
|
||||
now,
|
||||
})
|
||||
const resendTargetTask = attachedTask || task
|
||||
const refreshedTask = await updateTask(resendTargetTask.id, {
|
||||
result_code: 'kuaishou_industry_send_callback_resent',
|
||||
result_message: '电子凭证发码回调已重发成功',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_industry_send_callback_resent',
|
||||
{
|
||||
oid: voucher.oid,
|
||||
voucherCode: voucher.voucher_code,
|
||||
actor: buildAdminActionActor(session),
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return { task: mapTaskActionPayload(refreshedTask || resendTargetTask) }
|
||||
}
|
||||
|
||||
export async function resendAdminOrderKuaishouIndustryVoucherCodes(
|
||||
orderId: AdminEntityIdInput,
|
||||
session: AdminViewerSessionInput | null = null,
|
||||
): Promise<{ success: boolean; resentCount: number; failedCount: number; errorMessage: string }> {
|
||||
const order = await getOrderById(Number(orderId))
|
||||
const viewerContext = createAdminViewerContext(session)
|
||||
const now = nowIso()
|
||||
|
||||
if (!viewerContext.canManageTaskLifecycle) {
|
||||
throw createHttpError('当前账号没有权限重发电子凭证发码回调', {
|
||||
statusCode: 403,
|
||||
errorCode: 'admin_order_kuaishou_industry_resend_forbidden',
|
||||
})
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
throw createHttpError('订单不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'admin_order_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const vouchers = await resolveIndustryVouchersForOrder(order.platform_order_id)
|
||||
if (vouchers.length === 0) {
|
||||
throw createHttpError('当前订单没有关联电子凭证,无法重发发码回调', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_order_kuaishou_industry_voucher_missing',
|
||||
})
|
||||
}
|
||||
|
||||
let resentCount = 0
|
||||
let failedCount = 0
|
||||
let errorMessage = ''
|
||||
|
||||
for (const voucher of vouchers) {
|
||||
const result = await resendKuaishouIndustryVoucherSendCallback({
|
||||
voucherCode: voucher.voucher_code,
|
||||
oid: voucher.oid,
|
||||
preferredTotalGoodsValue: Number(order.total_amount || 0) || 0,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
resentCount += 1
|
||||
continue
|
||||
}
|
||||
|
||||
failedCount += 1
|
||||
errorMessage = errorMessage || String(result.error || '电子凭证发码回调重发失败').trim()
|
||||
}
|
||||
|
||||
if (failedCount > 0) {
|
||||
throw createHttpError(errorMessage || '电子凭证发码回调重发失败', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_order_kuaishou_industry_resend_failed',
|
||||
context: { resentCount, failedCount },
|
||||
})
|
||||
}
|
||||
|
||||
const tasks = await listTasksByOrderId(order.id)
|
||||
await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, {
|
||||
source: 'admin_order_resend_kuaishou_industry_code',
|
||||
now,
|
||||
})
|
||||
|
||||
return {
|
||||
success: failedCount === 0,
|
||||
resentCount,
|
||||
failedCount,
|
||||
errorMessage,
|
||||
}
|
||||
}
|
||||
|
||||
export async function consumeAdminTaskKuaishouIndustryVoucher(
|
||||
taskId: AdminEntityIdInput,
|
||||
payload: AdminTaskKuaishouIndustryConsumeInput = {},
|
||||
session: AdminViewerSessionInput | null = null,
|
||||
): Promise<AdminTaskActionResponse> {
|
||||
const task = await getRequiredTask(taskId)
|
||||
const now = nowIso()
|
||||
const viewerContext = createAdminViewerContext(session)
|
||||
|
||||
if (!viewerContext.canManageTaskLifecycle) {
|
||||
throw createHttpError('当前账号没有权限执行电子凭证核销', {
|
||||
statusCode: 403,
|
||||
errorCode: 'admin_task_kuaishou_industry_consume_forbidden',
|
||||
})
|
||||
}
|
||||
|
||||
const voucher = await getRequiredIndustryVoucherForTask(task)
|
||||
const result = await consumeKuaishouIndustryVoucher(voucher, {
|
||||
source: 'admin_manual_consume',
|
||||
task,
|
||||
token: voucher.token,
|
||||
consumeType: String(payload.consumeType || 'delivery').trim() || 'delivery',
|
||||
consumeTime: Date.now(),
|
||||
...(payload.storeName ? { storeName: payload.storeName } : {}),
|
||||
...(payload.storeAddress ? { storeAddress: payload.storeAddress } : {}),
|
||||
...(payload.expressCode ? { expressCode: payload.expressCode } : {}),
|
||||
...(payload.expressNo ? { expressNo: payload.expressNo } : {}),
|
||||
})
|
||||
|
||||
if (!result.ok || !result.voucher) {
|
||||
const message = String(result.errorMessage || '电子凭证核销失败').trim()
|
||||
const failedTask = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.MANUAL_REVIEW,
|
||||
result_code: 'kuaishou_industry_manual_consume_failed',
|
||||
result_message: message,
|
||||
last_error: message,
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_industry_manual_consume_failed',
|
||||
{
|
||||
oid: voucher.oid,
|
||||
voucherCode: voucher.voucher_code,
|
||||
errorMessage: message,
|
||||
actor: buildAdminActionActor(session),
|
||||
},
|
||||
now,
|
||||
)
|
||||
void failedTask
|
||||
throw createHttpError(message, {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_industry_consume_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const attachedConsumeTask = await attachKuaishouIndustryVoucherToTask(task, result.voucher, {
|
||||
source: 'admin_manual_consume',
|
||||
now,
|
||||
})
|
||||
const consumeTargetTask = attachedConsumeTask || task
|
||||
const taskContext = parseTaskContext(consumeTargetTask)
|
||||
const hasCloudFulfillmentContext = Boolean(
|
||||
taskContext.kuaishouCloudFulfillment &&
|
||||
typeof taskContext.kuaishouCloudFulfillment === 'object' &&
|
||||
!Array.isArray(taskContext.kuaishouCloudFulfillment),
|
||||
)
|
||||
const flow = hasCloudFulfillmentContext
|
||||
? normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||
: null
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
...(flow
|
||||
? {
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
consume: {
|
||||
...flow.consume,
|
||||
status: 'success',
|
||||
consumedAt: result.voucher.consumed_at || now,
|
||||
errorMessage: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
const updatedTask = await updateTask(consumeTargetTask.id, {
|
||||
task_status: TASK_STATUS.COMPLETED,
|
||||
delivery_status: 'delivered',
|
||||
result_code: 'kuaishou_industry_manual_consumed',
|
||||
result_message: '电子凭证已手动核销完成',
|
||||
redeemed_at: result.voucher.consumed_at || now,
|
||||
last_error: '',
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_industry_manual_consumed',
|
||||
{
|
||||
oid: result.voucher.oid,
|
||||
voucherCode: result.voucher.voucher_code,
|
||||
consumeSerialNum: result.voucher.consume_serial_num,
|
||||
actor: buildAdminActionActor(session),
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return { task: mapTaskActionPayload(updatedTask || consumeTargetTask) }
|
||||
}
|
||||
|
||||
async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise<KuaishouIndustryVoucherRow> {
|
||||
const vouchers = await listKuaishouIndustryVouchersByTaskId(task.id)
|
||||
const firstVoucher = vouchers[0]
|
||||
if (firstVoucher) {
|
||||
return firstVoucher
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContext(task)
|
||||
const voucherContext = taskContext.kuaishouIndustryVoucher || {}
|
||||
const voucherCode = String(voucherContext.voucherCode || voucherContext.eticketId || '').trim()
|
||||
if (voucherCode) {
|
||||
const voucher = await findKuaishouIndustryVoucherByCode(voucherCode, task.platform_order_id)
|
||||
if (voucher) {
|
||||
return voucher
|
||||
}
|
||||
}
|
||||
|
||||
throw createHttpError('当前任务没有关联电子凭证,无法执行该操作', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_industry_voucher_missing',
|
||||
})
|
||||
}
|
||||
|
||||
async function resolveIndustryVouchersForOrder(platformOrderId: string): Promise<KuaishouIndustryVoucherRow[]> {
|
||||
const normalizedOid = String(platformOrderId || '').trim()
|
||||
if (!normalizedOid) {
|
||||
return []
|
||||
}
|
||||
|
||||
return listKuaishouIndustryVouchersByOid(normalizedOid)
|
||||
}
|
||||
|
||||
function buildAdminActionActor(session: AdminViewerSessionInput | null) {
|
||||
return session
|
||||
? {
|
||||
userId: Number(session.userId || 0) || 0,
|
||||
username: String(session.username || '').trim(),
|
||||
role: String(session.role || '').trim(),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
|
||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
updateKuaishouIndustryVoucherByCode,
|
||||
upsertKuaishouIndustryVoucher,
|
||||
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
||||
@@ -31,6 +32,15 @@ import type { KuaishouIndustryVoucherRow, OrderRow } from '../../../types/reposi
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
type SendCodeCallbackParams = {
|
||||
oid: string
|
||||
sendType: string
|
||||
sellerId: string
|
||||
token: string
|
||||
eticketType?: string
|
||||
ext?: string
|
||||
}
|
||||
|
||||
export async function handleSendCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
@@ -125,6 +135,47 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function resendKuaishouIndustryVoucherSendCallback(input: {
|
||||
voucherCode: string
|
||||
oid?: string
|
||||
preferredTotalGoodsValue?: number
|
||||
}) {
|
||||
const voucher = await findKuaishouIndustryVoucherByCode(input.voucherCode, input.oid || '')
|
||||
if (!voucher) {
|
||||
return {
|
||||
success: false,
|
||||
error: '电子凭证不存在,无法重发发码回调',
|
||||
voucher: null,
|
||||
}
|
||||
}
|
||||
|
||||
const rawPayload = parseJsonObject(voucher.raw_payload_json)
|
||||
const body = parseJsonObject(rawPayload.body)
|
||||
const params = resolveSendCodeCallbackParams(voucher, body)
|
||||
const eticketType = String(params.eticketType || '').trim()
|
||||
const eticket = buildKuaishouIndustryEticketFromVoucher(voucher, eticketType)
|
||||
const result = await sendAndRecordCallback({
|
||||
oid: params.oid,
|
||||
sendType: params.sendType,
|
||||
etickets: [eticket],
|
||||
vouchers: [voucher],
|
||||
sellerId: params.sellerId,
|
||||
token: params.token,
|
||||
eticketType,
|
||||
source: 'admin_resend',
|
||||
...(input.preferredTotalGoodsValue !== undefined
|
||||
? { preferredTotalGoodsValue: input.preferredTotalGoodsValue }
|
||||
: {}),
|
||||
...(params.ext ? { ext: params.ext } : {}),
|
||||
})
|
||||
const updatedVoucher = await findKuaishouIndustryVoucherByCode(voucher.voucher_code, voucher.oid)
|
||||
|
||||
return {
|
||||
...result,
|
||||
voucher: updatedVoucher || voucher,
|
||||
}
|
||||
}
|
||||
|
||||
async function sendAndRecordCallback(input: {
|
||||
oid: string
|
||||
sendType: string
|
||||
@@ -135,6 +186,7 @@ async function sendAndRecordCallback(input: {
|
||||
eticketType?: string
|
||||
ext?: string
|
||||
preferredTotalGoodsValue?: number
|
||||
source?: string
|
||||
}) {
|
||||
const eticketItems = input.etickets.map((e) => ({
|
||||
id: String(e.id || ''),
|
||||
@@ -156,6 +208,7 @@ async function sendAndRecordCallback(input: {
|
||||
const totalGoodsValue = goodsValuePlan.totalGoodsValue
|
||||
|
||||
logIntegration('[kuaishou-industry/send-code]', '准备发起电子凭证发货回调', {
|
||||
source: input.source || 'send_code',
|
||||
oid: input.oid,
|
||||
sellerId: input.sellerId,
|
||||
sendType: input.sendType,
|
||||
@@ -202,6 +255,7 @@ async function sendAndRecordCallback(input: {
|
||||
|
||||
if (!result.success) {
|
||||
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,发码请求已拒绝继续', {
|
||||
source: input.source || 'send_code',
|
||||
oid: input.oid,
|
||||
error: result.error || '',
|
||||
response: result.response || null,
|
||||
@@ -210,6 +264,7 @@ async function sendAndRecordCallback(input: {
|
||||
}
|
||||
|
||||
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行完成', {
|
||||
source: input.source || 'send_code',
|
||||
oid: input.oid,
|
||||
response: result.response || null,
|
||||
})
|
||||
@@ -217,6 +272,20 @@ async function sendAndRecordCallback(input: {
|
||||
return result
|
||||
}
|
||||
|
||||
function resolveSendCodeCallbackParams(
|
||||
voucher: KuaishouIndustryVoucherRow,
|
||||
body: JsonObject,
|
||||
): SendCodeCallbackParams {
|
||||
return {
|
||||
oid: String(body.oid || voucher.oid || '').trim(),
|
||||
sendType: String(body.sendType || 'VIRTUAL').trim() || 'VIRTUAL',
|
||||
sellerId: String(body.sellerId || voucher.seller_id || '').trim(),
|
||||
token: String(body.token || voucher.token || '').trim(),
|
||||
eticketType: String(body.eticketType || '').trim(),
|
||||
ext: String(body.ext || '').trim(),
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSendCallbackFailureMessage(result: Awaited<ReturnType<typeof sendCallback>>) {
|
||||
const error = String(result.error || '').trim()
|
||||
if (error) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
AdminNotificationConfigInput,
|
||||
AdminNotificationTestInput,
|
||||
AdminScheduledJobsConfigInput,
|
||||
AdminTaskKuaishouIndustryConsumeInput,
|
||||
AdminTaskKuaishouCloudDispatchInput,
|
||||
AdminTaskManualDispatchInput,
|
||||
} from './write-inputs.js'
|
||||
@@ -54,6 +55,7 @@ export type AdminCloudtentaclesVirtualNumberRouteBody = AdminCloudtentaclesVirtu
|
||||
export type AdminCloudtentaclesFullFlowRouteBody = AdminCloudtentaclesFullFlowInput
|
||||
export type AdminTaskManualDispatchRouteBody = AdminTaskManualDispatchInput
|
||||
export type AdminTaskKuaishouCloudDispatchRouteBody = AdminTaskKuaishouCloudDispatchInput
|
||||
export type AdminTaskKuaishouIndustryConsumeRouteBody = AdminTaskKuaishouIndustryConsumeInput
|
||||
|
||||
export type AdminOrderRouteParams = { orderId?: string }
|
||||
export type AdminTaskRouteParams = { taskId?: string, bindingId?: string }
|
||||
|
||||
@@ -277,3 +277,11 @@ export type AdminTaskManualDispatchInput = {
|
||||
export type AdminTaskKuaishouCloudDispatchInput = {
|
||||
ticketCode?: string
|
||||
}
|
||||
|
||||
export type AdminTaskKuaishouIndustryConsumeInput = {
|
||||
consumeType?: string
|
||||
storeName?: string
|
||||
storeAddress?: string
|
||||
expressCode?: string
|
||||
expressNo?: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user