优化
This commit is contained in:
+30
-14
@@ -170,7 +170,7 @@
|
||||
<text class="remote-error" role="alert">{{ remoteError }}</text>
|
||||
<button class="evidence-retry-btn" :disabled="remoteLoading" @tap="refreshTask">刷新任务</button>
|
||||
</view>
|
||||
<button class="submit-btn" aria-label="提交拣货完成" :disabled="submitting || pickingCompleted || !summary.canSubmit" @tap="submitPicking">
|
||||
<button class="submit-btn" aria-label="提交拣货完成" :disabled="submitting || (!pickingCompleted && !summary.canSubmit)" @tap="submitPicking">
|
||||
{{ submitting ? '正在提交…' : submitButtonText }}
|
||||
</button>
|
||||
</view>
|
||||
@@ -470,10 +470,10 @@
|
||||
return ''
|
||||
},
|
||||
submitButtonText() {
|
||||
return this.pickingCompleted ? '该柜次已完成' : this.summary.buttonText
|
||||
return this.pickingCompleted ? '继续装柜取证' : this.summary.buttonText
|
||||
},
|
||||
submitStatusHint() {
|
||||
if (this.pickingCompleted) return '本柜次任务已完成'
|
||||
if (this.pickingCompleted) return '拣货已完成,请继续装柜取证'
|
||||
if (!this.summary.canSubmit) return '请先处理至少一件'
|
||||
if (this.summary.pending > 0) return '提交时需选择原因'
|
||||
return `已处理 ${this.summary.processed}/${this.summary.total}`
|
||||
@@ -1045,7 +1045,7 @@
|
||||
this.currentBox.currentContainerNo = task.currentContainerNo
|
||||
const remoteTask = normalizePickingTask(task)
|
||||
this.assignmentVersion = remoteTask.assignment?.version ?? remoteTask.assignmentVersion ?? (remoteTask.workflowStatus === 'UNASSIGNED' ? 0 : null)
|
||||
if (this.assignmentVersion === null) throw new Error('任务领取状态尚未同步,请刷新重试')
|
||||
if (remoteTask.workflowStatus !== 'AWAITING_REVIEW' && this.assignmentVersion === null) throw new Error('任务领取状态尚未同步,请刷新重试')
|
||||
let claimed = null
|
||||
if (remoteTask.workflowStatus === 'UNASSIGNED') {
|
||||
const operationId = `claim-${session.currentUser?.id || 'operator'}-${this.remoteTaskId}-${task.loadingPlanId}-${task.containerBookingId}`
|
||||
@@ -1083,7 +1083,10 @@
|
||||
this.debugSubmit('refresh.incremental', { previousTaskVersion, taskVersion: this.pickingVersion, addedItemIds, pickingCompleted: this.pickingCompleted })
|
||||
if (addedItemIds.length) this.showToast(`已同步 ${addedItemIds.length} 条新增待拣明细`, 'warning')
|
||||
}
|
||||
if (this.loadingStageReady) await this.loadLoadingEvidence()
|
||||
if (this.loadingStageReady) {
|
||||
this.activeStage = 'evidence'
|
||||
await this.loadLoadingEvidence()
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== this.taskRequestId) return
|
||||
this.debugSubmit('refresh.error', { requestId, message: error.message || '', code: error.code || '', statusCode: error.statusCode || '', requestIdFromServer: error.requestId || '', traceId: error.traceId || '' })
|
||||
@@ -1436,9 +1439,15 @@
|
||||
this.closeOverlay(false)
|
||||
this.requestBoxSwitch(entry.index)
|
||||
},
|
||||
submitPicking() {
|
||||
async submitPicking() {
|
||||
if (this.pickingCompleted) {
|
||||
await this.syncRemoteTask()
|
||||
if (this.loadingStageReady) this.activeStage = 'evidence'
|
||||
else this.showToast('装柜状态尚未同步,请刷新重试', 'warning')
|
||||
return
|
||||
}
|
||||
if (!this.requirePickingOperation()) return
|
||||
if (!this.summary.canSubmit || this.pickingCompleted) return
|
||||
if (!this.summary.canSubmit) return
|
||||
if (this.summary.pending > 0) {
|
||||
this.selectedIncompleteReason = ''
|
||||
this.openOverlay('submitConfirm')
|
||||
@@ -1465,13 +1474,6 @@
|
||||
try {
|
||||
if (!this.remoteTaskId) throw new Error('尚未进入服务端拣货任务,请联网刷新后重试')
|
||||
if (this.pickingVersion === null) throw new Error(VERSION_SYNC_MESSAGE)
|
||||
const operationIdentity = stableTaskOperationIdentity(this.draftIdentity())
|
||||
const submitId = this.submitId || stableOperationId('submit', operationIdentity, { itemStates: this.itemStates, partialQuantities: this.partialQuantities, incompleteReason })
|
||||
this.submitId = submitId
|
||||
const warehouseId = this.currentBox.warehouseId || session.currentUser?.warehouseId || session.currentUser?.warehouse
|
||||
const inventorySnapshot = await this.refreshInventorySnapshot()
|
||||
this.debugSubmit('submit.inventory-snapshot', { snapshotTokenLength: inventorySnapshot?.snapshotToken ? String(inventorySnapshot.snapshotToken).length : 0, taskVersion: inventorySnapshot?.version ?? null })
|
||||
if (!inventorySnapshot?.snapshotToken) throw new Error('库存快照未同步,请刷新任务后重试')
|
||||
const actions = this.currentBox.items.map((item) => {
|
||||
if (item.itemStatus && !['PENDING', 'PICKING'].includes(item.itemStatus)) return null
|
||||
const status = this.itemState(item)
|
||||
@@ -1482,6 +1484,20 @@
|
||||
if (incompleteReason) return { type: 'SKIP', itemId, reason: `${incompleteReason}(提交时确认未拣)`, version: Number(item.version || 0) }
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
if (!actions.length) {
|
||||
this.debugSubmit('submit.empty-refresh', { serverItemStatuses: this.currentBox.items.map((item) => item.itemStatus || null) })
|
||||
await this.syncRemoteTask()
|
||||
if (this.loadingStageReady) this.activeStage = 'evidence'
|
||||
else this.showToast('没有可提交的待拣明细,已刷新任务', 'warning')
|
||||
return
|
||||
}
|
||||
const operationIdentity = stableTaskOperationIdentity(this.draftIdentity())
|
||||
const submitId = this.submitId || stableOperationId('submit', operationIdentity, { itemStates: this.itemStates, partialQuantities: this.partialQuantities, incompleteReason })
|
||||
this.submitId = submitId
|
||||
const warehouseId = this.currentBox.warehouseId || session.currentUser?.warehouseId || session.currentUser?.warehouse
|
||||
const inventorySnapshot = await this.refreshInventorySnapshot()
|
||||
this.debugSubmit('submit.inventory-snapshot', { snapshotTokenLength: inventorySnapshot?.snapshotToken ? String(inventorySnapshot.snapshotToken).length : 0, taskVersion: inventorySnapshot?.version ?? null })
|
||||
if (!inventorySnapshot?.snapshotToken) throw new Error('库存快照未同步,请刷新任务后重试')
|
||||
this.debugSubmit('submit.batch', { localItemIds: this.currentBox.items.map((item) => item.id), actionItemIds: actions.map((action) => action.itemId), actionCount: actions.length })
|
||||
const submittedTask = await submitContainerPickingBatch({ taskId: this.remoteTaskId, loadingPlanId: this.currentBox.loadingPlanId, containerBookingId: this.currentBox.containerBookingId, version: Number(this.pickingVersion) }, actions, { warehouseId, operationId: submitId, inventorySnapshotToken: inventorySnapshot?.snapshotToken })
|
||||
this.debugSubmit('submit.success', { itemCount: actions.length, workflowStatus: submittedTask?.workflowStatus || null, taskStatus: submittedTask?.taskStatus || null })
|
||||
|
||||
+10
-3
@@ -15,7 +15,7 @@
|
||||
<text class="task-order">柜次号 {{ task.cabinetNo }}</text>
|
||||
<text class="task-order">{{ task.displayNo || '未命名订单' }}</text>
|
||||
<text class="task-meta">{{ task.warehouseName || (task.warehouseId ? `仓库 ${task.warehouseId}` : '缺少仓库信息') }} · 待处理 {{ task.pendingItemCount ?? task.itemCount ?? 0 }} 项</text>
|
||||
<button class="task-enter" :disabled="!isTaskReady(task)" @tap="enterTask(task)">{{ isTaskReady(task) ? '进入并开始拣货' : '任务暂不可进入' }}</button>
|
||||
<button class="task-enter" :disabled="!isTaskReady(task)" @tap="enterTask(task)">{{ taskActionLabel(task) }}</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="toast.visible" class="app-toast" :class="toast.type" role="status">{{ toast.message }}</view>
|
||||
@@ -36,13 +36,20 @@
|
||||
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.loadTasks() },
|
||||
onUnload() { if (this._stopNetwork) this._stopNetwork(); clearTimeout(this._toastTimer) },
|
||||
methods: {
|
||||
statusLabel(status) { return status === 'IN_PROGRESS' ? '拣货中' : '待领取' },
|
||||
statusLabel(status) {
|
||||
if (status === 'AWAITING_REVIEW') return '待上传证据/确认装柜'
|
||||
return status === 'IN_PROGRESS' ? '拣货中' : '待领取'
|
||||
},
|
||||
taskActionLabel(task) {
|
||||
if (!this.isTaskReady(task)) return '任务暂不可进入'
|
||||
return task.workflowStatus === 'AWAITING_REVIEW' ? '继续装柜取证' : '进入并开始拣货'
|
||||
},
|
||||
isMergedTask(task) { return task?.recordStatus === 'MERGED' || Boolean(task?.mergedIntoContainerBookingId) },
|
||||
clearMergedTaskDraft(task) {
|
||||
const operatorId = session.currentUser?.id || session.currentUser?.userId || session.currentUser?.accountId
|
||||
clearPickingDraft({ operatorId, taskId: task.taskId, loadingPlanId: task.loadingPlanId, containerBookingId: task.containerBookingId, warehouseId: task.warehouseId })
|
||||
},
|
||||
isTaskReady(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && task.loadingPlanId && task.containerBookingId && task.cabinetNo && task.currentContainerNo && task.warehouseId && task.version !== null && task.version !== undefined && Number.isInteger(Number(task.version)) && task.assignmentVersion !== null && task.assignmentVersion !== undefined && Number.isInteger(Number(task.assignmentVersion)) && ['UNASSIGNED', 'IN_PROGRESS'].includes(task.workflowStatus)) },
|
||||
isTaskReady(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && task.loadingPlanId && task.containerBookingId && task.cabinetNo && task.currentContainerNo && task.warehouseId && task.version !== null && task.version !== undefined && Number.isInteger(Number(task.version)) && (task.workflowStatus === 'AWAITING_REVIEW' || (task.assignmentVersion !== null && task.assignmentVersion !== undefined && Number.isInteger(Number(task.assignmentVersion)))) && ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'].includes(task.workflowStatus)) },
|
||||
async loadTasks() {
|
||||
if (!isOnline()) { this.error = '当前网络不可用,请联网后刷新'; return }
|
||||
this.loading = true; this.error = ''; this.notice = ''
|
||||
|
||||
@@ -43,6 +43,10 @@ test('picking task list uses remote loading orders and never falls back to demo
|
||||
assert.match(tasks, /listLoadingOrders/)
|
||||
assert.match(tasks, /当前网络不可用,请联网后刷新/)
|
||||
assert.match(tasks, /进入并开始拣货/)
|
||||
assert.match(tasks, /AWAITING_REVIEW[\s\S]*?待上传证据\/确认装柜/)
|
||||
assert.match(tasks, /task\.workflowStatus === 'AWAITING_REVIEW' \? '继续装柜取证'/)
|
||||
assert.match(tasks, /\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'\]\.includes\(task\.workflowStatus\)/)
|
||||
assert.match(tasks, /task\.workflowStatus === 'AWAITING_REVIEW' \|\| \(task\.assignmentVersion !== null/)
|
||||
assert.match(tasks, /暂无可领取的拣货任务/)
|
||||
assert.match(tasks, /集装箱号 \{\{ task\.currentContainerNo \}\}/)
|
||||
assert.match(tasks, /柜次号 \{\{ task\.cabinetNo \}\}/)
|
||||
|
||||
@@ -43,7 +43,7 @@ test('C02 identifiers and separate picking/inbound versions are normalized expli
|
||||
assert.match(page, /submit\.batch/)
|
||||
assert.match(page, /item\.itemStatus === 'PICKED'/)
|
||||
assert.match(page, /item\.itemStatus === 'PARTIAL'/)
|
||||
assert.match(page, /pickingCompleted \|\| !summary\.canSubmit/)
|
||||
assert.match(page, /!pickingCompleted && !summary\.canSubmit/)
|
||||
assert.match(page, /this\.loadingVersion = this\.remotePick\.inboundVersion \?\? this\.remotePick\.loadingVersion/)
|
||||
assert.match(page, /状态尚未同步,请刷新重试/)
|
||||
})
|
||||
@@ -62,7 +62,7 @@ test('picking page exposes five evidence slots, retry and remote completion stat
|
||||
assert.match(page, /createEvidenceUploadQueue/)
|
||||
assert.match(page, /retryEvidence/)
|
||||
assert.match(page, /uploadEvidenceFile/)
|
||||
assert.match(page, /submitting \|\| pickingCompleted \|\| !summary\.canSubmit/)
|
||||
assert.match(page, /submitting \|\| \(!pickingCompleted && !summary\.canSubmit\)/)
|
||||
assert.match(page, /completePicking\(/)
|
||||
assert.match(page, /sourceType: \['camera'\]/)
|
||||
assert.doesNotMatch(page, /sourceType: \['camera', 'album'\]/)
|
||||
|
||||
@@ -14,15 +14,15 @@ test('picking and photo workspaces are independently selectable', () => {
|
||||
test('a server task never inherits a completed marker from the previously opened local cabinet', () => {
|
||||
assert.match(page, /pickingCompleted\(\) \{[\s\S]*?this\.remoteTaskId \|\| this\.currentBox\.taskId[\s\S]*?if \(!this\.remotePick\) return false[\s\S]*?hasServerPending/)
|
||||
assert.match(page, /if \(options\.taskId\) \{[\s\S]*?this\.completedBoxes = \{\}[\s\S]*?this\.boxes = \[/)
|
||||
assert.match(page, /:disabled="submitting \|\| pickingCompleted \|\| !summary\.canSubmit"/)
|
||||
assert.match(page, /submitButtonText\(\)[\s\S]*?return this\.pickingCompleted \? '该柜次已完成'/)
|
||||
assert.match(page, /submitStatusHint\(\)[\s\S]*?if \(this\.pickingCompleted\) return '本柜次任务已完成'/)
|
||||
const submit = page.match(/submitPicking\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(page, /:disabled="submitting \|\| \(!pickingCompleted && !summary\.canSubmit\)"/)
|
||||
assert.match(page, /submitButtonText\(\)[\s\S]*?return this\.pickingCompleted \? '继续装柜取证'/)
|
||||
assert.match(page, /submitStatusHint\(\)[\s\S]*?if \(this\.pickingCompleted\) return '拣货已完成,请继续装柜取证'/)
|
||||
const submit = page.match(/async submitPicking\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.doesNotMatch(submit, /completedBoxes/)
|
||||
})
|
||||
|
||||
test('loading evidence is not requested before picking completion', () => {
|
||||
assert.match(page, /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/)
|
||||
assert.match(page, /if \(this\.loadingStageReady\) \{[\s\S]*?await this\.loadLoadingEvidence\(\)/)
|
||||
const loader = page.match(/async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(loader, /if \(!this\.loadingStageReady/)
|
||||
assert.match(loader, /await getEvidence\(/)
|
||||
@@ -80,6 +80,17 @@ test('incremental batch submits only current server-pending items and refreshes
|
||||
assert.match(page, /this\.pickingVersion = this\.remotePick\.pickingVersion \?\? this\.remotePick\.version/)
|
||||
})
|
||||
|
||||
test('completed detail overrides stale local state and an empty batch refreshes into evidence instead of submitting', () => {
|
||||
assert.match(page, /if \(item\.itemStatus === 'PICKED'\) serverStates\[item\.id\] = ITEM_STATUS\.PICKED/)
|
||||
assert.match(page, /if \(!this\.pickingCompleted\) this\.restoreRemoteDraft\(\)/)
|
||||
assert.match(page, /if \(this\.loadingStageReady\) \{[\s\S]*?this\.activeStage = 'evidence'[\s\S]*?await this\.loadLoadingEvidence\(\)/)
|
||||
const submit = page.match(/async submitPicking\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(submit, /if \(this\.pickingCompleted\)[\s\S]*?await this\.syncRemoteTask\(\)[\s\S]*?this\.activeStage = 'evidence'[\s\S]*?return/)
|
||||
const perform = page.match(/async performSubmit\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(perform, /if \(!actions\.length\) \{[\s\S]*?submit\.empty-refresh[\s\S]*?await this\.syncRemoteTask\(\)[\s\S]*?return/)
|
||||
assert.ok(perform.indexOf('if (!actions.length)') < perform.indexOf('submitContainerPickingBatch('))
|
||||
})
|
||||
|
||||
test('final completion preserves backend gate errors and refreshes after an atomic A0409', () => {
|
||||
const complete = page.match(/async completeLoadingTask\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(complete, /this\.isTaskConflict\(error\)[\s\S]*?await this\.refreshAfterTaskConflict\(error\.message \|\| '订舱、装柜关联或证据状态已变化,请刷新后重试'\)/)
|
||||
@@ -203,9 +214,10 @@ test('evidence save, void and loading completion ignore stale cabinet responses'
|
||||
|
||||
test('completed server tasks restore evidence stage without being claimed again', () => {
|
||||
assert.match(page, /const remoteTask = normalizePickingTask\(task\)/)
|
||||
assert.match(page, /remoteTask\.workflowStatus !== 'AWAITING_REVIEW' && this\.assignmentVersion === null/)
|
||||
assert.match(page, /if \(remoteTask\.workflowStatus === 'UNASSIGNED'\) \{[\s\S]*?claimContainerPickingTask/)
|
||||
assert.match(page, /else if \(!isPickingCompleteTask\(remoteTask\)\)/)
|
||||
assert.match(page, /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/)
|
||||
assert.match(page, /if \(this\.loadingStageReady\) \{[\s\S]*?this\.activeStage = 'evidence'[\s\S]*?await this\.loadLoadingEvidence\(\)/)
|
||||
})
|
||||
|
||||
test('picking mutations stay closed until the canonical task has been claimed', () => {
|
||||
|
||||
Reference in New Issue
Block a user