ソースを参照

宝时快上-新旧版本切换系统控制

zhaohuanhuan 2 ヶ月 前
コミット
8934a977f9

+ 5 - 4
src/views/inbound/putaway/components/LocationList.vue

@@ -11,9 +11,9 @@
       </thead>
       <tbody>
       <tr v-for="(item, index) in props.locationList" :key="index" v-if="props.locationList.length>0">
-        <td>{{ item.locationId }}</td>
-        <td>{{ (locationType[item.locationUsage] || item.locationUsage) }}</td>
-        <td>{{ item.qty || 0}}</td>
+        <td>{{ legacy ? item.location : item.locationId }}</td>
+        <td>{{ legacy ? (locationType[item.type] || item.type) : (locationType[item.locationUsage] || item.locationUsage) }}</td>
+        <td>{{ legacy ? (item.quantity || 0) : (item.qty || 0) }}</td>
         <td>{{ item.max || '无' }}</td>
       </tr>
       <tr v-else>
@@ -34,7 +34,7 @@ const locationType = {
   'AP': '补充拣货位',
   'CS': '箱拣货库位',
   'HP': '快拣补货位',
-  'PC': '箱/件合并拣货位',
+  'PC': '箱/件合并拣货位',
   'PT': '播种库位',
   'RS': '存储库位',
   'SS': '理货站',
@@ -43,6 +43,7 @@ const locationType = {
 }
 const props = defineProps({
   locationList: Array,
+  legacy: { type: Boolean, default: false },
 })
 </script>
 <style scoped lang="sass">

+ 125 - 111
src/views/inbound/putaway/task/index.vue

@@ -75,7 +75,7 @@
           >
             <template #right-icon>
               <van-button
-                v-if="barcodeActiveList.length > 0"
+                v-if="systemForcePublishEnabled && barcodeActiveList.length > 0"
                 type="primary"
                 size="mini"
                 plain
@@ -112,7 +112,7 @@
             </template>
           </van-search>
         </div>
-        <div class="barcode-input location-type-row">
+        <div class="barcode-input location-type-row" v-if="systemForcePublishEnabled" >
           <van-cell title="上架区域:" :value="locationTypeLabel"  is-link @click="locationTypeSheetShow = true" />
         </div>
       </div>
@@ -136,7 +136,7 @@
         <div class="btn" type="primary" size="large" round style="height: 36px" @click="onConfirm">上架</div>
       </div>
       <div>
-        <location-list :locationList="locationList" />
+        <location-list :locationList="locationList" :legacy="!systemForcePublishEnabled" />
       </div>
     </div>
   </div>
@@ -167,13 +167,6 @@
       <van-cell title="挂装区" @click="onSelectLocationType('HANGING')" />
     </van-cell-group>
   </van-action-sheet>
-  <!--  推荐库位列表-->
-  <van-action-sheet v-model:show="locationTrueFalseBy" cancel-text="取消" description="推荐库位列表"
-                    close-on-click-action>
-    <div style="max-height: 60vh;overflow: auto;">
-      <location-list :locationList="locationList" />
-    </div>
-  </van-action-sheet>
   <!--  组合商品上架数量-->
   <barcode-combine ref="barcodeCombineRef" @setCombine="setPutawayCombine" @cancel="onCombineCancel" :matched-sku="combineMatchedSku" />
 </template>
@@ -193,7 +186,8 @@ import { getCurrentTime } from '@/utils/date'
 import { getWaitPutawayListNew, setPutawayNew } from '@/api/putaway/index'
 import { getListCombineSku } from '@/api/picking'
 import { barcodeToUpperCase } from '@/utils/dataType.js'
-import { getRecommendedLocationNew } from '@/api/haikang/index'
+import { getRecommendedLocation, getRecommendedLocationNew } from '@/api/haikang/index'
+import { findSysParamByKey } from '@/api/basic/index'
 import { getOwnerList } from '@/hooks/basic/index'
 
 const router = useRouter()
@@ -204,12 +198,75 @@ try {
 } catch (error) {
   router.push('/login')
 }
-/** 精准推荐是否开启 */
-const forcePublishEnabled = ref(true)
+// 精准推荐内是否禁止强制上架(false = 强制上架,库位必须在推荐列表内)
+const forbidForcePutaway = ref(true)
+// 系统参数:是否开启精准推荐(开=getRecommendedLocationNew,关=getRecommendedLocation)
+const systemForcePublishEnabled = ref(false)
+async function loadForcePublishParam() {
+  try {
+    const res = await findSysParamByKey({ paramKey: 'FORCE_PUBLISH_ENABLED' })
+    systemForcePublishEnabled.value = res?.data === 'true'
+  } catch (e) {
+    console.error(e)
+  }
+}
+
+// --- 库位校验(精准推荐 + 强制上架) ---
+// 扫描步骤:2=商品条码 3=库位 4=上架数量
+const SCAN_TYPE = { BARCODE: 2, LOCATION: 3, QUANTITY: 4 }
+
+// 从推荐库位项取编码(精准推荐接口字段为 locationId)
+const getRecommendedLocationCode = (item) => barcodeToUpperCase(item?.locationId || '')
+
+// 是否需要校验扫描库位在推荐列表内(仅精准推荐开启且未禁止强制上架时校验)
+const shouldForceLocationMatch = () => {
+  if (!systemForcePublishEnabled.value) return false
+  if (!barcodeActiveList.value.length || !locationList.value.length) return false
+  const { lotAtt02 } = barcodeActiveList.value[0]
+  if (lotAtt02) return false // 有失效日期时不强制匹配推荐库位
+  return !forbidForcePutaway.value
+}
+
+// 判断扫描库位是否在推荐列表内
+const isLocationInRecommendedList = (locationCode) => {
+  if (!shouldForceLocationMatch()) return true
+  const scanned = barcodeToUpperCase(locationCode)
+  return locationList.value.some(item => getRecommendedLocationCode(item) === scanned)
+}
+
+// 从已排除库位记录中提取 locationId 列表(换一换时使用)
+const getExcludedLocationIds = (listByLot) => {
+  if (!listByLot.length) return undefined
+  return [...new Set(listByLot.map(loc => (typeof loc === 'string' ? loc : loc?.locationId)).filter(Boolean))]
+}
+
+// 单一批次命中:获取推荐库位并进入库位扫描
+const _activateSingleBatch = (batchItem, { showCombineDialog = false } = {}) => {
+  barcodeActiveList.value = batchItem
+  if (showCombineDialog) {
+    _showPutawayCombineDialog(batchItem)
+  } else {
+    searchCount.value = 1
+  }
+  _getRecommendedLocation(batchItem[0])
+  scanType.value = SCAN_TYPE.LOCATION
+  scanSuccess()
+}
+
+// 多批次命中:清空当前状态并弹出批次选择
+const _openMultiBatchPicker = () => {
+  locationList.value = []
+  barcodeActiveList.value = []
+  searchCount.value = ''
+  searchLocation.value = ''
+  lotBarcodeTrueFalseBy.value = true
+}
+
 // 页面初始化
 onMounted(async () => {
   openListener()
   scanInit(_handlerScan)
+  await loadForcePublishParam()
   loadData()
 })
 const warehouse = store.warehouse
@@ -220,7 +277,6 @@ const dataList = ref([])
 //
 const dataMap = ref({})
 //库位列表
-const locationTrueFalseBy = ref(false)
 const locationList = ref([])
 //商品条码
 const searchBarcode = ref('')
@@ -232,7 +288,7 @@ const searchCount = ref('')
 const taskInfo = ref({})
 //开始时间
 const currentTime = ref('--')
-const scanType = ref(2)
+const scanType = ref(SCAN_TYPE.BARCODE)
 const putweayType=ref('def')
 const lotAttributes = {
   lotAtt01: { title: '生产日期' },
@@ -301,7 +357,7 @@ const setBarcode = (code, type) => {
   const params = { warehouseId:warehouse, containerId: code }
   getWaitPutawayListNew(params).then(res => {
     reset()
-    scanType.value=2
+    scanType.value = SCAN_TYPE.BARCODE
     back.value = true
     if (!type) {//切换任务成功重启计时器
       currentTime.value = getCurrentTime()
@@ -354,9 +410,6 @@ const setBarcode = (code, type) => {
       dataList.value = [...asnToShelfList,...noAsnToShelfList]
       dataMap.value = groupedData(dataList.value)
       containerNo.value = code
-      // if (!type && code?.includes('TH-')) {
-      //   locationType.value = 'RETURN'
-      // }
       scanSuccess()
     } else {
       reset()
@@ -405,7 +458,7 @@ const matchingBarcodeItem = (data, barcode) => {
 const switchTask = () => {
   inputBarcodeType.value = 'switchTask'
   back.value = false
-  excludedLocations.value = {}
+  if (systemForcePublishEnabled.value) excludedLocations.value = {}
   locationType.value = ''
   inputBarcodeRef.value?.show('', `请扫描容器号`, '')
 }
@@ -448,6 +501,8 @@ const reset = () => {
   putawayCombineData.value = null
   combineMatchedSku.value = []
   excludedLocations.value = {}
+  forbidForcePutaway.value = true // 重置为默认不强制,待下次推荐接口返回后再更新
+  locationType.value = ''
 }
 // 组合商品上架数量弹框
 const _showPutawayCombineDialog = (batchItem) => {
@@ -475,18 +530,15 @@ const onCombineCancel = () => {
 }
 // 选择单据
 const onDetailActive = (item) => {
-  barcodeActiveList.value = item
   lotBarcodeTrueFalseBy.value = false
   if (putawayCombineData.value) {
+    barcodeActiveList.value = item
     _showPutawayCombineDialog(item)
     _getRecommendedLocation(item[0])
-    scanType.value = 3
+    scanType.value = SCAN_TYPE.LOCATION
     return
   }
-  searchCount.value = 1
-  scanType.value = 3
-  _getRecommendedLocation(item[0])
-  scanSuccess()
+  _activateSingleBatch(item)
 }
 const onAsnCancel = () => {
   if (searchBarcode.value === '' || (oldSearchBarcode.value.length != searchBarcode.value.length && oldSearchBarcode.value != '')) {
@@ -515,16 +567,9 @@ const _handlePutawayCombineProduct = (code) => {
       closeLoading()
       scanSuccess()
       if (lotBarcodeList.value.length === 1) {
-        barcodeActiveList.value = lotBarcodeList.value[0]
-        _showPutawayCombineDialog(lotBarcodeList.value[0])
-        _getRecommendedLocation(barcodeActiveList.value[0])
-        scanType.value = 3
+        _activateSingleBatch(lotBarcodeList.value[0], { showCombineDialog: true })
       } else {
-        locationList.value = []
-        barcodeActiveList.value = []
-        searchCount.value = ''
-        searchLocation.value = ''
-        lotBarcodeTrueFalseBy.value = true
+        _openMultiBatchPicker()
       }
     })
     .catch(() => {
@@ -537,7 +582,7 @@ const _handlePutawayCombineProduct = (code) => {
 
 // 扫描条码监听
 const _handlerScan = (code) => {
-  if (scanType.value == 2) {
+  if (scanType.value === SCAN_TYPE.BARCODE) {
     excludedLocations.value = {}
     searchBarcode.value = code
     oldSearchBarcode.value = code
@@ -545,50 +590,45 @@ const _handlerScan = (code) => {
     if (lotBarcodeList.value.length > 0) {
       putawayCombineData.value = null
       combineMatchedSku.value = []
-      if (lotBarcodeList.value.length == 1) {
-        barcodeActiveList.value = lotBarcodeList.value[0]
-        _getRecommendedLocation(barcodeActiveList.value[0])
-        scanType.value = 3
-        scanSuccess()
-      } else if (lotBarcodeList.value.length > 1) {
-        locationList.value = []
-        barcodeActiveList.value = []
-        searchCount.value = ''
-        searchLocation.value = ''
-        lotBarcodeTrueFalseBy.value = true
+      if (lotBarcodeList.value.length === 1) {
+        _activateSingleBatch(lotBarcodeList.value[0])
+      } else {
+        _openMultiBatchPicker()
       }
     } else {
       _handlePutawayCombineProduct(code)
     }
-  } else if (scanType.value == 3) {
+  } else if (scanType.value === SCAN_TYPE.LOCATION) {
     const scannedLocation = barcodeToUpperCase(code)
-    if (!forcePublishEnabled.value) {
-      const { lotAtt02 } = barcodeActiveList.value[0]
-      if (locationList.value.length > 0 && !lotAtt02) {
-        const recommendedLocations = locationList.value.map(item => barcodeToUpperCase(item.locationId || ''))
-        if (!recommendedLocations.includes(scannedLocation)) {
-          showNotify({ type: 'warning', duration: 3000, message: `扫描库位${scannedLocation}与推荐库位不一致,请确认` })
-          searchLocation.value=''
-          scanError()
-          return
-        }
-      }
+    if (!isLocationInRecommendedList(scannedLocation)) {
+      showNotify({ type: 'warning', duration: 3000, message: `扫描库位${scannedLocation}与推荐库位不一致,请确认` })
+      searchLocation.value = ''
+      scanError()
+      return
     }
     searchLocation.value = scannedLocation
-    scanType.value = 4
+    scanType.value = SCAN_TYPE.QUANTITY
     if (!searchCount.value) searchCount.value = 1
     scanSuccess()
   }
 }
-// 获取推荐库位
+// 获取推荐库位(根据 systemForcePublishEnabled 分流新旧接口)
 const _getRecommendedLocation = async (item, options = {}) => {
+  const { lotNumber, owner, sku, lotAtt08 } = item
+  // 未开启精准推荐:走旧接口,仅展示推荐列表,不校验库位一致性
+  if (!systemForcePublishEnabled.value) {
+    try {
+      const res = await getRecommendedLocation({ warehouse, lotNum: lotNumber, owner })
+      locationList.value = res.data || []
+    } catch (err) {
+      console.error(err)
+    }
+    searchCount.value = 1
+    return
+  }
   const { fromChangeLocation = false } = options
-  const { lotNumber, owner } = item
-  const { sku, quantity, lotAtt08 } = item
   const listByLot = fromChangeLocation ? (excludedLocations.value[lotNumber] || []) : []
-  const uniqueLocationIds = listByLot.length > 0
-    ? [...new Set(listByLot.map(loc => loc.locationId).filter(Boolean))]
-    : undefined
+  const uniqueLocationIds = getExcludedLocationIds(listByLot)
   try {
     const total = barcodeQuantity(barcodeActiveList.value)
     const params = { warehouse, lotNum: lotNumber, owner, sku, qty: total, lotAtt08 }
@@ -596,7 +636,7 @@ const _getRecommendedLocation = async (item, options = {}) => {
     if (fromChangeLocation && uniqueLocationIds) params.excludedLocations = uniqueLocationIds
     const res = await getRecommendedLocationNew(params)
     if (res.data) {
-      forcePublishEnabled.value = res.data.forbidForcePutaway
+      forbidForcePutaway.value = res.data.forbidForcePutaway // 货主策略:是否禁止强制上架
       const loc = res.data.locationList
       if (fromChangeLocation) {
         // 按批次维度存储已推荐库位(与接口 locationList 项一致,含 locationId),用于后续换一换排除
@@ -611,8 +651,8 @@ const _getRecommendedLocation = async (item, options = {}) => {
       searchCount.value = 1
     }
   } catch (err) {
-    forcePublishEnabled.value=true
-    locationList.value=[]
+    forbidForcePutaway.value = true
+    locationList.value = []
     scanError()
     console.error(err)
   }
@@ -625,7 +665,7 @@ const onChangeLocation = async () => {
     try {
       const item = barcodeActiveList.value[0]
       const lotNumber = item.lotNumber
-      const currentLoc = locationList.value?.[0]?.location ?? locationList.value?.[0]
+      const currentLoc = locationList.value?.[0]
       if (currentLoc) {
         const lotExcluded = excludedLocations.value[lotNumber] || []
         excludedLocations.value = {
@@ -649,22 +689,17 @@ const isCheck = () => {
     showToast({ duration: 3000, message: '请先扫描库位编号' })
     return false
   }
-  if (!forcePublishEnabled.value) {
-    if(barcodeActiveList.value.length ==0) {
-      showToast({ duration: 3000, message: '数据异常请重新扫描' })
-      scanError()
-      return
-    }
-    const { lotAtt02 } = barcodeActiveList.value[0]
-    if (locationList.value.length > 0 && !lotAtt02) {
-      const recommendedLocations = locationList.value.map(item => barcodeToUpperCase(item.locationId || ''))
-      if (!recommendedLocations.includes(barcodeToUpperCase(searchLocation.value))) {
-        locationRef.value?.focus()
-        scanError()
-        showToast({ duration: 3000, message: '库位与推荐库位不一致,无法上架' })
-        return false
-      }
-    }
+  if (barcodeActiveList.value.length === 0) {
+    showToast({ duration: 3000, message: '数据异常请重新扫描' })
+    scanError()
+    return false
+  }
+  // 扫描库位与提交上架共用同一套强制匹配规则
+  if (!isLocationInRecommendedList(searchLocation.value)) {
+    locationRef.value?.focus()
+    scanError()
+    showToast({ duration: 3000, message: '库位与推荐库位不一致,无法上架' })
+    return false
   }
   if (searchCount.value == '') {
     numberRef.value?.focus()
@@ -728,7 +763,7 @@ const onConfirm = () => {
       } else {
         showNotify({ type: 'success', message: '上架成功,请继续扫描商品进行上架', duration: 3000 })
         setBarcode(containerNo.value, 'success')
-        scanType.value = 2
+        scanType.value = SCAN_TYPE.BARCODE
       }
       reset()
       scanSuccess()
@@ -739,9 +774,9 @@ const onConfirm = () => {
     })
   }
 }
-const refresh=()=>{
+const refresh = () => {
   reset()
-  scanType.value=2
+  scanType.value = SCAN_TYPE.BARCODE
   loadData()
 }
 
@@ -852,30 +887,9 @@ window.onRefresh = loadData
         font-size: 16px
         font-weight: bold
 
-      ::v-deep(.van-search__label),
-      ::v-deep(.van-field__label)
-        width: 6.5em
-        min-width: 6.5em
-        max-width: 6.5em
-        flex: none
+      ::v-deep(.van-search__label)
         font-size: 16px
         font-weight: bold
-        margin-right: 0
-        text-align: left
-
-      &.location-type-row
-        ::v-deep(.van-cell__title)
-          width: 6.5em
-          min-width: 6.5em
-          max-width: 6.5em
-          flex: none
-          font-size: 16px
-          font-weight: bold
-
-        ::v-deep(.van-cell__value)
-          font-size: 14px
-          font-weight: bold
-          color: #323233
 
       .search-input-barcode
         ::v-deep(.van-search__field)