Files
admin-web/src/modules/overseas-warehouse/components/RelabelInventoryItemsEditor.vue
T
2026-10-20 15:48:27 +08:00

71 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import InlineHelp from '@/shared/components/InlineHelp.vue'
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 }} 箱)<InlineHelp title="箱任务填写说明"><p>普通入库托盘仅为冻结记账位置;每箱填写一个我方箱唛,SKU 数量按件填写,实际来源托盘由 PDA 现场确认。</p></InlineHelp></h3>
<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>