|
|
@@ -18,7 +18,7 @@
|
|
|
<div class="info-table">
|
|
|
<div class="table-row">
|
|
|
<div class="cell label">源库位</div>
|
|
|
- <div class="cell value input-cell">
|
|
|
+ <div class="cell value input-cell source-location-cell">
|
|
|
<van-search
|
|
|
ref="sourceLocationInputRef"
|
|
|
v-model.lazy="sourceLocation"
|
|
|
@@ -58,7 +58,7 @@
|
|
|
<div class="cell label label-small">属性仓</div>
|
|
|
<div class="cell value value-large">{{ productInfo.warehouseType }}</div>
|
|
|
<div class="cell label label-small">批号</div>
|
|
|
- <div class="cell value value-small"></div>
|
|
|
+ <div class="cell value value-small">{{ productInfo.lotNumber }}</div>
|
|
|
</div>
|
|
|
<div class="table-row">
|
|
|
<div class="cell label">生产日期</div>
|
|
|
@@ -68,7 +68,7 @@
|
|
|
</div>
|
|
|
<div class="table-row">
|
|
|
<div class="cell label">目标库位</div>
|
|
|
- <div class="cell value input-cell input-wide">
|
|
|
+ <div class="cell value input-cell input-wide source-location-cell">
|
|
|
<van-search
|
|
|
ref="targetLocationInputRef"
|
|
|
v-model.lazy="productInfo.targetLocationNew"
|
|
|
@@ -84,7 +84,7 @@
|
|
|
<div class="cell value editable" @dblclick="editMoveQty">
|
|
|
<template v-if="isEditingMoveQty">
|
|
|
<van-field
|
|
|
- v-model="productInfo.moveQty"
|
|
|
+ v-model="productInfo.actualMoveQty"
|
|
|
type="number"
|
|
|
autofocus
|
|
|
@blur="confirmMoveQty"
|
|
|
@@ -92,8 +92,8 @@
|
|
|
/>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
- <span>{{ productInfo.moveQty }}</span>
|
|
|
- <span v-if="!productInfo.moveQty" class="placeholder">双击编辑</span>
|
|
|
+ <span>{{ productInfo.actualMoveQty }}</span>
|
|
|
+ <span class="placeholder">双击编辑</span>
|
|
|
</template>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -391,8 +391,7 @@ const onBarcodeEnter = async () => {
|
|
|
const params = {
|
|
|
warehouse,
|
|
|
barcode: scanBarcode.value,
|
|
|
- location: sourceLocation.value,
|
|
|
- locationRegexp: '^(?!STAGE_|SORTATION_).*$'
|
|
|
+ location: sourceLocation.value
|
|
|
}
|
|
|
|
|
|
const res = await getInventory(params)
|
|
|
@@ -409,10 +408,16 @@ const onBarcodeEnter = async () => {
|
|
|
productInfo.barcode = inventoryData.barcode || inventoryData.barcode2
|
|
|
productInfo.qualityStatus = inventoryData.lotAtt08
|
|
|
productInfo.warehouseType = inventoryData.lotAtt05
|
|
|
- productInfo.batchNo = inventoryData.lotNumber
|
|
|
+ productInfo.lotNumber = inventoryData.lotAtt04 || ''
|
|
|
productInfo.productionDate = inventoryData.lotAtt01
|
|
|
productInfo.expiryDate = inventoryData.lotAtt02
|
|
|
- productInfo.moveQty = inventoryData.quantityAvailable + inventoryData.quantityAvailable
|
|
|
+ // 可移库数量
|
|
|
+ const availableQty = inventoryData.quantityAvailable + inventoryData.quantityVirtual
|
|
|
+ productInfo.moveQty = availableQty
|
|
|
+ // 计算推荐移库数量:取可移库数量和任务推荐数量的最小值
|
|
|
+ const taskRecommendQty = getTaskRecommendQty(sourceLocation.value)
|
|
|
+ productInfo.recommendMoveQty = taskRecommendQty > 0 ? Math.min(availableQty, taskRecommendQty) : availableQty
|
|
|
+ productInfo.actualMoveQty = '' // 用户实际填写数量初始为空
|
|
|
|
|
|
scanSuccess()
|
|
|
showToast('商品信息获取成功')
|
|
|
@@ -452,6 +457,7 @@ const resetExceptBoxCode = () => {
|
|
|
scanBarcode.value = ''
|
|
|
selectedBox.value = null
|
|
|
resetProductInfo()
|
|
|
+ productInfo.targetLocationNew = '' // 清空目标库位
|
|
|
scanType.value = 2
|
|
|
focusSourceLocationInput()
|
|
|
}
|
|
|
@@ -466,10 +472,12 @@ const resetProductInfo = () => {
|
|
|
productInfo.barcode = ''
|
|
|
productInfo.qualityStatus = ''
|
|
|
productInfo.warehouseType = ''
|
|
|
- productInfo.batchNo = ''
|
|
|
+ productInfo.lotNumber = ''
|
|
|
productInfo.productionDate = ''
|
|
|
productInfo.expiryDate = ''
|
|
|
productInfo.moveQty = ''
|
|
|
+ productInfo.recommendMoveQty = ''
|
|
|
+ productInfo.actualMoveQty = ''
|
|
|
}
|
|
|
|
|
|
// 加载料箱数据
|
|
|
@@ -541,54 +549,61 @@ const mergeDataList = ref<BoxRelatedMergeDetailsVO[]>([])
|
|
|
const buildClickableLocationsMap = (boxDetailsList: BoxRelatedMergeDetailsVO[]) => {
|
|
|
const map = new Map<string, ClickableLocationInfo>()
|
|
|
|
|
|
+ // 先对所有移库任务去重,避免同一条任务被处理多次导致数量翻倍
|
|
|
+ const uniqueTaskMap = new Map<string, LocationMergeDetails>()
|
|
|
boxDetailsList.forEach(boxDetail => {
|
|
|
boxDetail.mergeDetails?.forEach((detail: LocationMergeDetails) => {
|
|
|
- const sourceLocation = detail.sourceLocation
|
|
|
- const targetLocation = detail.targetLocation
|
|
|
- const moveQty = detail.moveQty || 0
|
|
|
-
|
|
|
- // 处理源库位(推荐清空库位)
|
|
|
- if (sourceLocation) {
|
|
|
- if (!map.has(sourceLocation)) {
|
|
|
- map.set(sourceLocation, {
|
|
|
- recommendType: 'clear',
|
|
|
- relatedLocations: [],
|
|
|
- sku: detail.sku || ''
|
|
|
- })
|
|
|
- }
|
|
|
- const sourceInfo = map.get(sourceLocation)!
|
|
|
- // 添加对应的保留库位
|
|
|
- if (targetLocation) {
|
|
|
- const existingIdx = sourceInfo.relatedLocations.findIndex(r => r.location === targetLocation)
|
|
|
- if (existingIdx === -1) {
|
|
|
- sourceInfo.relatedLocations.push({ location: targetLocation, quantity: moveQty })
|
|
|
- } else {
|
|
|
- sourceInfo.relatedLocations[existingIdx].quantity += moveQty
|
|
|
- }
|
|
|
- }
|
|
|
+ // 使用 sourceLocation + targetLocation + sku 作为唯一键
|
|
|
+ const key = `${detail.sourceLocation || ''}_${detail.targetLocation || ''}_${detail.sku || ''}_${detail.lotNum || ''}`
|
|
|
+ if (!uniqueTaskMap.has(key)) {
|
|
|
+ uniqueTaskMap.set(key, detail)
|
|
|
}
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- // 处理目标库位(推荐保留库位)
|
|
|
+ // 遍历去重后的任务
|
|
|
+ uniqueTaskMap.forEach((detail) => {
|
|
|
+ const sourceLocation = detail.sourceLocation
|
|
|
+ const targetLocation = detail.targetLocation
|
|
|
+ const moveQty = detail.moveQty || 0
|
|
|
+
|
|
|
+ // 处理源库位(推荐清空库位)
|
|
|
+ if (sourceLocation) {
|
|
|
+ if (!map.has(sourceLocation)) {
|
|
|
+ map.set(sourceLocation, {
|
|
|
+ recommendType: 'clear',
|
|
|
+ relatedLocations: [],
|
|
|
+ sku: detail.sku || ''
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const sourceInfo = map.get(sourceLocation)!
|
|
|
+ // 添加对应的保留库位
|
|
|
if (targetLocation) {
|
|
|
- if (!map.has(targetLocation)) {
|
|
|
- map.set(targetLocation, {
|
|
|
- recommendType: 'keep',
|
|
|
- relatedLocations: [],
|
|
|
- sku: detail.sku || ''
|
|
|
- })
|
|
|
+ const existingIdx = sourceInfo.relatedLocations.findIndex(r => r.location === targetLocation)
|
|
|
+ if (existingIdx === -1) {
|
|
|
+ sourceInfo.relatedLocations.push({ location: targetLocation, quantity: moveQty })
|
|
|
}
|
|
|
- const targetInfo = map.get(targetLocation)!
|
|
|
- // 添加对应的清空库位
|
|
|
- if (sourceLocation) {
|
|
|
- const existingIdx = targetInfo.relatedLocations.findIndex(r => r.location === sourceLocation)
|
|
|
- if (existingIdx === -1) {
|
|
|
- targetInfo.relatedLocations.push({ location: sourceLocation, quantity: moveQty })
|
|
|
- } else {
|
|
|
- targetInfo.relatedLocations[existingIdx].quantity += moveQty
|
|
|
- }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理目标库位(推荐保留库位)
|
|
|
+ if (targetLocation) {
|
|
|
+ if (!map.has(targetLocation)) {
|
|
|
+ map.set(targetLocation, {
|
|
|
+ recommendType: 'keep',
|
|
|
+ relatedLocations: [],
|
|
|
+ sku: detail.sku || ''
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const targetInfo = map.get(targetLocation)!
|
|
|
+ // 添加对应的清空库位
|
|
|
+ if (sourceLocation) {
|
|
|
+ const existingIdx = targetInfo.relatedLocations.findIndex(r => r.location === sourceLocation)
|
|
|
+ if (existingIdx === -1) {
|
|
|
+ targetInfo.relatedLocations.push({ location: sourceLocation, quantity: moveQty })
|
|
|
}
|
|
|
}
|
|
|
- })
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
clickableLocationsMap.value = map
|
|
|
@@ -602,10 +617,12 @@ const productInfo = reactive({
|
|
|
barcode: '',
|
|
|
qualityStatus: '',
|
|
|
warehouseType: '',
|
|
|
- batchNo: '',
|
|
|
+ lotNumber: '', // 批号,取lotAtt04
|
|
|
productionDate: '',
|
|
|
expiryDate: '',
|
|
|
- moveQty: '',
|
|
|
+ moveQty: '', // 可移库数量(库存可用数量)
|
|
|
+ recommendMoveQty: '', // 推荐移库数量(取可移库数量和任务推荐数量的最小值)
|
|
|
+ actualMoveQty: '', // 用户实际填写的移库数量
|
|
|
targetLocationNew: ''
|
|
|
})
|
|
|
|
|
|
@@ -969,15 +986,22 @@ const selectSubLocation = (station: StationItem, sub: SubLocation) => {
|
|
|
}
|
|
|
|
|
|
// 确认选择库位
|
|
|
-const confirmSelectLocation = () => {
|
|
|
+const confirmSelectLocation = async () => {
|
|
|
if (currentLocation.recommendType === 'clear') {
|
|
|
// 推荐清空库位,填入源库位
|
|
|
sourceLocation.value = currentLocation.id
|
|
|
showLocationPopup.value = false
|
|
|
showToast(`已选择源库位: ${currentLocation.id}`)
|
|
|
- // 切换到扫描商品条码
|
|
|
- scanType.value = 3
|
|
|
- focusBarcodeInput()
|
|
|
+
|
|
|
+ // 如果有SKU,自动查询库存信息
|
|
|
+ if (currentLocation.sku) {
|
|
|
+ scanBarcode.value = currentLocation.sku
|
|
|
+ await queryInventoryBySku(currentLocation.sku, currentLocation.id)
|
|
|
+ } else {
|
|
|
+ // 切换到扫描商品条码
|
|
|
+ scanType.value = 3
|
|
|
+ focusBarcodeInput()
|
|
|
+ }
|
|
|
} else {
|
|
|
// 推荐保留库位,填入目标库位
|
|
|
productInfo.targetLocationNew = currentLocation.id
|
|
|
@@ -986,6 +1010,79 @@ const confirmSelectLocation = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 根据SKU查询库存信息
|
|
|
+const queryInventoryBySku = async (sku: string, location: string) => {
|
|
|
+ try {
|
|
|
+ showLoadingToast({ message: '查询中...', forbidClick: true })
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ warehouse,
|
|
|
+ barcode: sku,
|
|
|
+ location: location
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await getInventory(params)
|
|
|
+ closeToast()
|
|
|
+
|
|
|
+ if (res.data && res.data.length > 0) {
|
|
|
+ const inventoryData = res.data[0]
|
|
|
+ // 保存完整的库存数据
|
|
|
+ currentInventoryData.value = inventoryData
|
|
|
+ // 填充商品信息
|
|
|
+ productInfo.targetLocation = location
|
|
|
+ productInfo.stockQty = inventoryData.quantity
|
|
|
+ productInfo.productName = inventoryData.productName
|
|
|
+ productInfo.barcode = inventoryData.barcode || inventoryData.barcode2
|
|
|
+ productInfo.qualityStatus = inventoryData.lotAtt08
|
|
|
+ productInfo.warehouseType = inventoryData.lotAtt05
|
|
|
+ productInfo.lotNumber = inventoryData.lotAtt04 || ''
|
|
|
+ productInfo.productionDate = inventoryData.lotAtt01
|
|
|
+ productInfo.expiryDate = inventoryData.lotAtt02
|
|
|
+ // 可移库数量
|
|
|
+ const availableQty = inventoryData.quantityAvailable + inventoryData.quantityVirtual
|
|
|
+ productInfo.moveQty = availableQty
|
|
|
+ // 计算推荐移库数量:取可移库数量和任务推荐数量的最小值
|
|
|
+ const taskRecommendQty = getTaskRecommendQty(location)
|
|
|
+ productInfo.recommendMoveQty = taskRecommendQty > 0 ? Math.min(availableQty, taskRecommendQty) : availableQty
|
|
|
+ productInfo.actualMoveQty = '' // 用户实际填写数量初始为空
|
|
|
+
|
|
|
+ scanSuccess()
|
|
|
+ showToast('商品信息获取成功')
|
|
|
+ // 切换到扫描目标库位
|
|
|
+ scanType.value = 4
|
|
|
+ focusTargetLocationInput()
|
|
|
+ } else {
|
|
|
+ scanError()
|
|
|
+ showToast('未找到库存信息')
|
|
|
+ currentInventoryData.value = null
|
|
|
+ resetProductInfo()
|
|
|
+ // 切换到扫描商品条码
|
|
|
+ scanType.value = 3
|
|
|
+ focusBarcodeInput()
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ closeToast()
|
|
|
+ scanError()
|
|
|
+ showToast(error.message || '查询失败')
|
|
|
+ // 切换到扫描商品条码
|
|
|
+ scanType.value = 3
|
|
|
+ focusBarcodeInput()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取任务推荐移库数量(从mergeDetails中获取对应源库位的moveQty)
|
|
|
+const getTaskRecommendQty = (location: string): number => {
|
|
|
+ let totalQty = 0
|
|
|
+ mergeDataList.value.forEach(boxDetail => {
|
|
|
+ boxDetail.mergeDetails?.forEach((detail: LocationMergeDetails) => {
|
|
|
+ if (detail.sourceLocation === location && detail.moveQty) {
|
|
|
+ totalQty += detail.moveQty
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return totalQty
|
|
|
+}
|
|
|
+
|
|
|
// 呼唤机器人
|
|
|
const callRobot = async () => {
|
|
|
try {
|
|
|
@@ -1030,31 +1127,32 @@ const submitMove = () => {
|
|
|
showToast('请先选择目标库位')
|
|
|
return
|
|
|
}
|
|
|
- if (!productInfo.moveQty || Number(productInfo.moveQty) <= 0) {
|
|
|
+ if (!productInfo.actualMoveQty || Number(productInfo.actualMoveQty) <= 0) {
|
|
|
scanError()
|
|
|
showToast('请输入有效的移库数量')
|
|
|
return
|
|
|
}
|
|
|
- if (Number(productInfo.moveQty) > Number(productInfo.stockQty)) {
|
|
|
+ if (Number(productInfo.actualMoveQty) > Number(productInfo.moveQty)) {
|
|
|
scanError()
|
|
|
- showToast('移库数量不能大于库存数量')
|
|
|
+ showToast('移库数量不能大于可移库数量')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
showConfirmDialog({
|
|
|
title: '移库确认',
|
|
|
- message: `${productInfo.barcode}从"${sourceLocation.value}"移动至"${productInfo.targetLocationNew}"共:${productInfo.moveQty}件`
|
|
|
+ message: `${productInfo.barcode}从"${sourceLocation.value}"移动至"${productInfo.targetLocationNew}"共:${productInfo.actualMoveQty}件`
|
|
|
})
|
|
|
.then(() => {
|
|
|
- const { traceId, lotNum, lotNumber, ownerCode, owner, sku } = currentInventoryData.value || {}
|
|
|
+ const { traceId, lotNumber, ownerCode, owner, sku } = currentInventoryData.value || {}
|
|
|
+ console.log(currentInventoryData.value)
|
|
|
const data = {
|
|
|
fmLocation: sourceLocation.value,
|
|
|
fmContainer: traceId || boxCode.value,
|
|
|
owner: ownerCode || owner || '',
|
|
|
sku: sku || productInfo.barcode,
|
|
|
- lotNum: lotNum || lotNumber || productInfo.batchNo || '',
|
|
|
+ lotNum: lotNumber,
|
|
|
warehouse,
|
|
|
- quantity: Number(productInfo.moveQty),
|
|
|
+ quantity: Number(productInfo.actualMoveQty),
|
|
|
toLocation: productInfo.targetLocationNew
|
|
|
}
|
|
|
showLoadingToast({ message: '提交中...', forbidClick: true })
|
|
|
@@ -1173,6 +1271,10 @@ const submitMove = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ &.source-location-cell {
|
|
|
+ flex: 2;
|
|
|
+ }
|
|
|
+
|
|
|
&.editable {
|
|
|
cursor: pointer;
|
|
|
min-height: 20px;
|