3 Komitmen 8dc99d2816 ... 181a6d4211

Pembuat SHA1 Pesan Tanggal
  zh 181a6d4211 库位合并 3 bulan lalu
  zh e3671758c0 Merge remote-tracking branch 'origin/master' 3 bulan lalu
  zh 43c2758dbf 库位合并 3 bulan lalu
1 mengubah file dengan 166 tambahan dan 64 penghapusan
  1. 166 64
      src/views/robot/merge/index.vue

+ 166 - 64
src/views/robot/merge/index.vue

@@ -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;