647556386
2026-01-22 68e75f8e483accd14d34ea64e228678c58b498bc
虚拟出入库合单优化
已修改8个文件
1914 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/AllcatedPallet.vue 576 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/Pallet.vue 534 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/outPicking.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/MESOperation/FeedbackMesService.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/TakeStockOrderService.cs 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 552 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/AllcatedPallet.vue
@@ -70,11 +70,11 @@
            <div class="input-label">物料条码</div>
            <el-input ref="barcodeInput" v-model="barcode" placeholder="请扫描或输入物料条码后按回车键" clearable
              :disabled="!form.locationType || !trayBarcode || !form.warehouseType"
              @keyup.enter.native="handleBarcodeSubmit" @clear="handleClear" @input="handleBarcodeInput"
              @keyup.enter.native="debounceHandleBarcodeSubmit" @clear="handleClear" @input="handleBarcodeInput"
              class="custom-input" size="medium">
              <template slot="append">
                <el-button :loading="loading" @click="handleBarcodeSubmit" type="primary" icon="el-icon-search"
                  :disabled="!form.locationType || !trayBarcode || !barcode || !from.warehouseType" size="medium">
                <el-button :loading="loading" @click="debounceHandleBarcodeSubmit" type="primary" icon="el-icon-search"
                  :disabled="!form.locationType || !trayBarcode || !barcode || !form.warehouseType" size="medium">
                  {{ loading ? '查询中...' : '查询' }}
                </el-button>
              </template>
@@ -84,7 +84,7 @@
          <div class="input-tips compact-tips">
            <p>提示:请先选择仓库 â†’ é€‰æ‹©ä»“库区域 â†’ è¾“入料箱码 â†’ è¾“入物料条码</p>
            <p v-if="!form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库</p>
            <p v-if="!form.locationType && !form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库区域</p>
            <p v-if="form.warehouseType && !form.locationType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库区域</p>
            <p v-if="form.warehouseType && form.locationType && !trayBarcode" class="warning-text">⚠️ è¯·å…ˆè¾“入料箱码</p>
          </div>
@@ -111,7 +111,6 @@
              <el-tag type="primary" size="small">未组盘 {{ totalStockCount }}</el-tag>
            </span>
          </div>
          <div class="table-container">
            <el-table :data="unpalletMaterials" stripe style="width: 100%" height="100%" size="small"
@@ -146,7 +145,7 @@
          <div v-if="materials.length === 0" class="empty-state compact">
            <i class="el-icon-document"></i>
            <p v-if="!form.warehouseType">请先选择仓库</p>
            <p v-if="!form.locationType">请先选择仓库区域</p>
            <p v-if="form.warehouseType && !form.locationType">请先选择仓库区域</p>
            <p v-else-if="!trayBarcode">请先输入料箱条码</p>
            <p v-else>暂无物料数据,请扫描或输入物料条码</p>
          </div>
@@ -171,9 +170,23 @@
    </template>
  </vol-box>
</template>
<script>
import http from '@/api/http.js';
import VolBox from '@/components/basic/VolBox.vue';
// é˜²æŠ–函数保持不变
function debounce(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}
export default {
  components: { VolBox },
@@ -193,27 +206,25 @@
      unpalletBarcodes: [],
      unpalletBarcodesLoading: false,
      unpalletMaterials: [], // æœªç»„盘详细数据列表
      unpalletMaterials: [],
      // æ‰«ç æžªç›¸å…³å˜é‡
      scanCode: '',
      lastKeyTime: null,
      isManualInput: false,
      isScanning: false,
      scanTimer: null,
      manualInputTimer: null,
      scanTarget: 'tray', // å½“前扫码目标: tray æˆ– material
      scanTarget: 'tray',
      isSubmitting: false,
      palletGroupedBarcodes: {},
      // åº“存统计相关变量
      totalStockSum: 0,
      totalStockCount: 0,
      uniqueUnit: '',
      sumLoading: false,
      sumError: '',
      // ä»“库相关变量
      warehouseTypes: [],
      warehouseLoading: false,
      // ä»“库区域相关变量
      locationTypes: [],
      locationLoading: false,
      form: {
@@ -245,22 +256,25 @@
  },
  computed: {
    // å½“前选择的仓库名称
    currentWarehouseName() {
      const warehouse = this.warehouseTypes.find(item => item.warehouseType === this.form.warehouseType);
      return warehouse ? warehouse.warehouseTypeDesc : '';
    },
    // å½“前选择的仓库区域描述
    currentLocationDesc() {
      const location = this.locationTypes.find(item => item.locationType === this.form.locationType)
      return location ? location.locationTypeDesc : ''
    },
    debounceHandleBarcodeSubmit() {
      return debounce(this.handleBarcodeSubmit, 500);
    }
  },
  watch: {
    visible(newVal, oldVal) {
      this.palletVisible = newVal;
      // å½“从 false å˜ä¸º true æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†æ‰“å¼€
      if (newVal === true && oldVal === false) {
        console.log('弹框打开,重置数据');
        this.resetData();
@@ -271,15 +285,16 @@
        });
      }
      // å½“从 true å˜ä¸º false æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†å…³é—­
      if (newVal === false && oldVal === true) {
        console.log('弹框关闭,重置数据');
        this.resetData();
      }
    },
    palletVisible(newVal) {
      this.$emit('update:visible', newVal);
    },
    docNo(newVal) {
      if (newVal) {
        this.palletForm = { palletCode: '', barcode: '' };
@@ -289,13 +304,16 @@
      }
    }
  },
  mounted() {
    document.addEventListener('keypress', this.handleKeyPress);
  },
  beforeDestroy() {
    document.removeEventListener('keypress', this.handleKeyPress);
    this.clearAllTimers();
  },
  methods: {
    open() {
      this.show = true;
@@ -305,6 +323,7 @@
      this.initwarehouseTypes();
      this.fetchUnpalletMaterialDetails();
    },
    validateLocationType(rule, value, callback) {
      if (!this.form.warehouseType) {
        callback(new Error('请先选择仓库'));
@@ -315,72 +334,104 @@
      }
    },
    // æ ¹æ®æ¡ç åˆ—表获取详细数据
    async fetchUnpalletMaterialDetails() {
      try {
        const response = await http.post('/api/InboundOrder/UnPalletGroupBarcode?orderNo=' + this.orderNo, {});
        if (response.status && Array.isArray(response.data)) {
          this.unpalletMaterials = response.data;
          this.unpalletBarcodes = response.data.map(item => item.barcode || '');
          this.totalStockCount = response.data.length;
        } else {
          this.unpalletMaterials = [];
        }
      } catch (err) {
        this.unpalletMaterials = this.unpalletBarcodes.map(barcode => ({
          barcode: barcode,
          materielCode: '-',
          batchNo: '-',
          stockQuantity: '-',
          unit: '-',
          supplyCode: '-',
          warehouseType: '-'
        }));
      }
    // èŽ·å–æœªç»„ç›˜ç‰©æ–™è¯¦æƒ…
    fetchUnpalletMaterialDetails() {
      this.unpalletBarcodesLoading = true;
      http.post('/api/InboundOrder/UnPalletGroupBarcode?orderNo=' + this.orderNo, {})
        .then(response => {
          if (response.status && Array.isArray(response.data)) {
            this.unpalletMaterials = response.data;
            this.unpalletBarcodes = response.data.map(item => item.barcode || '');
            this.totalStockCount = response.data.length;
          } else {
            this.unpalletMaterials = [];
          }
        })
        .catch(err => {
          console.error('获取未组盘物料失败:', err);
          this.unpalletMaterials = this.unpalletBarcodes.map(barcode => ({
            barcode: barcode,
            materielCode: '-',
            batchNo: '-',
            stockQuantity: '-',
            unit: '-',
            supplyCode: '-',
            warehouseType: '-'
          }));
        })
        .finally(() => {
          this.unpalletBarcodesLoading = false;
        });
    },
    async initLocationTypes() {
      try {
        const { data } = await this.http.post("api/LocationInfo/GetLocationTypes")
        this.locationTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    // åˆå§‹åŒ–仓库区域类型
    initLocationTypes() {
      this.locationLoading = true;
      this.http.post("api/LocationInfo/GetLocationTypes")
        .then(({ data }) => {
          this.locationTypes = data;
        })
        .catch(e => {
          console.error('获取区域类型失败:', e);
          this.$message.error('获取区域类型失败');
        })
        .finally(() => {
          this.locationLoading = false;
        });
    },
    async initwarehouseTypes() {
      try {
        const { data } = await this.http.post("api/Warehouse/GetwarehouseTypes")
        this.warehouseTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    // åˆå§‹åŒ–仓库类型
    initwarehouseTypes() {
      this.warehouseLoading = true;
      this.http.post("api/Warehouse/GetwarehouseTypes")
        .then(({ data }) => {
          this.warehouseTypes = data;
        })
        .catch(e => {
          console.error('获取仓库类型失败:', e);
          this.$message.error('获取仓库类型失败');
        })
        .finally(() => {
          this.warehouseLoading = false;
        });
    },
    async fetchStockStatistics(orderNo) {
      // å•据号为空时不查询
    // èŽ·å–åº“å­˜ç»Ÿè®¡
    fetchStockStatistics(orderNo) {
      if (!orderNo) {
        this.sumError = '单据号为空,无法统计';
        return;
        return Promise.resolve(null);
      }
      this.sumLoading = true;
      this.sumError = '';
      try {
        const response = await http.post('/api/InboundOrder/UnPalletQuantity?orderNo=' + orderNo, {});
        if (response.data) {
          this.totalStockSum = response.data.stockSumQuantity || 0;
          this.totalStockCount = response.data.stockCount || 0;
          this.uniqueUnit = response.data.uniqueUnit || '';
        }
      } catch (err) {
        this.sumError = '统计加载失败';
        this.totalStockSum = 0;
        this.totalStockCount = 0;
      } finally {
        this.sumLoading = false;
      }
      return http.post('/api/InboundOrder/UnPalletQuantity?orderNo=' + orderNo, {})
        .then(response => {
          if (response.data) {
            this.totalStockSum = response.data.stockSumQuantity || 0;
            this.totalStockCount = response.data.stockCount || 0;
            this.uniqueUnit = response.data.uniqueUnit || '';
          }
          return response.data;
        })
        .catch(err => {
          console.error('统计加载失败:', err);
          this.sumError = '统计加载失败';
          this.totalStockSum = 0;
          this.totalStockCount = 0;
          throw err;
        })
        .finally(() => {
          this.sumLoading = false;
        });
    },
    async validateForm() {
    // è¡¨å•验证
    validateForm() {
      return new Promise((resolve) => {
        if (!this.$refs.locationForm) {
          this.error = '表单未初始化';
@@ -394,10 +445,9 @@
            this.error = '';
            resolve(true);
          } else {
            if (!this.from.warehouseType) {
            if (!this.form.warehouseType) {
              this.error = '请先选择仓库';
            }
            else if (this.form.locationType === null || this.form.locationType === undefined || this.form.locationType === '') {
            } else if (!this.form.locationType) {
              this.error = '请先选择仓库区域';
            } else {
              this.error = '请检查表单填写是否正确';
@@ -407,7 +457,7 @@
        });
      });
    },
    // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
    focusTrayInput() {
      if (this.$refs.trayInput && this.$refs.trayInput.$el) {
        const inputEl = this.$refs.trayInput.$el.querySelector('input');
@@ -420,7 +470,6 @@
      }
    },
    // èšç„¦åˆ°ç‰©æ–™è¾“入框
    focusBarcodeInput() {
      if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
        const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
@@ -432,8 +481,10 @@
        }
      }
    },
    // é‡ç½®æ‰€æœ‰æ•°æ®
    resetData() {
      this.palletGroupedBarcodes = {};
      this.isSubmitting = false;
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
@@ -455,10 +506,10 @@
      this.form = {
        warehouseType: null,
        locationType: null
      }
      };
      this.warehouseTypes = [];
      this.locationTypes = [];
      // æ¸…除表单验证状态
      this.$nextTick(() => {
        if (this.$refs.locationForm) {
          this.$refs.locationForm.clearValidate();
@@ -466,7 +517,6 @@
      });
    },
    // æ¸…除所有计时器
    clearAllTimers() {
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
@@ -477,121 +527,108 @@
        this.scanTimer = null;
      }
    },
    handleDialogClose() {
      this.show = false;
      this.resetData();
    },
    // ç¡®è®¤æŒ‰é’®
    async handleConfirm() {
      if (!await this.validateForm()) return;
    handleConfirm() {
      this.validateForm()
        .then(valid => {
          if (!valid) return;
          if (this.materials.length === 0) {
            this.$message.warning('请至少添加一个物料');
            return;
          }
      if (this.materials.length === 0) {
        this.$message.warning('请至少添加一个物料');
        return;
      }
          if (!this.trayBarcode) {
            this.$message.warning('请输入托盘条码');
            return;
          }
      if (!this.trayBarcode) {
        this.$message.warning('请输入托盘条码');
        return;
      }
          const result = {
            warehouseType: this.form.warehouseType,
            warehouseName: this.currentWarehouseName,
            locationType: this.form.locationType,
            locationDesc: this.currentLocationDesc,
            trayBarcode: this.trayBarcode,
            materials: this.materials,
            docNo: this.docNo
          };
      const result = {
        warehouseType: this.form.warehouseType,
        warehouseName: this.currentWarehouseName,
        locationType: this.form.locationType,
        locationDesc: this.currentLocationDesc,
        trayBarcode: this.trayBarcode,
        materials: this.materials,
        docNo: this.docNo
      };
      // è§¦å‘父组件的 back-success äº‹ä»¶
      this.$emit('back-success', result);
      this.palletVisible = false;
          this.$emit('back-success', result);
          this.palletVisible = false;
        });
    },
    // å¤„理托盘输入
    handleTrayInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理物料输入
    handleBarcodeInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理托盘条码提交
    async handleTraySubmit() {
      // å…ˆç›´æŽ¥æ£€æŸ¥locationType,避免表单验证的异步问题
    handleTraySubmit() {
      this.barcode = '';
      this.materials = [];
      this.error = '';
      if (!this.form.warehouseType) {
        this.error = '请先选择仓库';
        return;
      }
      if (!this.form.locationType) {
        this.error = '请先选择仓库区域';
        //this.$message.warning('请先选择仓库区域');
        return;
      }
      // ç„¶åŽå†è¿›è¡Œå®Œæ•´çš„表单验证
      if (!await this.validateForm()) return;
      this.validateForm()
        .then(valid => {
          if (!valid) return;
          const currentTrayBarcode = this.trayBarcode.trim();
      const currentTrayBarcode = this.trayBarcode.trim();
          if (!currentTrayBarcode) {
            this.error = '请输入或扫描托盘条码';
            return;
          }
      if (!currentTrayBarcode) {
        this.error = '请输入或扫描托盘条码';
        return;
      }
          this.error = '';
      this.error = '';
          if (!this.trayBarcodeReg.test(currentTrayBarcode)) {
            this.$message("托盘号格式错误");
            this.focusTrayInput();
            return;
          }
      if (!this.trayBarcodeReg.test(currentTrayBarcode)) {
        // ElMessage.warning({
        //   message: '托盘号格式错误',
        //   type: 'warning',
        //   duration: 3000
        // })
        this.$message("托盘号格式错误");
        this.focusTrayInput();
        return;
      }
      // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
      this.focusBarcodeInput();
      // this.$message({
      //   message: `托盘条码已设置: ${currentTrayBarcode}`,
      //   type: 'success',
      //   duration: 2000
      // });
      this.$message.success(`托盘条码已设置: ${currentTrayBarcode}`);
          this.focusBarcodeInput();
          this.$message.success(`托盘条码已设置: ${currentTrayBarcode}`);
        });
    },
    // æ¸…除托盘
    clearTray() {
      this.trayBarcode = '';
      this.materials = [];
@@ -603,12 +640,10 @@
      });
    },
    // æ¸…空托盘输入
    handleTrayClear() {
      this.error = '';
    },
    // æ¸…空输入
    handleClear() {
      this.error = '';
      this.scanCode = '';
@@ -616,123 +651,171 @@
      this.isScanning = false;
    },
    // å¤„理物料条码提交
    async handleBarcodeSubmit() {
      if (!await this.validateForm()) return;
    handleBarcodeSubmit() {
      if (this.isSubmitting) {
        this.$message.warning('正在处理中,请稍候');
        return;
      }
      const currentBarcode = this.barcode.trim();
      if (!this.trayBarcode) {
        this.error = '请先输入托盘条码';
        this.focusTrayInput();
        return;
      }
      if (!currentBarcode) {
        this.error = '请输入或扫描物料条码';
        return;
      }
      this.focusBarcodeInput();
      this.error = '';
      this.loading = true;
      try {
        // è°ƒç”¨API查询物料信息
        const materialData = await this.fetchMaterialData(currentBarcode);
        if (!materialData || materialData.length === 0) {
          return;
        }
        this.materials = [];
        materialData.forEach(item => {
          this.materials.push({
            ...item,
            trayCode: this.trayBarcode,
            locationType: this.form.locationType,
            locationDesc: this.currentLocationDesc,
            scanTime: this.formatTime(new Date())
          });
        });
        this.orderNo = materialData[0].orderNo;
        await this.fetchStockStatistics(materialData[0].orderNo);
        await this.fetchUnpalletMaterialDetails();
        // æ¸…空物料输入框并保持聚焦
      const currentTrayGrouped = this.palletGroupedBarcodes[this.trayBarcode] || [];
      if (currentTrayGrouped.includes(currentBarcode)) {
        this.error = `条码 ${currentBarcode} å·²è¢«å½“前托盘组盘,请勿重复操作`;
        this.barcode = '';
        this.scanCode = ''; // æ¸…空扫码缓存
        this.isScanning = false;
        setTimeout(() => {
          this.focusBarcodeInput();
        }, 100);
      } catch (err) {
        this.error = err.message || '查询条码信息失败,请重试';
        this.focusBarcodeInput();
        setTimeout(() => {
          // é€‰ä¸­è¾“入框内的错误内容(确保focus完成后执行)
          const inputEl = this.$refs.barcodeInput?.$el?.querySelector('input');
          if (inputEl) inputEl.select();
        }, 100);
      } finally {
        this.loading = false;
        return;
      }
      this.isSubmitting = true;
      this.validateForm()
        .then(valid => {
          if (!valid) {
            this.isSubmitting = false;
            return;
          }
          if (!this.trayBarcode) {
            this.error = '请先输入托盘条码';
            this.focusTrayInput();
            this.isSubmitting = false;
            return;
          }
          if (!currentBarcode) {
            this.error = '请输入或扫描物料条码';
            this.isSubmitting = false;
            return;
          }
          this.focusBarcodeInput();
          this.error = '';
          this.loading = true;
          console.log('组盘请求参数:', {
            palletCode: this.trayBarcode,
            barcode: currentBarcode,
            locationTypeDesc: this.currentLocationDesc,
            locationType: this.form.locationType,
            warehouseType: this.form.warehouseType
          });
          return this.fetchMaterialData(currentBarcode);
        })
        .then(materialData => {
          if (!materialData || materialData.length === 0) {
            return;
          }
          this.materials = [];
          const newBarcodes = [];
          materialData.forEach(item => {
            this.materials.push({
              ...item,
              trayCode: this.trayBarcode,
              locationType: this.form.locationType,
              locationDesc: this.currentLocationDesc,
              scanTime: this.formatTime(new Date())
            });
            newBarcodes.push(item.barcode);
          });
          if (!this.palletGroupedBarcodes[this.trayBarcode]) {
            this.palletGroupedBarcodes[this.trayBarcode] = [];
          }
          this.palletGroupedBarcodes[this.trayBarcode] = [...new Set([...this.palletGroupedBarcodes[this.trayBarcode], ...newBarcodes])];
          this.orderNo = materialData[0].orderNo;
          return Promise.all([
            this.fetchStockStatistics(materialData[0].orderNo),
            this.fetchUnpalletMaterialDetails()
          ]);
        })
        .then(() => {
          this.barcode = '';
          this.scanCode = '';
          this.isScanning = false;
          setTimeout(() => {
            this.focusBarcodeInput();
          }, 100);
        })
        .catch(err => {
          console.error('处理物料条码失败:', err);
          this.error = err.message || '查询条码信息失败,请重试';
          this.focusBarcodeInput();
          setTimeout(() => {
            const inputEl = this.$refs.barcodeInput?.$el?.querySelector('input');
            if (inputEl) inputEl.select();
          }, 100);
        })
        .finally(() => {
          this.loading = false;
          this.isSubmitting = false;
        });
    },
    // API请求 - æ›¿æ¢ä¸ºå®žé™…çš„API调用
    async fetchMaterialData(barcode) {
      try {
        const response = await http.post('/api/Inbound/AllGroupPallet',
          {
            palletCode: this.trayBarcode,
            barcode: barcode,
            locationTypeDesc: this.currentLocationDesc,
            locationType: this.form.locationType, // æ·»åŠ ä»“åº“åŒºåŸŸä¿¡æ¯
            warehouseType: this.form.warehouseType
    // API请求
    fetchMaterialData(barcode) {
      return http.post('/api/Inbound/AllGroupPallet', {
        palletCode: this.trayBarcode,
        barcode: barcode,
        locationTypeDesc: this.currentLocationDesc,
        locationType: this.form.locationType,
        warehouseType: this.form.warehouseType
      })
        .then(response => {
          let materialData;
          if (typeof response.data === 'string') {
            try {
              materialData = JSON.parse(response.data);
            } catch (e) {
              console.error('解析响应数据失败:', e);
            }
          } else {
            materialData = response.data;
          }
        );
        let materialData;
        if (typeof response.data === 'string') {
          try {
            materialData = JSON.parse(response.data);
          } catch (e) {
          if (!response.status) {
            this.error = response.message || '查询条码信息失败,请重试';
            return [];
          }
        } else {
          materialData = response.data;
        }
        if (!response.status) {
          this.error = response.message || '查询条码信息失败,请重试';
        }
        return materialData;
      } catch (error) {
        console.error('API调用失败:', error);
      }
          return materialData || [];
        })
        .catch(error => {
          console.error('API调用失败:', error);
          this.$message.error('接口请求失败,请联系管理员');
          throw error;
        });
    },
    // å¤„理扫码枪输入
    handleKeyPress(event) {
      // å¦‚果是手动输入模式,不处理扫码枪逻辑
      if (this.isManualInput) {
      if (this.isManualInput || this.isSubmitting) {
        return;
      }
      const key = event.key;
      const currentTime = new Date().getTime();
      // å¿½ç•¥ç›´æŽ¥æŒ‰ä¸‹çš„回车键(由handleBarcodeSubmit处理)
      if (key === 'Enter') {
        event.preventDefault();
        if (this.scanCode.length > 0) {
          // é˜»æ­¢é»˜è®¤å›žè½¦è¡Œä¸ºï¼Œé¿å…è¡¨å•提交
          event.preventDefault();
          if (this.scanTarget === 'material' && !this.trayBarcode) {
            this.$message.warning('请先设置托盘条码');
            this.scanCode = '';
            this.lastKeyTime = null;
            return;
          }
          // æ‰«ç å®Œæˆï¼Œè‡ªåŠ¨è§¦å‘æŸ¥è¯¢
          this.isScanning = false;
          // æ ¹æ®å½“前扫码目标设置相应的输入框值
          if (this.scanTarget === 'tray') {
            this.trayBarcode = this.scanCode;
            this.handleTraySubmit();
@@ -746,7 +829,6 @@
        return;
      }
      // æž„建扫码内容(快速连续输入视为扫码)
      if (this.lastKeyTime && currentTime - this.lastKeyTime < 50) {
        this.scanCode += key;
        this.isScanning = true;
@@ -755,7 +837,6 @@
        this.isScanning = true;
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置扫描状态
      if (this.scanTimer) {
        clearTimeout(this.scanTimer);
      }
@@ -776,6 +857,23 @@
      const seconds = String(date.getSeconds()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    // ä»“库切换事件
    handleWarehouseChange() {
      this.form.locationType = null;
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
      this.error = '';
    },
    // åŒºåŸŸåˆ‡æ¢äº‹ä»¶
    handleLocationChange() {
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
      this.error = '';
    }
  }
}
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/Pallet.vue
@@ -175,7 +175,7 @@
import http from '@/api/http.js';
import VolBox from '@/components/basic/VolBox.vue';
// æ–°å¢žï¼šé˜²æŠ–函数
// é˜²æŠ–函数保持不变
function debounce(func, wait) {
  let timeout;
  return function () {
@@ -206,30 +206,25 @@
      unpalletBarcodes: [],
      unpalletBarcodesLoading: false,
      unpalletMaterials: [], // æœªç»„盘详细数据列表
      unpalletMaterials: [],
      // æ‰«ç æžªç›¸å…³å˜é‡
      scanCode: '',
      lastKeyTime: null,
      isManualInput: false,
      isScanning: false,
      scanTimer: null,
      manualInputTimer: null,
      scanTarget: 'tray', // å½“前扫码目标: tray æˆ– material
      isSubmitting: false, // æ–°å¢žï¼šæäº¤é”å®šï¼Œé˜²æ­¢é‡å¤è¯·æ±‚
      // ä¿®å¤ï¼šæ”¹ä¸ºæŒ‰æ‰˜ç›˜å­˜å‚¨å·²ç»„盘条码,避免状态混淆
      palletGroupedBarcodes: {}, // æ ¼å¼ï¼š{ [trayBarcode]: [barcode1, barcode2, ...] }
      scanTarget: 'tray',
      isSubmitting: false,
      palletGroupedBarcodes: {},
      // åº“存统计相关变量
      totalStockSum: 0,
      totalStockCount: 0,
      uniqueUnit: '',
      sumLoading: false,
      sumError: '',
      // ä»“库相关变量
      warehouseTypes: [],
      warehouseLoading: false,
      // ä»“库区域相关变量
      locationTypes: [],
      locationLoading: false,
      form: {
@@ -261,26 +256,25 @@
  },
  computed: {
    // å½“前选择的仓库名称
    currentWarehouseName() {
      const warehouse = this.warehouseTypes.find(item => item.warehouseType === this.form.warehouseType);
      return warehouse ? warehouse.warehouseTypeDesc : '';
    },
    // å½“前选择的仓库区域描述
    currentLocationDesc() {
      const location = this.locationTypes.find(item => item.locationType === this.form.locationType)
      return location ? location.locationTypeDesc : ''
    },
    // æ–°å¢žï¼šé˜²æŠ–后的物料提交方法
    debounceHandleBarcodeSubmit() {
      return debounce(this.handleBarcodeSubmit, 500);
    }
  },
  watch: {
    visible(newVal, oldVal) {
      this.palletVisible = newVal;
      // å½“从 false å˜ä¸º true æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†æ‰“å¼€
      if (newVal === true && oldVal === false) {
        console.log('弹框打开,重置数据');
        this.resetData();
@@ -291,15 +285,16 @@
        });
      }
      // å½“从 true å˜ä¸º false æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†å…³é—­
      if (newVal === false && oldVal === true) {
        console.log('弹框关闭,重置数据');
        this.resetData();
      }
    },
    palletVisible(newVal) {
      this.$emit('update:visible', newVal);
    },
    docNo(newVal) {
      if (newVal) {
        this.palletForm = { palletCode: '', barcode: '' };
@@ -309,13 +304,16 @@
      }
    }
  },
  mounted() {
    document.addEventListener('keypress', this.handleKeyPress);
  },
  beforeDestroy() {
    document.removeEventListener('keypress', this.handleKeyPress);
    this.clearAllTimers();
  },
  methods: {
    open() {
      this.show = true;
@@ -325,6 +323,7 @@
      this.initwarehouseTypes();
      this.fetchUnpalletMaterialDetails();
    },
    validateLocationType(rule, value, callback) {
      if (!this.form.warehouseType) {
        callback(new Error('请先选择仓库'));
@@ -335,74 +334,104 @@
      }
    },
    // æ ¹æ®æ¡ç åˆ—表获取详细数据
    async fetchUnpalletMaterialDetails() {
      try {
        const response = await http.post('/api/InboundOrder/UnPalletGroupBarcode?orderNo=' + this.orderNo, {});
        if (response.status && Array.isArray(response.data)) {
          this.unpalletMaterials = response.data;
          this.unpalletBarcodes = response.data.map(item => item.barcode || '');
          this.totalStockCount = response.data.length;
        } else {
          this.unpalletMaterials = [];
        }
      } catch (err) {
        this.unpalletMaterials = this.unpalletBarcodes.map(barcode => ({
          barcode: barcode,
          materielCode: '-',
          batchNo: '-',
          stockQuantity: '-',
          unit: '-',
          supplyCode: '-',
          warehouseType: '-'
        }));
      }
    // èŽ·å–æœªç»„ç›˜ç‰©æ–™è¯¦æƒ…
    fetchUnpalletMaterialDetails() {
      this.unpalletBarcodesLoading = true;
      http.post('/api/InboundOrder/UnPalletGroupBarcode?orderNo=' + this.orderNo, {})
        .then(response => {
          if (response.status && Array.isArray(response.data)) {
            this.unpalletMaterials = response.data;
            this.unpalletBarcodes = response.data.map(item => item.barcode || '');
            this.totalStockCount = response.data.length;
          } else {
            this.unpalletMaterials = [];
          }
        })
        .catch(err => {
          console.error('获取未组盘物料失败:', err);
          this.unpalletMaterials = this.unpalletBarcodes.map(barcode => ({
            barcode: barcode,
            materielCode: '-',
            batchNo: '-',
            stockQuantity: '-',
            unit: '-',
            supplyCode: '-',
            warehouseType: '-'
          }));
        })
        .finally(() => {
          this.unpalletBarcodesLoading = false;
        });
    },
    async initLocationTypes() {
      try {
        // ä¿®å¤ï¼šhttp æ”¹ä¸º this.$http(原代码错误)
        const { data } = await this.http.post("api/LocationInfo/GetLocationTypes")
        this.locationTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    // åˆå§‹åŒ–仓库区域类型
    initLocationTypes() {
      this.locationLoading = true;
      this.http.post("api/LocationInfo/GetLocationTypes")
        .then(({ data }) => {
          this.locationTypes = data;
        })
        .catch(e => {
          console.error('获取区域类型失败:', e);
          this.$message.error('获取区域类型失败');
        })
        .finally(() => {
          this.locationLoading = false;
        });
    },
    async initwarehouseTypes() {
      try {
        // ä¿®å¤ï¼šhttp æ”¹ä¸º this.$http(原代码错误)
        const { data } = await this.http.post("api/Warehouse/GetwarehouseTypes")
        this.warehouseTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    // åˆå§‹åŒ–仓库类型
    initwarehouseTypes() {
      this.warehouseLoading = true;
      this.http.post("api/Warehouse/GetwarehouseTypes")
        .then(({ data }) => {
          this.warehouseTypes = data;
        })
        .catch(e => {
          console.error('获取仓库类型失败:', e);
          this.$message.error('获取仓库类型失败');
        })
        .finally(() => {
          this.warehouseLoading = false;
        });
    },
    async fetchStockStatistics(orderNo) {
      // å•据号为空时不查询
    // èŽ·å–åº“å­˜ç»Ÿè®¡
    fetchStockStatistics(orderNo) {
      if (!orderNo) {
        this.sumError = '单据号为空,无法统计';
        return;
        return Promise.resolve(null);
      }
      this.sumLoading = true;
      this.sumError = '';
      try {
        const response = await http.post('/api/InboundOrder/UnPalletQuantity?orderNo=' + orderNo, {});
        if (response.data) {
          this.totalStockSum = response.data.stockSumQuantity || 0;
          this.totalStockCount = response.data.stockCount || 0;
          this.uniqueUnit = response.data.uniqueUnit || '';
        }
      } catch (err) {
        this.sumError = '统计加载失败';
        this.totalStockSum = 0;
        this.totalStockCount = 0;
      } finally {
        this.sumLoading = false;
      }
      return http.post('/api/InboundOrder/UnPalletQuantity?orderNo=' + orderNo, {})
        .then(response => {
          if (response.data) {
            this.totalStockSum = response.data.stockSumQuantity || 0;
            this.totalStockCount = response.data.stockCount || 0;
            this.uniqueUnit = response.data.uniqueUnit || '';
          }
          return response.data;
        })
        .catch(err => {
          console.error('统计加载失败:', err);
          this.sumError = '统计加载失败';
          this.totalStockSum = 0;
          this.totalStockCount = 0;
          throw err;
        })
        .finally(() => {
          this.sumLoading = false;
        });
    },
    async validateForm() {
    // è¡¨å•验证
    validateForm() {
      return new Promise((resolve) => {
        if (!this.$refs.locationForm) {
          this.error = '表单未初始化';
@@ -416,10 +445,9 @@
            this.error = '';
            resolve(true);
          } else {
            // ä¿®å¤ï¼šfrom æ”¹ä¸º form(原代码拼写错误)
            if (!this.form.warehouseType) {
              this.error = '请先选择仓库';
            } else if (this.form.locationType === null || this.form.locationType === undefined || this.form.locationType === '') {
            } else if (!this.form.locationType) {
              this.error = '请先选择仓库区域';
            } else {
              this.error = '请检查表单填写是否正确';
@@ -429,7 +457,7 @@
        });
      });
    },
    // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
    focusTrayInput() {
      if (this.$refs.trayInput && this.$refs.trayInput.$el) {
        const inputEl = this.$refs.trayInput.$el.querySelector('input');
@@ -442,7 +470,6 @@
      }
    },
    // èšç„¦åˆ°ç‰©æ–™è¾“入框
    focusBarcodeInput() {
      if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
        const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
@@ -454,11 +481,10 @@
        }
      }
    },
    // é‡ç½®æ‰€æœ‰æ•°æ®
    resetData() {
      // ä¿®å¤ï¼šæ¸…空按托盘存储的已组盘条码
      this.palletGroupedBarcodes = {};
      this.isSubmitting = false; // é‡ç½®æäº¤é”
      this.isSubmitting = false;
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
@@ -466,12 +492,12 @@
      this.unpalletMaterials = [];
      this.loading = false;
      this.error = '';
      this.scanCode = ''; // æ¸…空扫码缓存
      this.scanCode = '';
      this.lastKeyTime = null;
      this.isManualInput = false;
      this.isScanning = false;
      this.currentFocus = 'warehouse';
      this.scanTarget = 'tray'; // é‡ç½®æ‰«ç ç›®æ ‡
      this.scanTarget = 'tray';
      this.clearAllTimers();
      this.totalStockSum = 0;
      this.totalStockCount = 0;
@@ -480,10 +506,10 @@
      this.form = {
        warehouseType: null,
        locationType: null
      }
      };
      this.warehouseTypes = [];
      this.locationTypes = [];
      // æ¸…除表单验证状态
      this.$nextTick(() => {
        if (this.$refs.locationForm) {
          this.$refs.locationForm.clearValidate();
@@ -491,7 +517,6 @@
      });
    },
    // æ¸…除所有计时器
    clearAllTimers() {
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
@@ -502,81 +527,75 @@
        this.scanTimer = null;
      }
    },
    handleDialogClose() {
      this.show = false;
      this.resetData();
    },
    // ç¡®è®¤æŒ‰é’®
    async handleConfirm() {
      if (!await this.validateForm()) return;
    handleConfirm() {
      this.validateForm()
        .then(valid => {
          if (!valid) return;
          if (this.materials.length === 0) {
            this.$message.warning('请至少添加一个物料');
            return;
          }
      if (this.materials.length === 0) {
        this.$message.warning('请至少添加一个物料');
        return;
      }
          if (!this.trayBarcode) {
            this.$message.warning('请输入托盘条码');
            return;
          }
      if (!this.trayBarcode) {
        this.$message.warning('请输入托盘条码');
        return;
      }
          const result = {
            warehouseType: this.form.warehouseType,
            warehouseName: this.currentWarehouseName,
            locationType: this.form.locationType,
            locationDesc: this.currentLocationDesc,
            trayBarcode: this.trayBarcode,
            materials: this.materials,
            docNo: this.docNo
          };
      const result = {
        warehouseType: this.form.warehouseType,
        warehouseName: this.currentWarehouseName,
        locationType: this.form.locationType,
        locationDesc: this.currentLocationDesc,
        trayBarcode: this.trayBarcode,
        materials: this.materials,
        docNo: this.docNo
      };
      // è§¦å‘父组件的 back-success äº‹ä»¶
      this.$emit('back-success', result);
      this.palletVisible = false;
          this.$emit('back-success', result);
          this.palletVisible = false;
        });
    },
    // å¤„理托盘输入
    handleTrayInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理物料输入
    handleBarcodeInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理托盘条码提交
    async handleTraySubmit() {
      // ä¿®å¤ï¼šåˆ‡æ¢æ‰˜ç›˜æ—¶æ¸…空物料相关状态,避免旧数据残留
    handleTraySubmit() {
      this.barcode = '';
      this.materials = [];
      this.error = '';
      // å…ˆç›´æŽ¥æ£€æŸ¥locationType,避免表单验证的异步问题
      if (!this.form.warehouseType) {
        this.error = '请先选择仓库';
        return;
@@ -586,31 +605,30 @@
        return;
      }
      // ç„¶åŽå†è¿›è¡Œå®Œæ•´çš„表单验证
      if (!await this.validateForm()) return;
      this.validateForm()
        .then(valid => {
          if (!valid) return;
          const currentTrayBarcode = this.trayBarcode.trim();
      const currentTrayBarcode = this.trayBarcode.trim();
          if (!currentTrayBarcode) {
            this.error = '请输入或扫描托盘条码';
            return;
          }
      if (!currentTrayBarcode) {
        this.error = '请输入或扫描托盘条码';
        return;
      }
          this.error = '';
      this.error = '';
          if (!this.trayBarcodeReg.test(currentTrayBarcode)) {
            this.$message("托盘号格式错误");
            this.focusTrayInput();
            return;
          }
      if (!this.trayBarcodeReg.test(currentTrayBarcode)) {
        this.$message("托盘号格式错误");
        this.focusTrayInput();
        return;
      }
      // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
      this.focusBarcodeInput();
      this.$message.success(`托盘条码已设置: ${currentTrayBarcode}`);
          this.focusBarcodeInput();
          this.$message.success(`托盘条码已设置: ${currentTrayBarcode}`);
        });
    },
    // æ¸…除托盘
    clearTray() {
      this.trayBarcode = '';
      this.materials = [];
@@ -622,12 +640,10 @@
      });
    },
    // æ¸…空托盘输入
    handleTrayClear() {
      this.error = '';
    },
    // æ¸…空输入
    handleClear() {
      this.error = '';
      this.scanCode = '';
@@ -636,15 +652,15 @@
    },
    // å¤„理物料条码提交
    async handleBarcodeSubmit() {
    handleBarcodeSubmit() {
      if (this.isSubmitting) {
        this.$message.warning('正在处理中,请稍候');
        return;
      }
      // æ–°å¢žï¼šæ ¡éªŒå½“前条码是否已被当前托盘组盘
      const currentBarcode = this.barcode.trim();
      const currentTrayGrouped = this.palletGroupedBarcodes[this.trayBarcode] || [];
      if (currentTrayGrouped.includes(currentBarcode)) {
        this.error = `条码 ${currentBarcode} å·²è¢«å½“前托盘组盘,请勿重复操作`;
        this.barcode = '';
@@ -652,121 +668,135 @@
        return;
      }
      if (!await this.validateForm()) return;
      this.isSubmitting = true;
      this.validateForm()
        .then(valid => {
          if (!valid) {
            this.isSubmitting = false;
            return;
          }
          if (!this.trayBarcode) {
            this.error = '请先输入托盘条码';
            this.focusTrayInput();
            this.isSubmitting = false;
            return;
          }
      if (!this.trayBarcode) {
        this.error = '请先输入托盘条码';
        this.focusTrayInput();
        return;
      }
          if (!currentBarcode) {
            this.error = '请输入或扫描物料条码';
            this.isSubmitting = false;
            return;
          }
      if (!currentBarcode) {
        this.error = '请输入或扫描物料条码';
        return;
      }
      this.focusBarcodeInput();
      this.error = '';
      this.loading = true;
      this.isSubmitting = true; // å¼€å¯æäº¤é”
      try {
        // æ–°å¢žï¼šè°ƒè¯•日志,打印请求参数
        console.log('组盘请求参数:', {
          palletCode: this.trayBarcode,
          barcode: currentBarcode,
          locationTypeDesc: this.currentLocationDesc,
          locationType: this.form.locationType,
          warehouseType: this.form.warehouseType
        });
        // è°ƒç”¨API查询物料信息
        const materialData = await this.fetchMaterialData(currentBarcode);
        if (!materialData || materialData.length === 0) {
          return;
        }
        this.materials = [];
        const newBarcodes = []; // ä¸´æ—¶å­˜å‚¨æœ¬æ¬¡æ–°å¢žçš„æ¡ç 
        materialData.forEach(item => {
          this.materials.push({
            ...item,
            trayCode: this.trayBarcode,
            locationType: this.form.locationType,
            locationDesc: this.currentLocationDesc,
            scanTime: this.formatTime(new Date())
          });
          newBarcodes.push(item.barcode); // æ”¶é›†æœ¬æ¬¡ç»„盘的条码
        });
        // ä¿®å¤ï¼šæŒ‰æ‰˜ç›˜å­˜å‚¨å·²ç»„盘条码
        if (!this.palletGroupedBarcodes[this.trayBarcode]) {
          this.palletGroupedBarcodes[this.trayBarcode] = [];
        }
        this.palletGroupedBarcodes[this.trayBarcode] = [...new Set([...this.palletGroupedBarcodes[this.trayBarcode], ...newBarcodes])];
        this.orderNo = materialData[0].orderNo;
        await this.fetchStockStatistics(materialData[0].orderNo);
        await this.fetchUnpalletMaterialDetails();
        // æ¸…空物料输入框并保持聚焦
        this.barcode = '';
        this.scanCode = ''; // æ¸…空扫码缓存
        this.isScanning = false;
        setTimeout(() => {
          this.focusBarcodeInput();
        }, 100);
      } catch (err) {
        this.error = err.message || '查询条码信息失败,请重试';
        this.focusBarcodeInput();
        setTimeout(() => {
          // é€‰ä¸­è¾“入框内的错误内容(确保focus完成后执行)
          const inputEl = this.$refs.barcodeInput?.$el?.querySelector('input');
          if (inputEl) inputEl.select();
        }, 100);
      } finally {
        this.loading = false;
        this.isSubmitting = false; // å…³é—­æäº¤é”
      }
          this.error = '';
          this.loading = true;
          console.log('组盘请求参数:', {
            palletCode: this.trayBarcode,
            barcode: currentBarcode,
            locationTypeDesc: this.currentLocationDesc,
            locationType: this.form.locationType,
            warehouseType: this.form.warehouseType
          });
          return this.fetchMaterialData(currentBarcode);
        })
        .then(materialData => {
          if (!materialData || materialData.length === 0) {
            return;
          }
          this.materials = [];
          const newBarcodes = [];
          materialData.forEach(item => {
            this.materials.push({
              ...item,
              trayCode: this.trayBarcode,
              locationType: this.form.locationType,
              locationDesc: this.currentLocationDesc,
              scanTime: this.formatTime(new Date())
            });
            newBarcodes.push(item.barcode);
          });
          if (!this.palletGroupedBarcodes[this.trayBarcode]) {
            this.palletGroupedBarcodes[this.trayBarcode] = [];
          }
          this.palletGroupedBarcodes[this.trayBarcode] = [...new Set([...this.palletGroupedBarcodes[this.trayBarcode], ...newBarcodes])];
          this.orderNo = materialData[0].orderNo;
          return Promise.all([
            this.fetchStockStatistics(materialData[0].orderNo),
            this.fetchUnpalletMaterialDetails()
          ]);
        })
        .then(() => {
          this.barcode = '';
          this.scanCode = '';
          this.isScanning = false;
          setTimeout(() => {
            this.focusBarcodeInput();
          }, 100);
        })
        .catch(err => {
          console.error('处理物料条码失败:', err);
          this.error = err.message || '查询条码信息失败,请重试';
          this.focusBarcodeInput();
          setTimeout(() => {
            const inputEl = this.$refs.barcodeInput?.$el?.querySelector('input');
            if (inputEl) inputEl.select();
          }, 100);
        })
        .finally(() => {
          this.loading = false;
          this.isSubmitting = false;
        });
    },
    // API请求 - æ›¿æ¢ä¸ºå®žé™…çš„API调用
    async fetchMaterialData(barcode) {
      try {
        const response = await http.post('/api/Inbound/GroupPallet',
          {
            palletCode: this.trayBarcode,
            barcode: barcode,
            locationTypeDesc: this.currentLocationDesc,
            locationType: this.form.locationType, // æ·»åŠ ä»“åº“åŒºåŸŸä¿¡æ¯
            warehouseType: this.form.warehouseType
    // API请求
    fetchMaterialData(barcode) {
      return http.post('/api/Inbound/GroupPallet', {
        palletCode: this.trayBarcode,
        barcode: barcode,
        locationTypeDesc: this.currentLocationDesc,
        locationType: this.form.locationType,
        warehouseType: this.form.warehouseType
      })
        .then(response => {
          let materialData;
          if (typeof response.data === 'string') {
            try {
              materialData = JSON.parse(response.data);
            } catch (e) {
              console.error('解析响应数据失败:', e);
            }
          } else {
            materialData = response.data;
          }
        );
        let materialData;
        if (typeof response.data === 'string') {
          try {
            materialData = JSON.parse(response.data);
          } catch (e) { }
        } else {
          materialData = response.data;
        }
        if (!response.status) {
          this.error = response.message || '查询条码信息失败,请重试';
        }
        return materialData;
      } catch (error) {
        console.error('API调用失败:', error);
        this.$message.error('接口请求失败,请联系管理员');
        return [];
      }
          if (!response.status) {
            this.error = response.message || '查询条码信息失败,请重试';
            return [];
          }
          return materialData || [];
        })
        .catch(error => {
          console.error('API调用失败:', error);
          this.$message.error('接口请求失败,请联系管理员');
          throw error;
        });
    },
    // å¤„理扫码枪输入
    handleKeyPress(event) {
      // å¦‚果是手动输入模式或正在提交,不处理扫码枪逻辑
      if (this.isManualInput || this.isSubmitting) {
        return;
      }
@@ -774,11 +804,9 @@
      const key = event.key;
      const currentTime = new Date().getTime();
      // å¿½ç•¥ç›´æŽ¥æŒ‰ä¸‹çš„回车键(由handleBarcodeSubmit处理)
      if (key === 'Enter') {
        event.preventDefault(); // å¼ºåˆ¶é˜»æ­¢é»˜è®¤è¡Œä¸ºï¼Œé¿å…é‡å¤è§¦å‘
        event.preventDefault();
        if (this.scanCode.length > 0) {
          // æ–°å¢žï¼šæ‰«ç ç›®æ ‡ä¸ºç‰©æ–™æ—¶ï¼Œæ ¡éªŒå½“前托盘是否存在
          if (this.scanTarget === 'material' && !this.trayBarcode) {
            this.$message.warning('请先设置托盘条码');
            this.scanCode = '';
@@ -786,10 +814,8 @@
            return;
          }
          // æ‰«ç å®Œæˆï¼Œè‡ªåŠ¨è§¦å‘æŸ¥è¯¢
          this.isScanning = false;
          // æ ¹æ®å½“前扫码目标设置相应的输入框值
          if (this.scanTarget === 'tray') {
            this.trayBarcode = this.scanCode;
            this.handleTraySubmit();
@@ -803,7 +829,6 @@
        return;
      }
      // æž„建扫码内容(快速连续输入视为扫码)
      if (this.lastKeyTime && currentTime - this.lastKeyTime < 50) {
        this.scanCode += key;
        this.isScanning = true;
@@ -812,7 +837,6 @@
        this.isScanning = true;
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置扫描状态
      if (this.scanTimer) {
        clearTimeout(this.scanTimer);
      }
@@ -835,9 +859,8 @@
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    // ä»“库切换事件(补充实现,原代码缺失)
    // ä»“库切换事件
    handleWarehouseChange() {
      // åˆ‡æ¢ä»“库时清空区域和托盘相关状态
      this.form.locationType = null;
      this.trayBarcode = '';
      this.barcode = '';
@@ -845,9 +868,8 @@
      this.error = '';
    },
    // åŒºåŸŸåˆ‡æ¢äº‹ä»¶ï¼ˆè¡¥å……实现,原代码缺失)
    // åŒºåŸŸåˆ‡æ¢äº‹ä»¶
    handleLocationChange() {
      // åˆ‡æ¢åŒºåŸŸæ—¶æ¸…空托盘相关状态
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/NoStockOut.vue
@@ -33,8 +33,11 @@
              @input="handleOutboundInput"
              @keyup.enter="validateOutboundOrder"
              ref="outboundInputRef"
              :disabled="loading || submitLoading"
              :disabled="loading || submitLoading || isOutboundVerified"
            ></el-input>
            <!-- æ–°å¢žï¼šéªŒè¯çŠ¶æ€æç¤º -->
            <span v-if="isOutboundVerified" class="verified-tag">✓ å·²éªŒè¯</span>
            <span v-else-if="loading" class="loading-tag">✦ éªŒè¯ä¸­...</span>
          </el-form-item>
          <el-form-item label="采购单据:" name="purchaseOrderNo">
            <el-input
@@ -62,7 +65,6 @@
            label="扫描条码:"
            style="width: 80%"
            name="barcode"
            :rules="[{ required: true, message: '请扫描或输入条码', trigger: 'blur' }]"
          >
            <el-input
              ref="barcodeInputRef"
@@ -71,7 +73,7 @@
              @keydown.enter="debouncedHandleScan" 
              autofocus
              class="custom-input"
              :disabled="!orderForm.outboundOrderNo || loading || submitLoading"
              :disabled="!isOutboundVerified || loading || submitLoading"
            ></el-input>
          </el-form-item>
          <el-form-item>
@@ -80,7 +82,7 @@
              size="small" 
              @click="handleScan" 
              class="custom-button"
              :disabled="!orderForm.outboundOrderNo || loading || submitLoading"
              :disabled="!isOutboundVerified || loading || submitLoading"
            >
              <Search /> ç¡®è®¤æ‰«æ
            </el-button>
@@ -137,7 +139,8 @@
                  </div>
                </transition-group>
                <div class="empty-tip" v-if="scannedBarcodes.length === 0">
                  <span>暂无扫描记录,请先输入出库单据后扫描条码</span>
                  <span v-if="isOutboundVerified">暂无扫描记录,请扫描条码</span>
                  <span v-else>请先输入并验证有效的出库单据号</span>
                </div>
              </el-scrollbar>
            </div>
@@ -151,7 +154,7 @@
            type="primary" 
            size="small" 
            @click="submit" 
            :disabled="scannedBarcodes.length === 0 || !orderForm.outboundOrderNo || loading || submitLoading"
            :disabled="scannedBarcodes.length === 0 || !isOutboundVerified || loading || submitLoading"
            class="submit-btn"
          >
            <Check /> æäº¤å‡ºåº“
@@ -196,6 +199,8 @@
const loading = ref(false);
// æ–°å¢žï¼šæäº¤ä¸“属loading状态,控制遮罩层显示
const submitLoading = ref(false);
// æ ¸å¿ƒæ–°å¢žï¼šå‡ºåº“单验证状态标识
const isOutboundVerified = ref(false);
// æ¨¡æ¿å¼•用
const formRef = ref(null);
@@ -229,6 +234,7 @@
  orderForm.outboundOrderNo = "";
  orderForm.purchaseOrderNo = "";
  submitLoading.value = false; // æ‰“开弹窗时重置提交loading
  isOutboundVerified.value = false; // æ‰“开弹窗时重置出库单验证状态
  nextTick(() => {
    outboundInputRef.value?.focus(); // æ‰“开弹窗仍聚焦出库单输入框
  });
@@ -236,11 +242,12 @@
/**
 * éªŒè¯å‡ºåº“单据号的有效性
 * æ ¸å¿ƒä¿®æ”¹ï¼šéªŒè¯æˆåŠŸåŽç›´æŽ¥èšç„¦æ¡ç æ‰«ææ¡†ï¼Œå¤±è´¥åˆ™èšç„¦å›žå‡ºåº“å•è¾“å…¥æ¡†
 * æ ¸å¿ƒä¿®æ”¹ï¼šéªŒè¯æˆåŠŸåŽæ ‡è®°isOutboundVerified为true,失败则重置为false
 */
const validateOutboundOrder = async () => {
  const outboundOrderNo = orderForm.outboundOrderNo.trim();
  if (!outboundOrderNo) {
    ElMessage.warning("请输入出库单据号");
    return;
  }
@@ -252,8 +259,9 @@
    );
    if (res.status !== true) {
      // éªŒè¯å¤±è´¥ï¼šæ¸…空输入框,提示错误,聚焦回出库单输入框
      // éªŒè¯å¤±è´¥ï¼šæ¸…空输入框,提示错误,重置验证状态,聚焦回出库单输入框
      orderForm.outboundOrderNo = "";
      isOutboundVerified.value = false;
      ElMessage.error(res.message || "出库单据号验证失败,请检查单据号是否正确");
      nextTick(() => {
        outboundInputRef.value?.focus(); // å¤±è´¥èšç„¦å‡ºåº“单输入框
@@ -261,14 +269,16 @@
      return;
    }
    // éªŒè¯æˆåŠŸï¼šæç¤ºç”¨æˆ·ï¼Œç›´æŽ¥èšç„¦æ¡ç æ‰«ææ¡†ï¼ˆæ ¸å¿ƒä¿®æ”¹ï¼‰
    // éªŒè¯æˆåŠŸï¼šæ ‡è®°éªŒè¯çŠ¶æ€ä¸ºtrue,提示用户,聚焦条码扫描框
    isOutboundVerified.value = true;
    ElMessage.success("出库单据号验证通过");
    nextTick(() => {
      barcodeInputRef.value?.focus(); // æˆåŠŸç›´æŽ¥è·³è½¬åˆ°æ¡ç æ‰«ææ¡†
    });
  } catch (error) {
    // æŽ¥å£å¼‚常:清空输入框,提示错误,聚焦回出库单输入框
    // æŽ¥å£å¼‚常:清空输入框,提示错误,重置验证状态,聚焦回出库单输入框
    orderForm.outboundOrderNo = "";
    isOutboundVerified.value = false;
    ElMessage.error(`出库单据号验证异常:${error.message || "网络错误,请重试"}`);
    nextTick(() => {
      outboundInputRef.value?.focus(); // å¼‚常聚焦出库单输入框
@@ -280,8 +290,9 @@
// å‡ºåº“单输入处理
const handleOutboundInput = (value) => {
  // æ ¸å¿ƒä¿®æ”¹ï¼šè¾“入时自动重置验证状态(防止手动修改已验证的出库单号)
  if (value && value.trim()) {
    // å¯ä¿ç•™å‡ºåº“单号格式验证逻辑
    isOutboundVerified.value = false;
  }
};
@@ -294,10 +305,13 @@
// èšç„¦æ¡ç è¾“入框(复用函数)
const focusBarcodeInputDirectly = () => {
  if (orderForm.outboundOrderNo.trim()) {
  if (isOutboundVerified.value) {
    barcodeInputRef.value?.focus();
  } else {
    ElMessage.warning("请先输入有效的出库单据号");
    ElMessage.warning("请先输入并验证有效的出库单据号");
    nextTick(() => {
      outboundInputRef.value?.focus();
    });
  }
};
@@ -323,6 +337,15 @@
// æ‰«ææ¡ç æ ¸å¿ƒé€»è¾‘
const handleScan = async () => {
  // æ ¸å¿ƒæ–°å¢žï¼šå‰ç½®æ ¡éªŒï¼Œç¡®ä¿å‡ºåº“单已验证
  if (!isOutboundVerified.value) {
    ElMessage.warning("请先验证有效的出库单据号后再扫描条码");
    nextTick(() => {
      outboundInputRef.value?.focus();
    });
    return;
  }
  if (!formRef.value) return;
  await formRef.value.validateField('barcode');
@@ -347,8 +370,8 @@
      orderForm.purchaseOrderNo = purchaseOrderNo;
    } else {
      ElMessage.info("未查询到该条码对应的采购单号,继续验证条码有效性");
      formData.barcode = ""; // æ–°å¢žï¼šéªŒè¯å¤±è´¥æ—¶æ¸…空输入框
      nextTick(() => barcodeInputRef.value?.focus()); // æ–°å¢žï¼šèšç„¦æ¡ç è¾“入框
      formData.barcode = "";
      nextTick(() => barcodeInputRef.value?.focus());
    }
    // æ­¥éª¤2:验证条码并获取物料信息
@@ -361,8 +384,8 @@
    if (validateRes.status === true) {
      if (!Array.isArray(validateRes.data) || validateRes.data.length === 0) {
        ElMessage.warning("该条码验证成功,但未返回物料信息");
        formData.barcode = ""; // æ–°å¢žï¼šæ— ç‰©æ–™ä¿¡æ¯æ—¶ä¹Ÿæ¸…空输入框
        nextTick(() => barcodeInputRef.value?.focus()); // æ–°å¢žï¼šèšç„¦æ¡ç æ¡†
        formData.barcode = "";
        nextTick(() => barcodeInputRef.value?.focus());
      } else {
        const newItems = validateRes.data.map(item => ({
          barcode: item.barcode || '',
@@ -379,13 +402,12 @@
      }
    } else {
      ElMessage.error("扫描失败:" + (validateRes.message || '条码验证失败'));
      formData.barcode = ""; // æ–°å¢žï¼šéªŒè¯å¤±è´¥æ—¶æ¸…空输入框
      nextTick(() => barcodeInputRef.value?.focus()); // æ–°å¢žï¼šèšç„¦æ¡ç è¾“入框
      formData.barcode = "";
      nextTick(() => barcodeInputRef.value?.focus());
    }
  } catch (error) {
    ElMessage.error(error.message);
    formData.barcode = ""; // ä¿ç•™ï¼šæŽ¥å£å¼‚常时清空
    // æ–°å¢žï¼šå¼‚常时也强制聚焦条码框
    formData.barcode = "";
    nextTick(() => barcodeInputRef.value?.focus());
  } finally {
    loading.value = false;
@@ -437,9 +459,18 @@
// æäº¤å‡ºåº“
const submit = async () => {
  // æ ¸å¿ƒæ–°å¢žï¼šå‰ç½®æ ¡éªŒå‡ºåº“单验证状态
  if (!isOutboundVerified.value) {
    ElMessage.warning("出库单据号未验证,无法提交");
    nextTick(() => {
      outboundInputRef.value?.focus();
    });
    return;
  }
  if (scannedBarcodes.value.length === 0) {
    ElMessage.warning("请先扫描至少一条条码");
    nextTick(() => barcodeInputRef.value?.focus()); // æäº¤å¤±è´¥èšç„¦æ¡ç æ¡†
    nextTick(() => barcodeInputRef.value?.focus());
    return;
  }
@@ -460,13 +491,14 @@
      showDetailBox.value = false;
      scannedBarcodes.value = [];
      orderForm.purchaseOrderNo = "";
      isOutboundVerified.value = false; // æäº¤æˆåŠŸåŽé‡ç½®éªŒè¯çŠ¶æ€
    } else {
      ElMessage.error("出库提交失败:" + (res.message || '提交失败'));
      nextTick(() => barcodeInputRef.value?.focus()); // æäº¤å¤±è´¥èšç„¦æ¡ç æ¡†
      nextTick(() => barcodeInputRef.value?.focus());
    }
  } catch (error) {
    ElMessage.error("出库提交异常:" + error.message);
    nextTick(() => barcodeInputRef.value?.focus()); // å¼‚常聚焦条码框
    nextTick(() => barcodeInputRef.value?.focus());
  } finally {
    // å…³é—­æäº¤loading,隐藏遮罩层
    submitLoading.value = false;
@@ -525,6 +557,25 @@
  animation: el-loading-circle 1.5s linear infinite;
}
/* æ–°å¢žï¼šéªŒè¯çŠ¶æ€æ ‡ç­¾æ ·å¼ */
.verified-tag {
  color: #67c23a;
  font-size: 12px;
  margin-left: 8px;
  font-weight: 500;
}
.loading-tag {
  color: #409eff;
  font-size: 12px;
  margin-left: 8px;
  font-weight: 500;
  animation: spin 1s linear infinite;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.scan-list {
  width: 100%;
}
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/outPicking.vue
@@ -499,43 +499,81 @@
            }
        },
        loadUnpickedData() {
            return new Promise((resolve, reject) => {
                this.http.post(`/api/Outbound/QueryPickingTasks?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => {
                    if (response.status) {
                        if (response.data.outStockLockInfos.length > 0) {
                            this.unpickedData = response.data.outStockLockInfos;
                            this.matMixed = response.data.isMatMixed;
                            this.orderOver = response.data.orderOver;
                            this.calculateUnpickedStats()
                            // æ£€æŸ¥æ˜¯å¦éœ€è¦æ•´å‡ºç¡®è®¤
                            this.$nextTick(() => {
                                if (this.hasWholeOut()) {
                                    this.showWholeOutConfirm()
                                }
                            })
                            // è‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†
                            this.$nextTick(() => {
                                if (this.$refs.materialInput) {
                                    this.$refs.materialInput.focus()
                                }
                            })
                        } else {
                            if (flag) {
                                this.$message.warning('该托盘无未拣选任务')
                            }
                            this.unpickedData = []
    return new Promise((resolve, reject) => {
        // å…ˆæ¸…空之前的提示,避免重复提示
        this.$message.closeAll();
        this.http.post(`/api/Outbound/QueryPickingTasks?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => {
            if (response.status) {
                // æƒ…况1:有未拣选数据
                if (response.data.outStockLockInfos && response.data.outStockLockInfos.length > 0) {
                    this.unpickedData = response.data.outStockLockInfos;
                    this.matMixed = response.data.isMatMixed;
                    this.orderOver = response.data.orderOver;
                    this.calculateUnpickedStats()
                    // æ£€æŸ¥æ•´å‡ºç¡®è®¤
                    this.$nextTick(() => {
                        if (this.hasWholeOut()) {
                            this.showWholeOutConfirm()
                        }
                        resolve()
                    } else {
                        this.$message.error(response.message || '获取托盘数据失败')
                        this.unpickedData = []
                        reject(response.message || '获取托盘数据失败')
                    }
                }).catch(error => {
                    reject(error)
                })
            })
        },
                    })
                    // èšç„¦ç‰©æ–™æ¡ç è¾“入框
                    this.$nextTick(() => {
                        if (this.$refs.materialInput) {
                            this.$refs.materialInput.focus()
                        }
                    })
                }
                // æƒ…况2:无未拣选数据
                else {
                    this.unpickedData = [];
                    this.calculateUnpickedStats(); // é‡ç½®ç»Ÿè®¡æ•°æ®
                    // å‹å¥½æç¤º + äº¤äº’优化
                    this.$message.warning({
                        message: `托盘【${this.scanForm.palletCode}】暂无拣选记录`,
                        duration: 3000, // æç¤ºæ˜¾ç¤º3秒
                        showClose: true // å…è®¸æ‰‹åЍ关闭
                    });
                    // æ¸…空物料条码并聚焦回托盘码输入框
                    this.scanForm.materialBarcode = '';
                    this.$nextTick(() => {
                        if (this.$refs.palletInput) {
                            this.$refs.palletInput.focus();
                        }
                    });
                }
                resolve();
            }
            // æƒ…况3:接口返回失败
            else {
                this.unpickedData = [];
                this.calculateUnpickedStats();
                this.$message.error({
                    message: response.message || `获取托盘【${this.scanForm.palletCode}】拣选数据失败`,
                    duration: 5000,
                    showClose: true
                });
                reject(response.message || '获取托盘数据失败');
            }
        }).catch(error => {
            // æƒ…况4:网络/请求异常
            this.unpickedData = [];
            this.calculateUnpickedStats();
            this.$message.error({
                message: `获取托盘【${this.scanForm.palletCode}】拣选数据异常:${error.message || '网络错误,请重试'}`,
                duration: 5000,
                showClose: true
            });
            reject(error);
        })
    })
},
        loadPickedData() {
            return new Promise((resolve, reject) => {
@@ -546,8 +584,10 @@
                            this.calculatePickedStats()
                        } else {
                            this.pickedData = []
                        }
                        resolve()
                    } else {
                        this.$message.error(response.message || '获取托盘数据失败')
                        this.pickedData = []
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/MESOperation/FeedbackMesService.cs
@@ -56,6 +56,11 @@
                {
                    return webResponse = WebResponseContent.Instance.OK($"该单据无需回传MES");
                }
                if (outboundOrder.IsBatch == 0 && outboundOrder.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt())
                {
                    return webResponse = WebResponseContent.Instance.OK($"该单据属于不分批自动回传,不可手动分批回传");
                }
                List<Dt_MesReturnRecord> returnRecords = BaseDal.QueryData(x => x.OrderNo == orderNo && x.OrderId == outboundOrder.Id && x.ReturnStatus == 2);
                foreach (var item in returnRecords)
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/TakeStockOrderService.cs
@@ -30,6 +30,7 @@
using WIDESEA_DTO.CalcOut;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using WIDESEA_DTO.Basic;
namespace WIDESEA_InboundService
{
@@ -50,7 +51,8 @@
        private readonly IRepository<Dt_StockInfoDetail> _stockInfoDetailRepository;
        private readonly IOutboundService _outboundService;
        private readonly IFeedbackMesService _feedbackMesService;
        public TakeStockOrderService(IRepository<Dt_TakeStockOrder> BaseDal, IUnitOfWorkManage unitOfWorkManage,IRepository<Dt_TakeStockOrder> takeStockOrder,IRepository<Dt_StockInfo> stockInfoRepository,IRepository<Dt_TakeStockOrderDetail> takeStockOrderDetail,IRepository<Dt_Task> taskRepository,ILocationInfoService locationInfoService, IRepository<Dt_InboundOrder> inboundOrderRepository,IRepository<Dt_OutboundOrder> outboundOrderRepository,IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_OutboundOrderDetail> outboundOrderDetailRepository, IOutboundPickingService outboundPickingService, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IOutboundService outboundService,IFeedbackMesService feedbackMesService) : base(BaseDal)
        private readonly IESSApiService _eSSApiService;
        public TakeStockOrderService(IRepository<Dt_TakeStockOrder> BaseDal, IUnitOfWorkManage unitOfWorkManage,IRepository<Dt_TakeStockOrder> takeStockOrder,IRepository<Dt_StockInfo> stockInfoRepository,IRepository<Dt_TakeStockOrderDetail> takeStockOrderDetail,IRepository<Dt_Task> taskRepository,ILocationInfoService locationInfoService, IRepository<Dt_InboundOrder> inboundOrderRepository,IRepository<Dt_OutboundOrder> outboundOrderRepository,IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_OutboundOrderDetail> outboundOrderDetailRepository, IOutboundPickingService outboundPickingService, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IOutboundService outboundService,IFeedbackMesService feedbackMesService,IESSApiService eSSApiService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _takeStockOrder = takeStockOrder;
@@ -66,8 +68,20 @@
            _stockInfoDetailRepository = stockInfoDetailRepository;
            _outboundService = outboundService;
            _feedbackMesService = feedbackMesService;
            _eSSApiService = eSSApiService;
        }
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
            {"2-1","2-9" },
            {"3-1","3-9" },
        };
        private Dictionary<string, string> movestations = new Dictionary<string, string>
        {
            {"2-1","2-5" },
            {"3-1","3-5" },
        };
        public WebResponseContent ValidateBoxNo(string orderNo, string boxNo)
        {
            try
@@ -253,7 +267,7 @@
                    NextAddress = "",
                    OrderNo = takeStockOrder.OrderNo,
                    Roadway = newLocation.RoadwayNo,
                    SourceAddress = sourceAddress,
                    SourceAddress = stations.GetValueOrDefault(sourceAddress)??"",
                    TargetAddress = newLocation.LocationCode,
                    TaskStatus = (int)TaskStatusEnum.New,
                    TaskType = TaskTypeEnum.InInventory.ObjToInt(),
@@ -271,11 +285,11 @@
                _taskRepository.AddData(newTask);
                _unitOfWorkManage.CommitTran();
                //var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest
                //{
                //    slotCode = movestations[station],
                //    containerCode = palletCode
                //});
                var moveResult = _eSSApiService.MoveContainerAsync(new MoveContainerRequest
                {
                    slotCode = movestations[sourceAddress],
                    containerCode = boxNo
                });
                return content.OK();
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -2336,258 +2336,370 @@
            return WebResponseContent.Instance.OK("成功");
        }
        public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut)
    public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut)
    {
        try
        {
            try
            Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>()
                .Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt())
                .Includes(x => x.Details)
                .First();
            if (inboundOrder == null)
            {
                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
                }
                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
                if (outboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
                }
                return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
            }
                //存储入库单据明细信息
                var detailLists = new List<Dt_InboundOrderDetail>();
            Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt())
                .Includes(x => x.Details)
                .First();
            if (outboundOrder == null)
            {
                return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.outOder}");
            }
                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                    detail.Barcode == noStockOut.barCode &&
            var detailLists = new List<Dt_InboundOrderDetail>();
            var matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                detail.Barcode == noStockOut.barCode &&
                detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
            if (matchedDetail == null)
            {
                matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                    detail.OutBoxbarcodes == noStockOut.barCode &&
                    detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedDetail == null)
                {
                    matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                        detail.OutBoxbarcodes == noStockOut.barCode &&
                        detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                    if (matchedDetail == null)
                    {
                        return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„æ˜Žç»†ã€‚");
                    }
                    else
                    {
                        // æ·»åŠ æ‰€æœ‰éžå®ŒæˆçŠ¶æ€çš„æ˜Žç»†æ¡ç 
                        foreach (var detail in inboundOrder.Details)
                        {
                            if (detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                                !string.IsNullOrEmpty(detail.Barcode)&& detail.OutBoxbarcodes == noStockOut.barCode)
                            {
                                detailLists.Add(detail);
                            }
                        }
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(noStockOut.barCode))
                    {
                        detailLists.Add(matchedDetail);
                    }
                }
                var outDetails = new List<Dt_OutboundOrderDetail>();
                foreach (var item in detailLists)
                {
                    item.NoStockOutQty = 0;
                    var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == item.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()&& detail.OrderQuantity - detail.LockQuantity - detail.MoveQty - detail.NoStockOutQty > 0);
                    if (matchedCode != null)
                    {
                        if(matchedCode.OrderQuantity - matchedCode.LockQuantity - matchedCode.MoveQty - matchedCode.NoStockOutQty < 0)
                        {
                            return WebResponseContent.Instance.Error($"物料{item.MaterielCode}可出数量溢出{(matchedCode.LockQuantity + matchedCode.MoveQty + matchedCode.NoStockOutQty) - matchedCode.OrderQuantity}");
                        }
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{item.MaterielCode} å¯¹åº”的物料");
                    }
                    if (!matchedCode.BatchNo.IsNullOrEmpty() && matchedCode.BatchNo != "")
                    {
                        var matcheBatch = outboundOrder.Details.FirstOrDefault(detail => detail.BatchNo == item.BatchNo);
                        if (matcheBatch == null)
                        {
                            return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单批次中的{item.BatchNo} å¯¹åº”的物料。");
                        }
                    }
                    if (!matchedCode.SupplyCode.IsNullOrEmpty() && matchedCode.SupplyCode != "")
                    {
                        var matcheBatch = outboundOrder.Details.FirstOrDefault(detail => detail.SupplyCode == item.SupplyCode);
                        if (matcheBatch == null)
                        {
                            return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单供应商中的{item.SupplyCode} å¯¹åº”的物料。");
                        }
                    }
                    if (!outboundOrder.FactoryArea.IsNullOrEmpty() && outboundOrder.FactoryArea != "" && !inboundOrder.FactoryArea.IsNullOrEmpty() && inboundOrder.FactoryArea != "")
                    {
                        if (inboundOrder.FactoryArea != outboundOrder.FactoryArea)
                        {
                            return WebResponseContent.Instance.Error($"该条码{item.Barcode}对应的单据厂区与出库单据不一致!不允许出库。");
                        }
                    }
                    if(inboundOrder.BusinessType != "11")
                    {
                        if (!matchedCode.WarehouseCode.IsNullOrEmpty() && matchedCode.WarehouseCode != "")
                        {
                            var matcheBatch = outboundOrder.Details.FirstOrDefault(detail => detail.WarehouseCode == item.WarehouseCode);
                            if (matcheBatch == null)
                            {
                                return WebResponseContent.Instance.Error($"仓库不一致!在出库单的物料编码中未找到与采购单仓库中的{item.WarehouseCode} å¯¹åº”的物料。");
                            }
                        }
                    }
                    else
                    {
                        item.WarehouseCode = matchedCode.WarehouseCode;
                    }
                    //剩余入库数量即虚拟出入库剩余可出数量
                    decimal outQuantity = item.OrderQuantity - item.ReceiptQuantity;
                    if (outQuantity == 0)
                    {
                        return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量为0");
                    }
                    if (matchedCode.OrderQuantity - matchedCode.LockQuantity - matchedCode.MoveQty - matchedCode.NoStockOutQty < outQuantity)
                    {
                        return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量超出出库单出库数量{item.OrderQuantity - (matchedCode.OrderQuantity - matchedCode.LockQuantity - matchedCode.MoveQty)},不满足整包出库");
                    }
                    //单据出库锁定数量
                    item.NoStockOutQty += outQuantity;
                    matchedCode.NoStockOutQty += outQuantity;
                    //回传MES参数
                    List<Barcodes> barcodesList = new List<Barcodes>();
                    Barcodes barcodes = new Barcodes
                    {
                        Barcode = item.Barcode,
                        Qty = item.OrderQuantity,
                        SupplyCode = item?.SupplyCode ?? "",
                        BatchNo = item?.BatchNo ?? "",
                        Unit = item?.Unit ?? ""
                    };
                    if (!string.IsNullOrEmpty(matchedCode.documentsNO))
                    {
                        barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(matchedCode.documentsNO) ?? new List<Barcodes>();
                    }
                    barcodesList.Add(barcodes);
                    JsonSerializerSettings settings = new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    };
                    matchedCode.documentsNO = JsonConvert.SerializeObject(barcodesList, settings);
                    if ((matchedCode.LockQuantity + matchedCode.NoStockOutQty+matchedCode.MoveQty) > matchedCode.OrderQuantity)
                    {
                        return WebResponseContent.Instance.Error($"出库单明细数量溢出{matchedCode.OrderQuantity - matchedCode.LockQuantity-matchedCode.NoStockOutQty-matchedCode.MoveQty}");
                    }
                    outDetails.Add(matchedCode);
                }
                _unitOfWorkManage.BeginTran();
                _inboundOrderDetailService.UpdateData(detailLists);
                _outboundOrderDetailService.UpdateData(outDetails);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("成功",data:detailLists);
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut)
        {
            try
            {
                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
                }
                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedDetail == null)
                {
                    return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„æ˜Žç»†ã€‚");
                }
                matchedDetail.NoStockOutQty = 0;
                if (matchedDetail.ReceiptQuantity == 0 && matchedDetail.OverInQuantity == 0)
                else
                {
                    matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
                    // æ·»åŠ æ‰€æœ‰åŒ¹é…å¤–ç®±ç ä¸”éžå®ŒæˆçŠ¶æ€çš„æ˜Žç»†
                    foreach (var detail in inboundOrder.Details)
                    {
                        if (detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                            !string.IsNullOrEmpty(detail.Barcode) &&
                            detail.OutBoxbarcodes == noStockOut.barCode)
                        {
                            detailLists.Add(detail);
                        }
                    }
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(noStockOut.barCode))
                {
                    detailLists.Add(matchedDetail);
                }
            }
                var outDetails = new List<Dt_OutboundOrderDetail>();
                // éåŽ†æ¯ä¸ªå…¥åº“æ˜Žç»†
                foreach (var item in detailLists)
                {
                    // é‡ç½®å½“前入库明细的无库存出库数量
                    item.NoStockOutQty = 0;
                    // å½“前条码需要出库的总数量(入库单剩余可出数量)
                    decimal remainingBarcodeQty = item.OrderQuantity - item.ReceiptQuantity;
                    if (remainingBarcodeQty <= 0)
                    {
                        return WebResponseContent.Instance.Error($"该采购单中的条码{item.Barcode}对应的可出数量为0");
                    }
                    // ç­›é€‰å‡ºåº“单中符合条件的明细(同物料、非完成、有剩余可出数量)
                    var eligibleOutDetails = outboundOrder.Details.Where(detail =>
                        detail.MaterielCode == item.MaterielCode &&
                        detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                        (detail.OrderQuantity - detail.LockQuantity - detail.MoveQty - detail.NoStockOutQty) > 0).ToList();
                    if (!eligibleOutDetails.Any())
                    {
                        return WebResponseContent.Instance.Error($"在出库单中未找到物料{item.MaterielCode}的可出库明细");
                    }
                    // éåŽ†ç¬¦åˆæ¡ä»¶çš„å‡ºåº“æ˜Žç»†ï¼Œé€è¡Œåˆ†é…æ•°é‡
                    foreach (var outDetail in eligibleOutDetails)
                    {
                        // è®¡ç®—当前出库行的剩余可出数量
                        decimal rowRemainingQty = outDetail.OrderQuantity - outDetail.LockQuantity - outDetail.MoveQty - outDetail.NoStockOutQty;
                        if (rowRemainingQty <= 0) continue;
                        if (!outDetail.BatchNo.IsNullOrEmpty())
                        {
                            if (outDetail.BatchNo != item.BatchNo)
                            {
                                return WebResponseContent.Instance.Error($"出库单行批次{outDetail.BatchNo}与采购单批次{item.BatchNo}不匹配");
                            }
                        }
                        if (!outDetail.SupplyCode.IsNullOrEmpty())
                        {
                            if (outDetail.SupplyCode != item.SupplyCode)
                            {
                                return WebResponseContent.Instance.Error($"出库单行供应商{outDetail.SupplyCode}与采购单供应商{item.SupplyCode}不匹配");
                            }
                        }
                        if (!string.IsNullOrEmpty(outboundOrder.FactoryArea) && !string.IsNullOrEmpty(inboundOrder.FactoryArea))
                        {
                            if (inboundOrder.FactoryArea != outboundOrder.FactoryArea)
                            {
                                return WebResponseContent.Instance.Error($"该条码{item.Barcode}对应的单据厂区与出库单据不一致!不允许出库。");
                            }
                        }
                        if (inboundOrder.BusinessType != "11" && !outDetail.WarehouseCode.IsNullOrEmpty())
                        {
                            if (outDetail.WarehouseCode != item.WarehouseCode)
                            {
                                return WebResponseContent.Instance.Error($"仓库不一致!出库单行仓库{outDetail.WarehouseCode}与采购单仓库{item.WarehouseCode}不匹配");
                            }
                        }
                        else
                        {
                            item.WarehouseCode = outDetail.WarehouseCode;
                        }
                        // è®¡ç®—本次分配的数量(取剩余条码数量和当前行剩余数量的较小值)
                        decimal assignQty = Math.Min(remainingBarcodeQty, rowRemainingQty);
                        // æ›´æ–°å…¥åº“明细和出库明细的无库存出库数量
                        item.NoStockOutQty += assignQty;
                        outDetail.NoStockOutQty += assignQty;
                        // æ›´æ–°å‰©ä½™éœ€è¦åˆ†é…çš„æ¡ç æ•°é‡
                        remainingBarcodeQty -= assignQty;
                        // è®°å½•已更新的出库明细(去重)
                        if (!outDetails.Contains(outDetail))
                        {
                            outDetails.Add(outDetail);
                        }
                        // éªŒè¯å½“前行是否溢出
                        if ((outDetail.LockQuantity + outDetail.NoStockOutQty + outDetail.MoveQty) > outDetail.OrderQuantity)
                        {
                            return WebResponseContent.Instance.Error($"出库单明细{outDetail.Id}数量溢出,超出数量:{(outDetail.LockQuantity + outDetail.NoStockOutQty + outDetail.MoveQty) - outDetail.OrderQuantity}");
                        }
                        // å¤„理MES参数回传:记录当前条码分配到该行的实际数量
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        Barcodes barcodes = new Barcodes
                        {
                            Barcode = item.Barcode,
                            Qty = assignQty,
                            SupplyCode = item?.SupplyCode ?? "",
                            BatchNo = item?.BatchNo ?? "",
                            Unit = item?.Unit ?? ""
                        };
                        // ååºåˆ—化该行已有的条码记录
                        if (!string.IsNullOrEmpty(outDetail.documentsNO))
                        {
                            try
                            {
                                barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(outDetail.documentsNO) ?? new List<Barcodes>();
                            }
                            catch (JsonException ex)
                            {
                                return WebResponseContent.Instance.Error($"出库单明细{outDetail.Id}的documentsNO字段格式错误:{ex.Message}");
                            }
                        }
                        // æ·»åŠ æœ¬æ¬¡åˆ†é…çš„è®°å½•
                        barcodesList.Add(barcodes);
                        // åºåˆ—化回存
                        JsonSerializerSettings settings = new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };
                        outDetail.documentsNO = JsonConvert.SerializeObject(barcodesList, settings);
                        // æ¡ç æ•°é‡åˆ†é…å®Œæ¯•,退出当前行循环
                        if (remainingBarcodeQty <= 0)
                        {
                            break;
                        }
                    }
                    // æ‰€æœ‰ç¬¦åˆæ¡ä»¶çš„出库行遍历完后,条码数量仍有剩余
                    if (remainingBarcodeQty > 0)
                    {
                        return WebResponseContent.Instance.Error($"条码{item.Barcode}需出库数量{item.OrderQuantity - item.ReceiptQuantity},但出库单中物料{item.MaterielCode}剩余可出总量不足,仍剩余{remainingBarcodeQty}数量未分配");
                    }
                }
                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
                if (outboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
                }
                _unitOfWorkManage.BeginTran();
            _inboundOrderDetailService.UpdateData(detailLists);
            _outboundOrderDetailService.UpdateData(outDetails);
            _unitOfWorkManage.CommitTran();
                // æ’¤é”€å›žä¼ MES参数
            return WebResponseContent.Instance.OK("成功", data: detailLists);
        }
        catch (Exception ex)
        {
            _unitOfWorkManage.RollbackTran();
            return WebResponseContent.Instance.Error(ex.Message);
        }
    }
    public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut)
    {
        try
        {
            Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>()
                .Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt())
                .Includes(x => x.Details)
                .First();
            if (inboundOrder == null)
            {
                return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
            }
            var matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                detail.Barcode == noStockOut.barCode &&
                detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
            if (matchedDetail == null)
            {
                return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„æ˜Žç»†ã€‚");
            }
            // é‡ç½®å…¥åº“明细的无库存出库数量
            decimal revokedTotalQty = matchedDetail.NoStockOutQty; // è®°å½•需要撤销的总数量(入库单中已分配的数量)
            matchedDetail.NoStockOutQty = 0;
            if(inboundOrder.BusinessType == "11")
            {
                matchedDetail.WarehouseCode ="";
            }
            // é‡ç½®å…¥åº“明细状态
            if (matchedDetail.ReceiptQuantity == 0 && matchedDetail.OverInQuantity == 0)
            {
                matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
            }
            Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt())
                .Includes(x => x.Details)
                .First();
            if (outboundOrder == null)
            {
                return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.outOder}");
            }
            // æ‰¾åˆ°æ‰€æœ‰å…³è”该条码的出库明细行
            // åŒç‰©æ–™ã€éžå®ŒæˆçŠ¶æ€ã€documentsNO包含该条码
            var matchedCodeList = outboundOrder.Details.Where(detail =>
                detail.MaterielCode == matchedDetail.MaterielCode && // ç¡®ä¿ç‰©æ–™åŒ¹é…
                detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                !string.IsNullOrEmpty(detail.documentsNO) &&
                detail.documentsNO.Contains(noStockOut.barCode) // åŒ…含当前条码
            ).ToList();
            if (!matchedCodeList.Any())
            {
                return WebResponseContent.Instance.Error($"在出库单中未找到关联条码{noStockOut.barCode}的物料{matchedDetail.MaterielCode}明细。");
            }
            //逐行处理出库明细的撤销逻辑
            decimal remainingRevokeQty = revokedTotalQty; // å‰©ä½™éœ€è¦æ’¤é”€çš„æ•°é‡
            foreach (var matchedCode in matchedCodeList)
            {
                if (remainingRevokeQty <= 0) break; // æ‰€æœ‰æ•°é‡å·²æ’¤é”€ï¼Œé€€å‡ºå¾ªçޝ
                // å¤„理MES参数撤销
                List<Barcodes> barcodesList = new List<Barcodes>();
                Barcodes barcodes = new Barcodes
                {
                    Barcode = matchedDetail.Barcode,
                    Qty = matchedDetail.OrderQuantity,
                    SupplyCode = matchedDetail?.SupplyCode ?? "",
                    BatchNo = matchedDetail?.BatchNo ?? "",
                    Unit = matchedDetail?.Unit ?? ""
                };
                var matchedCode = outboundOrder.Details.FirstOrDefault(detail =>
                    detail.documentsNO.Contains(barcodes.Barcode) &&
                    detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()
                );
                if (matchedCode == null)
                {
                    return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} å¯¹åº”的物料。");
                }
                if (!string.IsNullOrEmpty(matchedCode.documentsNO))
                {
                    barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(matchedCode.documentsNO) ?? new List<Barcodes>();
                    try
                    {
                        barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(matchedCode.documentsNO) ?? new List<Barcodes>();
                    }
                    catch (JsonException ex)
                    {
                        return WebResponseContent.Instance.Error($"出库单明细{matchedCode.Id}的documentsNO字段格式错误:{ex.Message}");
                    }
                }
                barcodesList.RemoveAll(b =>
                    string.Equals(b.Barcode, barcodes.Barcode, StringComparison.OrdinalIgnoreCase)
                );
                // ç­›é€‰å‡ºå½“前条码的所有记录
                var barcodeRecords = barcodesList.Where(b =>
                    string.Equals(b.Barcode, noStockOut.barCode, StringComparison.OrdinalIgnoreCase)).ToList();
                if (!barcodeRecords.Any()) continue;
                // è®¡ç®—该行需要撤销的数量(累加该条码在该行的所有分配数量)
                decimal rowRevokeQty = barcodeRecords.Sum(b => b.Qty);
                // å®žé™…撤销数量:取该行可撤销数量和剩余需要撤销数量的较小值
                decimal actualRevokeQty = Math.Min(rowRevokeQty, remainingRevokeQty);
                // ç§»é™¤è¯¥è¡Œä¸­è¯¥æ¡ç çš„记录(或部分记录,若剩余撤销数量不足)
                if (actualRevokeQty < rowRevokeQty)
                {
                    // å‰©ä½™æ’¤é”€æ•°é‡ä¸è¶³ï¼Œåªç§»é™¤éƒ¨åˆ†è®°å½•(按数量扣减)
                    decimal tempQty = actualRevokeQty;
                    var removeList = new List<Barcodes>();
                    foreach (var record in barcodeRecords)
                    {
                        if (tempQty <= 0) break;
                        if (record.Qty <= tempQty)
                        {
                            removeList.Add(record);
                            tempQty -= record.Qty;
                        }
                        else
                        {
                            // è®°å½•数量拆分,扣减部分数量
                            record.Qty -= tempQty;
                            tempQty = 0;
                        }
                    }
                    barcodesList.RemoveAll(b => removeList.Contains(b));
                }
                else
                {
                    // ç§»é™¤è¯¥è¡Œä¸­è¯¥æ¡ç çš„æ‰€æœ‰è®°å½•
                    barcodesList.RemoveAll(b =>
                        string.Equals(b.Barcode, noStockOut.barCode, StringComparison.OrdinalIgnoreCase));
                }
                //重新序列化MES参数
                JsonSerializerSettings settings = new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                };
                matchedCode.documentsNO = JsonConvert.SerializeObject(barcodesList, settings);
                matchedCode.NoStockOutQty -= matchedDetail.OrderQuantity;
                //扣减出库明细的无库存出库数量
                matchedCode.NoStockOutQty = Math.Max(0, matchedCode.NoStockOutQty - actualRevokeQty);
                remainingRevokeQty -= actualRevokeQty;
                //重置出库明细状态
                if (matchedCode.LockQuantity == 0 && matchedCode.OverOutQuantity == 0)
                {
                    matchedCode.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
                }
                _unitOfWorkManage.BeginTran();
                _inboundOrderDetailService.UpdateData(matchedDetail);
                _outboundOrderDetailService.UpdateData(matchedCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            //若仍有未撤销的数量,说明数据不一致
            if (remainingRevokeQty > 0)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
                return WebResponseContent.Instance.Error($"撤销条码{noStockOut.barCode}时,出库单中可撤销数量不足,仍有{remainingRevokeQty}数量未撤销");
            }
        }
        public async Task<WebResponseContent> NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
            _unitOfWorkManage.BeginTran();
            _inboundOrderDetailService.UpdateData(matchedDetail);
            _outboundOrderDetailService.UpdateData(matchedCodeList);
            _unitOfWorkManage.CommitTran();
            return WebResponseContent.Instance.OK("条码撤销成功", data: new { RevokedQty = revokedTotalQty });
        }
        catch (Exception ex)
        {
            _unitOfWorkManage.RollbackTran();
            return WebResponseContent.Instance.Error(ex.Message);
        }
    }
    public async Task<WebResponseContent> NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
        {
            try
            {
@@ -2715,7 +2827,7 @@
                {
                    item.LockQuantity += item.NoStockOutQty;
                    item.OverOutQuantity += item.NoStockOutQty;
                    item.CurrentDeliveryQty = item.NoStockOutQty;
                    item.CurrentDeliveryQty += item.NoStockOutQty;
                    //添加回传MES参数
                    List<Barcodes> barcodesList = new List<Barcodes>();
                    List<Barcodes> documentsNOList = new List<Barcodes>();
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -1113,8 +1113,8 @@
                        {
                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
                            item.CurrentDeliveryQty = item.LockQuantity - item.OverOutQuantity;
                            item.OverOutQuantity = item.LockQuantity;
                            item.CurrentDeliveryQty = item.LockQuantity;
                        }
                        updateDetails.Add(item);
@@ -1930,6 +1930,12 @@
            {
                var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
                Dt_Task task = _taskRepository.QueryFirst(x => x.PalletCode == palletCode);
                if (task != null)
                {
                    return WebResponseContent.Instance.Error("任务信息列表存在该托盘的任务信息,不可取走空箱,请检查任务是否完成");
                }
                if (stock == null)
                {
                    return content.Error($"未找到托盘{palletCode}库存信息");