merge release test changes and preserve local frontend updates
This commit is contained in:
@@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<view class="pallet-work">
|
||||
<text class="heading">换标后组托</text>
|
||||
<view class="pallet-heading">
|
||||
<text class="heading">托盘</text>
|
||||
<button class="refresh-button" :disabled="busy" @tap="loadSources">刷新</button>
|
||||
</view>
|
||||
<view v-if="currentBindings.length">
|
||||
<text class="hint">已绑定箱唛:选择后扫描新目标托盘可改绑(已排车或出库不可直接改绑)。</text>
|
||||
<button v-for="binding in currentBindings" :key="binding.mark" :disabled="busy" @tap="selectRebind(binding)">{{ binding.mark }} → {{ binding.pallet }} · 重新绑定</button>
|
||||
</view>
|
||||
<view v-if="rebindSelection"><text class="hint">正在改绑:{{ rebindSelection.mark }},当前托盘:{{ rebindSelection.pallet }}</text><button :disabled="busy" @tap="rebindSelection = null; targetCode = ''; target = null">取消改绑</button></view>
|
||||
<view v-for="(entry, index) in bindingHistory" :key="index" class="hint">已记录组托:{{ entry.cartonMarks.join('、') }} → {{ entry.targetPallets.join('、') }}(含历史轮次)</view>
|
||||
<text class="hint">按原入仓单拆托,再合入同新目的仓的托盘。下方为入仓单在各托盘的箱数,并非历史箱唛定位;合托后仍归属原入仓单。</text>
|
||||
<button :disabled="busy" @tap="loadSources">刷新来源托盘</button>
|
||||
<view v-for="row in sourceRows" :key="row.positionId" class="source-row" :class="{ selected: selectedSource?.positionId === row.positionId }" @tap="selectSource(row)">
|
||||
<text>{{ row.palletCode }} · {{ row.inboundCode }}</text>
|
||||
<text class="hint">{{ row.locationCode || '未维护库位' }} · 在库 {{ row.stockPackages }} 箱 · 可拆 {{ row.movablePackages }} 箱</text>
|
||||
<view v-for="row in operableSourceRows" :key="row.positionId" class="source-row" :class="{ selected: selectedSource?.positionId === row.positionId }" @tap="selectSource(row)">
|
||||
<text>托盘号:{{ row.palletCode }} · 入仓码:{{ row.inboundCode }}</text>
|
||||
<text class="hint">{{ isReturnBulk ? '件数' : '箱数' }}:{{ row.stockPackages }} · 可拆{{ isReturnBulk ? '件数' : '箱数' }}:{{ row.movablePackages }}</text>
|
||||
</view>
|
||||
<text v-if="!busy && !sourceRows.length" class="hint">当前没有可操作的来源托盘</text>
|
||||
<ScanTargetInput v-model="sourceCode" name="relabel-source" label="实际来源托盘" placeholder="扫描实际来源托盘" :active="active === 'source'" @activate="activate('source')" @submit="resolve('source')" />
|
||||
<button :disabled="busy || !sourceCode.trim()" @tap="resolve('source')">确认来源托盘</button>
|
||||
<text v-if="!busy && !operableSourceRows.length" class="hint">当前没有可操作的来源托盘</text>
|
||||
<ScanTargetInput v-if="!isReturnBulk" v-model="sourceCode" name="relabel-source" label="实际来源托盘" placeholder="扫描实际来源托盘" :active="active === 'source'" @activate="activate('source')" @submit="resolve('source')" />
|
||||
<button v-if="!isReturnBulk" :disabled="busy || !sourceCode.trim()" @tap="resolve('source')">确认来源托盘</button>
|
||||
<text v-if="source" class="hint">已确认:{{ source.palletCode }}</text>
|
||||
<view v-if="selectedSource">
|
||||
<text class="hint">本次拆出箱数(可分多个来源托盘,逐批提交)</text>
|
||||
<input v-model="quantity" type="number" :disabled="busy" placeholder="输入实际拆出箱数" @input="marks = []" />
|
||||
<text class="hint">选择本批新目的仓</text>
|
||||
<view class="line"><button v-for="dest in destinations" :key="dest" :disabled="busy" @tap="selectedDestination = dest; marks = []">{{ selectedDestination === dest ? '✓ ' : '' }}{{ dest }}</button></view>
|
||||
<button :disabled="busy" @tap="fillBatch">按箱数填入本批换标箱</button>
|
||||
<text class="hint">请核对下面勾选的箱与现场搬动一致;数量不符时不能提交。此处确认的是新目标托盘绑定,不推断旧箱唛位置。</text>
|
||||
<view v-for="item in eligibleItems" :key="item.cartonMark" class="line" @tap="toggleMark(item.cartonMark)"><text>{{ marks.includes(item.cartonMark) ? '☑' : '☐' }} {{ item.cartonMark }} → {{ item.fbaCartonMark || item.destinationFbaWarehouseCode }}</text></view>
|
||||
<text class="hint">本批确认 {{ marks.length }} 箱 / 拆出 {{ quantity || 0 }} 箱</text>
|
||||
<text class="hint">本次{{ isReturnBulk && wholeSource ? '整托换标数量' : `拆出${isReturnBulk ? '件数' : '箱数'}` }}(可分多个来源托盘,逐批提交)</text>
|
||||
<template v-if="isReturnBulk">
|
||||
<text class="return-pallet-summary">{{ selectedSource.palletCode }}:{{ wholeSource ? '整托换标' : '部分拆箱' }} {{ quantity || 0 }} 件 → {{ selectedDestination || '目的仓待维护' }}</text>
|
||||
<text v-if="wholeSource" class="hint">贴标数量等于本托盘全部 {{ selectedSource.stockPackages }} 件,无需填写拆箱数量;直接新增带目的仓的托盘标签并绑定。</text>
|
||||
<template v-else><input v-model="quantity" type="number" :disabled="busy" :placeholder="`请输入拆箱数量(应为 ${plannedPackages} 件)`" /><text class="hint">贴标数量少于本托盘库存,请按现场实际选择来源托盘并填写拆箱数量;本托盘应拆 {{ plannedPackages }} 件。</text></template>
|
||||
<text class="hint">退货换标无需扫描箱唛。</text>
|
||||
</template>
|
||||
<template v-else><input v-model="quantity" type="number" :disabled="busy" placeholder="输入实际拆出箱数" @input="marks = []" /><text class="hint">选择本批新目的仓</text><view class="line"><button v-for="dest in destinations" :key="dest" :disabled="busy" @tap="selectedDestination = dest; marks = []">{{ selectedDestination === dest ? '✓ ' : '' }}{{ dest }}</button></view><button :disabled="busy" @tap="fillBatch">按箱数填入本批换标箱</button><text class="hint">请核对下面勾选的箱与现场搬动一致;数量不符时不能提交。此处确认的是新目标托盘绑定,不推断旧箱唛位置。</text><view v-for="item in eligibleItems" :key="item.cartonMark" class="line" @tap="toggleMark(item.cartonMark)"><text>{{ marks.includes(item.cartonMark) ? '☑' : '☐' }} {{ item.cartonMark }} → {{ item.fbaCartonMark || item.destinationFbaWarehouseCode }}</text></view><text class="hint">本批确认 {{ marks.length }} 个箱唛 / 拆出 {{ quantity || 0 }} 箱</text></template>
|
||||
</view>
|
||||
<ScanTargetInput v-model="targetCode" name="relabel-target" label="目标托盘" placeholder="扫描同目的仓目标托盘或新托盘号" :active="active === 'target'" @activate="activate('target')" @submit="confirmTargetAndBind" />
|
||||
<view class="line"><button :disabled="busy || !targetCode.trim()" @tap="confirmTargetAndBind">确认目标托盘并绑定</button><button :disabled="busy || !!newPallet" @tap="createPallet">新增托盘号</button></view>
|
||||
<text class="hint">确认后直接提交本批拆托合托,只有提示“绑定成功”才表示已保存。</text>
|
||||
<text v-if="target" class="hint">已识别目标:{{ target.palletCode }},尚未绑定成功</text>
|
||||
<view v-if="newPallet" class="hint"><text>新托盘号:{{ newPallet.palletCode }},请贴好托盘标签后扫描确认。</text><button @tap="printPallet">查看 / 打印托盘标签</button></view>
|
||||
<text v-if="message" class="hint">{{ message }}</text><text v-if="error" class="error">{{ error }}</text>
|
||||
@@ -60,11 +60,25 @@ export default {
|
||||
return [...latest.values()]
|
||||
},
|
||||
bindingHistory() { return (this.task.history || []).filter(entry => entry.action === 'PALLET_BIND').flatMap(entry => { try { const data = JSON.parse(entry.detail); return Array.isArray(data.cartonMarks) && Array.isArray(data.targetPallets) ? [data] : [] } catch { return [] } }) },
|
||||
destinations() { return [...new Set(this.completedItems().map(item => item.destinationFbaWarehouseCode).filter(Boolean))] },
|
||||
eligibleItems() { return this.completedItems().filter(item => item.destinationFbaWarehouseCode === this.selectedDestination) },
|
||||
readyToBind() { return this.source && this.target && this.selectedSource && this.source.palletCode === this.sourceCode.trim().toUpperCase() && this.target.palletCode === this.targetCode.trim().toUpperCase() && this.source.palletCode === this.selectedSource.palletCode && this.source.palletCode !== this.target.palletCode && Number.isSafeInteger(Number(this.quantity)) && Number(this.quantity) > 0 && Number(this.quantity) <= this.selectedSource.movablePackages && this.marks.length === Number(this.quantity) }
|
||||
destinations() { return [...new Set((this.isReturnBulk ? this.sourceItems() : this.completedItems()).map(item => item.destinationFbaWarehouseCode).filter(Boolean))] },
|
||||
eligibleItems() { return (this.isReturnBulk ? this.sourceItems() : this.completedItems()).filter(item => item.destinationFbaWarehouseCode === this.selectedDestination) },
|
||||
isReturnBulk() { return this.task.operationType === 'RETURN_BULK_SKU_LABEL' },
|
||||
operableSourceRows() { return this.sourceRows.filter(row => Number(row.movablePackages) > 0) },
|
||||
plannedPackages() { return this.eligibleItems.reduce((sum, item) => sum + Number(item.expectedPackages || 0), 0) },
|
||||
wholeSource() { return this.isReturnBulk && this.selectedSource && this.plannedPackages === Number(this.selectedSource.stockPackages || 0) },
|
||||
readyToBind() { return this.source && this.target && this.selectedSource && this.samePallet(this.source, this.selectedSource) && !this.samePallet(this.source, this.target) && Number.isSafeInteger(Number(this.quantity)) && Number(this.quantity) > 0 && Number(this.quantity) <= this.selectedSource.movablePackages && this.bindingSelectionValid() }
|
||||
},
|
||||
methods: {
|
||||
samePallet(left, right) {
|
||||
if (!left || !right) return false
|
||||
const code = value => String(value?.palletCode || '').trim().toUpperCase()
|
||||
const leftCode = code(left); const rightCode = code(right)
|
||||
const sameCode = !!leftCode && leftCode === rightCode
|
||||
if (sameCode) return true
|
||||
if (left.palletId == null || right.palletId == null) return false
|
||||
if (String(left.palletId) === String(right.palletId)) return true
|
||||
return false
|
||||
},
|
||||
selectRebind(binding) {
|
||||
if (this.busy) return
|
||||
uni.showModal({ title: '重新绑定托盘', content: `箱唛 ${binding.mark} 当前在 ${binding.pallet}。请扫描新的目标托盘。`, showCancel: false })
|
||||
@@ -94,10 +108,33 @@ export default {
|
||||
})
|
||||
if (this.error) this.showBindingMessage('改绑失败', this.error)
|
||||
},
|
||||
completedItems() { return (this.task.items || []).filter(item => this.selectedSource?.cartonMarks.includes(item.cartonMark) && Number(item.relabelledPackages) >= 1) },
|
||||
sourceItems() { return (this.task.items || []).filter(item => this.selectedSource?.cartonMarks.includes(item.cartonMark)) },
|
||||
completedItems() { return this.sourceItems().filter(item => this.isReturnBulk ? Number(item.expectedPackages) > 0 && Number(item.relabelledPackages) >= Number(item.expectedPackages) : Number(item.relabelledPackages) >= 1) },
|
||||
async loadSources() { await this.run(async () => { this.sourceRows = await findRelabelSources(this.task.relabelNo); this.selectedSource = null; this.source = null; this.marks = []; this.quantity = '' }) },
|
||||
selectSource(row) { if (this.busy) return; this.rebindSelection = null; this.selectedSource = row; this.source = null; this.sourceCode = ''; this.quantity = ''; this.marks = []; this.selectedDestination = this.completedItems()[0]?.destinationFbaWarehouseCode || ''; this.activate('source') },
|
||||
fillBatch() { const count = Number(this.quantity); if (!Number.isSafeInteger(count) || count <= 0 || count > this.selectedSource.movablePackages || count > this.eligibleItems.length) { this.error = '拆出数量必须为正整数,且不超过来源托盘可拆箱数和已完成换标箱数'; return } this.marks = this.eligibleItems.slice(0, count).map(item => item.cartonMark); this.error = '' },
|
||||
async selectSource(row) {
|
||||
if (this.busy) return
|
||||
this.rebindSelection = null; this.selectedSource = row; this.source = null; this.sourceCode = ''; this.quantity = ''; this.marks = []
|
||||
this.selectedDestination = this.sourceItems()[0]?.destinationFbaWarehouseCode || ''
|
||||
if (this.isReturnBulk) {
|
||||
const items = this.eligibleItems
|
||||
this.marks = items.map(item => item.cartonMark)
|
||||
if (items.reduce((sum, item) => sum + Number(item.expectedPackages || 0), 0) === Number(row.stockPackages || 0)) this.quantity = String(row.stockPackages)
|
||||
await this.run(async () => { const value = await resolvePallet(row.palletCode); if (this.selectedSource?.positionId === row.positionId) { this.source = value; this.sourceCode = value.palletCode } })
|
||||
this.activate(this.quantity ? 'target' : 'source')
|
||||
return
|
||||
}
|
||||
this.activate('source')
|
||||
},
|
||||
bindingSelectionValid() {
|
||||
const count = Number(this.quantity)
|
||||
if (this.isReturnBulk) {
|
||||
const packages = this.eligibleItems.reduce((sum, item) => sum + Number(item.expectedPackages || 0), 0)
|
||||
return count === packages && this.completedItems().length === this.eligibleItems.length && this.marks.length === this.eligibleItems.length
|
||||
&& this.marks.every(mark => this.eligibleItems.some(item => item.cartonMark === mark))
|
||||
}
|
||||
return this.marks.length === count && this.marks.every(mark => this.eligibleItems.some(item => item.cartonMark === mark))
|
||||
},
|
||||
fillBatch() { const count = Number(this.quantity); const packages = this.eligibleItems.reduce((sum, item) => sum + Number(item.expectedPackages || 0), 0); const invalid = !Number.isSafeInteger(count) || count <= 0 || count > this.selectedSource.movablePackages || (this.isReturnBulk ? count !== packages : count > this.eligibleItems.length); if (invalid) { this.error = this.isReturnBulk ? '拆出件数必须等于本批已完成贴标件数,且不能超过来源托盘可拆数量' : '拆出数量必须为正整数,且不超过来源托盘可拆箱数和已完成换标箱数'; return } this.marks = (this.isReturnBulk ? this.eligibleItems : this.eligibleItems.slice(0, count)).map(item => item.cartonMark); this.error = '' },
|
||||
toggleMark(mark) { if (this.busy) return; this.marks = this.marks.includes(mark) ? this.marks.filter(value => value !== mark) : [...this.marks, mark] },
|
||||
activate(value) { this.active = value; this.$emit('activate') },
|
||||
handleScan(value) { if (this.busy) return; this[this.active + 'Code'] = value; return this.active === 'target' ? this.confirmTargetAndBind() : this.resolve(this.active) },
|
||||
@@ -108,9 +145,10 @@ export default {
|
||||
this.message = ''
|
||||
let reason = ''
|
||||
if (!this.selectedSource) reason = '请先选择实际拆出货物的来源托盘。'
|
||||
else if (!this.source || this.source.palletCode !== this.sourceCode.trim().toUpperCase() || this.source.palletCode !== this.selectedSource.palletCode) reason = '请先扫描并确认实际来源托盘;选择来源列表不等于扫描确认。'
|
||||
else if (!Number.isSafeInteger(Number(this.quantity)) || Number(this.quantity) <= 0 || Number(this.quantity) > this.selectedSource.movablePackages) reason = '请填写有效的拆出箱数,不能超过来源托盘可拆数量。'
|
||||
else if (!this.selectedDestination || this.marks.length !== Number(this.quantity) || this.marks.some(mark => !this.eligibleItems.some(item => item.cartonMark === mark))) reason = '请选择本批目的仓和对应箱唛,勾选箱数必须等于拆出箱数。'
|
||||
else if (!this.samePallet(this.source, this.selectedSource)) reason = this.isReturnBulk ? '来源托盘识别失败,请重新选择。' : '请先扫描并确认实际来源托盘;选择来源列表不等于扫描确认。'
|
||||
else if (this.isReturnBulk && this.completedItems().length !== this.eligibleItems.length) reason = '请先完成本托盘的 SKU 贴标登记,再绑定新托盘;整托无需填写拆箱数量。'
|
||||
else if (!Number.isSafeInteger(Number(this.quantity)) || Number(this.quantity) <= 0 || Number(this.quantity) > this.selectedSource.movablePackages) reason = this.isReturnBulk ? '本托盘可操作件数不足,请刷新来源托盘。' : '请填写有效的拆出箱数,不能超过来源托盘可拆数量。'
|
||||
else if (!this.selectedDestination || !this.bindingSelectionValid()) reason = this.isReturnBulk ? `本托盘拆箱数量必须等于贴标预报分配数量 ${this.plannedPackages} 件。` : '请选择本批目的仓和对应箱唛,勾选箱数必须等于拆出箱数。'
|
||||
else if (!this.targetCode.trim()) reason = '请扫描或输入目标托盘号。'
|
||||
if (reason) { this.error = reason; this.showBindingMessage('尚未绑定', reason); return }
|
||||
await this.resolve('target')
|
||||
@@ -120,8 +158,8 @@ export default {
|
||||
},
|
||||
destination(mark) { return this.task.items.find(item => item.cartonMark === mark)?.destinationFbaWarehouseCode || '目的仓待维护' },
|
||||
async run(work) { if (this.busy) return; this.busy = true; this.$emit('busy', true); this.error = ''; try { await work() } catch (error) { this.error = error?.message || '托盘作业失败,请重新核对'; if (['409', 'A0409'].includes(String(error?.code))) { this.source = null; this.target = null; this.selectedSource = null; this.marks = []; this.error += ';请刷新来源托盘并重新扫码'; } } finally { this.busy = false; this.$emit('busy', false) } },
|
||||
async resolve(kind) { const code = this[kind + 'Code'].trim(); if (!code) return; this[kind] = null; await this.run(async () => { const value = await resolvePallet(code); if (kind === 'source' && value.palletCode !== this.selectedSource?.palletCode) throw new Error('请扫描所选原入仓单的实际来源托盘'); if (this[kind + 'Code'].trim() === code) this[kind] = value }) },
|
||||
async createPallet() { const payload = { relabelNo: this.task.relabelNo }; const key = warehouseIdempotencyKey('relabel', 'new-pallet', payload); await this.run(async () => { this.newPallet = await createRelabelPallet({ ...payload, idempotencyKey: key.value }); this.target = null; this.targetCode = ''; this.activate('target') }) },
|
||||
async resolve(kind) { const code = this[kind + 'Code'].trim(); if (!code) return; this[kind] = null; await this.run(async () => { const value = await resolvePallet(code); if (kind === 'source' && !this.samePallet(value, this.selectedSource)) throw new Error('请扫描所选原入仓单的实际来源托盘'); if (this[kind + 'Code'].trim() === code) this[kind] = value }) },
|
||||
async createPallet() { const payload = { relabelNo: this.task.relabelNo }; const key = warehouseIdempotencyKey('relabel', 'new-pallet', payload); await this.run(async () => { this.newPallet = await createRelabelPallet({ ...payload, idempotencyKey: key.value }); this.target = null; this.targetCode = this.newPallet.palletCode || ''; this.activate('target') }) },
|
||||
async bind() {
|
||||
if (!this.readyToBind) { this.error = '请核对来源托盘、拆出箱数、本批箱唛和目标托盘'; return }
|
||||
const expectation = value => ({ palletCode: value.palletCode, expectedVersion: value.palletVersion })
|
||||
@@ -145,11 +183,14 @@ export default {
|
||||
</script>
|
||||
<style scoped>
|
||||
.pallet-work{margin-top:18px;padding:14px;border:1px solid #d7e1ef;border-radius:10px}
|
||||
.pallet-heading{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
||||
.heading{display:block;font-size:18px;font-weight:700}
|
||||
.refresh-button{flex:none;margin:0;padding:6px 14px;font-size:13px;line-height:1.5}
|
||||
.hint{display:block;font-size:13px;color:#52647c;margin:10px 0;white-space:normal}
|
||||
.source-row{padding:12px;margin:10px 0;border:1px solid #d7e1ef;border-radius:8px}
|
||||
.source-row.selected{border-color:#2674d9;background:#eff6ff}
|
||||
.line{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:8px 0}.line text{flex:1;overflow-wrap:anywhere}
|
||||
input{padding:12px;background:#f3f6fa;border-radius:6px}button{font-size:13px;margin:8px 0}
|
||||
.error{display:block;color:#bc3030;font-size:13px}
|
||||
.return-pallet-summary{display:block;margin:10px 0;padding:12px;border-radius:8px;background:#eff6ff;color:#1f5fca;font-weight:700;overflow-wrap:anywhere}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user