优化快手电子凭证发码和手动处理
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
|
|
||||||
import { getAdminOrderDetail, getAdminOrders } from '../../services/admin/admin-read-service.js'
|
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 {
|
import type {
|
||||||
AdminOrderRouteParams,
|
AdminOrderRouteParams,
|
||||||
AdminOrderRouteQuery,
|
AdminOrderRouteQuery,
|
||||||
@@ -19,7 +20,11 @@ router.get('/orders', createJsonHandler(
|
|||||||
))
|
))
|
||||||
|
|
||||||
router.get('/orders/:orderId', 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',
|
successMessage: 'ok',
|
||||||
errorMessage: '读取订单详情失败',
|
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
|
export default router
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ import { Router } from 'express'
|
|||||||
import { getAdminTaskDetail, getAdminTasks } from '../../services/admin/admin-read-service.js'
|
import { getAdminTaskDetail, getAdminTasks } from '../../services/admin/admin-read-service.js'
|
||||||
import { closeAdminTask } from '../../services/admin/write/task-actions.js'
|
import { closeAdminTask } from '../../services/admin/write/task-actions.js'
|
||||||
import {
|
import {
|
||||||
|
consumeAdminTaskKuaishouIndustryVoucher,
|
||||||
dispatchAdminTaskKuaishouCloudFulfillment,
|
dispatchAdminTaskKuaishouCloudFulfillment,
|
||||||
prepareAdminTaskKuaishouCloudFulfillment,
|
prepareAdminTaskKuaishouCloudFulfillment,
|
||||||
rebindAdminTaskKuaishouCloudRole,
|
rebindAdminTaskKuaishouCloudRole,
|
||||||
refreshAdminTaskKuaishouCloudRoleInfo,
|
refreshAdminTaskKuaishouCloudRoleInfo,
|
||||||
|
resendAdminTaskKuaishouIndustryVoucherCode,
|
||||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||||
} from '../../services/admin/write/kuaishou-cloud-actions.js'
|
} from '../../services/admin/write/kuaishou-cloud-actions.js'
|
||||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||||
import type {
|
import type {
|
||||||
AdminTaskKuaishouCloudDispatchRouteBody,
|
AdminTaskKuaishouCloudDispatchRouteBody,
|
||||||
|
AdminTaskKuaishouIndustryConsumeRouteBody,
|
||||||
AdminTaskRouteParams,
|
AdminTaskRouteParams,
|
||||||
AdminTaskRouteQuery,
|
AdminTaskRouteQuery,
|
||||||
} from '../../types/admin/route-inputs.js'
|
} 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) {
|
function buildTaskAudit(action: string, req: Request, data: unknown) {
|
||||||
const result = data as AdminTaskActionResponse
|
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 { getOrderById, listOrders } from '../../repositories/order-repo.js'
|
||||||
import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js'
|
import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||||
import { listTaskEventsByTaskId } from '../../repositories/task-event-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 { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js'
|
||||||
import { resolveKuaishouFeifeiClaimUrl } from '../fulfillment/kuaishou-feifei/index.js'
|
import { resolveKuaishouFeifeiClaimUrl } from '../fulfillment/kuaishou-feifei/index.js'
|
||||||
import { createHttpError } from '../../utils/http.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))
|
const order = await getOrderById(Number(orderId))
|
||||||
|
|
||||||
if (!order) {
|
if (!order) {
|
||||||
@@ -97,7 +104,9 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<
|
|||||||
listOrderItemsByOrderId(order.id),
|
listOrderItemsByOrderId(order.id),
|
||||||
listTasksByOrderId(order.id),
|
listTasksByOrderId(order.id),
|
||||||
])
|
])
|
||||||
|
const kuaishouIndustryVouchers = await listKuaishouIndustryVouchersByOid(order.platform_order_id)
|
||||||
const itemSummary = summarizeOrderItems(items)
|
const itemSummary = summarizeOrderItems(items)
|
||||||
|
const viewerContext = createAdminViewerContext(session)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
order: {
|
order: {
|
||||||
@@ -138,6 +147,37 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<
|
|||||||
: JSON.parse(String(item.spec_json || '{}')),
|
: JSON.parse(String(item.spec_json || '{}')),
|
||||||
})),
|
})),
|
||||||
tasks: tasks.map((task) => mapAdminTaskSummary(task)),
|
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 primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
|
||||||
const [order, claimToken, taskEvents] = await Promise.all([
|
const [order, claimToken, taskEvents, taskKuaishouIndustryVouchers] = await Promise.all([
|
||||||
getOrderById(task.order_id),
|
getOrderById(task.order_id),
|
||||||
primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null),
|
primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null),
|
||||||
listTaskEventsByTaskId(task.id),
|
listTaskEventsByTaskId(task.id),
|
||||||
|
listKuaishouIndustryVouchersByTaskId(task.id),
|
||||||
])
|
])
|
||||||
const orderItems = order ? await listOrderItemsByOrderId(order.id) : []
|
const orderItems = order ? await listOrderItemsByOrderId(order.id) : []
|
||||||
const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null
|
const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null
|
||||||
@@ -200,6 +241,18 @@ export async function getAdminTaskDetail(
|
|||||||
mapKuaishouCloudFulfillmentContext(taskContext.kuaishouCloudFulfillment, {
|
mapKuaishouCloudFulfillmentContext(taskContext.kuaishouCloudFulfillment, {
|
||||||
cloudSourceLabelMap,
|
cloudSourceLabelMap,
|
||||||
}) || mapKuaishouCloudTaskStateProjection(task, { 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 {
|
return {
|
||||||
task: mapAdminTaskListItem(
|
task: mapAdminTaskListItem(
|
||||||
@@ -250,6 +303,7 @@ export async function getAdminTaskDetail(
|
|||||||
},
|
},
|
||||||
redeemResolution: mapRedeemResolutionContext(taskContext.redeemResolution),
|
redeemResolution: mapRedeemResolutionContext(taskContext.redeemResolution),
|
||||||
kuaishouCloudFulfillment,
|
kuaishouCloudFulfillment,
|
||||||
|
kuaishouIndustryVoucher,
|
||||||
manualDispatch: mapManualDispatchContext(taskContext.manualDispatch, viewerContext),
|
manualDispatch: mapManualDispatchContext(taskContext.manualDispatch, viewerContext),
|
||||||
events: taskEvents.map(mapAdminTaskEvent),
|
events: taskEvents.map(mapAdminTaskEvent),
|
||||||
operations: {
|
operations: {
|
||||||
@@ -289,11 +343,38 @@ export async function getAdminTaskDetail(
|
|||||||
viewerContext.canManageTaskLifecycle &&
|
viewerContext.canManageTaskLifecycle &&
|
||||||
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' &&
|
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' &&
|
||||||
canReturnKuaishouCloudFulfillmentStatus(task.task_status),
|
canReturnKuaishouCloudFulfillmentStatus(task.task_status),
|
||||||
|
canResendKuaishouIndustryVoucherCode:
|
||||||
|
viewerContext.canManageTaskLifecycle && hasKuaishouIndustryVoucher,
|
||||||
|
canConsumeKuaishouIndustryVoucher:
|
||||||
|
viewerContext.canManageTaskLifecycle &&
|
||||||
|
hasKuaishouIndustryVoucher &&
|
||||||
|
kuaishouIndustryVoucherStatus !== 'CONSUMED' &&
|
||||||
|
kuaishouIndustryVoucherSendCallbackStatus === 'success',
|
||||||
canViewSensitiveTaskData: viewerContext.canViewSensitiveTaskData,
|
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() {
|
function buildCloudSourceLabelMap() {
|
||||||
const labelMap = new Map<string, string>()
|
const labelMap = new Map<string, string>()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { getOrderById } from '../../../repositories/order-repo.js'
|
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 { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||||
|
import {
|
||||||
|
findKuaishouIndustryVoucherByCode,
|
||||||
|
listKuaishouIndustryVouchersByOid,
|
||||||
|
listKuaishouIndustryVouchersByTaskId,
|
||||||
|
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
||||||
import {
|
import {
|
||||||
buyCloudtentaclesSku,
|
buyCloudtentaclesSku,
|
||||||
getCloudtentaclesAsset,
|
getCloudtentaclesAsset,
|
||||||
@@ -14,6 +19,12 @@ import {
|
|||||||
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
||||||
import { consumeKuaishouEticket } from '../../platforms/kuaishou-eticket/consume-service.js'
|
import { consumeKuaishouEticket } from '../../platforms/kuaishou-eticket/consume-service.js'
|
||||||
import { isKuaishouEticketMockTicketCode } from '../../platforms/kuaishou-eticket/mock-ticket-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 {
|
import {
|
||||||
getKuaishouEticketSourceConfig,
|
getKuaishouEticketSourceConfig,
|
||||||
resolveKuaishouEticketShopConfig,
|
resolveKuaishouEticketShopConfig,
|
||||||
@@ -46,8 +57,12 @@ import type {
|
|||||||
AdminEntityIdInput,
|
AdminEntityIdInput,
|
||||||
AdminViewerSessionInput,
|
AdminViewerSessionInput,
|
||||||
} from '../../../types/admin/read-inputs.js'
|
} 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 { AdminTaskActionResponse } from '../../../types/admin/write-models.js'
|
||||||
|
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
|
||||||
|
|
||||||
export async function prepareAdminTaskKuaishouCloudFulfillment(
|
export async function prepareAdminTaskKuaishouCloudFulfillment(
|
||||||
taskId: AdminEntityIdInput,
|
taskId: AdminEntityIdInput,
|
||||||
@@ -675,3 +690,306 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(
|
|||||||
task: mapTaskActionPayload(updatedTask),
|
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 { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
|
||||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||||
import {
|
import {
|
||||||
|
findKuaishouIndustryVoucherByCode,
|
||||||
updateKuaishouIndustryVoucherByCode,
|
updateKuaishouIndustryVoucherByCode,
|
||||||
upsertKuaishouIndustryVoucher,
|
upsertKuaishouIndustryVoucher,
|
||||||
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
||||||
@@ -31,6 +32,15 @@ import type { KuaishouIndustryVoucherRow, OrderRow } from '../../../types/reposi
|
|||||||
|
|
||||||
type JsonObject = Record<string, any>
|
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 = {}) {
|
export async function handleSendCode(rawBody: JsonObject = {}) {
|
||||||
const config = getKuaishouIndustryConfig()
|
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: {
|
async function sendAndRecordCallback(input: {
|
||||||
oid: string
|
oid: string
|
||||||
sendType: string
|
sendType: string
|
||||||
@@ -135,6 +186,7 @@ async function sendAndRecordCallback(input: {
|
|||||||
eticketType?: string
|
eticketType?: string
|
||||||
ext?: string
|
ext?: string
|
||||||
preferredTotalGoodsValue?: number
|
preferredTotalGoodsValue?: number
|
||||||
|
source?: string
|
||||||
}) {
|
}) {
|
||||||
const eticketItems = input.etickets.map((e) => ({
|
const eticketItems = input.etickets.map((e) => ({
|
||||||
id: String(e.id || ''),
|
id: String(e.id || ''),
|
||||||
@@ -156,6 +208,7 @@ async function sendAndRecordCallback(input: {
|
|||||||
const totalGoodsValue = goodsValuePlan.totalGoodsValue
|
const totalGoodsValue = goodsValuePlan.totalGoodsValue
|
||||||
|
|
||||||
logIntegration('[kuaishou-industry/send-code]', '准备发起电子凭证发货回调', {
|
logIntegration('[kuaishou-industry/send-code]', '准备发起电子凭证发货回调', {
|
||||||
|
source: input.source || 'send_code',
|
||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
sellerId: input.sellerId,
|
sellerId: input.sellerId,
|
||||||
sendType: input.sendType,
|
sendType: input.sendType,
|
||||||
@@ -202,6 +255,7 @@ async function sendAndRecordCallback(input: {
|
|||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,发码请求已拒绝继续', {
|
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,发码请求已拒绝继续', {
|
||||||
|
source: input.source || 'send_code',
|
||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
error: result.error || '',
|
error: result.error || '',
|
||||||
response: result.response || null,
|
response: result.response || null,
|
||||||
@@ -210,6 +264,7 @@ async function sendAndRecordCallback(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行完成', {
|
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行完成', {
|
||||||
|
source: input.source || 'send_code',
|
||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
response: result.response || null,
|
response: result.response || null,
|
||||||
})
|
})
|
||||||
@@ -217,6 +272,20 @@ async function sendAndRecordCallback(input: {
|
|||||||
return result
|
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>>) {
|
function resolveSendCallbackFailureMessage(result: Awaited<ReturnType<typeof sendCallback>>) {
|
||||||
const error = String(result.error || '').trim()
|
const error = String(result.error || '').trim()
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
AdminNotificationConfigInput,
|
AdminNotificationConfigInput,
|
||||||
AdminNotificationTestInput,
|
AdminNotificationTestInput,
|
||||||
AdminScheduledJobsConfigInput,
|
AdminScheduledJobsConfigInput,
|
||||||
|
AdminTaskKuaishouIndustryConsumeInput,
|
||||||
AdminTaskKuaishouCloudDispatchInput,
|
AdminTaskKuaishouCloudDispatchInput,
|
||||||
AdminTaskManualDispatchInput,
|
AdminTaskManualDispatchInput,
|
||||||
} from './write-inputs.js'
|
} from './write-inputs.js'
|
||||||
@@ -54,6 +55,7 @@ export type AdminCloudtentaclesVirtualNumberRouteBody = AdminCloudtentaclesVirtu
|
|||||||
export type AdminCloudtentaclesFullFlowRouteBody = AdminCloudtentaclesFullFlowInput
|
export type AdminCloudtentaclesFullFlowRouteBody = AdminCloudtentaclesFullFlowInput
|
||||||
export type AdminTaskManualDispatchRouteBody = AdminTaskManualDispatchInput
|
export type AdminTaskManualDispatchRouteBody = AdminTaskManualDispatchInput
|
||||||
export type AdminTaskKuaishouCloudDispatchRouteBody = AdminTaskKuaishouCloudDispatchInput
|
export type AdminTaskKuaishouCloudDispatchRouteBody = AdminTaskKuaishouCloudDispatchInput
|
||||||
|
export type AdminTaskKuaishouIndustryConsumeRouteBody = AdminTaskKuaishouIndustryConsumeInput
|
||||||
|
|
||||||
export type AdminOrderRouteParams = { orderId?: string }
|
export type AdminOrderRouteParams = { orderId?: string }
|
||||||
export type AdminTaskRouteParams = { taskId?: string, bindingId?: string }
|
export type AdminTaskRouteParams = { taskId?: string, bindingId?: string }
|
||||||
|
|||||||
@@ -277,3 +277,11 @@ export type AdminTaskManualDispatchInput = {
|
|||||||
export type AdminTaskKuaishouCloudDispatchInput = {
|
export type AdminTaskKuaishouCloudDispatchInput = {
|
||||||
ticketCode?: string
|
ticketCode?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AdminTaskKuaishouIndustryConsumeInput = {
|
||||||
|
consumeType?: string
|
||||||
|
storeName?: string
|
||||||
|
storeAddress?: string
|
||||||
|
expressCode?: string
|
||||||
|
expressNo?: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { apiGet } from '@/lib/http'
|
import { apiGet, apiPost } from '@/lib/http'
|
||||||
import type { AdminOrderDetail, AdminOrderListItem, AdminPagination } from '@/types/admin'
|
import type { AdminOrderDetail, AdminOrderListItem, AdminPagination } from '@/types/admin'
|
||||||
|
|
||||||
export function fetchAdminOrders(params?: Record<string, unknown>) {
|
export function fetchAdminOrders(params?: Record<string, unknown>) {
|
||||||
@@ -11,3 +11,12 @@ export function fetchAdminOrders(params?: Record<string, unknown>) {
|
|||||||
export function fetchAdminOrderDetail(orderId: number | string) {
|
export function fetchAdminOrderDetail(orderId: number | string) {
|
||||||
return apiGet<AdminOrderDetail>(`/api/v1/admin/orders/${orderId}`)
|
return apiGet<AdminOrderDetail>(`/api/v1/admin/orders/${orderId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resendAdminOrderKuaishouIndustryVoucherCode(orderId: number | string) {
|
||||||
|
return apiPost<{
|
||||||
|
success: boolean
|
||||||
|
resentCount: number
|
||||||
|
failedCount: number
|
||||||
|
errorMessage: string
|
||||||
|
}>(`/api/v1/admin/orders/${orderId}/kuaishou-industry/resend-code`, {})
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,3 +91,26 @@ export function returnNumberAdminTaskKuaishouCloudFulfillment(taskId: number | s
|
|||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resendAdminTaskKuaishouIndustryVoucherCode(taskId: number | string) {
|
||||||
|
return apiPost<AdminTaskActionResponse>(
|
||||||
|
`/api/v1/admin/tasks/${taskId}/kuaishou-industry/resend-code`,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function consumeAdminTaskKuaishouIndustryVoucher(
|
||||||
|
taskId: number | string,
|
||||||
|
payload: {
|
||||||
|
consumeType?: string
|
||||||
|
storeName?: string
|
||||||
|
storeAddress?: string
|
||||||
|
expressCode?: string
|
||||||
|
expressNo?: string
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return apiPost<AdminTaskActionResponse>(
|
||||||
|
`/api/v1/admin/tasks/${taskId}/kuaishou-industry/consume`,
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,4 +69,19 @@ export interface AdminOrderDetail {
|
|||||||
spec: Record<string, unknown>
|
spec: Record<string, unknown>
|
||||||
}>
|
}>
|
||||||
tasks: AdminTaskListItem[]
|
tasks: AdminTaskListItem[]
|
||||||
|
kuaishouIndustryVouchers: Array<{
|
||||||
|
oid: string
|
||||||
|
voucherCode: string
|
||||||
|
unitIndex: number
|
||||||
|
status: string
|
||||||
|
sendCallbackStatus: string
|
||||||
|
sendCallbackAttemptCount: number
|
||||||
|
sendCallbackLastError: string
|
||||||
|
sendCallbackSentAt: string | null
|
||||||
|
consumedAt: string | null
|
||||||
|
destroyedAt: string | null
|
||||||
|
}>
|
||||||
|
operations: {
|
||||||
|
canResendKuaishouIndustryVoucherCode: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ export interface AdminTaskOperations {
|
|||||||
canRebindKuaishouCloudRole: boolean
|
canRebindKuaishouCloudRole: boolean
|
||||||
canDispatchKuaishouCloudFulfillment: boolean
|
canDispatchKuaishouCloudFulfillment: boolean
|
||||||
canReturnKuaishouCloudFulfillment: boolean
|
canReturnKuaishouCloudFulfillment: boolean
|
||||||
|
canResendKuaishouIndustryVoucherCode: boolean
|
||||||
|
canConsumeKuaishouIndustryVoucher: boolean
|
||||||
canViewSensitiveTaskData: boolean
|
canViewSensitiveTaskData: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +203,18 @@ export interface AdminTaskDetail {
|
|||||||
}
|
}
|
||||||
notes: string
|
notes: string
|
||||||
}
|
}
|
||||||
|
kuaishouIndustryVoucher: null | {
|
||||||
|
oid: string
|
||||||
|
voucherCode: string
|
||||||
|
unitIndex: number
|
||||||
|
status: string
|
||||||
|
sendCallbackStatus: string
|
||||||
|
sendCallbackLastError: string
|
||||||
|
sendCallbackAttemptCount: number
|
||||||
|
sendCallbackSentAt: string | null
|
||||||
|
consumedAt: string | null
|
||||||
|
destroyedAt: string | null
|
||||||
|
}
|
||||||
manualDispatch: null | {
|
manualDispatch: null | {
|
||||||
outcome: string
|
outcome: string
|
||||||
deliveryReference: string
|
deliveryReference: string
|
||||||
|
|||||||
@@ -3,27 +3,63 @@ import { onMounted, ref } from 'vue'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||||
import { fetchAdminOrderDetail } from '@/services/admin'
|
import { fetchAdminOrderDetail, resendAdminOrderKuaishouIndustryVoucherCode } from '@/services/admin'
|
||||||
import type { AdminOrderDetail } from '@/types/admin'
|
import type { AdminOrderDetail } from '@/types/admin'
|
||||||
|
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
|
||||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const actionLoading = ref(false)
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
const detail = ref<AdminOrderDetail | null>(null)
|
const detail = ref<AdminOrderDetail | null>(null)
|
||||||
|
|
||||||
type TagType = 'success' | 'primary' | 'warning' | 'danger' | 'info'
|
type TagType = 'success' | 'primary' | 'warning' | 'danger' | 'info'
|
||||||
|
|
||||||
onMounted(async () => {
|
async function loadDetail() {
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await fetchAdminOrderDetail(String(route.params.orderId || ''))
|
const response = await fetchAdminOrderDetail(String(route.params.orderId || ''))
|
||||||
detail.value = response.data
|
detail.value = response.data
|
||||||
|
errorMessage.value = ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '读取订单详情失败'
|
errorMessage.value = error instanceof Error ? error.message : '读取订单详情失败'
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
onMounted(loadDetail)
|
||||||
|
|
||||||
|
async function handleResendKuaishouIndustryCode() {
|
||||||
|
if (!detail.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await showConfirm(
|
||||||
|
`确认重发订单 ${detail.value.order.platformOrderId} 的电子凭证发码回调吗?系统会复用当前核销码。`,
|
||||||
|
'确认操作',
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '继续执行',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
actionLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await resendAdminOrderKuaishouIndustryVoucherCode(detail.value.order.orderId)
|
||||||
|
showSuccess(`电子凭证发码回调已重发,共 ${response.data.resentCount} 张`)
|
||||||
|
await loadDetail()
|
||||||
|
} catch (error) {
|
||||||
|
showError(error instanceof Error ? error.message : '重发电子凭证发码回调失败')
|
||||||
|
await loadDetail()
|
||||||
|
} finally {
|
||||||
|
actionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatJsonBlock(value: Record<string, unknown>) {
|
function formatJsonBlock(value: Record<string, unknown>) {
|
||||||
return JSON.stringify(value || {}, null, 2)
|
return JSON.stringify(value || {}, null, 2)
|
||||||
@@ -171,6 +207,33 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['fulfillmen
|
|||||||
</dl>
|
</dl>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<el-card v-if="detail.kuaishouIndustryVouchers.length > 0" shadow="never" class="section-card">
|
||||||
|
<div class="section-headline">
|
||||||
|
<h3>电子凭证</h3>
|
||||||
|
<el-button
|
||||||
|
v-if="detail.operations.canResendKuaishouIndustryVoucherCode"
|
||||||
|
:loading="actionLoading"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="handleResendKuaishouIndustryCode"
|
||||||
|
>
|
||||||
|
重发发码回调
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="detail.kuaishouIndustryVouchers" size="small">
|
||||||
|
<el-table-column prop="voucherCode" label="核销码" min-width="190" />
|
||||||
|
<el-table-column prop="status" label="券状态" width="110" />
|
||||||
|
<el-table-column prop="sendCallbackStatus" label="发码回调" width="120" />
|
||||||
|
<el-table-column label="回调时间" width="180">
|
||||||
|
<template #default="{ row }">{{ formatAdminDateTime(row.sendCallbackSentAt) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="sendCallbackAttemptCount" label="次数" width="80" />
|
||||||
|
<el-table-column label="异常">
|
||||||
|
<template #default="{ row }">{{ row.sendCallbackLastError || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never" class="section-card">
|
<el-card shadow="never" class="section-card">
|
||||||
<h3>履约任务</h3>
|
<h3>履约任务</h3>
|
||||||
<el-table :data="detail.tasks" size="small">
|
<el-table :data="detail.tasks" size="small">
|
||||||
@@ -240,6 +303,18 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['fulfillmen
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-headline {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-headline h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.order-hero {
|
.order-hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ const {
|
|||||||
submitKuaishouCloudDispatch,
|
submitKuaishouCloudDispatch,
|
||||||
submitKuaishouCloudRefreshRoleInfo,
|
submitKuaishouCloudRefreshRoleInfo,
|
||||||
submitKuaishouCloudReturnNumber,
|
submitKuaishouCloudReturnNumber,
|
||||||
|
submitKuaishouIndustryConsume,
|
||||||
|
submitKuaishouIndustryResendCode,
|
||||||
} = useAdminTaskKuaishouCloud(detail, runAction)
|
} = useAdminTaskKuaishouCloud(detail, runAction)
|
||||||
|
|
||||||
onMounted(loadDetail)
|
onMounted(loadDetail)
|
||||||
@@ -93,6 +95,8 @@ onMounted(loadDetail)
|
|||||||
@prepare-kuaishou-cloud="submitKuaishouCloudPrepare"
|
@prepare-kuaishou-cloud="submitKuaishouCloudPrepare"
|
||||||
@dispatch-kuaishou-cloud="submitKuaishouCloudDispatch"
|
@dispatch-kuaishou-cloud="submitKuaishouCloudDispatch"
|
||||||
@return-kuaishou-cloud="submitKuaishouCloudReturnNumber"
|
@return-kuaishou-cloud="submitKuaishouCloudReturnNumber"
|
||||||
|
@resend-industry-code="submitKuaishouIndustryResendCode"
|
||||||
|
@consume-industry-voucher="submitKuaishouIndustryConsume"
|
||||||
@mark-manual-review="
|
@mark-manual-review="
|
||||||
runAction(
|
runAction(
|
||||||
() => markAdminTaskManualReview(detail!.task.taskId),
|
() => markAdminTaskManualReview(detail!.task.taskId),
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const emit = defineEmits<{
|
|||||||
prepareKuaishouCloud: []
|
prepareKuaishouCloud: []
|
||||||
dispatchKuaishouCloud: []
|
dispatchKuaishouCloud: []
|
||||||
returnKuaishouCloud: []
|
returnKuaishouCloud: []
|
||||||
|
resendIndustryCode: []
|
||||||
|
consumeIndustryVoucher: []
|
||||||
markManualReview: []
|
markManualReview: []
|
||||||
submitManualDispatch: [outcome: 'delivered' | 'failed']
|
submitManualDispatch: [outcome: 'delivered' | 'failed']
|
||||||
closeTask: []
|
closeTask: []
|
||||||
@@ -70,6 +72,24 @@ const emit = defineEmits<{
|
|||||||
>
|
>
|
||||||
退还号码
|
退还号码
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canManageTaskLifecycle && detail.operations.canResendKuaishouIndustryVoucherCode"
|
||||||
|
:loading="actionLoading"
|
||||||
|
round
|
||||||
|
@click="emit('resendIndustryCode')"
|
||||||
|
>
|
||||||
|
重发发码回调
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canManageTaskLifecycle && detail.operations.canConsumeKuaishouIndustryVoucher"
|
||||||
|
:loading="actionLoading"
|
||||||
|
round
|
||||||
|
type="warning"
|
||||||
|
plain
|
||||||
|
@click="emit('consumeIndustryVoucher')"
|
||||||
|
>
|
||||||
|
手动核销
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canManageTaskLifecycle"
|
v-if="canManageTaskLifecycle"
|
||||||
:disabled="!detail.operations.canMarkManualReview"
|
:disabled="!detail.operations.canMarkManualReview"
|
||||||
|
|||||||
@@ -300,6 +300,25 @@ function formatRebindBinding(
|
|||||||
<div>
|
<div>
|
||||||
<span>核销码</span><strong>{{ flow.ticket.code || '-' }}</strong>
|
<span>核销码</span><strong>{{ flow.ticket.code || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>电子凭证状态</span><strong>{{ detail.kuaishouIndustryVoucher?.status || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>发码回调</span
|
||||||
|
><strong>{{ detail.kuaishouIndustryVoucher?.sendCallbackStatus || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>回调次数</span
|
||||||
|
><strong>{{ detail.kuaishouIndustryVoucher?.sendCallbackAttemptCount || 0 }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>回调时间</span
|
||||||
|
><strong>{{ formatAdminDateTime(detail.kuaishouIndustryVoucher?.sendCallbackSentAt) }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>回调异常</span
|
||||||
|
><strong>{{ detail.kuaishouIndustryVoucher?.sendCallbackLastError || '-' }}</strong>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>校验状态</span><strong>{{ flow.ticket.status || '-' }}</strong>
|
<span>校验状态</span><strong>{{ flow.ticket.status || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export function useAdminTaskDetail() {
|
|||||||
await loadDetail()
|
await loadDetail()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error instanceof Error ? error.message : '操作失败')
|
showError(error instanceof Error ? error.message : '操作失败')
|
||||||
|
await loadDetail()
|
||||||
} finally {
|
} finally {
|
||||||
actionLoading.value = false
|
actionLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { computed, type Ref } from 'vue'
|
import { computed, type Ref } from 'vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
consumeAdminTaskKuaishouIndustryVoucher,
|
||||||
dispatchAdminTaskKuaishouCloudFulfillment,
|
dispatchAdminTaskKuaishouCloudFulfillment,
|
||||||
prepareAdminTaskKuaishouCloudFulfillment,
|
prepareAdminTaskKuaishouCloudFulfillment,
|
||||||
refreshAdminTaskKuaishouCloudRoleInfo,
|
refreshAdminTaskKuaishouCloudRoleInfo,
|
||||||
|
resendAdminTaskKuaishouIndustryVoucherCode,
|
||||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||||
} from '@/services/admin'
|
} from '@/services/admin'
|
||||||
import type { AdminTaskActionResponse, AdminTaskDetail } from '@/types/admin'
|
import type { AdminTaskActionResponse, AdminTaskDetail } from '@/types/admin'
|
||||||
@@ -178,6 +180,30 @@ export function useAdminTaskKuaishouCloud(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitKuaishouIndustryResendCode() {
|
||||||
|
if (!detail.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await runAction(
|
||||||
|
() => resendAdminTaskKuaishouIndustryVoucherCode(detail.value!.task.taskId),
|
||||||
|
'电子凭证发码回调已重发',
|
||||||
|
`确认重发任务 ${detail.value.task.taskNo} 的电子凭证发码回调吗?系统会复用当前核销码,不会生成新码。`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitKuaishouIndustryConsume() {
|
||||||
|
if (!detail.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await runAction(
|
||||||
|
() => consumeAdminTaskKuaishouIndustryVoucher(detail.value!.task.taskId),
|
||||||
|
'电子凭证已执行核销',
|
||||||
|
`确认手动执行任务 ${detail.value.task.taskNo} 的电子凭证核销吗?请先确认快手侧已收到发码回调。`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kuaishouCloudFlow,
|
kuaishouCloudFlow,
|
||||||
kuaishouCloudRole,
|
kuaishouCloudRole,
|
||||||
@@ -189,5 +215,7 @@ export function useAdminTaskKuaishouCloud(
|
|||||||
submitKuaishouCloudDispatch,
|
submitKuaishouCloudDispatch,
|
||||||
submitKuaishouCloudRefreshRoleInfo,
|
submitKuaishouCloudRefreshRoleInfo,
|
||||||
submitKuaishouCloudReturnNumber,
|
submitKuaishouCloudReturnNumber,
|
||||||
|
submitKuaishouIndustryResendCode,
|
||||||
|
submitKuaishouIndustryConsume,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user