This commit is contained in:
2026-08-17 15:55:17 +08:00
parent bab911028d
commit db43e3049a
3 changed files with 30 additions and 9 deletions
+10 -6
View File
@@ -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 || completedBoxes[currentBoxIndex] || pickingCompleted || !summary.canSubmit" @tap="submitPicking">
<button class="submit-btn" aria-label="提交拣货完成" :disabled="submitting || pickingCompleted || !summary.canSubmit" @tap="submitPicking">
{{ submitting ? '正在提交…' : submitButtonText }}
</button>
</view>
@@ -421,7 +421,8 @@
return this.boxes.length - this.completedCount
},
pickingCompleted() {
if (this.remotePick) {
if (this.remoteTaskId || this.currentBox.taskId) {
if (!this.remotePick) return false
const hasServerPending = this.currentBox.items.some((item) => ['PENDING', 'PICKING'].includes(item.itemStatus))
return isPickingCompleteTask(this.remotePick) && !hasServerPending
}
@@ -469,10 +470,10 @@
return ''
},
submitButtonText() {
return this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted ? '该柜次已完成' : this.summary.buttonText
return this.pickingCompleted ? '该柜次已完成' : this.summary.buttonText
},
submitStatusHint() {
if (this.completedBoxes[this.currentBoxIndex] || 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}`
@@ -503,6 +504,9 @@
onLoad(options = {}) {
if (!requireSession()) return
if (options.taskId) {
// A local completion marker belongs to the previously opened task. The
// canonical server detail decides whether this task is complete.
this.completedBoxes = {}
this.boxes = [{
boxNo: String(options.currentContainerNo || options.cabinetNo || '同步中'), cabinetNo: options.cabinetNo ? String(options.cabinetNo) : null, currentContainerNo: options.currentContainerNo ? String(options.currentContainerNo) : null, orderNo: String(options.orderNo || options.inboundCode || ''),
taskId: String(options.taskId), loadingPlanId: null, containerBookingId: null,
@@ -1305,7 +1309,7 @@
} catch (error) {
if (!this.isEvidenceContextActive(contextId, identity)) return
if (this.isTaskConflict(error)) {
await this.refreshAfterTaskConflict(error.message || '装柜前置条件已变化,请刷新后重试')
await this.refreshAfterTaskConflict(error.message || '订舱、装柜关联或证据状态已变化,请刷新后重试')
return
}
this.remoteError = error.network ? '网络失败,装柜完成未提交' : (error.message || '装柜完成失败')
@@ -1434,7 +1438,7 @@
},
submitPicking() {
if (!this.requirePickingOperation()) return
if (!this.summary.canSubmit || this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted) return
if (!this.summary.canSubmit || this.pickingCompleted) return
if (this.summary.pending > 0) {
this.selectedIncompleteReason = ''
this.openOverlay('submitConfirm')
+7 -1
View File
@@ -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 \|\| completedBoxes\[currentBoxIndex\]/)
assert.match(page, /submitting \|\| pickingCompleted \|\| !summary\.canSubmit/)
assert.match(page, /completePicking\(/)
assert.match(page, /sourceType: \['camera'\]/)
assert.doesNotMatch(page, /sourceType: \['camera', 'album'\]/)
@@ -126,3 +126,9 @@ test('PDA cabinet picking adapter claims online and submits one atomic batch', (
assert.match(page, /availablePickQuantity/)
assert.match(page, /库存\/库位可用量不足/)
})
test('PDA final completion delegates automatic cabinet side effects to the task complete endpoint', () => {
const complete = source.match(/export function completeContainerPickingTask\(taskId, payload = \{\}\) \{[\s\S]*?\n\}/)?.[0] || ''
assert.match(complete, /\/first-leg\/pda-loading\/tasks\/\$\{encodeURIComponent\(taskId\)\}\/complete/)
assert.doesNotMatch(complete, /拖车|trucking/i)
})
+13 -2
View File
@@ -7,10 +7,20 @@ const page = await readFile(new URL('../pages/picking/picking.vue', import.meta.
test('picking and photo workspaces are independently selectable', () => {
assert.match(page, /v-if="activeStage === 'evidence' && loadingStageReady" class="loading-evidence-section"/)
assert.match(page, /isPickingCompleteTask\(task\)[\s\S]*?AWAITING_REVIEW[\s\S]*?COMPLETED/)
assert.match(page, /pickingCompleted\(\)[\s\S]*?if \(this\.remotePick\)[\s\S]*?hasServerPending[\s\S]*?isPickingCompleteTask\(this\.remotePick\) && !hasServerPending/)
assert.match(page, /pickingCompleted\(\)[\s\S]*?this\.remoteTaskId \|\| this\.currentBox\.taskId[\s\S]*?if \(!this\.remotePick\) return false[\s\S]*?hasServerPending[\s\S]*?isPickingCompleteTask\(this\.remotePick\) && !hasServerPending/)
assert.match(page, /请先处理并提交全部待拣明细/)
})
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.doesNotMatch(submit, /completedBoxes/)
})
test('loading evidence is not requested before picking completion', () => {
assert.match(page, /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/)
const loader = page.match(/async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
@@ -72,7 +82,8 @@ test('incremental batch submits only current server-pending items and refreshes
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 \|\| '装柜前置条件已变化,请刷新后重试'\)/)
assert.match(complete, /this\.isTaskConflict\(error\)[\s\S]*?await this\.refreshAfterTaskConflict\(error\.message \|\| '订舱、装柜关联或证据状态已变化,请刷新后重试'\)/)
assert.doesNotMatch(complete, /拖车|trucking/i)
const save = page.match(/async saveEvidence\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
assert.match(save, /this\.isTaskConflict\(error\)[\s\S]*?refreshAfterTaskConflict/)
const flush = page.match(/async flushPendingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''