import { buildClaimUrl } from '../claim/claim-service.js' import { getClaimTokenById } from '../../repositories/claim-token-repo.js' 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 { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js' import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { normalizeDateQuery, normalizePage, normalizePageSize, safeParseJson } from './admin-query-utils.js' import { canViewerCloseTask, canRegenerateClaimLinkForViewer, createAdminViewerContext, getTaskPrimaryClaimTokenId, isManualDispatchTask, mapKuaishouCloudFulfillmentContext, mapManualDispatchContext, mapRedeemResolutionContext, parseTaskContext, parseTaskState, resolveAdminTaskScreenshotUrl, resolveDisplayShopName, resolveOrderItemDeliveryMode, resolveOrderItemTitle, } from './admin-read-shared-helpers.js' import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js' import { buildOrderBindingSummary, getRequiredTask, mapAdminTaskEvent, mapAdminTaskListItem, mapAdminTaskSummary, } from './admin-task-read-helpers.js' import type { AdminOrderListResponse, AdminTaskListResponse, } from '../../types/admin/read-models.js' import type { AdminEntityIdInput, AdminOrderListQueryInput, AdminTaskListQueryInput, AdminViewerSessionInput, } from '../../types/admin/read-inputs.js' type JsonRecord = Record export async function getAdminOrders(query: AdminOrderListQueryInput = {}): Promise { const page = normalizePage(query.page) const pageSize = normalizePageSize(query.pageSize) const { items, total } = await listOrders({ page, pageSize, platformOrderId: String(query.platformOrderId || '').trim(), payStatus: String(query.payStatus || '').trim(), skuCode: String(query.skuCode || '').trim(), dateFrom: normalizeDateQuery(query.dateFrom), dateTo: normalizeDateQuery(query.dateTo, true), }) return { items: await Promise.all(items.map((item) => mapAdminOrderListItem(item))), pagination: { page, pageSize, total }, } } export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise { const order = await getOrderById(Number(orderId)) if (!order) { throw createHttpError('订单不存在', { statusCode: 404, errorCode: 'admin_order_not_found', }) } const [items, tasks] = await Promise.all([ listOrderItemsByOrderId(order.id), listTasksByOrderId(order.id), ]) const itemSummary = summarizeOrderItems(items) return { order: { orderId: order.id, provider: order.provider || '', platform: order.platform, shopId: order.shop_id || '', shopName: resolveDisplayShopName(order.provider, order.shop_id, order.shop_name), platformOrderId: order.platform_order_id, orderStatus: order.order_status, payStatus: order.pay_status, buyerId: order.buyer_id, buyerName: order.buyer_name, receiverContact: order.receiver_contact, totalAmount: formatFenToAmount(order.total_amount), totalAmountFen: normalizeFen(order.total_amount), currency: order.currency, paidAt: order.paid_at, createdAt: order.created_at, updatedAt: order.updated_at, itemSummary, rawPayload: order.raw_payload_json && typeof order.raw_payload_json === 'object' ? order.raw_payload_json : JSON.parse(String(order.raw_payload_json || '{}')), bindingSummary: buildOrderBindingSummary(tasks), }, items: items.map((item) => ({ orderItemId: item.id, skuCode: item.sku_code, skuName: item.sku_name, itemTitle: resolveOrderItemTitle(item), quantity: item.quantity, deliveryMode: resolveOrderItemDeliveryMode(tasks, item.id), spec: item.spec_json && typeof item.spec_json === 'object' ? item.spec_json : JSON.parse(String(item.spec_json || '{}')), })), tasks: tasks.map((task) => mapAdminTaskSummary(task)), } } export async function getAdminTasks( query: AdminTaskListQueryInput = {}, session: AdminViewerSessionInput | null = null, ): Promise { const page = normalizePage(query.page) const pageSize = normalizePageSize(query.pageSize) const { items, total } = await listTasks({ page, pageSize, status: String(query.status || '').trim(), platformOrderId: String(query.platformOrderId || '').trim(), taskNo: String(query.taskNo || '').trim(), skuCode: String(query.skuCode || '').trim(), roleId: String(query.roleId || '').trim(), dateFrom: normalizeDateQuery(query.dateFrom), dateTo: normalizeDateQuery(query.dateTo, true), }) const viewerContext = createAdminViewerContext(session) return { items: items.map((item) => mapAdminTaskListItem(item, viewerContext)), pagination: { page, pageSize, total }, } } export async function getAdminTaskDetail( taskId: AdminEntityIdInput, session: AdminViewerSessionInput | null = null, ): Promise { const task = await getTaskById(Number(taskId)) if (!task) { throw createHttpError('任务不存在', { statusCode: 404, errorCode: 'admin_task_not_found', }) } const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task) const [order, claimToken, taskEvents] = await Promise.all([ getOrderById(task.order_id), primaryClaimTokenId ? getClaimTokenById(primaryClaimTokenId) : Promise.resolve(null), listTaskEventsByTaskId(task.id), ]) const orderItems = order ? await listOrderItemsByOrderId(order.id) : [] const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null const taskContext = parseTaskContext(task) const taskState = parseTaskState(task) const viewerContext = createAdminViewerContext(session) const claimUrl = claimToken ? buildClaimUrl(claimToken.token) : '' const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, viewerContext) const kuaishouCloudFulfillment = mapKuaishouCloudFulfillmentContext( taskContext.kuaishouCloudFulfillment, { cloudSourceLabelMap: buildCloudSourceLabelMap() }, ) return { task: mapAdminTaskListItem({ ...task, sku_code: orderItem?.sku_code || '', sku_name: orderItem?.sku_name || '', claim_token: claimToken?.token || '', }, viewerContext), order: order ? { orderId: order.id, provider: order.provider || '', platform: order.platform, shopId: order.shop_id || '', shopName: order.shop_name || '', platformOrderId: order.platform_order_id, payStatus: order.pay_status, orderStatus: order.order_status, } : null, orderItem: orderItem ? { orderItemId: orderItem.id, skuCode: orderItem.sku_code, skuName: orderItem.sku_name, quantity: orderItem.quantity, } : null, claimToken: claimToken ? { primaryClaimTokenId: claimToken.id, token: viewerContext.canViewSensitiveTaskData ? claimToken.token : '', status: claimToken.status, expiredAt: claimToken.expired_at, claimUrl, } : null, artifacts: viewerContext.canViewSensitiveTaskData ? safeParseJson(task.artifacts_json) : {}, screenshotUrl, review: { required: false, screenshotCapturedAt: String(taskState.reviewCapturedAt || '').trim() || null, roleId: String(taskState.reviewRoleId || '').trim() || '', roleName: String(taskState.reviewRoleName || '').trim() || '', }, redeemResolution: mapRedeemResolutionContext(taskContext.redeemResolution), kuaishouCloudFulfillment, manualDispatch: mapManualDispatchContext(taskContext.manualDispatch, viewerContext), events: taskEvents.map(mapAdminTaskEvent), operations: { canRetry: viewerContext.canManageTaskLifecycle && !isManualDispatchTask(task) && ['retry_pending', 'manual_review'].includes(task.task_status), canRegenerateClaimLink: canRegenerateClaimLinkForViewer(task, viewerContext), canClose: canViewerCloseTask(task, viewerContext), canMarkManualReview: viewerContext.canManageTaskLifecycle && !['redeemed', 'closed', 'manual_review'].includes(task.task_status), canCompleteManualDispatch: viewerContext.canManageTaskLifecycle && isManualDispatchTask(task) && !['redeemed', 'closed'].includes(task.task_status), canPrepareKuaishouCloudFulfillment: viewerContext.canManageTaskLifecycle && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' && String(kuaishouCloudFulfillment?.dispatch.status || 'pending').trim() === 'pending' && String(kuaishouCloudFulfillment?.returnNumber.status || 'pending').trim() === 'pending' && ['pending_binding_prepare', 'manual_review', 'failed'].includes(String(task.task_status || '').trim()), canRefreshKuaishouCloudRoleInfo: viewerContext.canOperateAssistedTask && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' && Number(kuaishouCloudFulfillment?.binding.vnId || 0) > 0, canDispatchKuaishouCloudFulfillment: viewerContext.canManageTaskLifecycle && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' && ['waiting_binding'].includes(String(task.task_status || '').trim()), canReturnKuaishouCloudFulfillment: viewerContext.canManageTaskLifecycle && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' && ['dispatched_pending_return'].includes(String(task.task_status || '').trim()), canViewSensitiveTaskData: viewerContext.canViewSensitiveTaskData, }, } } function buildCloudSourceLabelMap() { const labelMap = new Map() for (const source of listCloudtentaclesSources().sources) { const key = String(source.key || '').trim() if (!key) continue const label = String(source.label || source.username || key).trim() || key labelMap.set(key, label) } return labelMap } export async function getAdminTaskScreenshotPath( taskId: AdminEntityIdInput, session: AdminViewerSessionInput | null = null, ): Promise { const task = await getRequiredTask(taskId) if (task.screenshot_path) { return task.screenshot_path } throw createHttpError('当前任务还没有可查看截图', { statusCode: 404, errorCode: 'admin_task_screenshot_not_found', }) }