refactor(fulfillment): 移除飞飞/乐玩通道,仅保留 affiliate_dash

新单只路由 affiliate_dash(未命中走 manual_dispatch);历史飞飞/乐玩任务保留只读 stub,避免订单重同步失败。同步清理 cloudtentacles 调度、Admin 写路径与相关 env。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-14 17:29:47 +08:00
co-authored by Cursor
parent 0cbcb89dd8
commit b3aad0044c
155 changed files with 661 additions and 23351 deletions
@@ -1,76 +0,0 @@
import { createHttpError } from '../../../utils/http.js'
import type { HttpErrorLike } from '../../../utils/http.js'
import type { JsonObject } from '../../../types/json.js'
import { resolvePersistedCloudtentaclesContextBySourceKeys } from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
export type { JsonObject }
export {
KUAISHOU_CLOUD_FIXED_VN_KEY,
isKuaishouCloudTask,
normalizeKuaishouCloudFlow,
normalizeKuaishouCloudRoleInfo,
resolveKuaishouCloudBindUrlExpiresAt,
} from '../../fulfillment/kuaishou-cloud/domain.js'
export {
prepareKuaishouCloudBindResourceWithFallback,
resolveKuaishouCloudBindingResources,
resolveKuaishouCloudVnKeyCandidates,
} from '../../fulfillment/kuaishou-cloud/binding-resources.js'
export type CloudtentaclesContext = {
baseUrl: string
token: string
deviceId: string
deviceType: number
resolvedSourceKey: string
}
export type CloudSkuLikeItem = {
id?: number | string
name?: string
price?: number | string
count?: number | string
[key: string]: unknown
}
export type { KuaishouCloudFlow } from '../../fulfillment/kuaishou-cloud/domain.js'
export type KuaishouCloudBindingResources = {
skuId: number
skuName: string
vnKey: string
skuItem: CloudSkuLikeItem | null
knapsackItem: CloudSkuLikeItem | null
resolvedByName: boolean
}
export type PreparedKuaishouCloudBindResource = {
vnKey: string
vnId: number
vnPhone: string
bindUrl: string
}
export function resolvePersistedCloudtentaclesContext(
sourceKeys: unknown[],
): CloudtentaclesContext {
try {
return resolvePersistedCloudtentaclesContextBySourceKeys(sourceKeys)
} catch (error) {
if (isMissingCloudtentaclesTokenError(error)) {
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
statusCode: 409,
errorCode: 'admin_task_kuaishou_cloud_missing_cloud_token',
cause: error,
})
}
throw error
}
}
function isMissingCloudtentaclesTokenError(error: unknown): boolean {
const current = error as HttpErrorLike | null
return String(current?.errorCode || '').trim() === 'kuaishou_cloud_missing_cloud_token'
}
@@ -18,15 +18,6 @@ import { nowIso } from '../../../utils/time.js'
import { TASK_STATUS } from '../../../domain/task-status.js'
import { createAdminViewerContext, parseTaskContext } from '../admin-read-shared-helpers.js'
import { getRequiredTask, mapTaskActionPayload } from '../admin-task-read-helpers.js'
import {
dispatchFulfillmentTask,
prepareFulfillmentBinding,
rebindFulfillmentRole,
refreshFulfillmentRole,
returnFulfillmentNumber,
} from '../../fulfillment/executors/registry.js'
import { isKuaishouCloudTask, normalizeKuaishouCloudFlow } from './kuaishou-cloud-helpers.js'
import type {
AdminEntityIdInput,
AdminViewerSessionInput,
@@ -35,195 +26,6 @@ import type { AdminTaskKuaishouIndustryConsumeInput } from '../../../types/admin
import type { AdminTaskActionResponse } from '../../../types/admin/write-models.js'
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
export async function prepareAdminTaskKuaishouCloudFulfillment(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
const task = await getRequiredTask(taskId)
const viewerContext = createAdminViewerContext(session)
if (!viewerContext.canManageTaskLifecycle) {
throw createHttpError('当前账号没有权限准备绑定资源', {
statusCode: 403,
errorCode: 'admin_task_prepare_kuaishou_cloud_forbidden',
})
}
if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', {
statusCode: 409,
errorCode: 'admin_task_not_kuaishou_cloud',
})
}
const result = await prepareFulfillmentBinding(task, {
source: 'admin_task_prepare',
actor: buildAdminActionActor(session),
force: true,
})
if (!result?.task) {
throw createHttpError('当前任务不支持准备绑定资源', {
statusCode: 409,
errorCode: 'admin_task_prepare_not_supported',
})
}
return {
task: mapTaskActionPayload(result.task),
claimUrl: String(result.claimUrl || ''),
token: String(result.token || ''),
}
}
export async function dispatchAdminTaskKuaishouCloudFulfillment(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
const task = await getRequiredTask(taskId)
const viewerContext = createAdminViewerContext(session)
if (!viewerContext.canManageTaskLifecycle) {
throw createHttpError('当前账号没有权限执行发货', {
statusCode: 403,
errorCode: 'admin_task_dispatch_kuaishou_cloud_forbidden',
})
}
if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', {
statusCode: 409,
errorCode: 'admin_task_not_kuaishou_cloud',
})
}
const result = await dispatchFulfillmentTask(task, {
source: 'admin_task_dispatch',
actor: buildAdminActionActor(session),
autoFinalize: false,
})
if (!result?.task) {
throw createHttpError('当前任务不支持 executor 发货', {
statusCode: 409,
errorCode: 'admin_task_dispatch_not_supported',
})
}
return {
task: mapTaskActionPayload(result.task),
}
}
export async function refreshAdminTaskKuaishouCloudRoleInfo(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
const task = await getRequiredTask(taskId)
const viewerContext = createAdminViewerContext(session)
if (!viewerContext.canOperateAssistedTask) {
throw createHttpError('当前账号没有权限刷新角色信息', {
statusCode: 403,
errorCode: 'admin_task_refresh_kuaishou_cloud_role_forbidden',
})
}
if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', {
statusCode: 409,
errorCode: 'admin_task_not_kuaishou_cloud',
})
}
const result = await refreshFulfillmentRole(task, {
source: 'admin_task_refresh_role',
actor: buildAdminActionActor(session),
forceProbe: true,
recordEvent: true,
})
if (!result?.task) {
throw createHttpError('当前任务不支持刷新角色信息', {
statusCode: 409,
errorCode: 'admin_task_refresh_not_supported',
})
}
return {
task: mapTaskActionPayload(result.task),
}
}
export async function rebindAdminTaskKuaishouCloudRole(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
const task = await getRequiredTask(taskId)
const viewerContext = createAdminViewerContext(session)
// 与刷新角色一致:客服可换绑协助处理 UID 绑错,不开放完整任务生命周期
if (!viewerContext.canOperateAssistedTask) {
throw createHttpError('当前账号没有权限换绑角色', {
statusCode: 403,
errorCode: 'admin_task_rebind_kuaishou_cloud_forbidden',
})
}
const result = await rebindFulfillmentRole(task, {
source: 'admin_task_rebind_role',
actor: buildAdminActionActor(session),
})
if (!result?.task) {
throw createHttpError('当前任务不支持换绑角色', {
statusCode: 409,
errorCode: 'admin_task_rebind_not_supported',
})
}
return {
task: mapTaskActionPayload(result.task),
claimUrl: String(result.claimUrl || ''),
token: String(result.token || ''),
}
}
export async function returnNumberAdminTaskKuaishouCloudFulfillment(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
const task = await getRequiredTask(taskId)
const viewerContext = createAdminViewerContext(session)
if (!viewerContext.canManageTaskLifecycle) {
throw createHttpError('当前账号没有权限退还号码', {
statusCode: 403,
errorCode: 'admin_task_return_kuaishou_cloud_forbidden',
})
}
if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', {
statusCode: 409,
errorCode: 'admin_task_not_kuaishou_cloud',
})
}
// admin 退号 = 清理虚拟号占位,不核销电子凭证
const result = await returnFulfillmentNumber(task, {
source: 'admin_task_return_number',
actor: buildAdminActionActor(session),
consumeIndustryVoucher: false,
})
if (!result?.task) {
throw createHttpError('当前任务不支持退还号码', {
statusCode: 409,
errorCode: 'admin_task_return_not_supported',
})
}
return {
task: mapTaskActionPayload(result.task),
}
}
export async function resendAdminTaskKuaishouIndustryVoucherCode(
taskId: AdminEntityIdInput,
session: AdminViewerSessionInput | null = null,
@@ -250,7 +52,7 @@ export async function resendAdminTaskKuaishouIndustryVoucherCode(
if (!result.success) {
const message = String(result.error || '电子凭证发码回调重发失败').trim()
const failedTask = await updateTask(task.id, {
await updateTask(task.id, {
task_status: TASK_STATUS.MANUAL_REVIEW,
result_code: 'kuaishou_industry_send_callback_resend_failed',
result_message: message,
@@ -268,7 +70,6 @@ export async function resendAdminTaskKuaishouIndustryVoucherCode(
},
now,
)
void failedTask
throw createHttpError(message, {
statusCode: 409,
errorCode: 'admin_task_kuaishou_industry_resend_failed',
@@ -405,7 +206,7 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
if (!result.ok || !result.voucher) {
const message = String(result.errorMessage || '电子凭证核销失败').trim()
const failedTask = await updateTask(task.id, {
await updateTask(task.id, {
task_status: TASK_STATUS.MANUAL_REVIEW,
result_code: 'kuaishou_industry_manual_consume_failed',
result_message: message,
@@ -423,7 +224,6 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
},
now,
)
void failedTask
throw createHttpError(message, {
statusCode: 409,
errorCode: 'admin_task_kuaishou_industry_consume_failed',
@@ -436,30 +236,6 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
})
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',
@@ -467,7 +243,7 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
result_message: '电子凭证已手动核销完成',
redeemed_at: result.voucher.consumed_at || now,
last_error: '',
context_json: JSON.stringify(nextContext),
context_json: JSON.stringify(taskContext),
updated_at: now,
})