This commit is contained in:
2026-10-20 14:00:49 +08:00
parent 3879a47673
commit 5679b156ca
28 changed files with 803 additions and 515 deletions
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { listRelabelInventoryCandidates, type CreateRelabelItemPayload, type RelabelInventoryCandidate } from '../api/relabelOrders'
const props = defineProps<{ customerId: string; warehouseId: string; operationType: CreateRelabelItemPayload['operationType']; shipmentNo: string; destination: string; requirement: string }>()
const emit = defineEmits<{ 'update:items': [CreateRelabelItemPayload[]] }>()
const candidates = ref<RelabelInventoryCandidate[]>([]), selected = ref(''), loading = ref(false), error = ref('')
const tasks = ref<Array<CreateRelabelItemPayload & { sourceLabel: string; sourceKey: string; available: number }>>([])
let generation = 0
type SourceOption = { key: string; label: string; candidate: RelabelInventoryCandidate; position: RelabelInventoryCandidate['positions'][number] | undefined; available: number }
const options = computed(() => candidates.value.flatMap<SourceOption>(candidate => candidate.returnCartonId
? Number(candidate.cartonAvailablePackages) > 0 ? [{ key: `return:${candidate.returnCartonId}`, label: `${candidate.inboundOrderNo} · ${candidate.cartonMark}`, candidate, position: undefined, available: 1 }] : []
: candidate.positions.map(position => ({ key: `inventory:${position.positionId}`, label: `${candidate.inboundOrderNo} · ${candidate.skuCode} · 记账托盘 ${position.palletNo || '未提供'} · 可用 ${position.availablePackages} 箱`, candidate, position, available: Number(position.availablePackages) }))))
/** 客户或仓库切换立即清空旧来源,异步返回只接收最后一次请求。 */
async function load() {
const current = ++generation
candidates.value = []; selected.value = ''; tasks.value = []; error.value = ''
if (!props.customerId || !props.warehouseId) { loading.value = false; return }
loading.value = true
try { const result = await listRelabelInventoryCandidates(props.customerId, props.warehouseId); if (current === generation) candidates.value = result }
catch { if (current === generation) error.value = '库存来源加载失败,请重试' }
finally { if (current === generation) loading.value = false }
}
watch(() => [props.customerId, props.warehouseId], load, { immediate: true })
watch(() => props.operationType, () => { tasks.value = [] })
function add() {
const option = options.value.find(item => item.key === selected.value)
if (!option) return
if (tasks.value.filter(item => item.sourceKey === option.key).length >= option.available) { error.value = '已达到该来源可用箱数'; return }
const source = option.candidate
tasks.value.push({ sourceLabel: option.label, sourceKey: option.key, available: option.available,
sourceInventoryBalanceId: source.inventoryBalanceId || undefined, sourceInventoryPositionId: option.position?.positionId,
sourceInboundOrderId: source.inboundOrderId || undefined, returnCartonId: source.returnCartonId || undefined,
skuCode: source.skuCode, productNameZh: source.productNameZh || source.skuCode,
totalPackageCount: 1, notifiedPackageCount: 1, cartonMark: source.cartonMark || '', fbaCartonMark: '',
operationType: props.operationType, shipmentNo: props.shipmentNo, newSkuCode: '',
skuItems: props.operationType === 'CARTON_LABEL' ? [] : (source.cartonSkuItems?.length ? source.cartonSkuItems : [{ skuCode: source.skuCode, quantity: 1 }]).map(item => ({ skuCode: item.skuCode, newSkuCode: '', quantity: item.quantity })) })
error.value = ''
}
/** 每箱任务数量固定为一,SKU子项单独按件提交,额外展示字段不进入正式请求。 */
watch(() => [tasks.value, props.shipmentNo, props.destination, props.requirement], () => {
emit('update:items', tasks.value.map(({ sourceLabel: _label, sourceKey: _key, available: _available, ...item }) => ({ ...item,
shipmentNo: props.shipmentNo.trim(), labelRequirement: props.requirement.trim() || undefined,
destinationFbaWarehouseCode: props.operationType === 'SKU_LABEL' ? undefined : props.destination.trim(),
newSkuCode: item.skuItems.map(sku => sku.newSkuCode).join(','),
skuItems: props.operationType === 'CARTON_LABEL' ? [] : item.skuItems })))
}, { deep: true, immediate: true })
</script>
<template>
<section class="density-dialog-section" data-tone="source" aria-label="库存与箱任务">
<h3 class="density-dialog-section__title">库存与箱任务({{ tasks.length }} 箱)</h3>
<p class="density-dialog-note">普通入库托盘仅为冻结记账位置;每箱填写一个我方箱唛,SKU 数量按件填写,实际来源托盘由 PDA 现场确认。</p>
<el-select v-model="selected" filterable :loading="loading" :disabled="!customerId || !warehouseId" placeholder="请选择正式库存来源">
<el-option v-for="option in options" :key="option.key" :label="option.label" :value="option.key" />
</el-select>
<el-button :disabled="!selected || loading" @click="add">添加箱任务</el-button>
<p v-if="error" role="alert">{{ error }} <el-button link @click="load">重试加载</el-button></p>
<section v-for="(item, index) in tasks" :key="index" class="density-dialog-section">
<p>{{ index + 1 }}. {{ item.sourceLabel }} <el-button link type="danger" @click="tasks.splice(index, 1)">移除</el-button></p>
<el-form-item label="我方箱唛" required><el-input v-model="item.cartonMark" :readonly="Boolean(item.returnCartonId)" maxlength="255" placeholder="请输入来源箱的我方箱唛" /></el-form-item>
<el-form-item label="中文品名" required><el-input v-model="item.productNameZh" maxlength="255" placeholder="请输入中文品名" /></el-form-item>
<el-form-item v-if="operationType !== 'SKU_LABEL'" label="新 FBA 箱唛" required><el-input v-model="item.fbaCartonMark" maxlength="255" placeholder="请输入新 FBA 箱唛" /></el-form-item>
<table v-if="operationType !== 'CARTON_LABEL'" class="density-table density-table--editable"><thead><tr><th>旧 SKU</th><th>新 SKU</th><th>件数</th><th>操作</th></tr></thead><tbody>
<tr v-for="(sku, skuIndex) in item.skuItems" :key="skuIndex"><td><el-input v-model="sku.skuCode" aria-label="旧 SKU" placeholder="请输入旧 SKU" /></td><td><el-input v-model="sku.newSkuCode" aria-label="新 SKU" placeholder="请输入新 SKU" /></td><td><el-input-number v-model="sku.quantity" :min="1" :precision="0" aria-label="SKU 件数" placeholder="请输入件数" /></td><td><el-button link @click="item.skuItems.splice(skuIndex, 1)">移除 SKU</el-button></td></tr>
</tbody></table>
<el-button v-if="operationType !== 'CARTON_LABEL'" @click="item.skuItems.push({ skuCode: '', newSkuCode: '', quantity: 1 })">添加 SKU</el-button>
</section>
<p v-if="!loading && !error && customerId && warehouseId && !options.length" role="status">当前客户及仓库没有可用库存来源。</p>
</section>
</template>