From 9709643583b76c45dde45b0b36c3490e90c6b1d4 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 28 May 2026 10:03:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BF=AB=E6=89=8B=20Cloud=20?= =?UTF-8?q?=E4=B8=BB=E9=93=BE=E8=B7=AF=E7=8A=B6=E6=80=81=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/domain/task-status.test.ts | 27 ++ apps/backend/src/domain/task-status.ts | 313 ++++++++++++++++++ .../src/services/admin/admin-read-service.ts | 20 +- .../admin/admin-read-shared-helpers.ts | 8 +- .../services/admin/admin-task-read-helpers.ts | 39 ++- .../claim/kuaishou-cloud-claim-context.ts | 11 +- .../claim/kuaishou-cloud-claim-service.ts | 31 +- .../fulfillment/kuaishou-cloud/index.ts | 23 +- .../kuaishou-cloud/task-finalization.ts | 14 +- apps/backend/src/services/open-91/response.ts | 3 +- .../services/order/delivery-task-service.ts | 37 +-- apps/frontend/src/domain/task-status.ts | 59 ++++ apps/frontend/src/types/claim.ts | 21 +- .../composables/useKuaishouCloudClaim.ts | 29 +- 14 files changed, 513 insertions(+), 122 deletions(-) create mode 100644 apps/backend/src/domain/task-status.test.ts create mode 100644 apps/backend/src/domain/task-status.ts create mode 100644 apps/frontend/src/domain/task-status.ts diff --git a/apps/backend/src/domain/task-status.test.ts b/apps/backend/src/domain/task-status.test.ts new file mode 100644 index 00000000..0b29282f --- /dev/null +++ b/apps/backend/src/domain/task-status.test.ts @@ -0,0 +1,27 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + TASK_STATUS, + canRedeemKuaishouCloudClaimStatus, + canTaskTransition, + isClaimInactiveTaskStatus, + isOpen91FailedTaskStatus, + shouldEnsureKuaishouCloudClaimLink, +} from './task-status.js' + +test('任务状态机声明快手 Cloud 主流程跳转', () => { + assert.equal(canTaskTransition(TASK_STATUS.PENDING_BINDING_PREPARE, TASK_STATUS.WAITING_BINDING), true) + assert.equal(canTaskTransition(TASK_STATUS.WAITING_BINDING, TASK_STATUS.ROLE_CONFIRMED), true) + assert.equal(canTaskTransition(TASK_STATUS.ROLE_CONFIRMED, TASK_STATUS.REDEEMING), true) + assert.equal(canTaskTransition(TASK_STATUS.REDEEMING, TASK_STATUS.DISPATCHED_PENDING_RETURN), true) + assert.equal(canTaskTransition(TASK_STATUS.DISPATCHED_PENDING_RETURN, TASK_STATUS.COMPLETED), true) +}) + +test('任务状态机收敛领取和 91 查询的业务判断', () => { + assert.equal(shouldEnsureKuaishouCloudClaimLink(TASK_STATUS.PENDING_BINDING_PREPARE), true) + assert.equal(canRedeemKuaishouCloudClaimStatus(TASK_STATUS.ROLE_CONFIRMED), true) + assert.equal(canRedeemKuaishouCloudClaimStatus(TASK_STATUS.WAITING_BINDING), false) + assert.equal(isClaimInactiveTaskStatus(TASK_STATUS.MANUAL_REVIEW), true) + assert.equal(isOpen91FailedTaskStatus(TASK_STATUS.CLOSED), true) +}) diff --git a/apps/backend/src/domain/task-status.ts b/apps/backend/src/domain/task-status.ts new file mode 100644 index 00000000..ccdf41c5 --- /dev/null +++ b/apps/backend/src/domain/task-status.ts @@ -0,0 +1,313 @@ +export const TASK_STATUS = { + PENDING_PAYMENT: 'pending_payment', + PAID: 'paid', + LINK_GENERATED: 'link_generated', + CLAIMED: 'claimed', + PENDING_BINDING_PREPARE: 'pending_binding_prepare', + WAITING_BINDING: 'waiting_binding', + ROLE_CONFIRMED: 'role_confirmed', + REDEEMING: 'redeeming', + DISPATCHED_PENDING_RETURN: 'dispatched_pending_return', + COMPLETED: 'completed', + REDEEMED: 'redeemed', + RETRY_PENDING: 'retry_pending', + MANUAL_REVIEW: 'manual_review', + FAILED: 'failed', + EXPIRED: 'expired', + CLOSED: 'closed', +} as const + +export type KnownTaskStatus = typeof TASK_STATUS[keyof typeof TASK_STATUS] +export type TaskStatus = KnownTaskStatus | (string & {}) + +const TASK_TRANSITIONS: Record = { + [TASK_STATUS.PENDING_PAYMENT]: [ + TASK_STATUS.PAID, + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.PAID]: [ + TASK_STATUS.LINK_GENERATED, + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.LINK_GENERATED]: [ + TASK_STATUS.CLAIMED, + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.CLAIMED]: [ + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.PENDING_BINDING_PREPARE]: [ + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.WAITING_BINDING]: [ + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.REDEEMING, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.ROLE_CONFIRMED]: [ + TASK_STATUS.REDEEMING, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.REDEEMING]: [ + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + ], + [TASK_STATUS.DISPATCHED_PENDING_RETURN]: [ + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + ], + [TASK_STATUS.RETRY_PENDING]: [ + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.REDEEMING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.MANUAL_REVIEW]: [ + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.REDEEMING, + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.FAILED]: [ + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.CLOSED, + ], + [TASK_STATUS.COMPLETED]: [], + [TASK_STATUS.REDEEMED]: [], + [TASK_STATUS.EXPIRED]: [], + [TASK_STATUS.CLOSED]: [], +} + +const FINAL_STATUSES = new Set([ + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, +]) + +const OPEN_91_FAILED_STATUSES = new Set([ + TASK_STATUS.FAILED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.CLOSED, +]) + +const CLAIM_INACTIVE_STATUSES = new Set([ + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, +]) + +const PAID_PREPARATION_STABLE_STATUSES = new Set([ + TASK_STATUS.LINK_GENERATED, + TASK_STATUS.CLAIMED, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.REDEEMING, + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, +]) + +const KUAISHOU_CLOUD_CLAIM_LINK_STATUSES = new Set([ + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.WAITING_BINDING, + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, +]) + +const KUAISHOU_CLOUD_ROLE_CONFIRM_SETTLED_STATUSES = new Set([ + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.REDEEMING, + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, +]) + +const KUAISHOU_CLOUD_REDEEM_SETTLED_STATUSES = new Set([ + TASK_STATUS.REDEEMING, + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, +]) + +const CLAIM_LINK_REGENERATABLE_STATUSES = new Set([ + TASK_STATUS.LINK_GENERATED, + TASK_STATUS.CLAIMED, + TASK_STATUS.ROLE_CONFIRMED, + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, +]) + +const KUAISHOU_CLOUD_RESULT_STATUSES = new Set([ + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, +]) + +const FULFILLMENT_COMPLETED_STATUSES = new Set([ + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, +]) + +const RETRYABLE_TASK_STATUSES = new Set([ + TASK_STATUS.RETRY_PENDING, + TASK_STATUS.MANUAL_REVIEW, +]) + +const KUAISHOU_CLOUD_PREPARABLE_STATUSES = new Set([ + TASK_STATUS.PENDING_BINDING_PREPARE, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, +]) + +export function normalizeTaskStatus(value: unknown): TaskStatus { + return String(value || '').trim() as TaskStatus +} + +export function canTaskTransition(from: unknown, to: unknown): boolean { + const current = normalizeTaskStatus(from) as KnownTaskStatus + const next = normalizeTaskStatus(to) as KnownTaskStatus + if (!current || !next || current === next) { + return Boolean(current && next) + } + + return Boolean(TASK_TRANSITIONS[current]?.includes(next)) +} + +export function getTaskNextStatuses(status: unknown): readonly KnownTaskStatus[] { + return TASK_TRANSITIONS[normalizeTaskStatus(status) as KnownTaskStatus] || [] +} + +export function isTaskFinalStatus(status: unknown): boolean { + return FINAL_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isOpen91FailedTaskStatus(status: unknown): boolean { + return OPEN_91_FAILED_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isClaimInactiveTaskStatus(status: unknown): boolean { + return CLAIM_INACTIVE_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isPaidPreparationStableStatus(status: unknown): boolean { + return PAID_PREPARATION_STABLE_STATUSES.has(normalizeTaskStatus(status)) +} + +export function shouldEnsureKuaishouCloudClaimLink(status: unknown): boolean { + return KUAISHOU_CLOUD_CLAIM_LINK_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isKuaishouCloudRoleConfirmSettledStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_ROLE_CONFIRM_SETTLED_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isKuaishouCloudRedeemSettledStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_REDEEM_SETTLED_STATUSES.has(normalizeTaskStatus(status)) +} + +export function canRedeemKuaishouCloudClaimStatus(status: unknown): boolean { + return normalizeTaskStatus(status) === TASK_STATUS.ROLE_CONFIRMED +} + +export function canRegenerateClaimLinkStatus(status: unknown): boolean { + return CLAIM_LINK_REGENERATABLE_STATUSES.has(normalizeTaskStatus(status)) +} + +export function hasKuaishouCloudRedeemResultStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_RESULT_STATUSES.has(normalizeTaskStatus(status)) +} + +export function isTaskFulfillmentCompletedStatus(status: unknown): boolean { + return FULFILLMENT_COMPLETED_STATUSES.has(normalizeTaskStatus(status)) +} + +export function canRetryTaskStatus(status: unknown): boolean { + return RETRYABLE_TASK_STATUSES.has(normalizeTaskStatus(status)) +} + +export function canMarkManualReviewTaskStatus(status: unknown): boolean { + const normalized = normalizeTaskStatus(status) + return normalized !== TASK_STATUS.MANUAL_REVIEW && !isTaskFinalStatus(normalized) +} + +export function canCompleteManualDispatchTaskStatus(status: unknown): boolean { + return !isTaskFinalStatus(status) +} + +export function canPrepareKuaishouCloudFulfillmentStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_PREPARABLE_STATUSES.has(normalizeTaskStatus(status)) +} + +export function canDispatchKuaishouCloudFulfillmentStatus(status: unknown): boolean { + return normalizeTaskStatus(status) === TASK_STATUS.WAITING_BINDING +} + +export function canReturnKuaishouCloudFulfillmentStatus(status: unknown): boolean { + return normalizeTaskStatus(status) === TASK_STATUS.DISPATCHED_PENDING_RETURN +} + +export function resolveInitialPaidTaskStatus(profile: { + requires_claim?: boolean + executor_key?: unknown +} | null | undefined): TaskStatus { + if (Boolean(profile?.requires_claim)) { + return TASK_STATUS.PAID + } + + if (String(profile?.executor_key || '').trim() === 'kuaishou_ct_assisted') { + return TASK_STATUS.PENDING_BINDING_PREPARE + } + + if (String(profile?.executor_key || '').trim() === 'manual_dispatch') { + return TASK_STATUS.MANUAL_REVIEW + } + + return TASK_STATUS.PAID +} diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index 04aa3f9f..e41113a1 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -8,6 +8,14 @@ import { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-co import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { normalizeDateQuery, normalizePage, normalizePageSize, safeParseJson } from './admin-query-utils.js' +import { + canCompleteManualDispatchTaskStatus, + canDispatchKuaishouCloudFulfillmentStatus, + canMarkManualReviewTaskStatus, + canPrepareKuaishouCloudFulfillmentStatus, + canReturnKuaishouCloudFulfillmentStatus, + canRetryTaskStatus, +} from '../../domain/task-status.js' import { canViewerCloseTask, canRegenerateClaimLinkForViewer, @@ -228,25 +236,25 @@ export async function getAdminTaskDetail( operations: { canRetry: viewerContext.canManageTaskLifecycle && !isManualDispatchTask(task) - && ['retry_pending', 'manual_review'].includes(task.task_status), + && canRetryTaskStatus(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), + canMarkManualReview: viewerContext.canManageTaskLifecycle && canMarkManualReviewTaskStatus(task.task_status), + canCompleteManualDispatch: viewerContext.canManageTaskLifecycle && isManualDispatchTask(task) && canCompleteManualDispatchTaskStatus(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()), + && canPrepareKuaishouCloudFulfillmentStatus(task.task_status), 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()), + && canDispatchKuaishouCloudFulfillmentStatus(task.task_status), canReturnKuaishouCloudFulfillment: viewerContext.canManageTaskLifecycle && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' - && ['dispatched_pending_return'].includes(String(task.task_status || '').trim()), + && canReturnKuaishouCloudFulfillmentStatus(task.task_status), canViewSensitiveTaskData: viewerContext.canViewSensitiveTaskData, }, } diff --git a/apps/backend/src/services/admin/admin-read-shared-helpers.ts b/apps/backend/src/services/admin/admin-read-shared-helpers.ts index 8415442b..25c351cc 100644 --- a/apps/backend/src/services/admin/admin-read-shared-helpers.ts +++ b/apps/backend/src/services/admin/admin-read-shared-helpers.ts @@ -4,6 +4,10 @@ import { parseTaskContext as parseTaskContextValue, parseTaskState as parseTaskStateValue, } from '../../utils/task-json.js' +import { + canRegenerateClaimLinkStatus, + isTaskFinalStatus, +} from '../../domain/task-status.js' import type { AdminViewerSessionInput } from '../../types/admin/read-inputs.js' import type { OrderItemRow, TaskRow } from '../../types/repository/rows.js' @@ -220,7 +224,7 @@ export function canViewerCloseTask(task: TaskLike, viewerContext: AdminViewerCon return false } - return !['redeemed', 'completed', 'closed'].includes(String(task?.task_status || '').trim()) + return !isTaskFinalStatus(task?.task_status) } export function resolveDisplayShopName(provider: unknown, shopId: unknown, shopName: unknown): string { @@ -295,7 +299,7 @@ export function canRegenerateClaimLinkForViewer(task: TaskLike, viewerContext: A return false } - const baseAllowed = ['link_generated', 'claimed', 'role_confirmed', 'retry_pending', 'manual_review'].includes(String(task.task_status || '').trim()) + const baseAllowed = canRegenerateClaimLinkStatus(task.task_status) if (!baseAllowed) { return false diff --git a/apps/backend/src/services/admin/admin-task-read-helpers.ts b/apps/backend/src/services/admin/admin-task-read-helpers.ts index 8c611e2a..812b204e 100644 --- a/apps/backend/src/services/admin/admin-task-read-helpers.ts +++ b/apps/backend/src/services/admin/admin-task-read-helpers.ts @@ -1,6 +1,11 @@ import { getTaskById } from '../../repositories/task-repo.js' import { createHttpError } from '../../utils/http.js' import { safeParseJson } from './admin-query-utils.js' +import { + TASK_STATUS, + isTaskFulfillmentCompletedStatus, + normalizeTaskStatus, +} from '../../domain/task-status.js' import { createAdminViewerContext, getTaskPrimaryClaimTokenId, @@ -173,7 +178,7 @@ export function buildOrderFulfillmentProgress( } function buildTaskFulfillmentState(task: TaskRow | null | undefined): TaskFulfillmentState { - const normalizedStatus = String(task?.task_status || '').trim() + const normalizedStatus = normalizeTaskStatus(task?.task_status) if (!normalizedStatus) { return { @@ -182,63 +187,63 @@ function buildTaskFulfillmentState(task: TaskRow | null | undefined): TaskFulfil } } - if (normalizedStatus === 'pending_payment') { + if (normalizedStatus === TASK_STATUS.PENDING_PAYMENT) { return { resourceStatus: 'pending_payment', customerStatus: 'not_started' } } - if (normalizedStatus === 'paid') { + if (normalizedStatus === TASK_STATUS.PAID) { return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' } } - if (normalizedStatus === 'pending_binding_prepare') { + if (normalizedStatus === TASK_STATUS.PENDING_BINDING_PREPARE) { return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' } } - if (normalizedStatus === 'waiting_binding') { + if (normalizedStatus === TASK_STATUS.WAITING_BINDING) { return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' } } - if (normalizedStatus === 'dispatched_pending_return') { + if (normalizedStatus === TASK_STATUS.DISPATCHED_PENDING_RETURN) { return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } - if (normalizedStatus === 'completed') { + if (normalizedStatus === TASK_STATUS.COMPLETED) { return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } - if (normalizedStatus === 'manual_review') { + if (normalizedStatus === TASK_STATUS.MANUAL_REVIEW) { return { resourceStatus: 'manual_review', customerStatus: 'customer_exception' } } - if (normalizedStatus === 'retry_pending') { + if (normalizedStatus === TASK_STATUS.RETRY_PENDING) { return { resourceStatus: 'retry_pending', customerStatus: 'customer_exception' } } - if (normalizedStatus === 'closed') { + if (normalizedStatus === TASK_STATUS.CLOSED) { return { resourceStatus: 'closed', customerStatus: 'closed' } } - if (normalizedStatus === 'expired') { + if (normalizedStatus === TASK_STATUS.EXPIRED) { return { resourceStatus: 'expired', customerStatus: 'expired' } } - if (normalizedStatus === 'link_generated') { + if (normalizedStatus === TASK_STATUS.LINK_GENERATED) { return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' } } - if (normalizedStatus === 'claimed') { + if (normalizedStatus === TASK_STATUS.CLAIMED) { return { resourceStatus: 'resource_ready', customerStatus: 'link_opened' } } - if (normalizedStatus === 'role_confirmed') { + if (normalizedStatus === TASK_STATUS.ROLE_CONFIRMED) { return { resourceStatus: 'resource_ready', customerStatus: 'customer_confirmed' } } - if (normalizedStatus === 'redeeming') { + if (normalizedStatus === TASK_STATUS.REDEEMING) { return { resourceStatus: 'resource_ready', customerStatus: 'customer_processing' } } - if (normalizedStatus === 'redeemed') { + if (normalizedStatus === TASK_STATUS.REDEEMED) { return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } @@ -254,7 +259,7 @@ function isTaskResourcePrepared(task: TaskRow | null | undefined): boolean { } function isTaskFulfillmentCompleted(task: TaskRow | null | undefined): boolean { - return ['redeemed', 'completed', 'dispatched_pending_return'].includes(String(task?.task_status || '').trim()) + return isTaskFulfillmentCompletedStatus(task?.task_status) } function normalizeRecord(value: unknown): JsonRecord { 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 52fe7465..911d8cb8 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts @@ -6,10 +6,11 @@ import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js' import { nowIso } from '../../utils/time.js' +import { TASK_STATUS, isTaskFinalStatus } from '../../domain/task-status.js' import { buildClaimUrl } from './claim-service.js' import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' -export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed']) +export const CLAIM_TERMINAL_STATUSES = new Set([TASK_STATUS.EXPIRED, TASK_STATUS.CLOSED]) export const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide' type JsonObject = Record @@ -410,16 +411,16 @@ function isClaimPlaceholderSkuName(value: string) { async function expireClaimContext(claimToken: ClaimTokenRow, task: TaskRow) { const now = nowIso() const nextClaimToken = await updateClaimToken(claimToken.id, { - status: 'expired', + status: TASK_STATUS.EXPIRED, updated_at: now, }) let nextTask = task - if (!CLAIM_TERMINAL_STATUSES.has(String(task.task_status || '')) && task.task_status !== 'redeemed') { + if (!isTaskFinalStatus(task.task_status)) { nextTask = await updateTask(task.id, { - task_status: 'expired', - user_action_status: 'expired', + task_status: TASK_STATUS.EXPIRED, + user_action_status: TASK_STATUS.EXPIRED, last_error: '领取链接已过期', updated_at: now, }) || task diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts index 2bc4a3e1..6a186b44 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts @@ -6,6 +6,13 @@ import { createTaskEvent } from '../../repositories/task-event-repo.js' import { getTaskById, updateTask, updateTaskStatusIfCurrent } from '../../repositories/task-repo.js' import { createHttpError } from '../../utils/http.js' import { addHours, nowIso } from '../../utils/time.js' +import { + TASK_STATUS, + canRedeemKuaishouCloudClaimStatus, + isKuaishouCloudRedeemSettledStatus, + isKuaishouCloudRoleConfirmSettledStatus, + normalizeTaskStatus, +} from '../../domain/task-status.js' import { getKuaishouEticketSourceConfig, listKuaishouEticketShopConfigs, @@ -119,7 +126,7 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js kuaishouCloudFulfillment: mockFlow, }), claimed_at: context.task.claimed_at || now, - task_status: 'waiting_binding', + task_status: TASK_STATUS.WAITING_BINDING, user_action_status: 'pending_claim', last_error: '', updated_at: now, @@ -357,7 +364,7 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) { }) } - if (['role_confirmed', 'redeeming', 'dispatched_pending_return', 'completed', 'manual_review'].includes(String(context.task.task_status || '').trim())) { + if (isKuaishouCloudRoleConfirmSettledStatus(context.task.task_status)) { return getKuaishouCloudClaimDetail(token) } @@ -388,10 +395,10 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) { } await updateTask(context.task.id, { - task_status: 'role_confirmed', + task_status: TASK_STATUS.ROLE_CONFIRMED, role_id: flow.binding.roleId, role_name: flow.binding.roleName, - user_action_status: 'role_confirmed', + user_action_status: TASK_STATUS.ROLE_CONFIRMED, role_confirmed_at: now, last_error: '', updated_at: now, @@ -417,20 +424,20 @@ export async function redeemKuaishouCloudClaim(token: unknown) { }) } - const currentStatus = String(context.task.task_status || '').trim() - if (['redeeming', 'dispatched_pending_return', 'completed', 'manual_review'].includes(currentStatus)) { + const currentStatus = normalizeTaskStatus(context.task.task_status) + if (isKuaishouCloudRedeemSettledStatus(currentStatus)) { return getKuaishouCloudClaimDetail(token) } - if (currentStatus !== 'role_confirmed') { + if (!canRedeemKuaishouCloudClaimStatus(currentStatus)) { throw createHttpError('请先确认角色信息', { statusCode: 409, errorCode: 'claim_kuaishou_cloud_role_not_confirmed', }) } - const lockedTask = await updateTaskStatusIfCurrent(context.task.id, 'role_confirmed', { - task_status: 'redeeming', + const lockedTask = await updateTaskStatusIfCurrent(context.task.id, TASK_STATUS.ROLE_CONFIRMED, { + task_status: TASK_STATUS.REDEEMING, user_action_status: 'not_required', last_error: '', updated_at: now, @@ -453,13 +460,13 @@ export async function redeemKuaishouCloudClaim(token: unknown) { }) } catch (error) { const latestTask = await getTaskById(lockedTask.id) - if (latestTask && String(latestTask.task_status || '').trim() !== 'redeeming') { + if (latestTask && normalizeTaskStatus(latestTask.task_status) !== TASK_STATUS.REDEEMING) { return getKuaishouCloudClaimDetail(token) } const message = error instanceof Error ? error.message : '兑换请求提交失败,请联系客服处理' await updateTask(lockedTask.id, { - task_status: 'manual_review', + task_status: TASK_STATUS.MANUAL_REVIEW, user_action_status: 'not_required', last_error: message, result_code: 'kuaishou_cloud_redeem_failed', @@ -569,7 +576,7 @@ async function completeMockKuaishouCloudClaimTask(task: TaskRow, timestamp: stri } await updateTask(task.id, { - task_status: 'completed', + task_status: TASK_STATUS.COMPLETED, delivery_status: 'success', result_code: 'mock_success', result_message: '开发 mock 已模拟兑换成功', diff --git a/apps/backend/src/services/fulfillment/kuaishou-cloud/index.ts b/apps/backend/src/services/fulfillment/kuaishou-cloud/index.ts index b83124ee..0b95016f 100644 --- a/apps/backend/src/services/fulfillment/kuaishou-cloud/index.ts +++ b/apps/backend/src/services/fulfillment/kuaishou-cloud/index.ts @@ -19,6 +19,7 @@ import { } from "../../notification/domain-notifications.js"; import { createHttpError } from "../../../utils/http.js"; import { nowIso } from "../../../utils/time.js"; +import { TASK_STATUS, normalizeTaskStatus } from "../../../domain/task-status.js"; import { KUAISHOU_CLOUD_FIXED_VN_KEY, isKuaishouCloudBindUrlFresh, @@ -126,9 +127,9 @@ export async function prepareKuaishouCloudFulfillmentTask( let readyTask = task; - if (String(task.task_status || "").trim() !== "waiting_binding") { + if (normalizeTaskStatus(task.task_status) !== TASK_STATUS.WAITING_BINDING) { readyTask = (await updateTask(task.id, { - task_status: "waiting_binding", + task_status: TASK_STATUS.WAITING_BINDING, claim_token: claimLinkState.token || task.claim_token || "", claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task), @@ -314,7 +315,7 @@ export async function prepareKuaishouCloudFulfillmentTask( }; const updatedTask = await updateTask(task.id, { - task_status: "waiting_binding", + task_status: TASK_STATUS.WAITING_BINDING, user_action_status: "pending_claim", claim_token: claimLinkState.token || task.claim_token || "", claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task), @@ -462,16 +463,16 @@ export async function refreshKuaishouCloudTaskBindUrl( bindProbeStatus: "pending", bindProbeMessage: "", roleName: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? flow.binding.roleName : "", roleId: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? flow.binding.roleId : "", }, role: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? flow.role : { status: "pending", @@ -488,17 +489,17 @@ export async function refreshKuaishouCloudTaskBindUrl( options.claimLinkState || (await ensureTaskClaimLink(task)); const updatedTask = await updateTask(task.id, { task_status: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? task.task_status - : "waiting_binding", + : TASK_STATUS.WAITING_BINDING, claim_token: claimLinkState.token || task.claim_token || "", claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task), role_id: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? task.role_id : "", role_name: - String(task.task_status || "").trim() === "role_confirmed" + normalizeTaskStatus(task.task_status) === TASK_STATUS.ROLE_CONFIRMED ? task.role_name : "", last_error: "", @@ -665,7 +666,7 @@ async function markKuaishouCloudBindUrlRefreshFailed( }; const updatedTask = await updateTask(task.id, { - task_status: "pending_binding_prepare", + task_status: TASK_STATUS.PENDING_BINDING_PREPARE, role_id: "", role_name: "", last_error: `绑定链接过期,旧号码已退还,新链接准备失败:${errorMessage}`, diff --git a/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts b/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts index 61cf10bd..2338348a 100644 --- a/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts +++ b/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts @@ -3,6 +3,7 @@ import { createTaskEvent } from "../../../repositories/task-event-repo.js"; import { updateTask } from "../../../repositories/task-repo.js"; import { createHttpError } from "../../../utils/http.js"; import { nowIso } from "../../../utils/time.js"; +import { TASK_STATUS, normalizeTaskStatus, type TaskStatus } from "../../../domain/task-status.js"; import { notifyKuaishouCloudConsumeFailed } from "../../notification/domain-notifications.js"; import { useCloudtentaclesSku } from "../../platforms/cloudtentacles/catalog-service.js"; import { backCloudtentaclesVirtualNumber } from "../../platforms/cloudtentacles/virtual-number-service.js"; @@ -78,7 +79,7 @@ export async function dispatchKuaishouCloudFulfillmentTask( if ( flow.dispatch.status === "success" && - String(task.task_status || "").trim() === "dispatched_pending_return" + normalizeTaskStatus(task.task_status) === TASK_STATUS.DISPATCHED_PENDING_RETURN ) { return { task, flow }; } @@ -163,7 +164,7 @@ export async function dispatchKuaishouCloudFulfillmentTask( }; let updatedTask = await updateTask(task.id, { - task_status: "dispatched_pending_return", + task_status: TASK_STATUS.DISPATCHED_PENDING_RETURN, delivery_status: "delivered", result_code: "kuaishou_cloud_dispatched", result_message: dispatchSummary, @@ -285,11 +286,10 @@ export async function returnKuaishouCloudFulfillmentTask( }); } + const taskStatus = normalizeTaskStatus(task.task_status); if ( flow.returnNumber.status === "success" && - ["completed", "manual_review"].includes( - String(task.task_status || "").trim() - ) + (taskStatus === TASK_STATUS.COMPLETED || taskStatus === TASK_STATUS.MANUAL_REVIEW) ) { return { task, flow }; } @@ -316,7 +316,7 @@ export async function returnKuaishouCloudFulfillmentTask( let consumeStatus = "pending"; let consumeErrorMessage = ""; let consumedAt = null; - let nextTaskStatus = "completed"; + let nextTaskStatus: TaskStatus = TASK_STATUS.COMPLETED; let nextResultCode = "kuaishou_cloud_completed"; let nextResultMessage = "cloudtentacles 发货、退号并完成快手核销"; @@ -369,7 +369,7 @@ export async function returnKuaishouCloudFulfillmentTask( } if (consumeStatus !== "success") { - nextTaskStatus = "manual_review"; + nextTaskStatus = TASK_STATUS.MANUAL_REVIEW; nextResultCode = "kuaishou_cloud_consume_failed"; nextResultMessage = consumeErrorMessage || "号码已退还,但快手核销未完成,请人工处理"; diff --git a/apps/backend/src/services/open-91/response.ts b/apps/backend/src/services/open-91/response.ts index a9c5f721..3d9f5ece 100644 --- a/apps/backend/src/services/open-91/response.ts +++ b/apps/backend/src/services/open-91/response.ts @@ -2,6 +2,7 @@ import { OPEN_91_DEFAULT_FAIL_CODE, OPEN_91_SUCCESS_MESSAGE, } from './config.js' +import { isOpen91FailedTaskStatus } from '../../domain/task-status.js' import { normalizeOpen91String } from './payload.js' type JsonObject = Record @@ -88,7 +89,7 @@ export function resolveOpen91QueryState( const failedTask = normalizedTasks.find((task) => { const status = normalizeOpen91String(task?.task_status) - return ['failed', 'manual_review', 'closed'].includes(status) && !readyTaskIdSet.has(Number(task?.id || 0)) + return isOpen91FailedTaskStatus(status) && !readyTaskIdSet.has(Number(task?.id || 0)) }) if (failedTask) { diff --git a/apps/backend/src/services/order/delivery-task-service.ts b/apps/backend/src/services/order/delivery-task-service.ts index e479fb45..cf9f717f 100644 --- a/apps/backend/src/services/order/delivery-task-service.ts +++ b/apps/backend/src/services/order/delivery-task-service.ts @@ -4,6 +4,12 @@ import { createTaskClaimToken } from '../claim/claim-service.js' import { notifyTaskAutoManualReview } from '../notification/domain-notifications.js' import { nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' +import { + TASK_STATUS, + isPaidPreparationStableStatus, + resolveInitialPaidTaskStatus, + shouldEnsureKuaishouCloudClaimLink, +} from '../../domain/task-status.js' import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' type JsonObject = Record @@ -130,7 +136,7 @@ export async function syncDeliveryTasksForOrderWithDeps( const createdAt = getNowIso() const initialStatus = order.pay_status === 'paid' ? resolvePaidTaskStatus(profile) - : 'pending_payment' + : TASK_STATUS.PENDING_PAYMENT const task = await createDeliveryTask({ orderId: order.id, @@ -271,19 +277,12 @@ async function preparePaidTask( const now = getNowIso() - if (['link_generated', 'claimed', 'role_confirmed', 'redeeming', 'redeemed', 'closed'].includes(task.task_status)) { + if (isPaidPreparationStableStatus(task.task_status)) { return task } if (isKuaishouCloudExecutor(task.executor_key)) { - if ([ - 'pending_binding_prepare', - 'waiting_binding', - 'dispatched_pending_return', - 'completed', - 'manual_review', - 'failed', - ].includes(String(task.task_status || '').trim())) { + if (shouldEnsureKuaishouCloudClaimLink(task.task_status)) { if (!task.primary_claim_token_id && !String(task.claim_token || '').trim()) { const claimToken = await createClaimToken(task.id) return updateDeliveryTask(task.id, { @@ -300,7 +299,7 @@ async function preparePaidTask( const claimToken = await createClaimToken(task.id) return updateDeliveryTask(task.id, { - task_status: 'pending_binding_prepare', + task_status: TASK_STATUS.PENDING_BINDING_PREPARE, claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, user_action_status: 'pending_claim', @@ -312,7 +311,7 @@ async function preparePaidTask( if (!task.requires_claim || String(task.executor_key || '').trim() === 'manual_dispatch') { const lastError = task.last_error || '当前任务需要人工履约处理' const updatedTask = await updateDeliveryTask(task.id, { - task_status: 'manual_review', + task_status: TASK_STATUS.MANUAL_REVIEW, user_action_status: 'not_required', last_error: lastError, updated_at: now, @@ -329,19 +328,7 @@ async function preparePaidTask( } function resolvePaidTaskStatus(profile: FulfillmentBindingLike | null | undefined): string { - if (Boolean(profile?.requires_claim)) { - return 'paid' - } - - if (isKuaishouCloudExecutor(profile?.executor_key)) { - return 'pending_binding_prepare' - } - - if (String(profile?.executor_key || '').trim() === 'manual_dispatch') { - return 'manual_review' - } - - return 'paid' + return resolveInitialPaidTaskStatus(profile) } function parseTaskContext(task: { context_json?: unknown } | null | undefined): TaskContext { diff --git a/apps/frontend/src/domain/task-status.ts b/apps/frontend/src/domain/task-status.ts new file mode 100644 index 00000000..796e27da --- /dev/null +++ b/apps/frontend/src/domain/task-status.ts @@ -0,0 +1,59 @@ +export const TASK_STATUS = { + PENDING_PAYMENT: "pending_payment", + PAID: "paid", + LINK_GENERATED: "link_generated", + CLAIMED: "claimed", + PENDING_BINDING_PREPARE: "pending_binding_prepare", + WAITING_BINDING: "waiting_binding", + ROLE_CONFIRMED: "role_confirmed", + REDEEMING: "redeeming", + DISPATCHED_PENDING_RETURN: "dispatched_pending_return", + COMPLETED: "completed", + REDEEMED: "redeemed", + RETRY_PENDING: "retry_pending", + MANUAL_REVIEW: "manual_review", + FAILED: "failed", + EXPIRED: "expired", + CLOSED: "closed", +} as const; + +export type KnownTaskStatus = (typeof TASK_STATUS)[keyof typeof TASK_STATUS]; +export type TaskStatus = KnownTaskStatus | (string & {}); + +const CLAIM_INACTIVE_STATUSES = new Set([ + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, +]); + +const KUAISHOU_CLOUD_RESULT_STATUSES = new Set([ + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, +]); + +export function normalizeTaskStatus(value: unknown): TaskStatus { + return String(value || "").trim() as TaskStatus; +} + +export function isClaimInactiveTaskStatus(status: unknown): boolean { + return CLAIM_INACTIVE_STATUSES.has(normalizeTaskStatus(status)); +} + +export function isKuaishouCloudRoleConfirmedStatus(status: unknown): boolean { + return normalizeTaskStatus(status) === TASK_STATUS.ROLE_CONFIRMED; +} + +export function isKuaishouCloudCompletedStatus(status: unknown): boolean { + const normalized = normalizeTaskStatus(status); + return normalized === TASK_STATUS.COMPLETED || normalized === TASK_STATUS.REDEEMED; +} + +export function hasKuaishouCloudRedeemResultStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_RESULT_STATUSES.has(normalizeTaskStatus(status)); +} diff --git a/apps/frontend/src/types/claim.ts b/apps/frontend/src/types/claim.ts index c8212012..3d0e6005 100644 --- a/apps/frontend/src/types/claim.ts +++ b/apps/frontend/src/types/claim.ts @@ -1,23 +1,8 @@ +import type { KnownTaskStatus } from '@/domain/task-status' + export type ClaimTokenStatus = 'active' | 'used' | 'expired' | 'revoked' | (string & {}) -export type ClaimTaskStatus = - | 'pending_payment' - | 'paid' - | 'pending_binding_prepare' - | 'waiting_binding' - | 'dispatched_pending_return' - | 'completed' - | 'failed' - | 'link_generated' - | 'claimed' - | 'role_confirmed' - | 'redeeming' - | 'redeemed' - | 'retry_pending' - | 'manual_review' - | 'expired' - | 'closed' - | (string & {}) +export type ClaimTaskStatus = KnownTaskStatus | (string & {}) export interface ClaimTaskInfo { taskId: number diff --git a/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts b/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts index 2309a2a3..e8010b40 100644 --- a/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts +++ b/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts @@ -3,6 +3,12 @@ import QRCode from "qrcode"; import { ElMessageBox } from "element-plus"; import { showError, showSuccess } from "@/lib/feedback"; +import { + hasKuaishouCloudRedeemResultStatus, + isClaimInactiveTaskStatus, + isKuaishouCloudCompletedStatus, + isKuaishouCloudRoleConfirmedStatus, +} from "@/domain/task-status"; import { formatAdminDateTime } from "@/utils/admin-time"; import { confirmKuaishouCloudClaimRole, @@ -75,7 +81,7 @@ export function useKuaishouCloudClaim(token: () => string) { const canEnterBindingStep = computed(() => isTicketVerified.value); const isRoleReady = computed(() => Boolean(roleName.value || roleId.value)); const isRoleConfirmed = computed( - () => String(task.value?.status || "").trim() === "role_confirmed" + () => isKuaishouCloudRoleConfirmedStatus(task.value?.status) ); const isDispatched = computed( () => String(flow.value?.dispatch.status || "").trim() === "success" @@ -83,28 +89,17 @@ export function useKuaishouCloudClaim(token: () => string) { const isCompleted = computed( () => - String(task.value?.status || "").trim() === "completed" || + isKuaishouCloudCompletedStatus(task.value?.status) || String(flow.value?.consume.status || "").trim() === "success" ); const hasRedeemResult = computed(() => { - const status = String(task.value?.status || "").trim(); - return ( - isDispatched.value || - [ - "dispatched_pending_return", - "completed", - "manual_review", - "failed", - ].includes(status) - ); + return isDispatched.value || hasKuaishouCloudRedeemResultStatus(task.value?.status); }); const canSubmitTicket = computed( () => - !["completed", "manual_review", "closed", "expired"].includes( - String(task.value?.status || "").trim() - ) && !submitting.value + !isClaimInactiveTaskStatus(task.value?.status) && !submitting.value ); const currentStep = computed(() => { @@ -323,12 +318,10 @@ export function useKuaishouCloudClaim(token: () => string) { } function resolveNextPollDelay() { - const taskStatus = String(task.value?.status || "").trim(); - if ( !flow.value || hasRedeemResult.value || - ["closed", "expired", "manual_review", "failed"].includes(taskStatus) + isClaimInactiveTaskStatus(task.value?.status) ) { return 0; }