diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.test.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.test.ts index 6beba4ec..d721dbb3 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.test.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.test.ts @@ -3,10 +3,25 @@ import test from 'node:test' import { CLAIM_TERMINAL_STATUSES, + MANUAL_FULFILLMENT_MESSAGE, resolveClaimProductImageUrl, + resolveManualFulfillmentMessage, } from './kuaishou-cloud-claim-context.js' import { TASK_STATUS } from '../../domain/task-status.js' +test('人工履约提示:manual_dispatch 给等待客服话术,自动履约与已下线渠道给空串', () => { + // 人工单(未命中自动发货映射 → 回落 manual_dispatch):领取页不许再让买家输 UID + assert.equal(resolveManualFulfillmentMessage('manual_dispatch'), MANUAL_FULFILLMENT_MESSAGE) + assert.equal(resolveManualFulfillmentMessage(' manual_dispatch '), MANUAL_FULFILLMENT_MESSAGE) + assert.equal(MANUAL_FULFILLMENT_MESSAGE, '人工处理中,请等待客服') + + // 自动发货 / 已下线渠道 / 空值都不算人工履约 + assert.equal(resolveManualFulfillmentMessage('affiliate_dash'), '') + assert.equal(resolveManualFulfillmentMessage('kuaishou_feifei'), '') + assert.equal(resolveManualFulfillmentMessage(''), '') + assert.equal(resolveManualFulfillmentMessage(undefined), '') +}) + test('CLAIM_TERMINAL_STATUSES 包含 closed 与 expired 状态', () => { assert.equal(CLAIM_TERMINAL_STATUSES.has(TASK_STATUS.CLOSED), true) assert.equal(CLAIM_TERMINAL_STATUSES.has(TASK_STATUS.EXPIRED), true) diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts index d3dba07f..0b6b7450 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts @@ -15,6 +15,7 @@ import { buildClaimUrl } from './claim-service.js' import { resolveProductImageUrl } from '../platforms/affiliate-dash/product-image.js' import { buildClaimIdentityPayload } from './claim-identity.js' import { normalizeAffiliateDashFlow } from '../fulfillment/affiliate-dash/index.js' +import { isManualDispatchExecutor } from '../fulfillment/executors/types.js' import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' import type { JsonObject } from '../../types/json.js' @@ -25,6 +26,20 @@ const RETIRED_CHANNEL_MESSAGE: Record = { kuaishou_ct_assisted: '乐玩履约渠道已下线,本链接仅可查看历史信息,无法继续领取', } +/** 领取页给人工履约单买家看的话术(人工派单 / 未命中自动发货映射)。 */ +export const MANUAL_FULFILLMENT_MESSAGE = '人工处理中,请等待客服' + +/** + * 人工履约提示话术:`manual_dispatch` 返回文案,其余执行器返回空串。 + * + * 为什么要在领取详情里显式区分:人工单没有自动发货通道(没有 claim token、也不会有 + * affiliate_dash 绑定二维码),买家在领取页输 UID 既无用又误导。前端据此隐藏「输 UID」步骤、 + * 只显示等待客服的说明;话术只在此处维护一份,前端只做兜底。 + */ +export function resolveManualFulfillmentMessage(executorKey: unknown): string { + return isManualDispatchExecutor(executorKey) ? MANUAL_FULFILLMENT_MESSAGE : '' +} + export type ClaimContext = { claimToken: ClaimTokenRow task: TaskRow @@ -290,6 +305,8 @@ function buildAffiliateDashClaimDetailPayload({ flowType: 'affiliate_dash' as const, channelOffline: false, channelOfflineMessage: '', + manualFulfillment: false, + manualFulfillmentMessage: '', claimIdentity, task: buildClaimTaskPayload(task, claimToken), order: buildClaimOrderPayload(order), @@ -372,6 +389,8 @@ function buildRetiredClaimDetailPayload({ claimToken, task, order, orderItem }: : ('kuaishou_cloud' as const), channelOffline: true, channelOfflineMessage: offlineMessage, + manualFulfillment: false, + manualFulfillmentMessage: '', claimIdentity, task: buildClaimTaskPayload(task, claimToken), order: buildClaimOrderPayload(order), @@ -413,6 +432,7 @@ function buildGenericClaimDetailPayload({ claimToken, task, order, orderItem }: const context = parseTaskContext(task) const claimIdentity = buildClaimIdentityPayload(context.claimIdentity) const productTitle = String(orderItem.sku_name || orderItem.sku_code || '').trim() + const manualFulfillmentMessage = resolveManualFulfillmentMessage(task.executor_key) return { tokenStatus: claimToken.status, @@ -420,6 +440,10 @@ function buildGenericClaimDetailPayload({ claimToken, task, order, orderItem }: flowType: String(task.executor_key || 'manual_dispatch').trim() || 'manual_dispatch', channelOffline: false, channelOfflineMessage: '', + // 人工履约单:买家在本页**无事可做**(没有自动发货通道可用),页面只展示等待话术, + // 不许再让买家输 UID ——否则会误导买家以为提交后就会自动发货。 + manualFulfillment: Boolean(manualFulfillmentMessage), + manualFulfillmentMessage, claimIdentity, task: buildClaimTaskPayload(task, claimToken), order: buildClaimOrderPayload(order), diff --git a/apps/frontend/src/pages/claim/ClaimPage.tsx b/apps/frontend/src/pages/claim/ClaimPage.tsx index 0b391c9a..8456526f 100644 --- a/apps/frontend/src/pages/claim/ClaimPage.tsx +++ b/apps/frontend/src/pages/claim/ClaimPage.tsx @@ -1,4 +1,5 @@ import { + ClockCircleOutlined, ExclamationCircleOutlined, LoadingOutlined, ReloadOutlined, @@ -301,6 +302,25 @@ export default function ClaimPage() {

{snapshot.channelOfflineMessage || '该履约渠道已下线,无法继续领取'}

+ ) : snapshot.isManualFulfillment ? ( + // 人工履约单(未命中自动发货映射 → manual_dispatch):没有自动发货通道, + // 买家输 UID 也没人自动发货,这里只如实告知「等客服」,不给任何操作入口。 + + +

+ {snapshot.manualFulfillmentMessage || '人工处理中,请等待客服'} +

+

+ 该订单由人工客服处理,本页无需任何操作;处理完成后刷新本页即可查看结果。 +

+ +
) : snapshot.currentStep === 1 ? (