fix(outbound): submit selected carton marks for partial PDA picking
This commit is contained in:
@@ -112,6 +112,15 @@ export function pickOutboundTask(taskNo, request) {
|
|||||||
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/pick`, request)
|
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/pick`, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function outboundPickPayload(item, position, packages) {
|
||||||
|
return {
|
||||||
|
outboundPalletItemId: item.outboundPalletItemId,
|
||||||
|
inventoryPositionId: position.inventoryPositionId,
|
||||||
|
packages: Number(packages),
|
||||||
|
cartonMarks: position.cartonMark ? [position.cartonMark] : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function completeOutboundTask(taskNo, request) {
|
export function completeOutboundTask(taskNo, request) {
|
||||||
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/complete`, request)
|
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/complete`, request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<view class="fulfillment-card summary-card"><view class="task-head"><text class="business-code">{{ current.taskNo || '—' }}</text><text class="status-badge">{{ statusLabel(current.status) }}</text></view><text class="task-meta">出库单 {{ current.outboundNo || '—' }} · {{ taskType(current) }}</text><view class="metrics"><view><text class="metric-value">{{ plannedPackages }}</text><text>计划箱数</text></view><view><text class="metric-value success">{{ pickedPackages }}</text><text>已拣箱数</text></view><view><text class="metric-value warning">{{ remainingPackages }}</text><text>待拣箱数</text></view></view><button class="link-button" :disabled="busy" @tap="closeTask">返回任务列表</button></view>
|
<view class="fulfillment-card summary-card"><view class="task-head"><text class="business-code">{{ current.taskNo || '—' }}</text><text class="status-badge">{{ statusLabel(current.status) }}</text></view><text class="task-meta">出库单 {{ current.outboundNo || '—' }} · {{ taskType(current) }}</text><view class="metrics"><view><text class="metric-value">{{ plannedPackages }}</text><text>计划箱数</text></view><view><text class="metric-value success">{{ pickedPackages }}</text><text>已拣箱数</text></view><view><text class="metric-value warning">{{ remainingPackages }}</text><text>待拣箱数</text></view></view><button class="link-button" :disabled="busy" @tap="closeTask">返回任务列表</button></view>
|
||||||
|
|
||||||
<view v-if="current.items.length" class="item-list">
|
<view v-if="current.items.length" class="item-list">
|
||||||
<view v-for="item in current.items" :key="item.palletNo || item.outboundPalletItemId" class="fulfillment-card item-card">
|
<view v-for="item in current.items" :key="item.outboundPalletItemId" class="fulfillment-card item-card">
|
||||||
<view class="task-head"><text class="business-code">{{ item.palletNo || '散件出库' }}</text><text>{{ item.pickedPackages }}/{{ item.plannedPackages }} 箱</text></view>
|
<view class="task-head"><text class="business-code">{{ item.palletNo || '散件出库' }}</text><text>{{ item.pickedPackages }}/{{ item.plannedPackages }} 箱</text></view>
|
||||||
<view v-for="position in item.positions" :key="businessLineKey(position)" class="outbound-line" :class="{ selected: selectedPosition === position }" @tap="selectPosition(item, position)">
|
<view v-for="position in item.positions" :key="businessLineKey(position)" class="outbound-line" :class="{ selected: selectedPosition === position }" @tap="selectPosition(item, position)">
|
||||||
<text class="line-code">{{ lineBusinessCode(position) }}</text><text class="line-meta">{{ position.inboundCode || '无入仓码' }} · 已拣 {{ position.pickedPackages }} 箱 · 可拣 {{ position.availablePackages }} 箱</text>
|
<text class="line-code">{{ lineBusinessCode(position) }}</text><text class="line-meta">{{ position.inboundCode || '无入仓码' }} · 已拣 {{ position.pickedPackages }} 箱 · 可拣 {{ position.availablePackages }} 箱</text>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import PortalHeader from '@/components/PortalHeader.vue'
|
import PortalHeader from '@/components/PortalHeader.vue'
|
||||||
import ScanTargetInput from '@/components/ScanTargetInput.vue'
|
import ScanTargetInput from '@/components/ScanTargetInput.vue'
|
||||||
import { completeOutboundTask, getOutboundTask, listOutboundTasks, pickOutboundTask, resolveOutboundTask } from '@/api/fulfillment.js'
|
import { completeOutboundTask, getOutboundTask, listOutboundTasks, outboundPickPayload, pickOutboundTask, resolveOutboundTask } from '@/api/fulfillment.js'
|
||||||
import { fulfillment, fulfillmentIdempotencyKey, releaseFulfillmentKey, restoreFulfillmentDraft, updateFulfillmentDraft } from '@/stores/fulfillment.js'
|
import { fulfillment, fulfillmentIdempotencyKey, releaseFulfillmentKey, restoreFulfillmentDraft, updateFulfillmentDraft } from '@/stores/fulfillment.js'
|
||||||
import { requireSession, session } from '@/stores/session.js'
|
import { requireSession, session } from '@/stores/session.js'
|
||||||
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
|
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
|
||||||
@@ -105,8 +105,8 @@
|
|||||||
selectPosition(item, position) { if (!position || position.availablePackages <= 0) return; this.selectedItem = item; this.selectedPosition = position; updateFulfillmentDraft('outbound', { scanCode: this.lineBusinessCode(position), packages: Math.max(1, Math.min(item.plannedPackages - item.pickedPackages, position.availablePackages)) }) },
|
selectPosition(item, position) { if (!position || position.availablePackages <= 0) return; this.selectedItem = item; this.selectedPosition = position; updateFulfillmentDraft('outbound', { scanCode: this.lineBusinessCode(position), packages: Math.max(1, Math.min(item.plannedPackages - item.pickedPackages, position.availablePackages)) }) },
|
||||||
async submitPick() {
|
async submitPick() {
|
||||||
if (!this.canPick) return
|
if (!this.canPick) return
|
||||||
const businessCode = this.lineBusinessCode(this.selectedPosition); const payload = { taskNo: this.current.taskNo, businessCode, packages: Number(this.packages) }; const key = fulfillmentIdempotencyKey('outbound', 'pick', payload)
|
const payload = outboundPickPayload(this.selectedItem, this.selectedPosition, this.packages); const key = fulfillmentIdempotencyKey('outbound', 'pick', { taskNo: this.current.taskNo, ...payload })
|
||||||
await this.run('提交出库拣货', async () => { await pickOutboundTask(this.current.taskNo, { outboundPalletItemId: this.selectedItem.outboundPalletItemId, inventoryPositionId: this.selectedPosition.inventoryPositionId, packages: Number(this.packages), idempotencyKey: key.value }); this.current = await getOutboundTask(this.current.taskNo); releaseFulfillmentKey('outbound', key.signature); this.selectedItem = null; this.selectedPosition = null; updateFulfillmentDraft('outbound', { scanCode: '', packages: 1 }); this.successMessage = '本次出库拣货已记录'; playSuccessSound() })
|
await this.run('提交出库拣货', async () => { await pickOutboundTask(this.current.taskNo, { ...payload, idempotencyKey: key.value }); this.current = await getOutboundTask(this.current.taskNo); releaseFulfillmentKey('outbound', key.signature); this.selectedItem = null; this.selectedPosition = null; updateFulfillmentDraft('outbound', { scanCode: '', packages: 1 }); this.successMessage = '本次出库拣货已记录'; playSuccessSound() })
|
||||||
},
|
},
|
||||||
async completeTask() {
|
async completeTask() {
|
||||||
if (!this.canComplete) return
|
if (!this.canComplete) return
|
||||||
|
|||||||
@@ -6,13 +6,24 @@ import { fulfillment, fulfillmentIdempotencyKey, releaseFulfillmentKey, resetFul
|
|||||||
import {
|
import {
|
||||||
completeDispatchPicking, completeOutboundTask, departDispatch, getDispatchTask, getOutboundTask,
|
completeDispatchPicking, completeOutboundTask, departDispatch, getDispatchTask, getOutboundTask,
|
||||||
listDispatchTasks, listOutboundTasks, normalizeDispatchTask, normalizeOutboundTask,
|
listDispatchTasks, listOutboundTasks, normalizeDispatchTask, normalizeOutboundTask,
|
||||||
pickDispatchItem, pickOutboundTask, resolveOutboundTask
|
pickDispatchItem, pickOutboundTask, resolveOutboundTask, outboundPickPayload
|
||||||
} from '../src/api/fulfillment.js'
|
} from '../src/api/fulfillment.js'
|
||||||
import { session } from '../src/stores/session.js'
|
import { session } from '../src/stores/session.js'
|
||||||
import { toDispatchError } from '../src/utils/dispatch-error.js'
|
import { toDispatchError } from '../src/utils/dispatch-error.js'
|
||||||
|
|
||||||
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
||||||
|
|
||||||
|
test('outbound partial pick submits the selected carton and distinguishes shared-pallet items', async () => {
|
||||||
|
const first = outboundPickPayload({ outboundPalletItemId: '11' }, { inventoryPositionId: '21', cartonMark: 'BOX-A' }, '1')
|
||||||
|
assert.deepEqual(first, { outboundPalletItemId: '11', inventoryPositionId: '21', packages: 1, cartonMarks: ['BOX-A'] })
|
||||||
|
assert.notDeepEqual(first, outboundPickPayload({ outboundPalletItemId: '12' }, { inventoryPositionId: '22', cartonMark: 'BOX-B' }, 1))
|
||||||
|
assert.deepEqual(outboundPickPayload({ outboundPalletItemId: '13' }, { inventoryPositionId: '23' }, 2).cartonMarks, [])
|
||||||
|
const page = await read('../src/pages/outbound/outbound.vue')
|
||||||
|
assert.match(page, /:key="item.outboundPalletItemId"/)
|
||||||
|
assert.match(page, /outboundPickPayload\(this.selectedItem, this.selectedPosition, this.packages\)/)
|
||||||
|
assert.match(page, /pickOutboundTask\(this.current.taskNo, \{ \.\.\.payload, idempotencyKey: key.value \}\)/)
|
||||||
|
})
|
||||||
|
|
||||||
function storageMock() {
|
function storageMock() {
|
||||||
const values = new Map()
|
const values = new Map()
|
||||||
globalThis.uni = {
|
globalThis.uni = {
|
||||||
|
|||||||
Reference in New Issue
Block a user