فهرست منبع

Merge remote-tracking branch 'origin/master'

zengjun 2 هفته پیش
والد
کامیت
2c7910cd5a

+ 35 - 0
src/api/haikang/index.ts

@@ -72,6 +72,28 @@ export function boxReturn(data:any) {
   })
 }
 
+/**
+ * 完成站点任务
+ */
+export function releaseCustomTaskStation(params:any) {
+  return request({
+    url: '/api/wms/robot/task/releaseCustomTaskStation',
+    method: 'post',
+    params,
+  })
+}
+
+/**
+ * 海康料箱入库接口
+ */
+export function taskContinue(params:any) {
+  return request({
+    url: '/api/wms/robot/task/taskContinue',
+    method: 'post',
+    params,
+  })
+}
+
 /**
  * 调空料箱并回库
  * @param data
@@ -155,4 +177,17 @@ export function asrsBoxAndStationUnbindTask(params:any) {
 
 
 
+/**
+ * 立库-高度检测
+ * @param data
+ */
+export function getAsrsHeight() {
+  return request({
+    url: 'http://58.33.243.164:50004/height',
+    method: 'get',
+  })
+}
+
+
+
 

+ 4 - 2
src/utils/request.ts

@@ -25,6 +25,7 @@ service.interceptors.request.use(
     return config;
   },
   (error:any) => {
+      if(error.config.url=='http://58.33.243.164:50004/height') return
     // 请求错误时做处理
     showToast({duration:5000,message:'网络开小车了,检查网络'})
     return Promise.reject(error);
@@ -51,10 +52,10 @@ service.interceptors.response.use(
         }
         return
       }
-      if(res.code===305 && response.config.url=='api/entryOrder/app/blind/checkBarcodeIgnoreCase'){
+      if(res.code===305 && response.config.url=='api/entryOrder/app/blind/checkBarcodeIgnoreCase' ){
         return res
       }
-      if( response.config.url=='api/wms/fluxPrint/print'){
+      if( response.config.url=='api/wms/fluxPrint/print' || response.config.url=='http://58.33.243.164:50004/height'){
           return res
       }
       if(res.message=='找不到此盲收任务'){
@@ -70,6 +71,7 @@ service.interceptors.response.use(
     return res;
   },
   (error:any) => {
+    if(error.config.url=='http://58.33.243.164:50004/height') return
     if (error.code === 'ECONNABORTED') {
       // 请求超时处理
       showNotify({duration:5000,message:'请求超时,请稍后重试!'})

+ 61 - 14
src/views/haikang/boxReturn/boxReturn/index.vue

@@ -20,7 +20,7 @@
         <van-tab title="解绑站点" name="2"  ></van-tab>
       </van-tabs>
         <div class="content-code">
-          <div class="barcode-input" >
+          <div class="barcode-input">
             <van-search
               ref="boxRef"
               v-model.lazy="scanBox"
@@ -61,7 +61,7 @@ import {
   asrsBoxAndStationUnbindTask,
   boxAndStationUnbindTask, createAsrsPalletInbound,
   createAsrsShelfInbound,
-  createHikBoxInboundTask
+  createHikBoxInboundTask, getAsrsHeight
 } from '@/api/haikang'
 import { useStore } from '@/store/modules/user'
 import { closeLoading, showLoading } from '@/utils/loading'
@@ -87,6 +87,27 @@ const mode=ref(route.query.type)
 // 扫描条码监听
 const _handlerScan = (code) => {
   if(!code) return
+  // 当 mode=='asrs' 且是解绑站点功能时,根据扫描类型处理
+  if(mode.value=='asrs' && active.value=='2'){
+    if(scanType.value == 1){
+      // 扫描料箱编码(选填)
+      let searchKey = barcodeToUpperCase(code);
+      const gridMatch = searchKey.match(/^([A-Z]+\d+)(?:-[AB]\d*)?$/);
+      if (gridMatch) {
+        searchKey = gridMatch[1]
+      }
+      scanBox.value = searchKey
+      scanType.value=2
+      tips.value='请扫描工作站台'
+    }else if(scanType.value == 2){
+      // 扫描工作站点(必填)
+      if(code){
+        scanStation.value = barcodeToUpperCase(code)
+        onConfirm()
+      }
+    }
+    return
+  }
   if (scanType.value == 1) {
     let searchKey = barcodeToUpperCase(code);
     const gridMatch = searchKey.match(/^([A-Z]+\d+)(?:-[AB]\d*)?$/);
@@ -112,20 +133,31 @@ const _handlerScan = (code) => {
   }
 }
 const onActive=(type)=>{
-  // if(type==2 && mode.value=='asrs'){
-  //   scanType.value=2
-  // }else{
-  //   scanType.value=1
-  // }
+  //
+  if(mode.value=='asrs' && type=='2'){
+    scanType.value=2
+    tips.value='请扫描工作站台'
+    scanBox.value=''
+    scanStation.value=''
+  }else if(type=='1'){
+    scanType.value=1
+    tips.value='请扫描料箱'
+    scanBox.value=''
+    scanStation.value=''
+  }
 }
 const onConfirm=()=>{
-  if(!scanBox.value ){
-    tips.value='请先扫描料箱编号'
-    scanType.value=1
-    showNotify({ type: 'danger', duration: 3000, message:'请先扫描料箱编号' })
-    scanError()
-    return
+  // 当 mode=='asrs' 且是解绑站点功能时,料箱编码为选填,只校验工作站点
+  if(!(mode.value=='asrs' && active.value=='2')){
+    if(!scanBox.value  ){
+      tips.value='请先扫描料箱编号'
+      scanType.value=1
+      showNotify({ type: 'danger', duration: 3000, message:'请先扫描料箱编号' })
+      scanError()
+      return
+    }
   }
+  // 工作站点为必填
   if(!scanStation.value){
     tips.value='请先扫描站点'
     scanType.value=2
@@ -166,6 +198,13 @@ const _createHikBoxInboundTask=()=>{
       palletCode: scanBox.value
     };
     url = createAsrsPalletInbound;
+  }else if (scanBox.value.startsWith('HK')) {
+    data = {
+      ...baseData,
+      deviceType:'HIK_INBOUND_STATION',
+      boxCode:scanBox.value,
+    };
+    url = createHikBoxInboundTask;
   }
   showLoading()
   url(data).then(res=>{
@@ -175,6 +214,9 @@ const _createHikBoxInboundTask=()=>{
     scanStation.value=''
     showNotify({ type: 'success', duration: 3000, message:'料箱入库成功,请继续扫描需要入库料箱' })
     scanSuccess()
+    if(mode.value=='asrs'){
+      getAsrsHeight().then(res=>{}).catch()
+    }
   }).catch(err=>{
     tips.value=err.message
     scanStation.value=''
@@ -207,7 +249,12 @@ const _boxAndStationUnbindTask=()=>{
 }
 //立库解绑
 const _asrsBoxAndStationUnbindTask=()=>{
-  const data={warehouse, stationCode:scanStation.value,containerCode:scanBox.value}
+  const data: any = {warehouse, stationCode:scanStation.value}
+  if(active.value=='2' && scanBox.value){
+    data.containerCode = scanBox.value
+  }else if(active.value!='2'){
+    data.containerCode = scanBox.value
+  }
   showLoading()
   asrsBoxAndStationUnbindTask(data).then(res=>{
     if(res.data){

+ 79 - 352
src/views/haikang/putaway/components/Shelf.vue

@@ -1,36 +1,20 @@
 <template>
-  <div class="shelf-container" :style="{ '--cols': cols, '--rows': rows }">
-
-    <!-- 当前显示的面 -->
-    <div class="shelf-rack">
-      <!-- 货架主体 -->
-      <div class="rack-body">
-
-        <!-- 货架支柱 -->
-        <div class="rack-pillars">
-          <div class="rack-main">
-            <!-- 货架层板 -->
-            <div class="rack-shelves">
-              <div
-                v-for="row in reversedRows"
-                :key="`shelf-${row}`"
-                class="rack-shelf"
-              >
-                <!-- 货架板 -->
-                <div class="shelf-board">
-                  <div
-                    v-for="col in cols"
-                    :key="`cell-${shelfName}-${currentFace}${row}${col}`"
-                    class="shelf-cell"
-                    :class="{ 'highlight': isHighlighted(`${shelfName}-${currentFace}${row}${col}`) }"
-                  >
-                    <!-- 完整编号显示 -->
-                    <div class="cell-number">
-                      {{ shelfName }}-{{ currentFace }}{{ row }}{{ col }}
-                    </div>
-                  </div>
-                </div>
-              </div>
+  <div class="shelf-container">
+    <div ref="shelfScrollRef" class="shelf-scroll">
+      <div class="shelf-grid">
+        <div
+          v-for="row in reversedRows"
+          :key="`shelf-${row}`"
+          class="shelf-row"
+        >
+          <div
+            v-for="col in cols"
+            :key="`cell-${shelfName}-${currentFace}${row}${col}`"
+            class="shelf-cell"
+            :class="{ highlight: isHighlighted(`${shelfName}-${currentFace}${row}${col}`) }"
+          >
+            <div class="cell-number">
+              {{ shelfName }}-{{ currentFace }}{{ row }}{{ col }}
             </div>
           </div>
         </div>
@@ -40,9 +24,8 @@
 </template>
 
 <script setup>
-import { computed } from 'vue';
+import { computed, nextTick, onMounted, ref, watch } from 'vue';
 
-// 接收父组件传递的数据
 const props = defineProps({
   shelfCode: {
     type: String,
@@ -62,63 +45,69 @@ const props = defineProps({
   }
 });
 
-// 计算属性:从大到小排列,让第一层在底部
 const reversedRows = computed(() => {
   return Array.from({ length: props.rows }, (_, i) => props.rows - i);
 });
 
-// 当前显示的面
 const currentFace = computed(() => {
-  // 优先根据货架编码判断显示的面
   if (props.shelfCode) {
     const match = props.shelfCode.match(/^([A-Z]+\d+)-([AB])$/);
     if (match) {
-      return match[2]; // 返回A或B
+      return match[2];
     }
   }
 
-  // 如果货架编码没有指定面,则根据highlightCells中的编号自动判断显示的面
   if (props.highlightCells && props.highlightCells.length > 0) {
-    // 检查是否有A面或B面的编号
     const hasAFace = props.highlightCells.some(cell => cell.includes('-A'));
     const hasBFace = props.highlightCells.some(cell => cell.includes('-B'));
 
-    // 如果同时有AB面,默认显示A面;如果只有一面,显示那一面
     if (hasAFace) return 'A';
     if (hasBFace) return 'B';
   }
-  return 'A'; // 默认显示A面
+  return 'A';
 });
 
-// 货架名称
 const shelfName = computed(() => {
-  // 从货架编码中提取货架名称,如 HJ001-A -> HJ001
   if (props.shelfCode) {
     const match = props.shelfCode.match(/^([A-Z]+\d+)-[AB]$/);
     if (match) {
       return match[1];
     }
   }
-  return 'HJ001'; // 默认值
+  return 'HJ001';
 });
 
-// 判断格口是否需要高亮
 const isHighlighted = (cellId) => {
   if (!cellId || !props.highlightCells || props.highlightCells.length === 0) return false;
-
   return props.highlightCells.includes(cellId);
 };
 
-// 暴露高亮方法
+const shelfScrollRef = ref(null);
+
+function scrollHighlightIntoView() {
+  const scrollEl = shelfScrollRef.value;
+  if (!scrollEl || !props.highlightCells?.length) return;
+  const highlighted = scrollEl.querySelector('.shelf-cell.highlight');
+  highlighted?.scrollIntoView({ block: 'center', behavior: 'smooth', inline: 'nearest' });
+}
+
+watch(
+  () => [props.highlightCells, props.shelfCode, props.rows, props.cols],
+  () => {
+    nextTick(scrollHighlightIntoView);
+  },
+  { deep: true }
+);
+
+onMounted(() => {
+  nextTick(scrollHighlightIntoView);
+});
+
 const highlightCell = (cellId) => {
-  // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
-  // 实际的高亮控制应该通过父组件更新 highlightCells prop
   console.log('Highlight cell:', cellId);
 };
 
 const clearHighlight = () => {
-  // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
-  // 实际的高亮控制应该通过父组件更新 highlightCells prop
   console.log('Clear highlight');
 };
 
@@ -126,336 +115,74 @@ defineExpose({ highlightCell, clearHighlight });
 </script>
 
 <style scoped>
+
 .shelf-container {
-  width: 75%;
-  margin: 0;
-  padding: 0;
-  background:
-    linear-gradient(135deg, rgba(248, 249, 250, 0.95) 0%, rgba(233, 236, 239, 0.95) 100%),
-    radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
-    radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
-  border-radius: 16px;
-  box-shadow:
-    0 8px 32px rgba(0, 0, 0, 0.12),
-    0 2px 8px rgba(0, 0, 0, 0.06),
-    inset 0 1px 0 rgba(255, 255, 255, 0.3),
-    inset 0 -1px 0 rgba(0, 0, 0, 0.05);
+  box-sizing: border-box;
+  width: 80%;
+  max-width: 100%;
+  margin: 0 auto;
+  border-radius: 10px;
+  border: 1px solid #e5e7eb;
+  background: #f3f4f6;
   overflow: hidden;
-  position: relative;
-  border: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.shelf-container::before {
-  content: '';
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  height: 2px;
-  background: linear-gradient(90deg,
-    transparent 0%,
-    rgba(255, 255, 255, 0.9) 20%,
-    rgba(255, 255, 255, 0.6) 50%,
-    rgba(255, 255, 255, 0.9) 80%,
-    transparent 100%);
-  border-radius: 16px 16px 0 0;
-}
-
-.shelf-container::after {
-  content: '';
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  height: 1px;
-  background: linear-gradient(90deg,
-    transparent 0%,
-    rgba(0, 0, 0, 0.1) 50%,
-    transparent 100%);
 }
 
-.shelf-rack {
-  background:
-    linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
-    radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.05) 0%, transparent 40%),
-    radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
-  overflow: hidden;
-  position: relative;
-  backdrop-filter: blur(12px) saturate(1.2);
-  border-radius: 14px;
+.shelf-scroll {
+  box-sizing: border-box;
+  height: 100px;
   margin: 2px;
+  padding: 2px;
+  border-radius: 8px;
+  background: #fff;
+  overflow-y: auto;
+  overflow-x: hidden;
+  -webkit-overflow-scrolling: touch;
 }
 
-.rack-title {
-  margin: 0;
-  font-size: 14px;
-  font-weight: 500;
-  letter-spacing: 0.5px;
-}
-
-.rack-footer {
-  background: #f5f5f5;
-  padding: 8px 15px;
-  text-align: center;
-  border-radius: 3px;
-}
-
-.rack-footer .rack-title {
-  margin: 0;
-  font-size: 18px;
-  font-weight: 600;
-  color: #409eff;
-  letter-spacing: 0.5px;
-}
-
-.rack-body {
-  position: relative;
-  display: block;
-  min-height: calc(28px * var(--rows) + 4px);
-}
-
-/* 货架支柱 */
-.rack-pillars {
-  display: flex;
-  align-items: stretch;
-  position: relative;
-}
-
-.rack-pillar {
-  width: 5px;
-  background:
-    linear-gradient(180deg,
-      #9ca3af 0%,
-      #6b7280 20%,
-      #4b5563 50%,
-      #374151 80%,
-      #1f2937 100%);
-  position: relative;
-  border-radius: 2.5px;
-  box-shadow:
-    0 3px 8px rgba(0, 0, 0, 0.2),
-    inset 0 1px 0 rgba(255, 255, 255, 0.15),
-    inset 0 -1px 0 rgba(0, 0, 0, 0.2),
-    inset 1px 0 0 rgba(255, 255, 255, 0.05);
-  border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.left-pillar {
-  margin-right: 10px;
-  transform: skewY(-1deg);
-}
-
-.right-pillar {
-  margin-left: 10px;
-  transform: skewY(1deg);
-}
-
-.rack-main {
-  flex: 1;
-  position: relative;
-}
-
-/* 货架层板 */
-.rack-shelves {
-  flex: 1;
+.shelf-grid {
   display: flex;
   flex-direction: column;
-  gap: 0px;
-}
-
-.rack-shelf {
-  position: relative;
-  flex: 1;
 }
 
-.shelf-board {
-  background: #fafafa;
-  padding: 2px 1px;
-  margin: 0;
+.shelf-row {
   display: flex;
-  gap: 0px;
-  align-items: center;
-  position: relative;
+  flex: 0 0 auto;
+  min-height: 28px;
+  padding: 1px 0;
+  background: #fafafa;
 }
 
-/* 货架格子 */
 .shelf-cell {
-  flex: 1;
+  flex: 1 1 0;
+  min-width: 0;
+  min-height: 24px;
+  margin: 1px;
   display: flex;
-  flex-direction: column;
   align-items: center;
   justify-content: center;
-  background:
-    linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 249, 250, 0.95) 100%),
-    radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.8) 0%, transparent 50%),
-    radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.02) 0%, transparent 50%);
-  border: 1px solid rgba(222, 226, 230, 0.8);
-  border-bottom: 2px solid rgba(226, 232, 240, 0.9);
-  border-radius: 6px;
-  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-  position: relative;
-  min-width: 50px;
-  min-height: 24px;
-  margin: 0.5px;
-  box-shadow:
-    0 1px 3px rgba(0, 0, 0, 0.06),
-    inset 0 1px 0 rgba(255, 255, 255, 0.4),
-    inset 0 -1px 0 rgba(0, 0, 0, 0.05);
-  cursor: pointer;
-}
-
-.shelf-cell:hover {
-  transform: translateY(-0.5px) scale(1.01);
-  box-shadow:
-    0 3px 8px rgba(0, 0, 0, 0.1),
-    0 1px 3px rgba(0, 0, 0, 0.06),
-    inset 0 1px 0 rgba(255, 255, 255, 0.5),
-    inset 0 -1px 0 rgba(0, 0, 0, 0.03);
-  border-color: #a0aec0;
-  background:
-    linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(241, 243, 244, 0.98) 100%),
-    radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.9) 0%, transparent 50%),
-    radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.01) 0%, transparent 50%);
+  border: 1px solid #e5e7eb;
+  border-radius: 4px;
+  background: #fff;
 }
 
-/* 单元格编号 */
 .cell-number {
-  font-size: 16px;
+  font-size: 11px;
   font-weight: 600;
-  color: #1a202c;
+  color: #111827;
   text-align: center;
-  line-height: 1;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
-  letter-spacing: 0.01em;
-  text-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.03);
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+  line-height: 1.2;
+  padding: 1px 2px;
+  word-break: break-all;
 }
 
 .shelf-cell.highlight {
-  background:
-    linear-gradient(135deg, rgba(240, 253, 244, 0.95) 0%, rgba(220, 252, 231, 0.95) 100%),
-    radial-gradient(circle at 30% 30%, rgba(34, 197, 94, 0.1) 0%, transparent 60%),
-    radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.3) 0%, transparent 60%);
+  background: #dcfce7;
   border-color: #22c55e;
-  border-width: 1.5px;
-  box-shadow:
-    0 4px 12px rgba(34, 197, 94, 0.25),
-    0 2px 6px rgba(34, 197, 94, 0.15),
-    0 0 0 1px rgba(34, 197, 94, 0.1),
-    inset 0 1px 0 rgba(255, 255, 255, 0.2);
-  transform: translateY(-0.5px) scale(1.02);
-  animation: highlight-glow 2s ease-in-out infinite alternate;
+  border-width: 2px;
 }
 
 .shelf-cell.highlight .cell-number {
   color: #166534;
-  font-weight: 800;
-  text-shadow:
-    0 1px 2px rgba(22, 101, 52, 0.2),
-    0 0 0 1px rgba(34, 197, 94, 0.1);
-  background: linear-gradient(135deg, #166534 0%, #15803d 100%);
-  -webkit-background-clip: text;
-  -webkit-text-fill-color: transparent;
-  background-clip: text;
-}
-
-@keyframes highlight-glow {
-  0% {
-    box-shadow:
-      0 4px 12px rgba(34, 197, 94, 0.25),
-      0 2px 6px rgba(34, 197, 94, 0.15),
-      0 0 0 1px rgba(34, 197, 94, 0.1),
-      inset 0 1px 0 rgba(255, 255, 255, 0.2);
-  }
-  100% {
-    box-shadow:
-      0 6px 16px rgba(34, 197, 94, 0.35),
-      0 3px 8px rgba(34, 197, 94, 0.2),
-      0 0 0 1px rgba(34, 197, 94, 0.15),
-      inset 0 1px 0 rgba(255, 255, 255, 0.3);
-  }
-}
-
-/* 响应式调整 */
-@media (max-width: 768px) {
-  .shelf-container {
-    width: 260px !important;
-    height: 100px !important;
-    padding: 0;
-    margin: 0 auto;
-    border-radius: 12px;
-    box-shadow:
-      0 6px 24px rgba(0, 0, 0, 0.15),
-      0 2px 8px rgba(0, 0, 0, 0.08),
-      inset 0 1px 0 rgba(255, 255, 255, 0.2),
-      inset 0 -1px 0 rgba(0, 0, 0, 0.03);
-    border: 1px solid rgba(255, 255, 255, 0.15);
-  }
-
-  .shelf-rack {
-    background:
-      linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
-      radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.03) 0%, transparent 40%),
-      radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.03) 0%, transparent 40%);
-    backdrop-filter: blur(8px) saturate(1.1);
-    border-radius: 10px;
-    margin: 1px;
-  }
-
-  .rack-body {
-    min-height: calc(18px * var(--rows) + 4px);
-    height: calc(100% - 8px);
-  }
-
-  .rack-pillars {
-    position: relative;
-    margin-bottom: 3px;
-  }
-
-  .rack-pillar {
-    width: 3px;
-  }
-
-  .left-pillar {
-    margin-right: 3px;
-  }
-
-  .right-pillar {
-    margin-left: 3px;
-  }
-
-  .rack-shelves {
-    gap: 2px;
-  }
-
-  .shelf-board {
-    padding: 1px 0px;
-    gap: 1px;
-  }
-
-  .shelf-cell {
-    min-height: 16px;
-    min-width: 32px;
-    padding: 1px;
-    border-width: 1px;
-    margin: 0.5px;
-    border-radius: 3px;
-  }
-
-  .cell-number {
-    font-size: 11px;
-    font-weight: 600;
-  }
-
-  .shelf-cell:hover {
-    transform: none;
-  }
-
-  .shelf-cell.highlight {
-    border-color: #22c55e;
-    border-width: 1.5px;
-    box-shadow: 0 1px 4px rgba(34, 197, 94, 0.15), 0 0 0 1px rgba(34, 197, 94, 0.08);
-  }
+  font-weight: 700;
 }
 </style>

+ 46 - 26
src/views/haikang/putaway/putaway/index.vue

@@ -5,18 +5,18 @@
         <van-icon name="arrow-left" size="25" /><div style="color: #fff">返回</div>
       </template>
       <template #right>
-        <div class="nav-right" @click="setPutaway(1)">料箱回库</div>
+        <div class="nav-right" @click="setPutaway(1)">{{ boxLabel }}回库</div>
       </template>
     </van-nav-bar>
     <div class="putaway">
       <div class="putaway-top">
         <div class="putaway-tips">
           <div style="flex: 1"><van-notice-bar left-icon="volume-o">{{ tips }}</van-notice-bar></div>
-          <div class="putaway-tips-box" v-if="dataList.length>0">剩余料箱:{{ dataList[0].remainBoxCount || 0 }}</div>
+          <div class="putaway-tips-box" v-if="dataList.length>0">剩余{{ boxLabel }}:{{ dataList[0].remainBoxCount || 0 }}</div>
         </div>
         <div class="putaway-input">
           <div class="code-input">
-            <van-field v-model="workBinNo" placeholder="请扫描料箱号" label="料箱编号:" left-icon="" :class="[scanType===1?'search-input-barcode':'']"
+            <van-field v-model="workBinNo" :placeholder="`请扫描${boxLabel}号`" :label="`${boxLabel}编号:`" left-icon="" :class="[scanType===1?'search-input-barcode':'']"
               autocomplete="off" readonly @click="setBarcodeInput(workBinNo,1)"></van-field>
             <van-field v-model="searchBarcode" placeholder="请扫描商品条码" label="商品条码:" left-icon="" readonly :class="[scanType===2?'search-input-barcode':'']"
               autocomplete="off" @click="setBarcodeInput(searchBarcode,2)"></van-field>
@@ -56,7 +56,7 @@
               :cols="barcodeActive.containerCol"
             ></shelf>
           </div>
-          <div  style="font-size: 14px;" v-if="barcodeActive.gridNum" >料箱格口:{{ barcodeActive.gridNum }}</div>
+          <div  style="font-size: 14px;" v-if="barcodeActive.gridNum" >{{ boxLabel }}格口:{{ barcodeActive.gridNum }}</div>
         </div>
       </div>
       <div class="putaway-bottom"  v-if="equipmentBarcodeList.length>0">
@@ -104,7 +104,7 @@
   </div>
 </template>
 <script setup lang="ts">
-import { onMounted, onUnmounted, ref } from 'vue'
+import { computed, onMounted, onUnmounted, ref } from 'vue'
 import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess } from '@/utils/android'
 import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
 import { useStore } from '@/store/modules/user'
@@ -114,9 +114,10 @@ import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
 import Box from '@/views/haikang/putaway/components/Box.vue'
 import Shelf from '@/views/haikang/putaway/components/Shelf.vue'
 import GoBoxBack from '@/views/haikang/putaway/components/GoBoxBack.vue'
-import { boxReturn, getShelveItemInfo, outboundEmptyBoxAndBindTask, setBoxInboundInventory } from '@/api/haikang'
+import { boxReturn,taskContinue, getShelveItemInfo, outboundEmptyBoxAndBindTask, setBoxInboundInventory } from '@/api/haikang'
 import { closeLoading, showLoading } from '@/utils/loading'
 import {useRoute} from "vue-router";
+import {releaseCustomTaskStation} from "../../../../api/haikang";
 try {
   getHeader()
   androidFocus()
@@ -133,12 +134,13 @@ const warehouse = store.warehouse
 const dataList=ref([])
 const route = useRoute()
 const mode=ref(route.query.type)
+const boxLabel = computed(() => mode.value ? '容器' : '料箱')
 //料箱编号
 const workBinNo = ref('')
 //扫描类型
 const scanType = ref(1) //料箱1、商品条码2,站台3
 //提示
-const tips = ref('请扫描料箱编号')
+const tips = ref(mode.value ? '请扫描容器编号' : '请扫描料箱编号')
 //匹配库位条码
 const matchedBarcodeItem=ref([])
 //条码
@@ -381,8 +383,8 @@ const reset=()=>{
 const inputCheck=()=>{
   if(!workBinNo.value){
     scanType.value = 1
-    tips.value = `请扫描料箱编号`
-    showNotify({ type: 'danger', duration: 3000, message: `请扫描料箱编号` })
+    tips.value = `请扫描${boxLabel.value}编号`
+    showNotify({ type: 'danger', duration: 3000, message: `请扫描${boxLabel.value}编号` })
     scanError()
     return false
   }
@@ -447,23 +449,33 @@ const setPutaway=async (type)=>{
   }else if(type==1){
     if(locationDetailList.value.length==0){
       scanType.value = 1
-      tips.value = `请先扫描料箱编号`
-      showNotify({ type: 'danger', duration: 3000, message: `请先扫描料箱编号,查询待上架信息` })
+      tips.value = `请先扫描${boxLabel.value}编号`
+      showNotify({ type: 'danger', duration: 3000, message: `请先扫描${boxLabel.value}编号,查询待上架信息` })
       scanError()
       return false
     }
-    showConfirmDialog({
-      title: '料箱回库',
-      message: `${ workBinNo.value },是否执行料箱回库?`,
+      showConfirmDialog({
+      title: `${boxLabel.value}回库`,
+      message: `${ workBinNo.value },是否执行${boxLabel.value}回库?`,
     })
       .then(() => {
         showLoading()
-        const {container,asnCode}=locationDetailList.value[0]
-        boxReturn({warehouse,container,boxCode:workBinNo.value,externalCode:asnCode,taskType:mode.value?'ASRS':'HIK'}).then(res=>{
+        const {container,asnCode,stationCode,locationId,equipment}=locationDetailList.value[0]
+        const apiCall = mode.value
+          ? boxReturn
+          : taskContinue
+        const params = mode.value
+          ? {warehouse,container,boxCode:workBinNo.value,stationCode,locationId,externalCode:equipment,taskType:'ASRS'}
+          : {warehouse, boxCode: workBinNo.value, stationCode, externalCode: asnCode, emptyRobot: false}
+        apiCall(params).then(res=>{
           closeLoading()
           scanSuccess()
-          tips.value = `料箱回库成功,请继续扫描料箱编号`
-          showNotify({ type: 'success', duration: 3000, message: `料箱回库成功,请继续扫描料箱编号` })
+          if(!mode.value && stationCode && dataList.value[0]?.remainBoxCount==1 ){
+            const params = {warehouse, stationCode, taskCode: equipment}
+            releaseCustomTaskStation(params).then(res=>{})
+          }
+          tips.value = `${boxLabel.value}回库成功,请继续扫描${boxLabel.value}编号`
+          showNotify({ type: 'success', duration: 3000, message: `${boxLabel.value}回库成功,请继续扫描${boxLabel.value}编号` })
           reset()
           barcodeActive.value={}
           workBinNo.value=''
@@ -486,13 +498,21 @@ const setGoBack=async (item)=>{
   const res= await _setBoxInboundInventory()
   if(res){
     if(item.active==1){
-      const {warehouse,container,boxCode,asnCode}=barcodeActive.value
+      const {warehouse,container,boxCode,asnCode,stationCode,locationId,equipment}=barcodeActive.value
       showLoading()
-      const boxRes= await boxReturn({warehouse,container,boxCode,externalCode:asnCode,taskType:mode.value?'ASRS':'HIK'}).catch(err=>{
+      const apiCall = mode.value ? boxReturn : taskContinue
+      const params = mode.value
+        ? {warehouse,container,boxCode:workBinNo.value,externalCode:equipment,stationCode,locationId,taskType:'ASRS'}
+        : {warehouse, boxCode:workBinNo.value, stationCode, externalCode: asnCode, emptyRobot: false}
+      const boxRes= await apiCall(params).catch(err=>{
         closeLoading()
       })
       closeLoading()
       if(boxRes){
+        if(!mode.value && stationCode && dataList.value[0]?.remainBoxCount==1 ){
+          const params = {warehouse, stationCode, taskCode: equipment}
+          releaseCustomTaskStation(params).then(res=>{})
+        }
         reset()
         barcodeActive.value={}
         workBinNo.value=''
@@ -500,9 +520,9 @@ const setGoBack=async (item)=>{
         searchLocation.value=''
         recommendedLocation.value=''
         scanType.value = 1
-        tips.value = `请扫描料箱编号`
+        tips.value = `请扫描${boxLabel.value}编号`
         binScanList.value=[]
-        showNotify({ type: 'success', duration: 3000, message: `请扫描下一个料箱编号` })
+        showNotify({ type: 'success', duration: 3000, message: `请扫描下一个${boxLabel.value}编号` })
         scanSuccess()
       }
     }else if(item.active==2){
@@ -518,7 +538,7 @@ const setGoBack=async (item)=>{
         recommendedLocation.value=''
         scanType.value = 1
         binScanList.value=[]
-        tips.value = `请扫描料箱编号`
+        tips.value = `请扫描${boxLabel.value}编号`
         scanError()
       }))
       closeLoading()
@@ -531,8 +551,8 @@ const setGoBack=async (item)=>{
         recommendedLocation.value=''
         scanType.value = 1
         binScanList.value=[]
-        tips.value = `料箱调取成功,请继续扫描料箱编号`
-        showNotify({ type: 'success', duration: 3000, message: `料箱调取成功,请继续扫描料箱编号` })
+        tips.value = `${boxLabel.value}调取成功,请继续扫描${boxLabel.value}编号`
+        showNotify({ type: 'success', duration: 3000, message: `${boxLabel.value}调取成功,请继续扫描${boxLabel.value}编号` })
         scanSuccess()
       }
     }
@@ -561,7 +581,7 @@ const _setBoxInboundInventory = async () => {
 //设置条码
 const inputBarcodeRef = ref(null)
 const setBarcodeInput = (code, type) => {
-  const typeMap = { 1: '请扫描料箱编号', 2: '请扫商品条码', 3: '请扫站台编号', 4: '请扫上架库位' }
+  const typeMap = { 1: `请扫描${boxLabel.value}编号`, 2: '请扫商品条码', 3: '请扫站台编号', 4: '请扫上架库位' }
   scanType.value = type
   inputBarcodeRef.value?.show(code, typeMap[type])
 }

+ 99 - 0
src/views/inbound/putaway/components/BarcodeCombine.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="goods">
+    <van-dialog v-model:show="goodsTrueFalseBy"
+                :beforeClose="beforeClose"
+                title="组合商品上架"
+                show-cancel-button>
+        <div style="width:100%;max-height:150px;overflow:auto">
+          <div v-for="(item,index) in matchedSkuList" :key="index">
+            <van-cell center :title="item.matchedJson.barcode" :label="item.matchedJson.skuName">
+              <template #value>
+                <div>{{ item.matchedJson.quantity }}件/套</div>
+                <div class="goods-tips">待上架:{{ (item.expectedQuantity || 0) - (item.receivedQuantity || 0) }}件</div>
+              </template>
+            </van-cell>
+          </div>
+        </div>
+        <div class="goods-number">可上架套数:{{ maxCount }}</div>
+        <van-field label="上架套数" type="number" class="code-input" v-model="count" ref="countRef" placeholder="上架套数" />
+    </van-dialog>
+  </div>
+</template>
+<script setup>
+/** 组合商品上架弹框 */
+import { computed, ref } from 'vue'
+import { showToast } from 'vant'
+const goodsTrueFalseBy = ref(false)
+const countRef = ref(null)
+const count = ref('')
+const props = defineProps({
+  matchedSku: { type: Array, default: () => [] },
+})
+const matchedSkuList = computed(() => props.matchedSku)
+const maxCount = computed(() => {
+  const min = Math.min(
+    ...props.matchedSku.map((item) => ((item.expectedQuantity || 0) - (item.receivedQuantity || 0)) / (item.matchedJson?.quantity || 1))
+  )
+  return Number.isFinite(min) ? Math.floor(min) : 0
+})
+const show = () => {
+  count.value = ''
+  goodsTrueFalseBy.value = true
+  setTimeout(() => {
+    countRef.value?.focus()
+  }, 200)
+}
+const dataResult = (data) =>
+  data.map((item) => {
+    const { matchedJson, ...rest } = item
+    return { ...rest, quantity: matchedJson.quantity * Number(count.value) }
+  })
+const emit = defineEmits(['setCombine', 'cancel'])
+const beforeClose = (action) =>
+  new Promise((resolve) => {
+    if (action === 'confirm') {
+      if (count.value == '') {
+        showToast('请输入上架套数')
+        return resolve(false)
+      }
+      if (Number(count.value) <= 0) {
+        showToast('请输入有效上架套数')
+        return resolve(false)
+      }
+      if (Number(count.value) > maxCount.value) {
+        showToast({ duration: 3000, message: '上架套数不能大于可上架套数' })
+        return resolve(false)
+      }
+      const dataList = dataResult(matchedSkuList.value)
+      emit('setCombine', { dataList, count: Number(count.value) })
+    } else {
+      emit('cancel')
+    }
+    resolve(true)
+  })
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.goods
+  .code-input
+    font-size: 16px
+    font-weight: bold
+    border-bottom: 2px solid #0077ff
+  :deep(.van-cell--center)
+    padding: 5px 20px
+  :deep(.van-cell__title)
+    text-align: left !important
+  :deep(.van-cell__value)
+    width: 40% !important
+    flex: 0 0 40% !important
+    color: #000
+  .goods-number
+    text-align: left
+    font-size: 16px
+    padding-left: 20px
+    margin-top: 5px
+  .goods-tips
+    font-size: 12px
+    text-align: right
+    color: #333
+</style>

+ 84 - 4
src/views/inbound/putaway/task/index.vue

@@ -148,6 +148,8 @@
       <location-list :locationList="locationList" />
     </div>
   </van-action-sheet>
+  <!--  组合商品上架数量-->
+  <barcode-combine ref="barcodeCombineRef" @setCombine="setPutawayCombine" @cancel="onCombineCancel" :matched-sku="combineMatchedSku" />
 </template>
 
 <script setup>
@@ -155,6 +157,7 @@ import { onMounted, onUnmounted, ref, computed } from 'vue'
 import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
 import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
 import LocationList from '@/views/inbound/putaway/components/LocationList.vue'
+import BarcodeCombine from '@/views/inbound/putaway/components/BarcodeCombine.vue'
 import { openListener,closeListener,scanInit } from '@/utils/keydownListener.js'
 import { useRouter } from 'vue-router'
 import { closeLoading, showLoading } from '@/utils/loading'
@@ -162,6 +165,7 @@ import { useStore } from '@/store/modules/user'
 import { showNotify, showToast } from 'vant'
 import { getCurrentTime } from '@/utils/date'
 import { getWaitPutawayListNew, setPutawayNew } from '@/api/putaway/index'
+import { getListCombineSku } from '@/api/picking'
 import { barcodeToUpperCase } from '@/utils/dataType.js'
 import { getRecommendedLocation } from '@/api/haikang/index'
 import { getOwnerList } from '@/hooks/basic/index'
@@ -376,6 +380,10 @@ const switchTask = () => {
 const lotBarcodeList = ref([])
 const lotBarcodeTrueFalseBy = ref(false)
 const barcodeActiveList = ref([])
+// 组合商品
+const barcodeCombineRef = ref(null)
+const putawayCombineData = ref(null)
+const combineMatchedSku = ref([])
 const reset = () => {
   searchCount.value = ''
   searchBarcode.value = ''
@@ -383,11 +391,43 @@ const reset = () => {
   oldSearchBarcode.value = ''
   locationList.value = []
   barcodeActiveList.value = []
+  putawayCombineData.value = null
+  combineMatchedSku.value = []
+}
+// 组合商品上架数量弹框
+const _showPutawayCombineDialog = (batchItem) => {
+  if (!putawayCombineData.value || !batchItem?.length) return
+  const total = batchItem.reduce((sum, i) => sum + Number(i.quantity || 0), 0)
+  combineMatchedSku.value = [{
+    matchedJson: putawayCombineData.value,
+    expectedQuantity: total,
+    receivedQuantity: 0,
+  }]
+  barcodeCombineRef.value?.show()
+}
+// 组合商品确认上架数量
+const setPutawayCombine = ({ dataList }) => {
+  if (dataList?.[0]?.quantity != null) {
+    searchCount.value = String(dataList[0].quantity)
+  }
+  showNotify({ type: 'success', duration: 2000, message: `已填入上架数量:${searchCount.value},请扫描库位并确认上架` })
+}
+// 组合商品取消
+const onCombineCancel = () => {
+  const qtyPerSet = putawayCombineData.value?.quantity ?? 1
+  const total = barcodeQuantity(barcodeActiveList.value)
+  searchCount.value = String(total < qtyPerSet ? 1 : qtyPerSet)
 }
 // 选择单据
 const onDetailActive = (item) => {
   barcodeActiveList.value = item
   lotBarcodeTrueFalseBy.value = false
+  if (putawayCombineData.value) {
+    _showPutawayCombineDialog(item)
+    _getRecommendedLocation(item[0].lotNumber, item[0].owner)
+    scanType.value = 3
+    return
+  }
   searchCount.value = 1
   scanType.value = 3
   _getRecommendedLocation(item[0].lotNumber, item[0].owner)
@@ -400,6 +440,46 @@ const onAsnCancel = () => {
     locationList.value = []
   }
 }
+// 商品条码不匹配时,查询组合条码
+const _handlePutawayCombineProduct = (code) => {
+  showLoading()
+  getListCombineSku({ combineSku: barcodeToUpperCase(code), workEnvironment: 'inbound' })
+    .then((res) => {
+      const _err = (msg) => {
+        closeLoading()
+        scanError()
+        showNotify({ type: 'danger', duration: 3000, message: msg })
+        reset()
+      }
+      if (!res.data?.length) return _err(`${code}-商品条码不匹配,请重新扫描`)
+      if (res.data.length > 1) return _err('不支持多商品组合商品')
+      const combineBarcode = res.data[0].barcode
+      lotBarcodeList.value = matchingBarcodeItem(dataMap.value, combineBarcode)
+      if (lotBarcodeList.value.length === 0) return _err('组合商品与待上架数据不匹配,请检查组合商品配置!')
+      putawayCombineData.value = res.data[0]
+      closeLoading()
+      scanSuccess()
+      if (lotBarcodeList.value.length === 1) {
+        barcodeActiveList.value = lotBarcodeList.value[0]
+        _showPutawayCombineDialog(lotBarcodeList.value[0])
+        _getRecommendedLocation(barcodeActiveList.value[0].lotNumber, barcodeActiveList.value[0].owner)
+        scanType.value = 3
+      } else {
+        locationList.value = []
+        barcodeActiveList.value = []
+        searchCount.value = ''
+        searchLocation.value = ''
+        lotBarcodeTrueFalseBy.value = true
+      }
+    })
+    .catch(() => {
+      closeLoading()
+      scanError()
+      showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
+      reset()
+    })
+}
+
 // 扫描条码监听
 const _handlerScan = (code) => {
   if (scanType.value == 2) {
@@ -407,6 +487,8 @@ const _handlerScan = (code) => {
     oldSearchBarcode.value = code
     lotBarcodeList.value = matchingBarcodeItem(dataMap.value, 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].lotNumber, barcodeActiveList.value[0].owner)
@@ -420,14 +502,12 @@ const _handlerScan = (code) => {
         lotBarcodeTrueFalseBy.value = true
       }
     } else {
-      scanError()
-      showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
-      reset()
+      _handlePutawayCombineProduct(code)
     }
   } else if (scanType.value == 3) {
     searchLocation.value = barcodeToUpperCase(code)
     scanType.value = 4
-    searchCount.value = 1
+    if (!searchCount.value) searchCount.value = 1
     scanSuccess()
   }
 }

+ 100 - 0
src/views/inbound/takeDelivery/components/BarcodeCombine.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="goods">
+    <van-dialog v-model:show="goodsTrueFalseBy"
+                :beforeClose="beforeClose"
+                title="组合商品收货"
+                show-cancel-button>
+        <div style="width:100%;max-height:150px;overflow:auto">
+          <div v-for="(item,index) in matchedSkuList" :key="index">
+            <van-cell center :title="item.matchedJson.barcode" :label="item.matchedJson.skuName">
+              <template #value>
+                <div>{{ item.matchedJson.quantity }}件/套</div>
+                <div class="goods-tips">待收货:{{ (item.expectedQuantity || 0) - (item.receivedQuantity || 0) }}件</div>
+              </template>
+            </van-cell>
+          </div>
+        </div>
+        <div class="goods-number">可收货套数:{{ maxCount }}</div>
+        <van-field label="收货套数" type="number" class="code-input" v-model="count" ref="countRef" placeholder="收货套数" />
+    </van-dialog>
+  </div>
+</template>
+<script setup>
+/** 组合商品弹框 */
+import { computed, ref } from 'vue'
+import { showToast } from 'vant'
+const goodsTrueFalseBy = ref(false)
+const countRef = ref(null)
+const count = ref('')
+const props = defineProps({
+  matchedSku: { type: Array, default: () => [] },
+  container: { type: String, default: '' }
+})
+const matchedSkuList = computed(() => props.matchedSku)
+const maxCount = computed(() => {
+  const min = Math.min(
+    ...props.matchedSku.map((item) => ((item.expectedQuantity || 0) - (item.receivedQuantity || 0)) / (item.matchedJson?.quantity || 1))
+  )
+  return Number.isFinite(min) ? Math.floor(min) : 0
+})
+const show = () => {
+  count.value = ''
+  goodsTrueFalseBy.value = true
+  setTimeout(() => {
+    countRef.value?.focus()
+  }, 200)
+}
+const dataResult = (data) =>
+  data.map((item) => {
+    const { matchedJson, ...rest } = item
+    return { ...rest, quantity: matchedJson.quantity * Number(count.value), containerId: props.container }
+  })
+const emit = defineEmits(['setCombine', 'cancel'])
+const beforeClose = (action) =>
+  new Promise((resolve) => {
+    if (action === 'confirm') {
+      if (count.value == '') {
+        showToast('请输入收货数量')
+        return resolve(false)
+      }
+      if (Number(count.value) <= 0) {
+        showToast('请输入标准收货数量')
+        return resolve(false)
+      }
+      if (Number(count.value) > maxCount.value) {
+        showToast({ duration: 3000, message: '收货数量不能大于应收数量' })
+        return resolve(false)
+      }
+      const dataList = dataResult(matchedSkuList.value)
+      emit('setCombine', { dataList, count: Number(count.value) })
+    } else {
+      emit('cancel')
+    }
+    resolve(true)
+  })
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.goods
+  .code-input
+    font-size: 16px
+    font-weight: bold
+    border-bottom: 2px solid #0077ff
+  :deep(.van-cell--center)
+    padding: 5px 20px
+  :deep(.van-cell__title)
+    text-align: left !important
+  :deep(.van-cell__value)
+    width: 40% !important
+    flex: 0 0 40% !important
+    color: #000
+  .goods-number
+    text-align: left
+    font-size: 16px
+    padding-left: 20px
+    margin-top: 5px
+  .goods-tips
+    font-size: 12px
+    text-align: right
+    color: #333
+</style>

+ 19 - 0
src/views/inbound/takeDelivery/task/hooks/barcodeCombine.js

@@ -0,0 +1,19 @@
+import { barcodeToUpperCase } from '@/utils/dataType'
+
+/** 组合商品:匹配ASN明细中条码,附加 matchedJson */
+export function receivingBarcodeCombine(asnDetailList, combineSkuMap) {
+  if (!combineSkuMap || !Array.isArray(asnDetailList) || !asnDetailList.length) return []
+  const mapUpper = Object.fromEntries(
+    Object.entries(combineSkuMap).filter(([barcode]) => barcode).map(([barcode, config]) => [barcodeToUpperCase(barcode), config])
+  )
+  return asnDetailList
+    .map((item) => {
+      const barcodes = [item.barcode, item.barcode2, item.sku].filter(Boolean)
+      const matchedBar = barcodes.find((bar) => mapUpper[barcodeToUpperCase(bar)])
+      if (!matchedBar) return null
+      const matched = mapUpper[barcodeToUpperCase(matchedBar)]
+      const remaining = (item.expectedQuantity || 0) - (item.receivedQuantity || 0)
+      return remaining >= matched.quantity ? { ...item, matchedJson: matched } : null
+    })
+    .filter(Boolean)
+}

+ 131 - 60
src/views/inbound/takeDelivery/task/index.vue

@@ -102,7 +102,7 @@
               <span class="custom-title">{{ item.label }}</span>
             </template>
             <template #value>
-              <div>{{ item.mapping }}</div>
+              <div>{{ lotMap[item.mapping] ||  item.mapping }} </div>
             </template>
           </van-cell>
         </van-cell-group>
@@ -129,6 +129,8 @@
   <attribute ref="attributeRef" @set-attribute="setAttribute" />
   <!--  商品批次属性-->
   <lot-date ref="lotDateRef" @select-lot-date="selectLotDate" />
+  <!--  组合商品-->
+  <barcode-combine ref="barcodeCombineRef" @setCombine="setCombineReceiving" @cancel="onCombineCancel" :container="containerNo" :matched-sku="combineMatchedSku" />
   <!--  唯一码-->
   <unique-code-input ref="uniqueCodeRef"
                      v-model:uniqueCodeList="uniqueCodeList"
@@ -143,7 +145,7 @@
     v-model:show="lotQualityTrueFalseBy"
     cancel-text="取消"
     close-on-click-action
-    description="请选择质量状态"
+    :description="'请选择'+lotTitle"
   >
     <van-cell-group>
       <van-cell v-for="(value,key) in lotQualityMap" @click="onSelectLotQuality(key)">
@@ -168,6 +170,7 @@ import {
   getReceivingAsnDetails,
   setProductAttribute, setReceiving,
 } from '@/api/takeDelivery/index'
+import { getListCombineSku } from '@/api/picking'
 import { closeLoading, showLoading } from '@/utils/loading'
 import { useStore } from '@/store/modules/user'
 import { showNotify, showToast } from 'vant'
@@ -175,7 +178,9 @@ import { isAttribute } from '@/views/inbound/takeDelivery/task/hooks/attribute'
 import Attribute from '@/views/inbound/takeDelivery/components/Attribute.vue'
 import LotDate from '@/views/inbound/takeDelivery/components/LotDate.vue'
 import UniqueCodeInput from '@/views/inbound/takeDelivery/components/UniqueCodeInput.vue'
-import { barcodeToUpperCase, toMap } from '@/utils/dataType.js'
+import BarcodeCombine from '@/views/inbound/takeDelivery/components/BarcodeCombine.vue'
+import { receivingBarcodeCombine } from '@/views/inbound/takeDelivery/task/hooks/barcodeCombine'
+import { barcodeToUpperCase, toMap } from '@/utils/dataType'
 import { getCurrentTime } from '@/utils/date'
 
 const router = useRouter()
@@ -201,6 +206,8 @@ const taskInfo = ref({ receivedQty: 0, expectedQty: 0 })
 const currentTime = ref('--')
 const scanType = ref(2)
 
+//任务号下所有asn单数据
+const allAsnDetailList=ref([])
 const type=localStorage.getItem('checkAllType')?JSON.parse(localStorage.getItem('checkAllType')):true
 const checkAllType=ref(type)
 // 页面初始化
@@ -244,10 +251,9 @@ const stopTimer = () => {
 
 const back = ref(true)
 const inputBarcodeType = ref('task')
-//输入框组件
 const inputBarcodeRef = ref(null)
 const oldSearchBarcode = ref('')
-// 设置容器号
+// 任务号/容器号:code 为任务号时拉取任务及ASN明细
 const setBarcode = (code, type) => {
   if (inputBarcodeType.value === 'lot') {
     lotData.value.forEach((lot) => {
@@ -271,9 +277,6 @@ const setBarcode = (code, type) => {
         taskNo.value=''
         taskInfo.value={}
         switchTask()
-      }else {
-        taskInfo.value=res.data
-        taskNo.value=code
       }
       containerNo.value=''
       stopTimer()
@@ -284,11 +287,15 @@ const setBarcode = (code, type) => {
         startTimer()
         containerNo.value=''
       }
-      taskInfo.value=res.data
-      taskNo.value=code
     }
+    taskInfo.value=res.data
+    taskNo.value=code
     scanType.value=2
     uniqueCodeList.value=[]
+    const params = { warehouse, asnNos: taskInfo.value?.asnNos.join(',') }
+    getReceivingAsnDetails(params).then(res => {
+      allAsnDetailList.value = res.data
+    })
     scanSuccess()
   }).catch(err=>{
     inputBarcodeRef.value?.show('', '请扫描开单任务号',err.message)
@@ -298,9 +305,8 @@ const setBarcode = (code, type) => {
     closeLoading()
   })
 }
-// setBarcode('BSSH20250605000006')
+// setBarcode('BSSH20260318000003')
 
-//切换任务
 const switchTask = () => {
   inputBarcodeType.value = 'switchTask'
   back.value = false
@@ -314,17 +320,24 @@ const asnInfo = ref({})
 const reset = () => {
   asnInfo.value = {}
   lotData.value = []
+  lotMap.value={}
   searchCount.value = ''
   searchBarcode.value = ''
   oldSearchBarcode.value = ''
   uniqueCodeList.value = []
+  combineMatchedSku.value = []
+  combineAsnSelectList.value = []
+  isCombineSelectMode.value = false
+  combineReceivingData.value = []
 }
-// 选择单据
 const onDetailActive = (item) => {
+  if (isCombineSelectMode.value) {
+    _onCombineAsnSelected(item)
+    return
+  }
   asnInfo.value = item
   asnDetailsTrueFalseBy.value = false
   searchCount.value=1
-  // searchCount.value = asnInfo.value.expectedQuantity - asnInfo.value.receivedQuantity
   _getProductAttribute(item)
   _getProductLot(item)
   _getCommodityRule(item)
@@ -333,56 +346,126 @@ const onAsnCancel = () => {
   if (searchBarcode.value === '' || (oldSearchBarcode.value.length != searchBarcode.value.length && oldSearchBarcode.value != '')) {
     asnInfo.value = {}
     lotData.value = []
+    lotMap.value={}
     searchCount.value = ''
   }
 }
 const uniqueCodeList = ref([])
 
-// 扫描条码监听
+// 组合商品
+const barcodeCombineRef = ref(null)
+const combineMatchedSku = ref([])
+const combineAsnSelectList = ref([])
+const isCombineSelectMode = ref(false)
+const combineReceivingData = ref([]) // 确认实收数后暂存,完成收货时提交
+
+// 组合商品只支持1个
+const _handleCombineProduct = (code) => {
+  showLoading()
+  getListCombineSku({ combineSku: barcodeToUpperCase(code), workEnvironment: 'receiving' }).then((res) => {
+    const _err = (msg) => { closeLoading(); scanError(); showNotify({ type: 'danger', duration: 3000, message: msg }); reset() }
+    if (!res.data?.length) return _err(`${code}-商品条码不匹配,请重新扫描`)
+    if (res.data.length > 1) return _err('不支持多商品组合商品')
+    const combineData = res.data
+    const matchedList = receivingBarcodeCombine(allAsnDetailList.value, toMap(combineData, 'barcode'))
+    if (!matchedList.length) return _err('组合商品与待收货数据不匹配,请检查组合商品配置!')
+    const asnGroupMap = matchedList.reduce((acc, detail) => {
+      const key = detail.asnNo
+      if (!acc[key]) acc[key] = { asnNo: detail.asnNo, customerId: detail.customerId, expectedQuantity: 0, list: [] }
+      acc[key].list.push(detail)
+      acc[key].expectedQuantity += (detail.expectedQuantity || 0) - (detail.receivedQuantity || 0)
+      return acc
+    }, {})
+    const asnOptions = Object.values(asnGroupMap)
+    if (asnOptions.length > 1) {
+      isCombineSelectMode.value = true
+      combineAsnSelectList.value = asnOptions
+      combineMatchedSku.value = matchedList
+      asnDetailsList.value = asnOptions.map((opt) => ({ asnNo: opt.asnNo, customerId: opt.customerId, expectedQuantity: opt.expectedQuantity }))
+      asnDetailsTrueFalseBy.value = true
+    } else {
+      _showCombineDialog(matchedList)
+    }
+    closeLoading()
+    scanSuccess()
+  }).catch(() => { closeLoading(); scanError() })
+}
+
+const _showCombineDialog = (matchedList) => {
+  combineMatchedSku.value = matchedList
+  asnInfo.value = matchedList[0]
+  _getProductAttribute(matchedList[0])
+  _getProductLot(matchedList[0])
+  _getCommodityRule(matchedList[0])
+  barcodeCombineRef.value?.show()
+}
+
+// 组合商品取消:收货数量=1套总件数
+const onCombineCancel = () => {
+  const total = combineMatchedSku.value.reduce((sum, row) => sum + (row.matchedJson?.quantity || 0), 0)
+  searchCount.value = total ? String(total) : '1'
+  combineReceivingData.value = []
+}
+
+const _onCombineAsnSelected = (item) => {
+  const selected = combineAsnSelectList.value.find((opt) => opt.asnNo === item.asnNo)
+  if (selected?.list) _showCombineDialog(selected.list)
+  asnDetailsTrueFalseBy.value = isCombineSelectMode.value = false
+  combineAsnSelectList.value = []
+}
+
+// 组合商品确认实收数
+const setCombineReceiving = ({ dataList }) => {
+  if (!dataList?.length) return
+  const total = dataList.reduce((sum, row) => sum + (row.quantity || 0), 0)
+  searchCount.value = String(total)
+  combineReceivingData.value = dataList
+  showNotify({ type: 'success', duration: 2000, message: `已填入收货数量:${total},请点击完成收货提交` })
+}
+
+// 条码扫描:scanType 2商品/4数量/3唯一码/5容器
 const _handlerScan = (code) => {
   if (scanType.value == 2) {
     searchBarcode.value = code
     oldSearchBarcode.value = code
-    const params = { warehouse, barcode: code, asnNos: taskInfo.value?.asnNos.join(',') }
-    showLoading()
-    getReceivingAsnDetails(params).then(res => {
+      if ( allAsnDetailList.value.length > 0) {
+        const upperCode = barcodeToUpperCase(code) || ''
+        const clientMatched = allAsnDetailList.value.filter((detail) => {
+          const bars = [detail.barcode, detail.barcode2, detail.sku].filter(Boolean)
+          return bars.some((bar) => bar && barcodeToUpperCase(bar) === upperCode)
+        })
+        asnDetailsList.value = clientMatched
+      }
       uniqueCodeList.value=[]
-      asnDetailsList.value = res.data
-      if (res.data.length > 0) {
+
+      if (asnDetailsList.value.length > 0) {
         scanSuccess()
         closeLoading()
-        if (res.data.length == 1) {
-          const item = res.data[0]
+        if (asnDetailsList.value.length == 1) {
+          const item = asnDetailsList.value[0]
           asnInfo.value = item
-          // searchCount.value = item.expectedQuantity - item.receivedQuantity
-          searchCount.value=1
+          searchCount.value = 1
           _getProductAttribute(item)
           _getProductLot(item)
           _getCommodityRule(item)
         }
-        if (res.data.length > 1) {
+        if (asnDetailsList.value.length > 1) {
           asnInfo.value = {}
           lotData.value = []
+          lotMap.value={}
           searchCount.value = ''
           uniqueCodeList.value = []
           asnDetailsTrueFalseBy.value = true
         }
       } else {
-        scanError()
-        showNotify({ type: 'danger', duration: 3000, message: `暂未查询到条码《${code}》信息请重试` })
-        reset()
-        closeLoading()
+        _handleCombineProduct(code)
       }
-    }).catch(() => {
-      scanError()
-      closeLoading()
-    })
   } else if (scanType.value == 3) {
     if (code) {
       const uniqueCodeScanType = uniqueCodeRef.value?.uniqueCodeScanType
       if (checkAllType.value && uniqueCodeScanType === 'barcode') {
         const barcode = Array.from(new Set([asnInfo.value.barcode, asnInfo.value.barcode2, asnInfo.value.sku].filter(Boolean)));
-        if (barcode.some(item => barcodeToUpperCase(item) === barcodeToUpperCase(code))) {
+        if (barcode.some((bar) => barcodeToUpperCase(bar) === barcodeToUpperCase(code))) {
           scanSuccess();
           uniqueCodeRef.value.uniqueCodeScanType = 'unique'
           uniqueCodeRef.value.uniqueBarcode = code
@@ -428,9 +511,7 @@ const _handlerScan = (code) => {
     scanType.value=2
   }
 }
-/**
- * 物理属性
- */
+// 物理属性
 const attributeRef = ref(null)
 const attributeMap = ref({})
 const attributeTrueFalseBy = ref(true)
@@ -461,15 +542,10 @@ const setAttribute = (data) => {
     scanError()
   })
 }
-/**
- * 物理属性 end
- */
 
-/**
- * 商品批次属性
- */
-// 获取商品批次属性
+// 批次属性
 const lotData = ref([])
+const lotMap = ref({})
 const _getProductLot = (item) => {
   const params = { warehouse: item.warehouse, owner: item.customerId, barcode: item.sku }
   getProductLot(params).then(res => {
@@ -478,8 +554,12 @@ const _getProductLot = (item) => {
       if (lotField.startsWith('lotAtt') && lotField.length === 8) {
         lot.mapping = item[lotField]
       }
+      if(lot.format){
+        const format =JSON.parse(lot.format)
+        lotMap.value= {...lotMap.value, ...format }
+      }
     })
-    lotData.value = res.data
+    lotData.value = res.data.filter(item => item.lotAttFlag!='隐藏')
     _calculateShelfLife(item, lotData.value)
   })
 }
@@ -506,10 +586,12 @@ const lotField = ref('')
 const lotDateRef = ref(null)
 const lotQualityTrueFalseBy=ref(false)
 const lotQualityMap=ref({})
+const lotTitle=ref('质量状态')
 const onLot = (item) => {
+  lotTitle.value=item.label
   lotField.value = item.field
   if (item.field == 'lotAtt05' ) return
-  if ( item.field == 'lotAtt08'){
+  if (item.type == 'Enum' ){
     lotQualityMap.value=JSON.parse(item.format)
     lotQualityTrueFalseBy.value = true
     return
@@ -573,12 +655,7 @@ const selectLotDate = (date) => {
   inputBarcodeType.value = 'task'
 }
 
-/**
- * 商品批次属性end
- */
-/**
- * 唯一码
- */
+// 唯一码
 const uniqueCodeRef = ref(null)
 //规则列表
 const uniqueRuleList = ref([])
@@ -598,12 +675,9 @@ const _getCommodityRule = (item) => {
     uniqueRuleMap.value = toMap(res.data, 'type', 'uniqueRegExp')
   })
 }
-/**
- * 唯一码end
- */
 const containerNoRef = ref(null)
 const numberRef = ref(null)
-// 完成收货校验
+// 完成收货前校验
 const isCheck = () => {
   if (!asnInfo.value.asnNo) {
     scanError()
@@ -679,7 +753,7 @@ const isCheck = () => {
   }
   return true
 }
-// 收货
+// 完成收货
 const onConfirm = () => {
   if(isCheck()){
     const lotMap = toMap(lotData.value, 'field', 'mapping')
@@ -722,9 +796,6 @@ const loadData = () => {
   if (!taskNo.value) {
     inputBarcodeRef.value?.show('', '请扫描开单任务号','')
     return
-  } else {
-    // currentTime.value=getCurrentTime()
-    // startTimer()
   }
 }
 onUnmounted(() => {

+ 1 - 1
src/views/outbound/check/activity/index.vue

@@ -384,7 +384,7 @@ const weightConfirmed=ref(false) // 重量是否已确认
 const containerNoMap={
   'WH01':'FJ-WH01-20',
   'WH02':'FJ-WH02-20',
-  'WH10':'FJ-WH10-01',
+  'WH10':'FJ-WH10-1',
   'WH99':'FJ-WH99-01',
 }
 //返拣容器

+ 1 - 1
src/views/outbound/check/large/index.vue

@@ -632,7 +632,7 @@ const _reset=()=>{
 const containerNoMap={
   'WH01':'FJ-WH01-20',
   'WH02':'FJ-WH02-20',
-  'WH10':'FJ-WH10-01',
+  'WH10':'FJ-WH10-1',
   'WH99':'FJ-WH99-01',
 }
 //返拣容器

+ 105 - 0
src/views/outbound/check/moveStock/components/MoveStockCombine.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="goods">
+    <van-dialog v-model:show="goodsTrueFalseBy"
+                :beforeClose="beforeClose"
+                title="组合商品还库"
+                show-cancel-button>
+      <div style="width:100%;max-height:150px;overflow:auto">
+        <div v-for="(item,index) in matchedSkuList" :key="index">
+          <van-cell center :title="item.matchedJson.barcode" :label="item.matchedJson.skuName">
+            <template #value>
+              <div>{{ item.matchedJson.quantity }}件/套</div>
+              <div class="goods-tips">可还:{{ item.expectedQuantity || 0 }}件 </div>
+            </template>
+          </van-cell>
+          <div class="goods-lot">生产:{{ item.productionDate || '--' }}, 失效:{{ item.expirationDate || '--' }}</div>
+        </div>
+      </div>
+      <div class="goods-number">可还套数:{{ maxCount }}</div>
+      <van-field label="还库套数" type="number" class="code-input" v-model="count" ref="countRef" placeholder="还库套数" autocomplete="off" />
+    </van-dialog>
+  </div>
+</template>
+<script setup>
+/** 还库-组合商品弹框 */
+import { computed, ref } from 'vue'
+import { showToast } from 'vant'
+const goodsTrueFalseBy = ref(false)
+const countRef = ref(null)
+const count = ref('')
+const props = defineProps({
+  matchedSku: { type: Array, default: () => [] }
+})
+const matchedSkuList = computed(() => props.matchedSku)
+const maxCount = computed(() => {
+  const min = Math.min(
+    ...props.matchedSku.map((item) => (item.expectedQuantity || 0) / (item.matchedJson?.quantity || 1))
+  )
+  return Number.isFinite(min) ? Math.floor(min) : 0
+})
+const show = () => {
+  count.value = ''
+  goodsTrueFalseBy.value = true
+  setTimeout(() => {
+    countRef.value?.focus()
+  }, 200)
+}
+const dataResult = (data) =>
+  data.map((item) => {
+    const { matchedJson, ...rest } = item
+    return { ...rest, quantity: matchedJson.quantity * Number(count.value) }
+  })
+const emit = defineEmits(['setCombine', 'cancel'])
+const beforeClose = (action) =>
+  new Promise((resolve) => {
+    if (action === 'confirm') {
+      if (count.value == '') {
+        showToast('请输入还库套数')
+        return resolve(false)
+      }
+      if (Number(count.value) <= 0) {
+        showToast('请输入正确还库套数')
+        return resolve(false)
+      }
+      if (Number(count.value) > maxCount.value) {
+        showToast({ duration: 3000, message: '还库套数不能大于可还套数' })
+        return resolve(false)
+      }
+      const dataList = dataResult(matchedSkuList.value)
+      emit('setCombine', { dataList, count: Number(count.value) })
+    } else {
+      emit('cancel')
+    }
+    resolve(true)
+  })
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.goods
+  .code-input
+    font-size: 16px
+    font-weight: bold
+    border-bottom: 2px solid #0077ff
+  :deep(.van-cell--center)
+    padding: 5px 20px
+  :deep(.van-cell__title)
+    text-align: left !important
+  :deep(.van-cell__value)
+    width: 40% !important
+    flex: 0 0 40% !important
+    color: #000
+  .goods-number
+    text-align: left
+    font-size: 16px
+    padding-left: 20px
+    margin-top: 5px
+  .goods-tips
+    font-size: 12px
+    text-align: right
+    color: #333
+  .goods-lot
+    font-size: 12px
+    color: #666
+    margin-left: 20px
+    text-align: left
+</style>

+ 124 - 31
src/views/outbound/check/moveStock/index.vue

@@ -6,16 +6,15 @@
       fixed
       placeholder
       @click-left="goBack"
+      @click-right="refresh"
     >
       <template #left>
         <van-icon name="arrow-left" size="25" />
         <div  style="color: #fff">返回</div>
       </template>
-<!--      <template #right>-->
-<!--        <div class="nav-right" @click="onClickRight">-->
-<!--          <van-icon name="list-switch" size="25" />-->
-<!--        </div>-->
-<!--      </template>-->
+      <template #right>
+        <div style="color: #fff">刷新<van-icon name="replay" /></div>
+      </template>
     </van-nav-bar>
     <div class="move-stock">
       <div class="code">
@@ -32,6 +31,8 @@
             v-model="searchBarcode"
             placeholder="请扫描商品条码"
             @search="_handlerScan(searchBarcode)"
+            @input="onBarcodeChange"
+            @clear="onBarcodeClear"
             label="商品条码:"
             left-icon=""
             :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
@@ -124,6 +125,8 @@
     />
     <!-- 条码输入组件 -->
     <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
+    <!-- 组合商品还库 -->
+    <move-stock-combine ref="moveStockCombineRef" :matched-sku="combineMatchedSku" @set-combine="setCombineMoveStock" @cancel="onCombineCancel" />
     <van-dialog v-model:show="moveStockTrueFalseBy"
                 :beforeClose="beforeClose"
                 :title="'商品条码:'+ model.sku +',还库:'+location" show-cancel-button  >
@@ -171,17 +174,20 @@
 </template>
 
 <script setup>
-import { onMounted, onUnmounted, ref, computed } from 'vue'
-import { useRouter } from 'vue-router'
-import { useStore } from '@/store/modules/user'
-import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
-import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
-import { getInventory, getInventoryList, movementReturn } from '@/api/check/index'
+import {computed, onMounted, onUnmounted, ref} from 'vue'
+import {useRouter} from 'vue-router'
+import {useStore} from '@/store/modules/user'
+import {androidFocus, getHeader, goBack, scanError, scanSuccess} from '@/utils/android'
+import {closeListener, openListener, scanInit} from '@/utils/keydownListener'
+import {getInventory, getInventoryList, movementReturn} from '@/api/check/index'
 import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
-import { closeLoading, showLoading } from '@/utils/loading'
+import {closeLoading, showLoading} from '@/utils/loading'
 import nodataUrl from '@/assets/nodata.png'
-import { showNotify, showToast } from 'vant'
-import { barcodeToUpperCase } from '@/utils/dataType.js'
+import {showNotify, showToast} from 'vant'
+import {barcodeToUpperCase} from '@/utils/dataType.js'
+import {getListCombineSku} from '@/api/picking'
+import MoveStockCombine from './components/MoveStockCombine.vue'
+
 const router = useRouter()
 const store = useStore()
 try {
@@ -207,12 +213,17 @@ const totalList=ref([])
 const searchRef=ref(null)
 //扫描条码
 const searchBarcode=ref('')
+const oldSearchBarcode=ref('')
 //扫描库位
 const location=ref('')
 //
 const model=ref({})
 const countRef=ref(null)
 const back=ref(true)
+// 组合商品还库
+const moveStockCombineRef = ref(null)
+const combineData = ref(null)
+const combineMatchedSku = ref([])
 // 页面初始化
 onMounted(() => {
   openListener()
@@ -238,7 +249,8 @@ const setBarcode = (code) => {
   scanType.value = 2
   containerNo.value = code
   searchBarcode.value=''
-  moveList.value=''
+  moveList.value=[]
+  combineData.value=null
   location.value=''
   // if(searchBarcode.value){
   //   _getInventoryList(searchBarcode.value)
@@ -251,6 +263,23 @@ const _setContainerNo=()=>{
   inputBarcodeRef.value?.show('', '请扫描反拣容器')
 }
 
+// 商品条码调整时,清空还库推荐、库存列表、还库库位
+const onBarcodeChange = () => {
+  if (searchBarcode.value === '' || (oldSearchBarcode.value && oldSearchBarcode.value.length !== searchBarcode.value.length)) {
+    moveList.value = []
+    totalList.value = []
+    location.value = ''
+    combineData.value = null
+  }
+}
+const onBarcodeClear = () => {
+  moveList.value = []
+  totalList.value = []
+  location.value = ''
+  combineData.value = null
+  oldSearchBarcode.value = ''
+}
+
 // 扫描条码监听
 const _handlerScan = (code) => {
   console.log(code)
@@ -266,8 +295,35 @@ const _handlerScan = (code) => {
   }
 }
 
+// 商品条码查询为空时,查询组合商品列表
+const _handleCombineProduct = async (code) => {
+  try {
+    showLoading()
+    const res = await getListCombineSku({ combineSku: barcodeToUpperCase(code), workEnvironment: 'movement' })
+    closeLoading()
+    const _err = (msg) => {
+      scanError()
+      searchBarcode.value = ''
+      showNotify({ type: 'danger', duration: 5000, message: msg })
+    }
+    if (!res.data?.length) return _err(`条码:${code},未找到可还库库存,请检查条码!`)
+    if (res.data.length > 1) return _err('组合商品不支持多商品')
+    const innerBarcode = res.data[0].barcode
+    combineData.value = res.data[0]
+    searchBarcode.value = code
+    oldSearchBarcode.value = code
+    _getInventoryList(innerBarcode, true)
+    scanSuccess()
+  } catch (err) {
+    closeLoading()
+    scanError()
+    searchBarcode.value = ''
+    showNotify({ type: 'danger', duration: 5000, message: `条码查询失败,请检查条码!` })
+  }
+}
+
 // 获取库存数据
-const _getInventoryList = async (barcode) => {
+const _getInventoryList = async (barcode, fromCombineQuery = false) => {
   const data = { warehouse, location: containerNo.value, barcode }
   try {
     showLoading()
@@ -275,25 +331,43 @@ const _getInventoryList = async (barcode) => {
     closeLoading()
     moveList.value = res.data
     if (res.data.length === 0) {
-      scanError()
-      searchBarcode.value = ''
-      showNotify({ duration: 5000, message: `条码:${barcode},未找到可还库库存,请检查条码!` })
+      _handleCombineProduct(barcode)
       return
     }
+    // 仅当直接扫描普通条码命中时清除组合标记;从组合商品内件查询来时保留
+    if (!fromCombineQuery) combineData.value = null
     scanSuccess()
-    searchBarcode.value= res.data[0].sku
-    const params={ warehouse, barcode,locationRegexp:'^(?!STAGE_|SORTATION_|REVERSEPICK_|FJ-|TRANSFER_).*$',queryLocationInfo:true }
-    if(barcode==='') return
-    getInventory(params).then(res=>{
-      totalList.value=res.data
+    if (!fromCombineQuery) {
+      searchBarcode.value = res.data[0].sku
+      oldSearchBarcode.value = res.data[0].sku
+    }
+    const params = { warehouse, barcode, locationRegexp: '^(?!STAGE_|SORTATION_|REVERSEPICK_|FJ-|TRANSFER_).*$', queryLocationInfo: true }
+    if (barcode === '') return
+    getInventory(params).then(res => {
+      totalList.value = res.data
     })
-    scanType.value=3
+    scanType.value = 3
   } catch (err) {
     closeLoading()
     console.error(err)
   }
 }
 
+// 刷新
+const refresh = () => {
+  moveList.value = []
+  totalList.value = []
+  location.value = ''
+  combineData.value = null
+  scanType.value = 2
+  if (containerNo.value && searchBarcode.value) {
+    oldSearchBarcode.value = ''
+    _getInventoryList(searchBarcode.value)
+  } else {
+    loadData()
+  }
+}
+
 //移动数量
 const moveCount=ref('')
 //移动弹框
@@ -311,11 +385,30 @@ const onMove=(item)=>{
     return
   }
   model.value=item
-  moveCount.value=''
-  moveStockTrueFalseBy.value=true
-  setTimeout(()=>{
-    countRef.value?.focus()
-  },300)
+  if (combineData.value) {
+    combineMatchedSku.value = [{ ...item, matchedJson: combineData.value, expectedQuantity: item.availableQty }]
+    moveStockCombineRef.value?.show()
+  } else {
+    moveCount.value=''
+    moveStockTrueFalseBy.value=true
+    setTimeout(()=>{
+      countRef.value?.focus()
+    },300)
+  }
+}
+
+// 组合商品还库确认
+const setCombineMoveStock = ({ dataList }) => {
+  if (!dataList?.[0]) return
+  const row = dataList[0]
+  moveCount.value = String(row.quantity)
+  model.value = { ...model.value, ...row }
+  createMoveStock()
+}
+
+// 组合商品还库取消
+const onCombineCancel = () => {
+  moveCount.value = ''
 }
 /**
  * 确认还库
@@ -373,6 +466,7 @@ const  createMoveStock=()=>{
       location.value=''
       moveList.value=[]
       totalList.value=[]
+      combineData.value=null
     }else {
       location.value=''
       _getInventoryList(searchBarcode.value)
@@ -434,7 +528,6 @@ const onSelectMode = async (value) => {
     inputBarcodeRef.value?.show()
   }
 }
-
 // 数据刷新
 const loadData = () => {
   if(searchBarcode.value){

+ 11 - 0
src/views/processing/register/index.vue

@@ -99,6 +99,17 @@ const dateConfirm = (e) => {
 }
 //提交登记
 const onConfirm = () => {
+  scanError()
+  showConfirmDialog({
+    title: '温馨提示',
+    message:'该功能已下线,请使用新版加工',
+    theme: 'round-button',
+    confirmButtonText:'我知道了',
+    showCancelButton: false,
+  })
+      .then(() => {
+      }).catch(() => {})
+  return
   if(!ownerCode.value){
     showNotify({ type: 'warning', duration: 3000, message: '请选择货主' })
     scanError()