export const COMPLETION_PHASE = Object.freeze({ READY: 'READY', SUBMITTING: 'SUBMITTING', RECONCILING: 'RECONCILING', PROCESSING: 'PROCESSING', FAILED: 'FAILED', COMPLETED: 'COMPLETED' }) const PROCESSING_STATUSES = new Set(['COMPLETING', 'PROCESSING']) const COMPLETED_STATUSES = new Set(['COMPLETED']) const FAILED_STATUSES = new Set(['COMPLETION_FAILED']) function normalizedStatus(value) { return String(value || '').trim().toUpperCase() } /** * Resolve already-normalized task/command facts into a UI phase. DTO field * extraction deliberately stays outside this module until the backend schema * is frozen. */ export function resolveCompletionPhase({ taskStatus, commandStatus, submitting = false, reconciling = false } = {}) { if (reconciling) return COMPLETION_PHASE.RECONCILING if (submitting) return COMPLETION_PHASE.SUBMITTING const statuses = [taskStatus, commandStatus].map(normalizedStatus) if (statuses.some((status) => COMPLETED_STATUSES.has(status))) return COMPLETION_PHASE.COMPLETED if (statuses.some((status) => FAILED_STATUSES.has(status))) return COMPLETION_PHASE.FAILED if (statuses.some((status) => PROCESSING_STATUSES.has(status))) return COMPLETION_PHASE.PROCESSING return COMPLETION_PHASE.READY } export function completionPresentation(phase, failureStage = '') { if (phase === COMPLETION_PHASE.SUBMITTING) return { label: '正在提交', action: '', blocked: true } if (phase === COMPLETION_PHASE.RECONCILING) return { label: '正在确认提交结果', action: '', blocked: true } if (phase === COMPLETION_PHASE.PROCESSING) return { label: '完成处理中', action: '', blocked: true } if (phase === COMPLETION_PHASE.COMPLETED) return { label: '装柜已完成', action: '', blocked: true } if (phase === COMPLETION_PHASE.FAILED) { return { label: failureStage ? `完成失败:${failureStage}` : '完成失败', action: '重试完成', blocked: true, retryable: true } } return { label: '待确认装柜', action: '确认装柜完成', blocked: false } } export function shouldReconcileCompletion(error = {}) { const code = normalizedStatus(error.code || error.errCode) return Boolean(error.network || error.timeout || code === 'TIMEOUT' || code === 'REQUEST_TIMEOUT') } /** 受理后立即查询真实任务,串行短轮询只等待终态,不重发完成命令。 */ export async function observeLoadingCompletion({ initialTask, readTask, isActive, onTask, intervalMs = 250, maxWaitMs = 10000, now = Date.now, wait = (delay) => new Promise((resolve) => setTimeout(resolve, delay)) }) { const started = now() let task = initialTask const phase = () => resolveCompletionPhase({ taskStatus: task?.taskStatus || task?.workflowStatus, commandStatus: task?.completionCommand?.status || task?.commandStatus }) while (phase() === COMPLETION_PHASE.PROCESSING && isActive() && now() - started < maxWaitMs) { const latest = await readTask() if (!isActive()) return null if (!latest) throw new Error('服务端未返回装柜完成状态,请刷新任务') task = latest onTask(task) if (phase() !== COMPLETION_PHASE.PROCESSING) break const remaining = maxWaitMs - (now() - started) if (remaining > 0) await wait(Math.min(intervalMs, remaining)) } return isActive() ? task : null } /** * A retry keeps the original operationId and the exact accepted request facts. * expectedTaskVersion participates in requestHash and must not be refreshed. */ export function refreshCompletionAttempt(previousAttempt = {}, latestFacts = {}) { if (!previousAttempt.operationId) throw new Error('缺少原完成操作标识,不能安全重试') const expectedTaskVersion = previousAttempt.expectedTaskVersion if (expectedTaskVersion === null || expectedTaskVersion === undefined) throw new Error('缺少服务端最新任务版本,不能安全重试') return { ...previousAttempt, ...latestFacts, operationId: previousAttempt.operationId, expectedTaskVersion: Number(expectedTaskVersion) } }