pan
2025-11-12 ca4a0e7ffc11dc6f4c19b75f625444b06768ea15
提交
已添加1个文件
已修改28个文件
1462 ■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/Pallet.vue 821 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/WarehouseService.cs 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Basic/LocationGroupDTO.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Basic/SupplierDTO.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IBasicService/ILocationInfoService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IBasicService/IWarehouseService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_LocationInfo.cs 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_WarehouseArea.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Inbound/Dt_InboundOrder.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrder.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Basic/LocationInfoController.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Basic/WarehouseController.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundOrderController.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/Pallet.vue
@@ -12,50 +12,72 @@
  >
  <div class="barcode-scanner-container">
      
      <!-- æ‰˜ç›˜ä¿¡æ¯æ˜¾ç¤º -->
      <div class="tray-info" v-if="trayBarcode">
        <i class="el-icon-s-management"></i> å½“前料箱: {{ trayBarcode }}
       <!--  <el-button
          class="small-button"
          type="text"
          @click="clearTray"
          style="float: right;"
        >
          æ¸…除托盘
        </el-button> -->
      <!-- ä»“库区域选择 - ç´§å‡‘布局 -->
      <div class="location-section compact">
        <el-form :model="form" :rules="rules" ref="locationForm" class="compact-form">
          <el-form-item label="仓库区域" prop="locationType" class="location-select compact-item">
            <el-select
              v-model="form.locationType"
              placeholder="请选择仓库区域"
              clearable
              filterable
              @change="handleLocationChange"
              style="width: 100%"
              :loading="locationLoading"
              size="medium"
            >
              <el-option
                v-for="item in locationTypes"
                :key="item.locationType"
                :label="item.locationTypeDesc"
                :value="item.locationType"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      
      <!-- æ‰«ç æˆ–手动输入区域 -->
      <div class="input-section">
        <el-card shadow="hover">
          <div slot="header">
            <span><i class="el-icon-scanner"></i> </span>
      <!-- æ‰˜ç›˜ä¿¡æ¯æ˜¾ç¤º - ç´§å‡‘布局 -->
      <div class="tray-info compact" v-if="trayBarcode">
        <i class="el-icon-s-management"></i> å½“前料箱: {{ trayBarcode }}
        <span class="location-info" v-if="form.locationType">
          | ä»“库区域: {{ currentLocationDesc }}
        </span>
      </div>
      <!-- æ‰«ç åŒºåŸŸ - ç´§å‡‘布局 -->
      <div class="input-section compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-scanner"></i> æ‰«ç åŒºåŸŸ</span>
            <span class="scan-status">
              <span class="scan-indicator"></span>扫码就绪
              <span class="scan-indicator"></span>
              {{ form.locationType ? '扫码就绪' : '请先选择仓库区域' }}
            </span>
          </div>
          
          <!-- æ‰˜ç›˜æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group">
    <div class="input-label">料箱码</div>
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">料箱码</div>
            <el-input
              ref="trayInput"
              v-model="trayBarcode"
              placeholder="请扫描或输入料箱码后按回车键"
              clearable
              :disabled="!form.locationType"
              @keyup.enter.native="handleTraySubmit"
              @clear="handleTrayClear"
              @input="handleTrayInput"
                 class="custom-input"
              class="custom-input"
              size="medium"
            >
              <template slot="prepend">
                <span> æ–™ç®±ç </span>
              </template>
              <template slot="append">
                <el-button 
                  @click="handleTraySubmit"
                  type="primary"
                  icon="el-icon-position"
                  :disabled="!form.locationType || !trayBarcode"
                  size="medium"
                >
                  ç¡®è®¤
                </el-button>
@@ -64,29 +86,28 @@
          </div>
          
          <!-- ç‰©æ–™æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group">
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">物料条码</div>
            <el-input
              ref="barcodeInput"
              v-model="barcode"
              placeholder="请扫描或输入物料条码后按回车键"
              clearable
              :disabled="!trayBarcode"
              :disabled="!form.locationType || !trayBarcode"
              @keyup.enter.native="handleBarcodeSubmit"
              @clear="handleClear"
              @input="handleBarcodeInput"
                    class="custom-input"
              class="custom-input"
              size="medium"
            >
              <template slot="prepend">
                <span>物料条码</span>
              </template>
              <template slot="append">
                <el-button 
                  :loading="loading" 
                  @click="handleBarcodeSubmit"
                  type="primary"
                  icon="el-icon-search"
                  :disabled="!trayBarcode"
                  :disabled="!form.locationType || !trayBarcode || !barcode"
                  size="medium"
                >
                  {{ loading ? '查询中...' : '查询' }}
                </el-button>
@@ -94,22 +115,23 @@
            </el-input>
          </div>
          
          <div class="input-tips">
            <p>提示:先输入料箱码,然后输入物料条码</p>
          <div class="input-tips compact-tips">
            <p>提示:请先选择仓库区域,然后输入料箱码,最后输入物料条码</p>
            <p v-if="!form.locationType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库区域</p>
            <p v-if="form.locationType && !trayBarcode" class="warning-text">⚠️ è¯·å…ˆè¾“入料箱码</p>
          </div>
        
        </el-card>
      </div>
      <!-- åŠ è½½çŠ¶æ€ -->
      <div v-if="loading" class="loading">
      <div v-if="loading" class="loading compact">
        <el-progress :percentage="100" status="success" :show-text="false" />
        <p>正在查询物料信息...</p>
      </div>
      <!-- é”™è¯¯æç¤º -->
      <div v-if="error" class="error-message">
      <div v-if="error" class="error-message compact">
        <el-alert
          :title="error"
          type="error"
@@ -119,68 +141,45 @@
        />
      </div>
      <!-- ç‰©æ–™åˆ—表 -->
      <div class="material-list">
        <el-card shadow="hover">
          <div slot="header">
      <!-- ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåŠ¨æ¡ -->
      <div class="material-list compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-tickets"></i> ç»„盘数据</span>
            <span class="list-actions">
              <el-tag type="primary">共 {{ materials.length }} æ¡è®°å½•</el-tag>
              <el-tag type="primary">已组盘 {{ totalStockCount }}</el-tag>
              <el-tag type="primary">库存数量 {{ totalStockSum }}<span>{{ uniqueUnit }}</span></el-tag>
              <el-tag v-if="trayBarcode" type="success">托盘: {{ trayBarcode }}</el-tag>
    <!--           <el-button
                v-if="materials.length > 0"
                class="small-button clear-all-btn"
                type="danger"
                icon="el-icon-delete"
                @click="clearAllMaterials"
              >
                æ¸…空列表
              </el-button> -->
          <!--     <el-button
                class="small-button clear-all-btn"
                @click="debugMode = !debugMode"
              >
                {{ debugMode ? '隐藏调试' : '显示调试' }}
              </el-button> -->
              <el-tag type="primary" size="small">共 {{ materials.length }} æ¡</el-tag>
              <el-tag type="primary" size="small">已组盘 {{ totalStockCount }}</el-tag>
              <el-tag type="primary" size="small">库存 {{ totalStockSum }}{{ uniqueUnit }}</el-tag>
              <el-tag v-if="trayBarcode" type="success" size="small">托盘: {{ trayBarcode }}</el-tag>
              <el-tag v-if="form.locationType" type="info" size="small">区域: {{ currentLocationDesc }}</el-tag>
            </span>
          </div>
          
          <div v-if="materials.length === 0" class="empty-state">
          <div v-if="materials.length === 0" class="empty-state compact">
            <i class="el-icon-document"></i>
            <p v-if="!trayBarcode">请先输入料箱条码</p>
            <p v-if="!form.locationType">请先选择仓库区域</p>
            <p v-else-if="!trayBarcode">请先输入料箱条码</p>
            <p v-else>暂无物料数据,请扫描或输入物料条码</p>
          </div>
          
          <el-table
            v-else
            :data="materials"
            stripe
            style="width: 100%"
          >
            <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
              <el-table-column prop="barcode" label="条码" min-width="140"></el-table-column>
            <el-table-column prop="materielCode" label="物料编码" min-width="150"></el-table-column>
            <el-table-column prop="batchNo" label="批次" min-width="150"></el-table-column>
            <el-table-column prop="stockQuantity" label="数量" min-width="130"></el-table-column>
            <el-table-column prop="unit" label="单位" width="80" align="center"></el-table-column>
            <el-table-column prop="supplyCode" label="供应商" min-width="130"></el-table-column>
            <el-table-column prop="warehouseCode" label="仓库" min-width="120"></el-table-column>
     <!--        <el-table-column label="操作" width="100" align="center">
              <template slot-scope="scope">
                <el-button
                  v-if="scope"
                  class="small-button"
                  type="danger"
                  icon="el-icon-delete"
                  circle
                  @click="removeMaterial(scope.$index)"
                ></el-button>
              </template>
            </el-table-column> -->
          </el-table>
          <div class="table-container" v-else>
            <el-table
              :data="materials"
              stripe
              style="width: 100%"
              height="100%"
              size="small"
            >
              <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
              <el-table-column prop="barcode" label="条码" min-width="140" show-overflow-tooltip></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="batchNo" label="批次" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="stockQuantity" label="数量" min-width="130" align="right"></el-table-column>
              <el-table-column prop="unit" label="单位" width="80" align="center"></el-table-column>
              <el-table-column prop="supplyCode" label="供应商" min-width="130" show-overflow-tooltip></el-table-column>
              <el-table-column prop="warehouseCode" label="仓库" min-width="120" show-overflow-tooltip></el-table-column>
            </el-table>
          </div>
        </el-card>
      </div>
    </div>
@@ -191,7 +190,6 @@
    </div> -->
    </vol-box>
</template>
<script>
import http from '@/api/http.js';
import VolBox from '@/components/basic/VolBox.vue';
@@ -234,7 +232,22 @@
          totalStockCount: 0,
          uniqueUnit: '',
          sumLoading: false,
          sumError: ''
          sumError: '',
        // ä»“库区域相关变量
        locationTypes: [],
        locationLoading: false,
        form: {
            locationType: null
        },
    rules: {
      locationType: [
        {
          validator: this.validateLocationType,
          trigger: 'change'
        }
      ]
    }
    }
  },
  computed: {
@@ -242,7 +255,12 @@
      get() { return this.visible; },
      set(newVal) { this.$emit('update:visible', newVal); }
    },
    currentDocNo() { return this.docNo; }
    currentDocNo() { return this.docNo; },
        // å½“前选择的仓库区域描述
    currentLocationDesc() {
        const location = this.locationTypes.find(item => item.locationType === this.form.locationType)
        return location ? location.locationTypeDesc : ''
    }
  },
  watch: {
    visible(newVal, oldVal) {
@@ -254,7 +272,8 @@
      this.resetData();
      this.$nextTick(() => {
        setTimeout(() => {
          this.focusTrayInput();
         // this.focusTrayInput();
            this.initLocationTypes(); // åˆå§‹åŒ–仓库区域
          this.fetchStockStatistics(); // åŠ è½½ç»Ÿè®¡æ•°æ®
        }, 300);
      });
@@ -286,7 +305,8 @@
        
        // ä½¿ç”¨setTimeout确保DOM完全渲染后再聚焦
        setTimeout(() => {
          this.focusTrayInput();
         // this.focusTrayInput();
                  this.focusLocationSelect();
        }, 300);
      },
      beforeDestroy() {
@@ -295,6 +315,76 @@
         this.clearAllTimers();
      },
      methods: {
         /**
   * è‡ªå®šä¹‰ä»“库区域验证
   * å…è®¸å€¼ä¸º0,因为0是合法的locationType
   */
  validateLocationType(rule, value, callback) {
    // æ£€æŸ¥å€¼æ˜¯å¦ä¸ºnull、undefined或空字符串,但允许数字0
    if (value === null || value === undefined || value === '') {
      callback(new Error('请选择仓库区域'));
    } else {
      // å€¼ä¸º0或其他有效值都通过验证
      callback();
    }
  },
         /**
         * åˆå§‹åŒ–仓库区域数据
         */
        async initLocationTypes() {
            this.locationLoading = true;
            this.error = '';
            try {
                const response = await http.post('/api/LocationInfo/GetLocationTypes');
                if (response.status && Array.isArray(response.data)) {
                    this.locationTypes = response.data;
                    if (this.locationTypes.length === 0) {
                        this.error = '未获取到仓库区域数据';
                    } else {
                        // å¦‚果有默认区域,可以在这里设置
                        // this.form.locationType = this.locationTypes[0].locationType;
                    }
                } else {
                    this.error = '获取仓库区域数据失败';
                }
            } catch (error) {
                console.error('获取仓库区域失败:', error);
                this.error = `获取仓库区域失败: ${error.message || '网络错误'}`;
            } finally {
                this.locationLoading = false;
            }
        },
 /**
 * ä»“库区域变更处理
 */
handleLocationChange(value) {
 console.log('选择仓库区域:', value, '类型:', typeof value, this.currentLocationDesc);
    // ç«‹å³æ¸…除错误信息
    this.error = '';
    // æ‰‹åŠ¨è§¦å‘è¡¨å•éªŒè¯æ›´æ–°
    this.$nextTick(() => {
      if (this.$refs.locationForm) {
        // æ¸…除该字段的验证状态,然后重新验证
        this.$refs.locationForm.clearValidate('locationType');
        // çŸ­æš‚延迟后重新验证,确保DOM已更新
        setTimeout(() => {
          this.$refs.locationForm.validateField('locationType', (errorMsg) => {
            if (!errorMsg && (value === 0 || value)) {
              console.log('仓库区域验证通过:', value);
              // åŒºåŸŸé€‰æ‹©åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°æ‰˜ç›˜è¾“å…¥æ¡†
              this.focusTrayInput();
            }
          });
        }, 100);
    }
  });
},
        // æ–°å¢žï¼šæŸ¥è¯¢åŽç«¯åº“存统计数据(调用之前的 SumQuantity æŽ¥å£ï¼‰
    async fetchStockStatistics() {
      // å•据号为空时不查询
@@ -326,6 +416,69 @@
        this.sumLoading = false;
      }
    },
/**
 * è¡¨å•验证
 */
async validateForm() {
  return new Promise((resolve) => {
    if (!this.$refs.locationForm) {
      this.error = '表单未初始化';
      this.$message.warning('请先选择仓库区域');
      resolve(false);
      return;
    }
    this.$refs.locationForm.validate((valid) => {
      if (valid) {
        this.error = '';
        resolve(true);
      } else {
        // æ‰‹åŠ¨æ£€æŸ¥locationType,正确处理值为0的情况
        if (this.form.locationType === null || this.form.locationType === undefined || this.form.locationType === '') {
          this.error = '请先选择仓库区域';
          this.$message.warning('请先选择仓库区域');
        } else {
          // å¦‚果值存在(包括0),但验证不通过,可能是其他验证错误
          this.error = '请检查表单填写是否正确';
        }
        resolve(false);
      }
    });
  });
},
        // èšç„¦åˆ°ä»“库区域选择
        focusLocationSelect() {
            if (this.$refs.locationForm) {
                const selectEl = this.$el.querySelector('.el-select .el-input__inner');
                if (selectEl) {
                    selectEl.focus();
                    this.currentFocus = 'location';
                }
            }
        },
        // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
        focusTrayInput() {
            if (this.$refs.trayInput && this.$refs.trayInput.$el) {
                const inputEl = this.$refs.trayInput.$el.querySelector('input');
                if (inputEl) {
                    inputEl.focus();
                    this.currentFocus = 'tray';
                    this.scanTarget = 'tray';
                }
            }
        },
        // èšç„¦åˆ°ç‰©æ–™è¾“入框
        focusBarcodeInput() {
            if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
                const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
                if (inputEl) {
                    inputEl.focus();
                    this.currentFocus = 'material';
                    this.scanTarget = 'material';
                }
            }
        },
         // é‡ç½®æ‰€æœ‰æ•°æ®
    resetData() {
      console.log('重置弹框数据');
@@ -338,13 +491,20 @@
      this.lastKeyTime = null;
      this.isManualInput = false;
      this.isScanning = false;
      this.currentFocus = 'tray';
      this.currentFocus = 'location';
      this.scanTarget = 'tray';
      this.clearAllTimers();
      this.totalStockSum = 0;
      this.totalStockCount = 0;
      this.sumLoading = false;
      this.sumError = '';
        this.form.locationType = null;
          // æ¸…除表单验证状态
  this.$nextTick(() => {
    if (this.$refs.locationForm) {
      this.$refs.locationForm.clearValidate();
    }
  });
    },
    
    // æ¸…除所有计时器
@@ -366,7 +526,16 @@
      // ä½¿ç”¨setTimeout确保DOM完全渲染后再聚焦
      this.$nextTick(() => {
        setTimeout(() => {
          this.focusTrayInput();
            this.initLocationTypes(); // åˆå§‹åŒ–仓库区域
             // ç¡®ä¿è¡¨å•引用存在后再聚焦
      if (this.$refs.locationForm) {
        this.focusLocationSelect();
      } else {
        // å¦‚果表单引用还不存在,稍后重试
        setTimeout(() => {
          this.focusLocationSelect();
        }, 500);
      }
        }, 300);
      });
    },
@@ -383,7 +552,9 @@
    },
    
    // ç¡®è®¤æŒ‰é’®
    handleConfirm() {
   async  handleConfirm() {
           if (!await this.validateForm()) return;
      if (this.materials.length === 0) {
        this.$message.warning('请至少添加一个物料');
        return;
@@ -395,6 +566,8 @@
      }
      
      const result = {
        locationType: this.form.locationType,
        locationDesc: this.currentLocationDesc,
        trayBarcode: this.trayBarcode,
        materials: this.materials,
        docNo: this.docNo
@@ -404,84 +577,70 @@
      this.$emit('back-success', result);
      this.palletVisible = false;
    },
        // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
        focusTrayInput() {
          if (this.$refs.trayInput && this.$refs.trayInput.$el) {
            const inputEl = this.$refs.trayInput.$el.querySelector('input');
            if (inputEl) {
              inputEl.focus();
              this.currentFocus = 'tray';
              this.scanTarget = 'tray';
            }
          }
        },
        // èšç„¦åˆ°ç‰©æ–™è¾“入框
        focusBarcodeInput() {
          if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
            const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
            if (inputEl) {
              inputEl.focus();
              this.currentFocus = 'material';
              this.scanTarget = 'material';
            }
          }
        },
        // å¤„理托盘输入
    // å¤„理托盘输入
        handleTrayInput() {
          // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
          this.isManualInput = true;
          this.isScanning = false;
          // æ¸…除之前的计时器
          if (this.manualInputTimer) {
            clearTimeout(this.manualInputTimer);
          }
          // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
          this.manualInputTimer = setTimeout(() => {
            this.isManualInput = false;
          }, 1000);
            // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
            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);
        },
        // å¤„理托盘条码提交
        handleTraySubmit() {
          const currentTrayBarcode = this.trayBarcode.trim();
          if (!currentTrayBarcode) {
            this.error = '请输入或扫描托盘条码';
            return;
          }
            // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
            this.isManualInput = true;
            this.isScanning = false;
            // æ¸…除之前的计时器
            if (this.manualInputTimer) {
                clearTimeout(this.manualInputTimer);
            }
            // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
            this.manualInputTimer = setTimeout(() => {
                this.isManualInput = false;
            }, 1000);
        },
       // å¤„理托盘条码提交
async handleTraySubmit() {
  // å…ˆç›´æŽ¥æ£€æŸ¥locationType,避免表单验证的异步问题
  if (!this.form.locationType) {
    this.error = '请先选择仓库区域';
    this.$message.warning('请先选择仓库区域');
    return;
  }
  // ç„¶åŽå†è¿›è¡Œå®Œæ•´çš„表单验证
  if (!await this.validateForm()) return;
  const currentTrayBarcode = this.trayBarcode.trim();
  if (!currentTrayBarcode) {
    this.error = '请输入或扫描托盘条码';
    return;
  }
          this.error = '';
          // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
          this.focusBarcodeInput();
          this.$message({
            message: `托盘条码已设置: ${currentTrayBarcode}`,
            type: 'success',
            duration: 2000
          });
        },
  this.error = '';
  // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
  this.focusBarcodeInput();
  this.$message({
    message: `托盘条码已设置: ${currentTrayBarcode}`,
    type: 'success',
    duration: 2000
  });
},
        
        // æ¸…除托盘
        clearTray() {
@@ -510,6 +669,7 @@
        
        // å¤„理物料条码提交
        async handleBarcodeSubmit() {
                    if (!await this.validateForm()) return;
          const currentBarcode = this.barcode.trim();
          
          if (!this.trayBarcode) {
@@ -553,6 +713,8 @@
          this.materials.push({
            ...item, 
             trayCode: this.trayBarcode,
               locationType: this.form.locationType,
                            locationDesc: this.currentLocationDesc,
               scanTime: this.formatTime(new Date())
          });
        });
@@ -590,7 +752,8 @@
           {
            palletCode: this.trayBarcode,
            orderNo: this.docNo,
            barcodes: barcode
            barcodes: barcode,
                    locationType: this.form.locationType // æ·»åŠ ä»“åº“åŒºåŸŸä¿¡æ¯
          } 
        );
        
@@ -729,135 +892,299 @@
    .barcode-scanner-container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
      padding: 10px;
      display: flex;
      flex-direction: column;
      height: 100%;
      gap: 8px;
    }
    /* ç´§å‡‘布局样式 */
    .compact {
      margin-bottom: 0;
    }
    .compact-form {
      margin-bottom: 0;
    }
    .compact-item {
      margin-bottom: 0;
    }
    .compact-card {
      margin-bottom: 0;
    }
    .compact-card >>> .el-card__body {
      padding: 12px;
    }
    .compact-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 !important;
    }
    .compact-header >>> .el-card__header {
      padding: 8px 12px;
    }
    .compact-input {
      margin: 8px 0;
    }
    .compact-tips {
      margin-top: 8px;
      font-size: 11px;
    }
    /* ä»“库区域选择 - ç´§å‡‘ */
    .location-section.compact {
      margin-bottom: 8px;
    }
    .location-section.compact >>> .el-form-item {
      margin-bottom: 0;
    }
    /* æ‰˜ç›˜ä¿¡æ¯ - ç´§å‡‘ */
    .tray-info.compact {
      padding: 6px 10px;
      margin-bottom: 8px;
      font-size: 13px;
    }
    /* æ‰«ç åŒºåŸŸ - ç´§å‡‘ */
    .input-section.compact {
      margin-bottom: 8px;
      flex-shrink: 0;
    }
    /* ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåЍ */
    .material-list.compact {
      flex: 1;
      min-height: 0; /* é‡è¦ï¼šå…è®¸flex子项收缩 */
      display: flex;
      flex-direction: column;
    }
    .material-list.compact >>> .el-card {
      display: flex;
      flex-direction: column;
      height: 100%;
    }
    .material-list.compact >>> .el-card__body {
      flex: 1;
      display: flex;
      flex-direction: column;
      padding: 0;
      min-height: 0;
    }
    .table-container {
      flex: 1;
      min-height: 0;
      overflow: hidden;
    }
    .material-list.compact >>> .el-table {
      flex: 1;
    }
    .material-list.compact >>> .el-table__body-wrapper {
      overflow-y: auto;
    }
    /* ç´§å‡‘的空状态 */
    .empty-state.compact {
      padding: 20px 0;
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    .empty-state.compact i {
      font-size: 36px;
      margin-bottom: 8px;
    }
    .empty-state.compact p {
      font-size: 13px;
    }
    /* å…¶ä»–原有样式调整 */
    .page-title {
      text-align: center;
      margin-bottom: 30px;
      margin-bottom: 15px;
    }
    .input-section {
      margin-bottom: 30px;
    }
    .scan-status {
      float: right;
      font-size: 12px;
      color: #67C23A;
    }
    .scan-indicator {
      display: inline-block;
      width: 10px;
      height: 10px;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background-color: #67C23A;
      margin-right: 5px;
      animation: pulse 1.5s infinite;
    }
    @keyframes pulse {
      0% { opacity: 1; }
      50% { opacity: 0.4; }
      100% { opacity: 1; }
    }
    .input-wrapper {
      position: relative;
      margin-bottom: 15px;
    }
    .input-tips {
      margin-top: 10px;
      font-size: 12px;
      margin-top: 6px;
      color: #909399;
    }
    .loading {
      text-align: center;
      margin: 20px 0;
    .warning-text {
        color: #E6A23C;
        font-weight: bold;
    }
    .loading p {
      margin-top: 10px;
    .loading.compact {
      text-align: center;
      margin: 10px 0;
      padding: 5px;
    }
    .loading.compact p {
      margin-top: 5px;
      color: #409EFF;
      font-size: 12px;
    }
    .error-message {
      margin-bottom: 20px;
    .error-message.compact {
      margin: 5px 0;
    }
    .material-list {
      margin-top: 30px;
    .error-message.compact >>> .el-alert {
      padding: 6px 12px;
    }
    .list-actions {
      float: right;
      display: flex;
      align-items: center;
      gap: 4px;
    }
    .list-actions >>> .el-tag {
      height: 24px;
      line-height: 22px;
      padding: 0 6px;
    }
    .clear-all-btn {
      margin-left: 10px;
      margin-left: 8px;
    }
    .empty-state {
      text-align: center;
      padding: 40px 0;
      color: #909399;
    }
    .empty-state i {
      font-size: 48px;
      margin-bottom: 10px;
    }
    .material-code {
      font-family: 'Courier New', monospace;
      font-weight: bold;
      color: #409EFF;
    }
    .tray-info {
      background: #f0f9ff;
      padding: 10px 15px;
      border-radius: 4px;
      margin-bottom: 15px;
      border-left: 4px solid #409EFF;
    .location-info {
        color: #606266;
        font-weight: normal;
    }
    .debug-info {
      background: #f5f7fa;
      padding: 10px;
      padding: 8px;
      border-radius: 4px;
      margin-top: 10px;
      font-size: 12px;
      margin-top: 8px;
      font-size: 11px;
      color: #909399;
    }
    .small-button {
      padding: 7px 9px;
      font-size: 12px;
      padding: 6px 8px;
      font-size: 11px;
    }
.custom-input-group {
  display: flex;
  align-items: center;
  width: 100%;
  margin: 20px 0;
  border: 1px solid #DCDFE6;
  border-radius: 4px;
  overflow: hidden;
  background: #fff;
}
.input-label {
  padding: 0 15px;
  background: #F5F7FA;
  border-right: 1px solid #DCDFE6;
  color: #606266;
  font-size: 14px;
  white-space: nowrap;
  height: 40px;
  line-height: 40px;
  flex-shrink: 0;
}
    /* è¾“入框组样式调整 */
    .custom-input-group {
      display: flex;
      align-items: center;
      width: 100%;
      margin: 8px 0;
      border: 1px solid #DCDFE6;
      border-radius: 4px;
      overflow: hidden;
      background: #fff;
    }
.input-container {
  display: flex;
  flex: 1;
  align-items: center;
}
    .input-label {
      padding: 0 12px;
      background: #F5F7FA;
      border-right: 1px solid #DCDFE6;
      color: #606266;
      font-size: 13px;
      white-space: nowrap;
      height: 36px;
      line-height: 36px;
      flex-shrink: 0;
      min-width: 70px;
      text-align: center;
    }
.custom-input {
  flex: 1;
}
    .input-container {
      display: flex;
      flex: 1;
      align-items: center;
    }
.custom-input ::v-deep .el-input__inner {
  border: none;
  border-radius: 0;
  height: 40px;
  line-height: 40px;
}
    .custom-input {
      flex: 1;
    }
    .custom-input >>> .el-input__inner {
      border: none;
      border-radius: 0;
      height: 36px;
      line-height: 36px;
      font-size: 13px;
    }
    /* å“åº”式调整 */
    @media (max-width: 768px) {
      .barcode-scanner-container {
        padding: 5px;
      }
      .custom-input-group {
        flex-direction: column;
        border: none;
      }
      .input-label {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid #DCDFE6;
        margin-bottom: 5px;
      }
      .input-container {
        width: 100%;
        border: 1px solid #DCDFE6;
        border-radius: 4px;
      }
    }
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue
@@ -48,6 +48,14 @@
                  :disabled="!currentLockInfo"
                />
              </el-form-item>
              <!-- ç‰©æ–™ä¿¡æ¯æ˜¾ç¤º -->
              <el-form-item label="物料编码" v-if="currentMaterialInfo">
                <el-input v-model="currentMaterialInfo.materielCode" readonly />
              </el-form-item>
              <el-form-item label="物料名称" v-if="currentMaterialInfo">
                <el-input v-model="currentMaterialInfo.materielName" readonly />
              </el-form-item>
            </el-form>
            <div class="current-info" v-if="currentPallet">
@@ -141,6 +149,7 @@
      },
      currentPallet: null,
      currentLockInfo: null,
      currentMaterialInfo: null, // æ–°å¢žï¼šå½“前物料信息
      pickedList: [],
      pickedColumns: [
        { field: 'barcode', title: '物料条码', width: 150 },
@@ -153,7 +162,8 @@
        { field: 'action', title: '操作', width: 80, slot: true }
      ],
      splitVisible: false,
      maxPickQuantity: 0
      maxPickQuantity: 0,
      allLockInfos: [] // æ–°å¢žï¼šä¿å­˜æ‰€æœ‰é”å®šä¿¡æ¯ï¼Œç”¨äºŽæ¡ç åŒ¹é…
    }
  },
  computed: {
@@ -174,7 +184,7 @@
      if (!orderId) return
      try {
        const result = await this.http.post(`api/OutboundOrder/GetById?id=${orderId}`)
        const result = await this.http.get(`api/OutboundOrder/GetById?id=${orderId}`)
        if (result.status) {
          this.orderInfo = result.data
        }
@@ -195,7 +205,7 @@
        )
        if (result.status) {
          this.currentPallet = result.data
          this.loadPalletLockInfo()
          await this.loadPalletLockInfo()
          this.$message.success(`托盘 ${this.scanForm.palletCode} è¯†åˆ«æˆåŠŸ`)
        } else {
          this.$message.error(result.message)
@@ -212,42 +222,148 @@
        const result = await this.http.get(
          `api/OutboundPicking/GetPalletLockInfos?palletCode=${this.currentPallet.palletCode}`
        )
        if (result.status && result.data.length > 0) {
          this.currentLockInfo = result.data[0]
          this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty
        if (result.status) {
          this.allLockInfos = result.data
          // é»˜è®¤é€‰æ‹©ç¬¬ä¸€ä¸ªé”å®šä¿¡æ¯
          if (this.allLockInfos.length > 0) {
            this.currentLockInfo = this.allLockInfos[0]
            this.currentMaterialInfo = {
              materielCode: this.currentLockInfo.materielCode,
              materielName: this.currentLockInfo.materielName
            }
            this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty
          }
        }
      } catch (error) {
        console.error('加载锁定信息失败:', error)
      }
    },
    // æ ¹æ®æ¡ç æŸ¥æ‰¾å¯¹åº”的锁定信息和物料信息
    findLockInfoByBarcode(barcode) {
      if (!this.allLockInfos || this.allLockInfos.length === 0) {
        return null
      }
      // é¦–先精确匹配当前条码
      let lockInfo = this.allLockInfos.find(x => x.currentBarcode === barcode)
      if (lockInfo) {
        return lockInfo
      }
      // å¦‚果没有精确匹配,查找该条码对应的物料是否在锁定信息中
      // è¿™é‡Œéœ€è¦è°ƒç”¨åŽç«¯æŽ¥å£éªŒè¯æ¡ç å¯¹åº”的物料
      return null
    },
    async handleBarcodeScan() {
      // å®žçŽ°æ‰«ç ç¡®è®¤é€»è¾‘
      if (!this.scanForm.barcode) {
        this.$message.warning('请输入物料条码')
        return
      }
      if (!this.currentPallet) {
        this.$message.warning('请先扫描托盘条码')
        return
      }
      if (this.scanForm.quantity <= 0) {
        this.$message.warning('请输入有效的拣选数量')
        return
      }
      try {
        // éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        const materialInfo = await this.validateBarcode(this.scanForm.barcode)
        if (!materialInfo) {
          this.$message.error('无效的物料条码')
          return
        }
        // æŸ¥æ‰¾å¯¹åº”的锁定信息
        const targetLockInfo = this.findLockInfoByBarcodeAndMaterial(this.scanForm.barcode, materialInfo.materielCode)
        if (!targetLockInfo) {
          this.$message.error('该物料条码不在当前托盘的锁定信息中')
          return
        }
        // æ£€æŸ¥æ‹£é€‰æ•°é‡
        const availableQuantity = targetLockInfo.assignQuantity - targetLockInfo.pickedQty
        if (this.scanForm.quantity > availableQuantity) {
          this.$message.error(`拣选数量超过可用数量,剩余可拣选:${availableQuantity}`)
          return
        }
        // å‡†å¤‡è¯·æ±‚数据
        const request = {
          orderDetailId: targetLockInfo.orderDetailId,
          barcode: this.scanForm.barcode,
          quantity: this.scanForm.quantity,
          materielCode: materialInfo.materielCode, // ä¼ é€’物料编码
          pickQuantity: this.scanForm.quantity,
          locationCode: this.currentPallet.locationCode,
          palletCode: this.currentPallet.palletCode,
          orderId: this.orderInfo.id
          stockId: targetLockInfo.stockId,
          outStockLockInfoId: targetLockInfo.id // ä¼ é€’锁定信息ID
        }
        const result = await this.http.post('api/OutboundPicking/ConfirmPicking', request)
        if (result.status) {
          this.$message.success('拣选确认成功')
          // é‡ç½®è¡¨å•
          this.scanForm.barcode = ''
          this.scanForm.quantity = 1
          this.loadPickedHistory()
          this.currentMaterialInfo = null
          // åˆ·æ–°æ•°æ®
          this.loadOrderInfo()
          this.loadPickedHistory()
          this.loadPalletLockInfo()
        } else {
          this.$message.error(result.message)
        }
      } catch (error) {
        this.$message.error('拣选确认失败')
        this.$message.error('拣选确认失败: ' + (error.message || '未知错误'))
      }
    },
    // æ ¹æ®æ¡ç å’Œç‰©æ–™ç¼–码查找锁定信息
    findLockInfoByBarcodeAndMaterial(barcode, materielCode) {
      if (!this.allLockInfos || this.allLockInfos.length === 0) {
        return null
      }
      // é¦–先尝试精确匹配条码
      let lockInfo = this.allLockInfos.find(x =>
        x.currentBarcode === barcode && x.materielCode === materielCode
      )
      if (lockInfo) {
        return lockInfo
      }
      // å¦‚果精确匹配失败,只匹配物料编码(允许从同一物料的不同条码拣选)
      lockInfo = this.allLockInfos.find(x =>
        x.materielCode === materielCode &&
        (x.assignQuantity - x.pickedQty) > 0
      )
      return lockInfo
    },
    // éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
    async validateBarcode(barcode) {
      try {
        const result = await this.http.get(`api/OutboundPicking/ValidateBarcode?barcode=${barcode}`)
        if (result.status) {
          return result.data
        } else {
          this.$message.error(result.message)
          return null
        }
      } catch (error) {
        this.$message.error('条码验证失败')
        return null
      }
    },
@@ -261,7 +377,8 @@
        const result = await this.http.post('api/BackToStock/GenerateBackToStockTask', {
          palletCode: this.currentPallet.palletCode,
          currentLocation: '拣选位'
          currentLocation: '拣选位',
          operator: '当前用户'
        })
        if (result.status) {
@@ -311,6 +428,8 @@
    resetCurrentPallet() {
      this.currentPallet = null
      this.currentLockInfo = null
      this.currentMaterialInfo = null
      this.allLockInfos = []
      this.scanForm.palletCode = ''
    },
@@ -351,4 +470,49 @@
    this.loadPickedHistory()
  }
}
</script>
</script>
<style scoped>
.picking-confirm {
  padding: 20px;
}
.page-header {
  margin-bottom: 20px;
}
.title {
  font-size: 18px;
  font-weight: bold;
}
.scan-section {
  margin-bottom: 20px;
}
.action-buttons {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.action-buttons .el-button {
  width: 100%;
}
.current-info {
  margin-top: 15px;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
}
.current-info p {
  margin: 5px 0;
  font-size: 14px;
}
.summary-info {
  margin-bottom: 15px;
}
</style>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -36,13 +36,13 @@
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        public  IRepository<Dt_LocationInfo> Repository => BaseDal;
        public readonly IRepository<Dt_LocationType> _locationTypeRepository;
        public LocationInfoService(IRepository<Dt_LocationInfo> BaseDal, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockInfoRepository ) : base(BaseDal)
        public LocationInfoService(IRepository<Dt_LocationInfo> BaseDal, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_LocationType> locationTypeRepository) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoRepository = stockInfoRepository;
            _locationTypeRepository = locationTypeRepository;
        }
        /// <summary>
@@ -173,6 +173,12 @@
            return Repository.QueryData(x => locationCodes.Contains(x.LocationCode));
        }
        public List<LocationTypeDto> GetLocationTypes()
        {
          return   _locationTypeRepository.Db.Queryable<Dt_LocationType>().Select(x=>
              new LocationTypeDto { LocationType=x.LocationType,LocationTypeDesc=x.LocationTypeDesc}).ToList();
        }
        /// <summary>
        /// åˆå§‹åŒ–货位
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/WarehouseService.cs
@@ -12,16 +12,19 @@
using WIDESEA_Common.CommonEnum;
using WIDESEA_Core.Caches;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Model.Models.Basic;
using WIDESEA_DTO.Basic;
namespace WIDESEA_BasicService
{
    public partial class WarehouseService : ServiceBase<Dt_Warehouse, IRepository<Dt_Warehouse>>, IWarehouseService
    {
        private readonly ICacheService _cacheService;
        public WarehouseService(IRepository<Dt_Warehouse> BaseDal,ICacheService cacheService) : base(BaseDal)
        private readonly IRepository<Dt_WarehouseArea> _warehouseArearepository;
        public WarehouseService(IRepository<Dt_Warehouse> BaseDal, ICacheService cacheService, IRepository<Dt_WarehouseArea> warehouseArearepository) : base(BaseDal)
        {
            _cacheService = cacheService;
            _warehouseArearepository = warehouseArearepository;
        }
        public IRepository<Dt_Warehouse> Repository => BaseDal;
@@ -80,6 +83,35 @@
            return WarehouseDisableStatus(new int[] { key });
        }
        public async Task<WebResponseContent> ReceiveWarehouseArea(List<WarehouseAreaDto> models)
        {
            var lists = _warehouseArearepository.Db.Queryable<Dt_WarehouseArea>().ToList();
            foreach (var item in models)
            {
                var dbfirst = lists.FirstOrDefault(x => x.Code == item.Code);
                if (dbfirst != null)
                {
                    dbfirst.Code = item.Code;
                    dbfirst.Name = item.Name;
                    dbfirst.FactoryArea = item.FactoryArea;
                    _warehouseArearepository.UpdateData(dbfirst);
                }
                else
                {
                    _warehouseArearepository.AddData(new Dt_WarehouseArea { Code=item.Code,Name=item.Name,FactoryArea=item.FactoryArea});
                }
            }
            return WebResponseContent.Instance.OK();
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Basic/LocationGroupDTO.cs
@@ -1,8 +1,10 @@
using System;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_DTO.Basic
{
@@ -32,4 +34,10 @@
        public int EnableStatusB { get; set; }
    }
    public class LocationTypeDto
    {
        public int LocationType { get; set; }
        public string LocationTypeDesc { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Basic/SupplierDTO.cs
@@ -1,9 +1,11 @@
using System;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.Attributes;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_DTO.Basic
{
@@ -76,4 +78,13 @@
        [PropertyValidate("操作类型", NotNullAndEmpty = true,Check = new object[] { 0, 1, 2 })]
        public string OperateType { get; set; }
    }
    public class WarehouseAreaDto
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public string FactoryArea { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs
@@ -31,6 +31,8 @@
        public decimal PickQuantity { get; set; }
        public string LocationCode { get; set; }
        public string PalletCode { get; set; }
        public int StockId { get; set; } // åº“å­˜ID
        public int OutStockLockInfoId { get; set; } // å‡ºåº“锁定信息ID
    }     
    public class DirectOutboundRequest
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IBasicService/ILocationInfoService.cs
@@ -59,6 +59,8 @@
        List<Dt_LocationInfo> GetLocationInfos(List<string> locationCodes);
        List<LocationTypeDto> GetLocationTypes();
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IBasicService/IWarehouseService.cs
@@ -6,6 +6,7 @@
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Basic;
using WIDESEA_Model.Models;
namespace WIDESEA_IBasicService
@@ -13,5 +14,7 @@
    public interface IWarehouseService : IService<Dt_Warehouse>
    {
        IRepository<Dt_Warehouse> Repository { get; }
        Task<WebResponseContent> ReceiveWarehouseArea(List<WarehouseAreaDto> models);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs
@@ -21,8 +21,9 @@
 
        Task<List<Dt_OutStockLockInfo>> GetByOrderDetailId(int orderDetailId);
        Task<List<Dt_OutStockLockInfo>> GetByPalletCode(string palletCode, int? status = null);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, int? taskNum = null);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, string barcode = null, int? taskNum = null);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder,Dt_OutboundOrderDetail outboundOrderDetail,Dt_StockInfo outStock, decimal assignQuantity, string barcode = null);
        Task<List<Dt_OutStockLockInfo>> GetPalletLockInfos(string palletCode);
        Task<WebResponseContent> UpdateLockInfoBarcode(int lockInfoId, string newBarcode);
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
@@ -18,7 +18,7 @@
        Task<WebResponseContent> CancelPicking(CancelPickingRequest request);
        Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
         Task<WebResponseContent> ValidateBarcode(string barcode);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs
@@ -12,7 +12,8 @@
        List<Dt_StockInfo> GetStockInfos(string materielCode, string lotNo, List<string> locationCodes);
        List<Dt_StockInfo> GetUseableStocks(string materielCode, string batchNo);
        List<Dt_StockInfo> GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, decimal needQuantity, out decimal residueQuantity);
        void AddMaterielGroup(Dt_StockInfo stockInfo);
        (List<Dt_StockInfo>, Dictionary<int, decimal>) GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, decimal needQuantity, out decimal residueQuantity);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs
@@ -358,6 +358,7 @@
                        Status = 0,
                        OrderNo = inboundOrder.InboundOrderNo,
                        BusinessType = inboundOrder.BusinessType,
                        ProductionDate=DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")
                    });
                    item.ReceiptQuantity = item.BarcodeQty;
@@ -376,7 +377,7 @@
                {
                    inboundOrder.OrderStatus = InOrderStatusEnum.入库中.ObjToInt();
                }
                inboundOrder.Operator = App.User.UserName;
                content = MaterielGroupUpdateData(inboundOrder, dbinboundOrderDetails, stockInfo);
                if (content.Status)
                {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_LocationInfo.cs
@@ -30,7 +30,7 @@
        /// è´§ä½ç¼–号
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 30, ColumnDescription = "货位编号")]
        public string LocationCode {  get; set; }
        public string LocationCode { get; set; }
        /// <summary>
        /// è´§ä½åç§°
@@ -42,13 +42,13 @@
        /// å··é“编号
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 20, ColumnDescription = "巷道编号")]
        public string RoadwayNo {  get; set; }
        public string RoadwayNo { get; set; }
        /// <summary>
        /// è´§ä½è¡Œ
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "货位行")]
        public int Row {  get; set; }
        public int Row { get; set; }
        /// <summary>
        /// è´§ä½åˆ—
@@ -60,25 +60,25 @@
        /// è´§ä½å±‚
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "货位层")]
        public int Layer {  get; set; }
        public int Layer { get; set; }
        /// <summary>
        /// è´§ä½æ·±åº¦
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "货位深度")]
        public int Depth {  get; set; }
        public int Depth { get; set; }
        /// <summary>
        /// è´§ä½ç±»åž‹
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "货位类型")]
        public int LocationType {  get; set; }
        public int LocationType { get; set; }
        /// <summary>
        /// è´§ä½çŠ¶æ€
        /// </summary>
        [SugarColumn(IsNullable = true, DefaultValue = "0", ColumnDescription = "货位状态")]
        public int LocationStatus {  get; set; }
        public int LocationStatus { get; set; }
        /// <summary>
        /// ç¦ç”¨çŠ¶æ€
@@ -90,6 +90,19 @@
        /// å¤‡æ³¨
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 200, ColumnDescription = "备注")]
        public string Remark {  get; set; }
        public string Remark { get; set; }
    }
    [SugarTable(nameof(Dt_LocationType), "货位区域类型")]
    public class Dt_LocationType : BaseEntity
    {
        /// <summary>
        /// ä¸»é”®
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键")]
        public int Id { get; set; }
        public int LocationType { get; set; }
        public string LocationTypeDesc { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_WarehouseArea.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_Model.Models.Basic
{
    [SugarTable(nameof(Dt_WarehouseArea), "仓库区域信息")]
    public class Dt_WarehouseArea : BaseEntity
    {
        /// <summary>
        /// ä¸»é”®
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键")]
        public int Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public string FactoryArea { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Inbound/Dt_InboundOrder.cs
@@ -93,6 +93,8 @@
        [SugarColumn(IsNullable = true, Length = 200, ColumnDescription = "备注")]
        public string Remark { get; set; }
        [SugarColumn(IsNullable = false, Length = 50,   ColumnDescription = "操作者")]
        public string Operator { get; set; }
        /// <summary>
        /// å…¥åº“单明细
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -108,6 +108,8 @@
        [SugarColumn(IsNullable = true, ColumnDescription = "任务号")]
        public int? TaskNum { get; set; }
        public string SupplyCode { get; set; }
        public string WarehouseCode { get; set; }
        /// <summary>
        /// çŠ¶æ€ çŠ¶æ€ï¼š0-已分配 1-部分拣选 2-已拣选 3-已完成
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrder.cs
@@ -99,6 +99,9 @@
        [SugarColumn(IsNullable = true, Length = 200, ColumnDescription = "备注")]
        public string Remark { get; set; }
        [SugarColumn(IsNullable = false, Length = 50, ColumnDescription = "操作者")]
        public string Operator { get; set; }
        /// <summary>
        /// å‡ºåº“单明细
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs
@@ -132,7 +132,7 @@
        [SugarColumn(IsNullable = true, ColumnDescription = "备注")]
        public string Remark { get; set; }
        [SugarColumn(IsIgnore = true)]
        [Navigate(NavigateType.ManyToOne, nameof(Dt_StockInfo.Id), nameof(Id))]
        public Dt_StockInfo StockInfo { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs
@@ -32,59 +32,52 @@
            _recordService = recordService;
        }
        public Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, int? taskNum = null)
        {
            Dt_OutStockLockInfo outStockLockInfo = new Dt_OutStockLockInfo()
            {
                PalletCode = outStock.PalletCode,
                AssignQuantity = assignQuantity,
                MaterielCode = outboundOrderDetail.MaterielCode,
                BatchNo = outboundOrderDetail.BatchNo ?? outStock.Details.FirstOrDefault()?.BatchNo,
                LocationCode = outStock.LocationCode,
                MaterielName = outboundOrderDetail.MaterielName,
                OrderDetailId = outboundOrderDetail.Id,
                OrderNo = outboundOrder.OrderNo,
                OrderType = outboundOrder.OrderType,
                OriginalQuantity = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).Sum(x => x.StockQuantity),
                Status = taskNum == null ? OutLockStockStatusEnum.已分配.ObjToInt() : OutLockStockStatusEnum.出库中.ObjToInt(),
                StockId = outStock.Id,
                TaskNum = taskNum,
                OrderQuantity = outboundOrderDetail.OrderQuantity,
                Unit = outboundOrderDetail.Unit,
                //ProductionDate = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).FirstOrDefault()?.ProductionDate,
                //EffectiveDate = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).FirstOrDefault()?.EffectiveDate
            };
            return outStockLockInfo;
        }
        /// <summary>
        /// åˆ›å»ºå‡ºåº“锁定信息
        /// åˆ›å»ºå‡ºåº“锁定信息 - ä¿®å¤ç‰ˆæœ¬
        /// </summary>
        public Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, string barcode = null, int? taskNum = null)
        public Dt_OutStockLockInfo GetOutStockLockInfo(
            Dt_OutboundOrder outboundOrder,
            Dt_OutboundOrderDetail outboundOrderDetail,
            Dt_StockInfo outStock,
            decimal assignQuantity,
            string barcode = null)
        {
            // èŽ·å–åº“å­˜æ˜Žç»†ä¸­çš„æ¡ç ä¿¡æ¯ï¼ˆå¦‚æžœæœªæŒ‡å®šæ¡ç ï¼Œä½¿ç”¨ç¬¬ä¸€ä¸ªå¯ç”¨æ¡ç ï¼‰
            var stockDetails = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode && x.StockQuantity > x.OutboundQuantity)
                .OrderBy(x => x.ProductionDate).ToList();
            // èŽ·å–åº“å­˜æ˜Žç»†ä¿¡æ¯
            var stockDetails = outStock.Details
                .Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode)
                .ToList();
            if (!stockDetails.Any())
            {
                throw new Exception($"未找到物料[{outboundOrderDetail.MaterielCode}]的可用库存明细");
                throw new Exception($"未找到物料[{outboundOrderDetail.MaterielCode}]的库存明细");
            }
            // ç¡®å®šæ¡ç ï¼ˆå¦‚果未指定,使用最早入库的条码)
            var targetBarcode = barcode;
            if (string.IsNullOrEmpty(targetBarcode))
            // ç¡®å®šæ¡ç 
            string targetBarcode;
            if (!string.IsNullOrEmpty(barcode))
            {
                targetBarcode = stockDetails.First().Barcode;
                // éªŒè¯æŒ‡å®šçš„æ¡ç æ˜¯å¦å­˜åœ¨
                var specifiedBarcodeDetail = stockDetails.FirstOrDefault(x => x.Barcode == barcode);
                if (specifiedBarcodeDetail == null)
                {
                    throw new Exception($"指定的条码[{barcode}]在库存中不存在");
                }
                targetBarcode = barcode;
            }
            // èŽ·å–è¯¥æ¡ç çš„å¯ç”¨æ•°é‡
            var barcodeDetail = stockDetails.FirstOrDefault(x => x.Barcode == targetBarcode);
            if (barcodeDetail == null)
            else
            {
                throw new Exception($"条码[{targetBarcode}]在库存中不存在");
                // ä½¿ç”¨ç¬¬ä¸€ä¸ªå¯ç”¨æ¡ç 
                var firstAvailableDetail = stockDetails
                    .Where(x => x.StockQuantity > x.OutboundQuantity)
                    .OrderBy(x => x.ProductionDate)
                    .FirstOrDefault();
                if (firstAvailableDetail == null)
                {
                    throw new Exception($"物料[{outboundOrderDetail.MaterielCode}]没有可用库存");
                }
                targetBarcode = firstAvailableDetail.Barcode;
            }
            return new Dt_OutStockLockInfo()
@@ -101,19 +94,18 @@
                OriginalQuantity = outStock.Details
                    .Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode)
                    .Sum(x => x.StockQuantity),
                Status = taskNum == null ? (int)OutLockStockStatusEnum.已分配 : (int)OutLockStockStatusEnum.出库中,
                Status = (int)OutLockStockStatusEnum.已分配,
                StockId = outStock.Id,
                TaskNum = taskNum,
                Unit = outboundOrderDetail.Unit,
                // æ–°å¢žå­—段赋值
                CurrentBarcode = targetBarcode, // å½“前分配的条码
                OriginalLockQuantity = assignQuantity, // åŽŸå§‹é”å®šæ•°é‡
                IsSplitted = 0 // åˆå§‹æœªæ‹†åŒ…
                SupplyCode     = outboundOrderDetail.SupplyCode,
                WarehouseCode = outboundOrderDetail.WarehouseCode,
                // æ–°å¢žå­—段
                CurrentBarcode = targetBarcode,
                OriginalLockQuantity = assignQuantity,
                IsSplitted = 0
            };
        }
        /// <summary>
        /// æ ¹æ®è®¢å•明细ID获取出库锁定信息
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -1,4 +1,5 @@
using WIDESEA_Common.LocationEnum;
using Microsoft.Extensions.Logging;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
@@ -24,8 +25,8 @@
        private readonly IRecordService _recordService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly ILocationStatusChangeRecordService _locationStatusChangeRecordService;
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IOutboundOrderService outboundOrderService) : base(BaseDal)
        private readonly ILogger<OutboundOrderDetailService> _logger;
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IOutboundOrderService outboundOrderService, ILogger<OutboundOrderDetailService> logger) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockService = stockService;
@@ -35,6 +36,7 @@
            _locationInfoService = locationInfoService;
            _locationStatusChangeRecordService = locationStatusChangeRecordService;
            _outboundOrderService = outboundOrderService;
            _logger = logger;
        }
@@ -85,10 +87,10 @@
                }
                // åˆ†é…åº“存(按先进先出)
                List<Dt_StockInfo> autoAssignStocks = _stockService.StockInfoService.GetOutboundStocks(
                var (autoAssignStocks, stockAllocations) = _stockService.StockInfoService.GetOutboundStocks(
                    stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                if (residueQuantity > 0)
                if (residueQuantity > 0 && residueQuantity == needQuantity)
                {
                    throw new Exception($"物料[{item.MaterielCode}]库存不足,需要{needQuantity},可用{needQuantity - residueQuantity}");
                }
@@ -96,7 +98,7 @@
                outStocks.AddRange(autoAssignStocks);
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, outStockLockInfos, outboundOrder);
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, stockAllocations, outStockLockInfos, outboundOrder);
            }
            locationInfos.AddRange(_locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).Distinct().ToList()));
@@ -105,52 +107,59 @@
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»† - ä¿®å¤ç‰ˆæœ¬
        /// </summary>
        private void DistributeLockQuantityByFIFO(
            List<Dt_OutboundOrderDetail> details,
            List<Dt_StockInfo> assignStocks,
            Dictionary<int, decimal> stockAllocations,
            List<Dt_OutStockLockInfo> outStockLockInfos,
            Dt_OutboundOrder outboundOrder)
        {
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序出库单明细(假设先创建的明细需要优先满足)
            var sortedDetails = details
                .OrderBy(x => x.Id) // æŒ‰ID排序,假设先创建的ID小
                .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity > 0) // åªå¤„理还需要分配的数量
                .OrderBy(x => x.Id)
                .ToList();
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序库存(生产日期最早的优先)
            var sortedStockDetails = assignStocks
            if (!sortedDetails.Any()) return;
            // èŽ·å–æ‰€æœ‰åˆ†é…äº†åº“å­˜çš„æ˜Žç»†ï¼ŒæŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åº
            var allocatedStockDetails = assignStocks
                .SelectMany(x => x.Details)
                .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode))
                .Where(x => stockAllocations.ContainsKey(x.Id))
                .OrderBy(x => x.ProductionDate)
                .ThenBy(x => x.StockId)
                .ToList();
            // ä¸ºæ¯ä¸ªåº“存明细创建分配记录
            foreach (var stockDetail in sortedStockDetails)
            foreach (var stockDetail in allocatedStockDetails)
            {
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                var allocatedQuantity = stockDetail.OutboundQuantity; // è¿™ä¸ªåº“存明细分配的数量
                if (!stockAllocations.TryGetValue(stockDetail.Id, out decimal allocatedQuantity))
                    continue;
                if (allocatedQuantity <= 0) continue;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                decimal remainingAllocate = allocatedQuantity;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                foreach (var detail in sortedDetails)
                {
                    if (remainingAllocate <= 0) break;
                    // è®¡ç®—这个明细还需要分配的数量
                    var alreadyAssigned = outStockLockInfos
                        .Where(x => x.OrderDetailId == detail.Id && x.StockId == stockInfo.Id)
                        .Sum(x => x.AssignQuantity);
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity - alreadyAssigned;
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                    if (detailNeed <= 0) continue;
                    // åˆ†é…æ•°é‡
                    var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                    // éªŒè¯æ¡ç æ˜¯å¦å­˜åœ¨
                    if (string.IsNullOrEmpty(stockDetail.Barcode))
                    {
                        throw new Exception($"库存明细ID[{stockDetail.Id}]的条码为空");
                    }
                    // åˆ›å»ºå‡ºåº“锁定信息
                    var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
@@ -162,10 +171,32 @@
                    remainingAllocate -= assignQuantity;
                }
                // å¦‚果还有剩余分配数量,说明逻辑有误
                // å¦‚果还有剩余分配数量,重新分配或记录警告
                if (remainingAllocate > 0)
                {
                    throw new Exception($"库存分配逻辑错误,剩余未分配数量:{remainingAllocate}");
                    // é‡æ–°åˆ†é…ç»™å…¶ä»–需要分配的明细
                    foreach (var detail in sortedDetails)
                    {
                        if (remainingAllocate <= 0) break;
                        var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                        if (detailNeed <= 0) continue;
                        var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stockInfo, assignQuantity, stockDetail.Barcode);
                        outStockLockInfos.Add(lockInfo);
                        detail.LockQuantity += assignQuantity;
                        remainingAllocate -= assignQuantity;
                    }
                    // å¦‚果还有剩余,记录警告但不抛出异常
                    if (remainingAllocate > 0)
                    {
                        _logger.LogWarning($"库存分配后仍有剩余数量未分配: {remainingAllocate}, æ¡ç : {stockDetail.Barcode}");
                    }
                }
            }
        }
@@ -219,7 +250,7 @@
                        var (barcode, barcodeQuantity) = GetBarcodeForAllocation(barcodeAllocation, canAssign);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stock, canAssign, barcode,null);
                            outboundOrder, detail, stock, canAssign, barcode);
                        outStockLockInfos.Add(lockInfo);
                        availableAssign -= canAssign;
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -46,6 +46,47 @@
            _splitPackageService = splitPackageService;
        }
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            try
            {
                if (string.IsNullOrEmpty(barcode))
                {
                    return WebResponseContent.Instance.Error("条码不能为空");
                }
                // æ ¹æ®æ¡ç æŸ¥è¯¢åº“存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Includes(x => x.StockInfo)
                    .Where(x => x.Barcode == barcode)
                    .FirstAsync();
                if (stockDetail == null)
                {
                    return WebResponseContent.Instance.Error("条码不存在");
                }
                var result = new
                {
                    Barcode = barcode,
                    MaterielCode = stockDetail.MaterielCode,
                    BatchNo = stockDetail.BatchNo,
                    AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
                    LocationCode = stockDetail.StockInfo?.LocationCode,
                    PalletCode = stockDetail.StockInfo?.PalletCode
                };
                return WebResponseContent.Instance.OK(null, result);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ‰«ç æ‹£é€‰ç¡®è®¤ - ç®€åŒ–版本
        /// åªå¤„理实际拣选的库存扣减
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -87,15 +87,25 @@
        /// <param name="needQuantity"></param>
        /// <param name="residueQuantity"></param>
        /// <returns></returns>
        public List<Dt_StockInfo> GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, decimal needQuantity, out decimal residueQuantity)
        public (List<Dt_StockInfo>, Dictionary<int, decimal>) GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, decimal needQuantity, out decimal residueQuantity)
        {
            List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序(按条码的生产日期)
            Dictionary<int, decimal> stockAllocations = new Dictionary<int, decimal>(); // è®°å½•每个库存明细的分配数量
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序所有相关的库存明细
            var sortedStockDetails = stockInfos
                .SelectMany(x => x.Details)
                .Where(x => x.MaterielCode == materielCode && x.StockQuantity > x.OutboundQuantity)
                .OrderBy(x => x.ProductionDate).ThenBy(x => x.StockId)
                .Where(x => x.MaterielCode == materielCode &&
                           x.StockQuantity > x.OutboundQuantity) // æœ‰å¯ç”¨åº“å­˜
                .OrderBy(x => x.ProductionDate) // æŒ‰ç”Ÿäº§æ—¥æœŸæŽ’序,先进先出
                .ThenBy(x => x.StockId)         // ç›¸åŒç”Ÿäº§æ—¥æœŸæŒ‰åº“å­˜ID排序
                .ToList();
            if (!sortedStockDetails.Any())
            {
                residueQuantity = needQuantity;
                return (outStocks, stockAllocations);
            }
            // è®¡ç®—总可用库存
            var stockTotalQuantity = sortedStockDetails.Sum(x => x.StockQuantity - x.OutboundQuantity);
@@ -103,6 +113,7 @@
            if (stockTotalQuantity < needQuantity)
            {
                residueQuantity = needQuantity - stockTotalQuantity;
                // ä¸æŠ›å‡ºå¼‚常,允许部分分配
            }
            else
            {
@@ -111,17 +122,22 @@
            decimal remainingNeed = needQuantity;
            // æŒ‰æ¡ç åˆ†é…åº“å­˜
            // æŒ‰å…ˆè¿›å…ˆå‡ºé¡ºåºåˆ†é…åº“å­˜
            foreach (var detail in sortedStockDetails)
            {
                if (remainingNeed <= 0) break;
                decimal availableQuantity = detail.StockQuantity - detail.OutboundQuantity;
                if (availableQuantity <= 0) continue;
                decimal allocateQuantity = Math.Min(availableQuantity, remainingNeed);
                // æ›´æ–°å‡ºåº“数量
                detail.OutboundQuantity += allocateQuantity;
                remainingNeed -= allocateQuantity;
                // è®°å½•分配数量
                stockAllocations[detail.Id] = allocateQuantity;
                // å¦‚果这个库存还没添加到出库列表中,就添加
                var stockInfo = stockInfos.First(x => x.Id == detail.StockId);
@@ -132,8 +148,9 @@
            }
            residueQuantity = remainingNeed;
            return outStocks;
            return (outStocks, stockAllocations);
        }
        /// <summary>
        /// æ ¹æ®æ¡ç èŽ·å–åº“å­˜ä¿¡æ¯
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Basic/LocationInfoController.cs
@@ -45,12 +45,17 @@
        public WebResponseContent TTTTTAssignLocation()
        {
            var sddd = Service.AssignLocation();
            return WebResponseContent.Instance.OK("resdasd", sddd);
        }
        [HttpPost, Route("GetLocationTypes"), AllowAnonymous, MethodParamsValidate]
        public WebResponseContent GetLocationTypes()
        {
            var lists = Service.GetLocationTypes();
            return WebResponseContent.Instance.OK("", lists);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Basic/WarehouseController.cs
@@ -1,9 +1,15 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WIDESEA_Core;
using WIDESEA_Core.Attributes;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
using WIDESEA_WMSServer.Controllers.Inbound;
namespace WIDESEA_WMSServer.Controllers.Basic
{
@@ -14,9 +20,34 @@
    [ApiController]
    public class WarehouseController : ApiBaseController<IWarehouseService, Dt_Warehouse>
    {
        public WarehouseController(IWarehouseService service) : base(service)
        private readonly ILogger<WarehouseController> _logger;
        public WarehouseController(IWarehouseService service, ILogger<WarehouseController> logger) : base(service)
        {
            _logger = logger;
        }
        /// <summary>
        /// æŽ¥æ”¶MES ä»“库信息
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost, Route("ReceiveWarehouse"), AllowAnonymous, MethodParamsValidate]
        public async Task<WebResponseContent> ReceiveWarehouse([FromBody] List<WarehouseAreaDto> model)
        {
            if (model == null || !model.Any())
            {
                return WebResponseContent.Instance.Error("仓库数据不能为空");
            }
            _logger.LogInformation("WarehouseController ReceiveWarehouse:  " + JsonConvert.SerializeObject(model));
            var content = await Service.ReceiveWarehouseArea(model);
            if (content.Status) return WebResponseContent.Instance.OK(200);
            else return WebResponseContent.Instance.Error(content.Message);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundOrderController.cs
@@ -62,7 +62,7 @@
        /// <summary>
        /// æ ¹æ®ID获取出库单
        /// </summary>ss
        [HttpPost, Route("GetById"), AllowAnonymous, MethodParamsValidate]
        [HttpGet, Route("GetById"), AllowAnonymous, MethodParamsValidate]
        public async Task<WebResponseContent> GetById(int id)
        {
            var order = await Service.GetById(id);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
@@ -46,7 +46,14 @@
        {
            return await Service.ConfirmPicking(request);
        }
        /// <summary>
        /// éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        /// </summary>
        [HttpGet("ValidateBarcode")]
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            return await Service.ValidateBarcode(barcode);
        }
        /// <summary>
        /// æ‹†åŒ…操作
        /// </summary>