71 lines
6.8 KiB
Vue
71 lines
6.8 KiB
Vue
<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>
|