85 lines
3.9 KiB
JavaScript
85 lines
3.9 KiB
JavaScript
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)
|
|
}
|
|
}
|