z8018
2025-12-17 53719f76ffb7a71db70c8b71bb5dd975904fbc83
整箱出库
已修改12个文件
1333 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/api/http.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/Index.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/out copyPicking.vue 558 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/outPicking.vue 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/CodeChunks.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/SemanticSymbols.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/OutboundCompleteRequestDTO.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundQueryService.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs 326 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundController.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/api/http.js
@@ -13,8 +13,8 @@
let loadingInstance;
let loadingStatus = false;
if (process.env.NODE_ENV == 'development') {
    axios.defaults.baseURL = 'http://127.0.0.1:9291/';
    // axios.defaults.baseURL = window.webConfig.webApiBaseUrl;
    // axios.defaults.baseURL = 'http://127.0.0.1:9291/';
    axios.defaults.baseURL = window.webConfig.webApiBaseUrl;
}
else if (process.env.NODE_ENV == 'debug') {
    axios.defaults.baseURL = 'http://127.0.0.1:8098/';
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/Index.vue
@@ -333,20 +333,20 @@
    const createSocket = (url) => {
      // åˆ›å»ºWebSocket连接
      //"ws://127.0.0.1:9295/admin"
      client = new WebSocket(url);
      // client = new WebSocket(url);
      client.onopen = function () {
        client.onmessage = handleMessage;
        store.commit("setWebsocket", client);
        console.log("WebSocket è¿žæŽ¥æˆåŠŸ");
      };
      // client.onopen = function () {
      //   client.onmessage = handleMessage;
      //   store.commit("setWebsocket", client);
      //   console.log("WebSocket è¿žæŽ¥æˆåŠŸ");
      // };
      client.onclose = function () {
        console.log("WebSocket è¿žæŽ¥å…³é—­");
        setTimeout(createSocket, 10000);
      };
      // client.onclose = function () {
      //   console.log("WebSocket è¿žæŽ¥å…³é—­");
      //   setTimeout(createSocket, 10000);
      // };
      client.onerror = function () {};
      // client.onerror = function () {};
    };
    const changeTheme = (name) => {
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/out copyPicking.vue
@@ -21,7 +21,13 @@
            <div class="scan-section">
                <el-alert title="请使用扫码枪扫描,支持回车自动确认" type="info" :closable="false" show-icon class="scan-alert">
                    <template #default>
                        <span>1. è¯·å…ˆæ‰«ææ‰˜ç›˜ç  â†’ 2. å†æ‰«æç‰©æ–™æ¡ç </span>
                        <div>
                            <div>1. è¯·å…ˆæ‰«ææ‰˜ç›˜ç  â†’ 2. å†æ‰«æç‰©æ–™æ ‡ç­¾ç </div>
                            <div style="margin-top: 8px; font-size: 13px; color: #666;">
                                <i class="el-icon-info" style="color: #409EFF;"></i>
                                æ”¯æŒæ‰«æ‰˜ç›˜ç æ•´ç®±å‡ºåº“:扫描托盘码后可直接进行整箱物料拣选,无需再扫描物料标签码。
                            </div>
                        </div>
                    </template>
                </el-alert>
@@ -71,6 +77,41 @@
                        </el-col>
                    </el-row>
                </el-form>
                <!-- åˆ†æ‹£ç»Ÿè®¡ä¿¡æ¯ -->
                <div class="picking-stats" v-if="scanForm.palletCode && unpickedData.length > 0">
                    <el-divider content-position="left">
                        <span style="color: #409EFF; font-size: 14px;">
                            <i class="el-icon-data-analysis"></i> æ‰˜ç›˜åˆ†æ‹£ç»Ÿè®¡
                        </span>
                    </el-divider>
                    <div class="stats-container">
                        <div class="stat-item">
                            <el-tag type="primary" size="medium" effect="dark">
                                <i class="el-icon-s-order"></i>
                                åˆ†æ‹£æ€»æ•°ï¼š<b>{{ calculateTotalAssignQuantity() }}</b>
                            </el-tag>
                        </div>
                        <div class="stat-item">
                            <el-tag type="success" size="medium" effect="dark">
                                <i class="el-icon-circle-check"></i>
                                å·²åˆ†æ‹£ï¼š<b>{{ calculateTotalSortedQuantity() }}</b>
                            </el-tag>
                        </div>
                        <div class="stat-item">
                            <el-tag type="warning" size="medium" effect="dark">
                                <i class="el-icon-time"></i>
                                æœªåˆ†æ‹£ï¼š<b>{{ calculateTotalUnsortedQuantity() }}</b>
                            </el-tag>
                        </div>
                        <div class="stat-item">
                            <el-tag type="success" size="medium" effect="dark">
                                <i class="el-icon-box"></i>
                                æ˜¯å¦æ•´å‡ºï¼š{{ hasWholeOut() ? '是' : '否' }}
                            </el-tag>
                        </div>
                    </div>
                </div>
            </div>
        </el-card>
@@ -198,6 +239,53 @@
                    </el-card>
                </el-col>
            </el-row>
            <!-- æ‰˜ç›˜ç‰©æ–™åº“存信息 -->
            <!-- <div class="pallet-inventory" v-if="scanForm.palletCode && unpickedData.length > 0">
                <el-divider content-position="left">
                    <span style="color: #67C23A; font-size: 14px;">
                        <i class="el-icon-goods"></i> æ‰˜ç›˜ç‰©æ–™åº“存信息
                    </span>
                </el-divider>
                <div class="inventory-container">
                    <el-table :data="unpickedData" size="small" :show-header="true" :border="true" stripe
                        highlight-current-row max-height="200" class="inventory-table">
                        <el-table-column type="index" label="序号" width="50" align="center" />
                        <el-table-column prop="materielCode" label="物料编码" width="100" show-overflow-tooltip />
                        <el-table-column prop="materielName" label="物料名称" width="120" show-overflow-tooltip />
                        <el-table-column prop="batchNo" label="批次号" width="90" />
                        <el-table-column label="当前库存" width="80" align="right">
                            <template #default="scope">
                                <el-text type="primary" tag="b">{{ scope.row.currentStock || 0 }}</el-text>
                            </template>
                        </el-table-column>
                        <el-table-column label="分拣数量" width="80" align="right">
                            <template #default="scope">
                                <el-text type="warning">{{ scope.row.assignQuantity }}</el-text>
                            </template>
                        </el-table-column>
                        <el-table-column label="已分拣" width="70" align="right">
                            <template #default="scope">
                                <el-text type="success">{{ scope.row.sortedQuantity || 0 }}</el-text>
                            </template>
                        </el-table-column>
                        <el-table-column label="剩余库存" width="80" align="right">
                            <template #default="scope">
                                <el-text type="info">{{ calculateRemainingStock(scope.row) }}</el-text>
                            </template>
                        </el-table-column>
                        <el-table-column prop="unit" label="单位" width="100" align="center" />
                        <el-table-column prop="locationCode" label="库位" width="150" />
                        <el-table-column label="状态" width="80" align="center">
                            <template #default="scope">
                                <el-tag :type="getStockStatusType(scope.row)" size="mini">
                                    {{ getStockStatusText(scope.row) }}
                                </el-tag>
                            </template>
                        </el-table-column>
                    </el-table>
                </div>
            </div> -->
        </div>
        <print-view ref="printView" @parentcall="parentcall"></print-view>
@@ -212,6 +300,81 @@
                    <el-button @click="confirmDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="executeConfirm" :loading="executeLoading">
                        ç¡®å®š
                    </el-button>
                </span>
            </template>
        </el-dialog>
        <!-- æ•´å‡ºç¡®è®¤å¯¹è¯æ¡† -->
        <el-dialog v-model="wholeOutDialogVisible" title="整出操作确认" width="500px" :before-close="handleWholeOutDialogClose" custom-class="whole-out-dialog">
            <div class="whole-out-content" v-if="wholeOutInfo">
                <!-- è­¦å‘Šæç¤º -->
                <el-alert title="该托盘包含需要整出的物料" type="warning" :closable="false" show-icon class="whole-out-alert">
                    <template #default>
                        <div>整出操作将一次性拣选该物料的所有库存,请确认信息无误后执行</div>
                    </template>
                </el-alert>
                <!-- æ‰˜ç›˜ä¿¡æ¯ -->
                <div class="info-section">
                    <h4 class="section-title">
                        <i class="el-icon-box"></i>
                        æ‰˜ç›˜ä¿¡æ¯
                    </h4>
                    <div class="info-grid">
                        <div class="info-item">
                            <label>托盘码:</label>
                            <span class="info-value">{{ wholeOutInfo.palletCode }}</span>
                        </div>
                        <div class="info-item">
                            <label>库位:</label>
                            <span class="info-value">{{ wholeOutInfo.locationCode || '未指定' }}</span>
                        </div>
                    </div>
                </div>
                <!-- æ•´å‡ºç‰©æ–™è¯¦æƒ… -->
                <div class="info-section">
                    <h4 class="section-title">
                        <i class="el-icon-s-grid"></i>
                        æ•´å‡ºç‰©æ–™è¯¦æƒ…
                    </h4>
                    <div class="info-grid">
                        <div class="info-item">
                            <label>物料编码:</label>
                            <span class="info-value">{{ wholeOutInfo.materielCode }}</span>
                        </div>
                        <div class="info-item">
                            <label>物料名称:</label>
                            <span class="info-value">{{ wholeOutInfo.materielName }}</span>
                        </div>
                        <div class="info-item">
                            <label>批次号:</label>
                            <span class="info-value">{{ wholeOutInfo.batchNo }}</span>
                        </div>
                        <div class="info-item">
                            <label>整出数量:</label>
                            <span class="info-value highlight">{{ wholeOutInfo.assignQuantity }} {{ wholeOutInfo.unit }}</span>
                        </div>
                        <div class="info-item">
                            <label>当前库存:</label>
                            <span class="info-value">{{ wholeOutInfo.currentStock || wholeOutInfo.originalQuantity }} {{ wholeOutInfo.unit }}</span>
                        </div>
                    </div>
                </div>
                <!-- æ“ä½œæç¤º -->
                <div class="operation-tip">
                    <i class="el-icon-info"></i>
                    <span>确认执行整出操作吗?此操作将一次性拣选该物料的所有库存。</span>
                </div>
            </div>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="wholeOutDialogVisible = false" size="medium">取消</el-button>
                    <el-button type="warning" @click="executeWholeOut" :loading="executeLoading" size="medium">
                        <i class="el-icon-check"></i>
                        ç¡®è®¤æ•´å‡º
                    </el-button>
                </span>
            </template>
@@ -253,7 +416,10 @@
            confirmDialogVisible: false,
            confirmMessage: '',
            currentAction: null,
            executeLoading: false
            executeLoading: false,
            matMixed: true,
            wholeOutDialogVisible: false,
            wholeOutInfo: null
        }
    },
    computed: {
@@ -300,8 +466,9 @@
            try {
                this.http.post(`/api/Outbound/QueryPickingTasks?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => {
                    if (response.status) {
                        if (response.data.length > 0) {
                            this.unpickedData = response.data
                        if (response.data.outStockLockInfos.length > 0) {
                            this.unpickedData = response.data.outStockLockInfos;
                            this.matMixed = response.data.isMatMixed;
                            this.calculateUnpickedStats()
                            // è‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†
@@ -384,7 +551,12 @@
                // this.$message.success(`托盘码: ${this.scanForm.palletCode}`)
                this.loadPalletData()
                // æ£€æŸ¥æ˜¯å¦éœ€è¦æ•´å‡ºç¡®è®¤
                this.$nextTick(() => {
                    if (this.hasWholeOut() && this.matMixed) {
                        this.showWholeOutConfirm()
                    }
                })
            }
        },
@@ -504,6 +676,69 @@
            }
        },
        // æ•´å‡ºç¡®è®¤ç›¸å…³æ–¹æ³•
        showWholeOutConfirm() {
            // èŽ·å–éœ€è¦æ•´å‡ºçš„ç‰©æ–™ä¿¡æ¯
            const wholeOutItem = this.unpickedData.find(item => item.assignQuantity === item.originalQuantity);
            if (wholeOutItem) {
                this.wholeOutInfo = {
                    palletCode: this.scanForm.palletCode,
                    locationCode: wholeOutItem.locationCode,
                    materielCode: wholeOutItem.materielCode,
                    materielName: wholeOutItem.materielName,
                    batchNo: wholeOutItem.batchNo,
                    assignQuantity: wholeOutItem.assignQuantity,
                    currentStock: wholeOutItem.currentStock,
                    originalQuantity: wholeOutItem.originalQuantity,
                    unit: wholeOutItem.unit
                };
                this.wholeOutDialogVisible = true;
            }
        },
        handleWholeOutDialogClose() {
            if (!this.executeLoading) {
                this.wholeOutDialogVisible = false;
                this.wholeOutInfo = null;
            }
        },
        executeWholeOut() {
            if (!this.wholeOutInfo) {
                this.$message.error('整出信息无效');
                return;
            }
            this.executeLoading = true;
            try {
                // è°ƒç”¨æ•´å‡ºæŽ¥å£ï¼Œè¿™é‡Œä½¿ç”¨ç‰©æ–™ç¼–码作为条码
                this.http.post('/api/Outbound/CompleteOutboundWithBarcode', {
                    orderNo: this.orderNo,
                    palletCode: this.scanForm.palletCode,
                    barcode: this.wholeOutInfo.materielCode, // ä½¿ç”¨ç‰©æ–™ç¼–码进行整出
                    operator: this.getUserName()
                }).then(response => {
                    if (response.status) {
                        if (response.data.scannedDetail.isUnpacked && response.data.scannedDetail.materialCodes.length > 0) {
                            this.$refs.printView.open(response.data.scannedDetail.materialCodes);
                        }
                        this.$message.success('整出操作成功');
                        this.wholeOutDialogVisible = false;
                        this.wholeOutInfo = null;
                        this.loadPalletData();
                    } else {
                        this.$message.error(response.message || '整出操作失败');
                    }
                });
            } catch (error) {
                console.error('整出操作失败:', error);
                this.$message.error('整出操作失败');
            } finally {
                this.executeLoading = false;
            }
        },
        quickPick(row) {
            this.scanForm.materialBarcode = row.materielCode
            this.handleConfirmPick()
@@ -587,6 +822,71 @@
            }
            return '未登录用户'
        },
        // è®¡ç®—分拣总数
        calculateTotalAssignQuantity() {
            return this.unpickedData.reduce((sum, item) => {
                return sum + (item.assignQuantity || 0)
            }, 0)
        },
        // è®¡ç®—已分拣总数
        calculateTotalSortedQuantity() {
            return this.unpickedData.reduce((sum, item) => {
                return sum + (item.sortedQuantity || 0)
            }, 0)
        },
        // è®¡ç®—未分拣总数
        calculateTotalUnsortedQuantity() {
            return this.unpickedData.reduce((sum, item) => {
                const assignQty = item.assignQuantity || 0
                const sortedQty = item.sortedQuantity || 0
                return sum + Math.max(0, assignQty - sortedQty)
            }, 0)
        },
        // æ£€æŸ¥æ˜¯å¦åŒ…含整出
        hasWholeOut() {
            return this.unpickedData.some(item => item.assignQuantity === item.originalQuantity) && this.matMixed;
        },
        // è®¡ç®—剩余库存
        calculateRemainingStock(row) {
            const currentStock = row.currentStock || 0
            const assignQty = row.assignQuantity || 0
            return Math.max(0, currentStock - assignQty)
        },
        // èŽ·å–åº“å­˜çŠ¶æ€ç±»åž‹
        getStockStatusType(row) {
            const currentStock = row.currentStock || 0
            const assignQty = row.assignQuantity || 0
            const sortedQty = row.sortedQuantity || 0
            if (sortedQty >= assignQty) {
                return 'success' // å·²å®Œæˆ
            } else if (currentStock < assignQty) {
                return 'danger' // åº“存不足
            } else {
                return 'warning' // è¿›è¡Œä¸­
            }
        },
        // èŽ·å–åº“å­˜çŠ¶æ€æ–‡æœ¬
        getStockStatusText(row) {
            const currentStock = row.currentStock || 0
            const assignQty = row.assignQuantity || 0
            const sortedQty = row.sortedQuantity || 0
            if (sortedQty >= assignQty) {
                return '已完成'
            } else if (currentStock < assignQty) {
                return '库存不足'
            } else {
                return '分拣中'
            }
        },
        getStatusType(status) {
@@ -802,4 +1102,252 @@
::v-deep .el-descriptions__content {
    color: #606266;
}
/* è¡¨æ ¼å¢žå¼ºæ ·å¼ */
::v-deep .el-table th {
    background-color: #fafafa;
    font-weight: 600;
    color: #303133;
}
::v-deep .el-table .el-text {
    font-weight: 500;
}
/* æ ‡ç­¾æ ·å¼å¢žå¼º */
::v-deep .el-tag--small {
    font-weight: 500;
}
/* æç¤ºä¿¡æ¯æ ·å¼å¢žå¼º */
.scan-alert ::v-deep .el-alert__content {
    width: 100%;
}
.scan-alert ::v-deep .el-alert__description {
    margin-top: 8px;
}
/* åˆ†æ‹£ç»Ÿè®¡ä¿¡æ¯æ ·å¼ */
.picking-stats {
    margin-top: 20px;
    padding: 0 10px;
}
.stats-container {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
}
.stat-item {
    display: inline-flex;
    align-items: center;
}
.stat-item .el-tag {
    display: flex;
    align-items: center;
    padding: 6px 12px;
    font-size: 13px;
    border-radius: 20px;
}
.stat-item .el-tag i {
    margin-right: 6px;
    font-size: 14px;
}
.stat-item b {
    margin-left: 4px;
    font-size: 14px;
}
/* åˆ†å‰²çº¿æ ·å¼ */
::v-deep .el-divider__text {
    background-color: #f5f5f5;
    padding: 0 20px;
}
/* æ‰˜ç›˜åº“存信息样式 */
.pallet-inventory {
    margin-top: 20px;
    padding: 0 10px;
}
.inventory-container {
    margin-top: 10px;
}
.inventory-table {
    width: 100%;
}
.inventory-table ::v-deep .el-table__header {
    background-color: #f0f9ff;
}
.inventory-table ::v-deep .el-table__header th {
    background-color: #e1f3ff;
    color: #1f2937;
    font-weight: 600;
    font-size: 12px;
    padding: 8px 0;
}
.inventory-table ::v-deep .el-table__body td {
    padding: 6px 0;
    font-size: 12px;
}
.inventory-table ::v-deep .el-table__row {
    cursor: pointer;
}
.inventory-table ::v-deep .el-table__row:hover {
    background-color: #f0f9ff;
}
/* åº“存表格中的标签样式 */
.inventory-table ::v-deep .el-tag--mini {
    font-size: 11px;
    padding: 1px 6px;
    height: 18px;
    line-height: 16px;
}
/* æ•´å‡ºç¡®è®¤å¼¹çª—样式 */
::v-deep .whole-out-dialog {
    border-radius: 8px;
}
::v-deep .whole-out-dialog .el-dialog__header {
    background: linear-gradient(135deg, #E6A23C 0%, #d9971a 100%);
    color: white;
    padding: 15px 20px;
    border-radius: 8px 8px 0 0;
}
::v-deep .whole-out-dialog .el-dialog__title {
    color: white;
    font-weight: bold;
    font-size: 16px;
}
::v-deep .whole-out-dialog .el-dialog__headerbtn .el-dialog__close {
    color: white;
    font-size: 18px;
}
.whole-out-content {
    padding: 0 10px;
}
.whole-out-alert {
    margin-bottom: 20px;
}
.whole-out-alert ::v-deep .el-alert__description {
    margin-top: 5px;
    font-size: 14px;
    color: #666;
}
.info-section {
    margin-bottom: 20px;
}
.section-title {
    color: #303133;
    font-size: 15px;
    font-weight: bold;
    margin-bottom: 10px;
    padding-bottom: 5px;
    border-bottom: 2px solid #E6A23C;
    display: flex;
    align-items: center;
}
.section-title i {
    margin-right: 8px;
    color: #E6A23C;
    font-size: 16px;
}
.info-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}
.info-item {
    display: flex;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px solid #f0f0f0;
}
.info-item label {
    font-weight: 500;
    color: #606266;
    min-width: 80px;
    font-size: 14px;
}
.info-item .info-value {
    color: #303133;
    font-size: 14px;
    flex: 1;
}
.info-item .info-value.highlight {
    color: #E6A23C;
    font-weight: bold;
    font-size: 15px;
}
.operation-tip {
    background-color: #fff7e6;
    border: 1px solid #ffd591;
    border-radius: 6px;
    padding: 12px 15px;
    margin-top: 20px;
    display: flex;
    align-items: center;
}
.operation-tip i {
    color: #E6A23C;
    margin-right: 8px;
    font-size: 16px;
    flex-shrink: 0;
}
.operation-tip span {
    color: #606266;
    font-size: 14px;
    line-height: 1.5;
}
/* å“åº”式调整 */
@media (max-width: 768px) {
    .info-grid {
        grid-template-columns: 1fr;
        gap: 8px;
    }
    .info-item {
        padding: 6px 0;
    }
    .info-item label {
        min-width: 70px;
        font-size: 13px;
    }
    .info-item .info-value {
        font-size: 13px;
    }
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/outPicking.vue
@@ -1,5 +1,6 @@
<template>
    <div class="picking-container">
    <div class="picking-container" v-loading="globalLoading" element-loading-text="处理中..."
        element-loading-background="rgba(255, 255, 255, 0.8)">
        <!-- é¡¶éƒ¨è®¢å•信息 -->
        <el-card class="order-info-card" shadow="never">
            <div class="order-header">
@@ -105,7 +106,7 @@
                            </el-tag>
                        </div>
                        <div class="stat-item">
                            <el-tag type="success" size="medium" effect="dark">
                            <el-tag :type="hasWholeOut() ? 'success' : 'warning'" size="medium" effect="dark">
                                <i class="el-icon-box"></i>
                                æ˜¯å¦æ•´å‡ºï¼š{{ hasWholeOut() ? '是' : '否' }}
                            </el-tag>
@@ -304,6 +305,84 @@
                </span>
            </template>
        </el-dialog>
        <!-- æ•´å‡ºç¡®è®¤å¯¹è¯æ¡† -->
        <el-dialog v-model="wholeOutDialogVisible" title="整出操作确认" width="500px"
            :before-close="handleWholeOutDialogClose" custom-class="whole-out-dialog" style="margin-right: 0px;">
            <div class="whole-out-content" v-if="wholeOutInfo">
                <!-- è­¦å‘Šæç¤º -->
                <el-alert title="该托盘包含需要整出的物料" type="warning" :closable="false" show-icon class="whole-out-alert">
                    <template #default>
                        <div>整出操作将一次性拣选该物料的所有库存,请确认信息无误后执行</div>
                    </template>
                </el-alert>
                <!-- æ‰˜ç›˜ä¿¡æ¯ -->
                <div class="info-section">
                    <h4 class="section-title">
                        <i class="el-icon-box"></i>
                        æ‰˜ç›˜ä¿¡æ¯
                    </h4>
                    <div class="info-grid">
                        <div class="info-item">
                            <label>托盘码:</label>
                            <span class="info-value">{{ wholeOutInfo.palletCode }}</span>
                        </div>
                        <div class="info-item">
                            <label>库位:</label>
                            <span class="info-value">{{ wholeOutInfo.locationCode || '未指定' }}</span>
                        </div>
                    </div>
                </div>
                <!-- æ•´å‡ºç‰©æ–™è¯¦æƒ… -->
                <div class="info-section">
                    <h4 class="section-title">
                        <i class="el-icon-s-grid"></i>
                        æ•´å‡ºç‰©æ–™è¯¦æƒ…
                    </h4>
                    <div class="info-grid">
                        <div class="info-item">
                            <label>物料编码:</label>
                            <span class="info-value">{{ wholeOutInfo.materielCode }}</span>
                        </div>
                        <div class="info-item">
                            <label>物料名称:</label>
                            <span class="info-value">{{ wholeOutInfo.materielName }}</span>
                        </div>
                        <div class="info-item">
                            <label>批次号:</label>
                            <span class="info-value">{{ wholeOutInfo.batchNo }}</span>
                        </div>
                        <div class="info-item">
                            <label>整出数量:</label>
                            <span class="info-value highlight">{{ wholeOutInfo.assignQuantity }} {{ wholeOutInfo.unit
                            }}</span>
                        </div>
                        <div class="info-item">
                            <label>当前库存:</label>
                            <span class="info-value">{{ wholeOutInfo.currentStock || wholeOutInfo.originalQuantity }} {{
                                wholeOutInfo.unit }}</span>
                        </div>
                    </div>
                </div>
                <!-- æ“ä½œæç¤º -->
                <div class="operation-tip">
                    <i class="el-icon-info"></i>
                    <span>确认执行整出操作吗?此操作将一次性拣选该物料的所有库存。</span>
                </div>
            </div>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="wholeOutDialogVisible = false" size="medium">取消</el-button>
                    <el-button type="warning" @click="executeWholeOut" :loading="executeLoading" size="medium">
                        <i class="el-icon-check"></i>
                        ç¡®è®¤æ•´å‡º
                    </el-button>
                </span>
            </template>
        </el-dialog>
    </div>
</template>
@@ -342,7 +421,10 @@
            confirmMessage: '',
            currentAction: null,
            executeLoading: false,
            matMixed: true
            matMixed: true,
            wholeOutDialogVisible: false,
            wholeOutInfo: null,
            globalLoading: false
        }
    },
    computed: {
@@ -370,30 +452,40 @@
            })
        },
        loadPalletData() {
        async loadPalletData() {
            if (!this.scanForm.palletCode) {
                this.unpickedData = []
                return
            }
            try {
                this.loadUnpickedData();
                this.loadPickedData();
                this.globalLoading = true
                await Promise.all([
                    this.loadUnpickedData(),
                    this.loadPickedData()
                ]);
            } catch (error) {
                console.error('加载托盘数据失败:', error)
                this.unpickedData = []
            } finally {
                this.globalLoading = false
            }
        },
        loadUnpickedData() {
            try {
            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.calculateUnpickedStats()
                            // æ£€æŸ¥æ˜¯å¦éœ€è¦æ•´å‡ºç¡®è®¤
                            this.$nextTick(() => {
                                if (this.hasWholeOut()) {
                                    this.showWholeOutConfirm()
                                }
                            })
                            // è‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†
                            this.$nextTick(() => {
                                if (this.$refs.materialInput) {
@@ -404,20 +496,21 @@
                            this.$message.warning('该托盘无未拣选任务')
                            this.unpickedData = []
                        }
                        resolve()
                    } else {
                        this.$message.error(response.message || '获取托盘数据失败')
                        this.unpickedData = []
                        reject(response.message || '获取托盘数据失败')
                    }
                }
                )
            } catch (error) {
                }).catch(error => {
                console.error('加载未拣选数据失败:', error)
            }
                    reject(error)
                })
            })
        },
        loadPickedData() {
            try {
            return new Promise((resolve, reject) => {
                this.http.post(`/api/Outbound/QueryPickedList?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => {
                    if (response.status) {
                        if (response.data.length > 0) {
@@ -426,16 +519,17 @@
                        } else {
                            this.pickedData = []
                        }
                        resolve()
                    } else {
                        this.$message.error(response.message || '获取托盘数据失败')
                        this.pickedData = []
                        reject(response.message || '获取托盘数据失败')
                    }
                }
                )
            } catch (error) {
                }).catch(error => {
                console.error('加载已拣选数据失败:', error)
            }
                    reject(error)
                })
            })
        },
        // è®¡ç®—未拣选
@@ -473,8 +567,6 @@
            if (this.scanForm.palletCode) {
                // this.$message.success(`托盘码: ${this.scanForm.palletCode}`)
                this.loadPalletData()
            }
        },
@@ -501,6 +593,7 @@
            }
            this.confirmLoading = true
            this.globalLoading = true
            try {
                this.http.post('/api/Outbound/CompleteOutboundWithBarcode', {
@@ -527,6 +620,7 @@
                this.$message.error('拣选确认失败')
            } finally {
                this.confirmLoading = false
                this.globalLoading = false
            }
        },
@@ -554,6 +648,7 @@
        executeConfirm() {
            this.executeLoading = true
            this.globalLoading = true
            try {
                let apiUrl = ''
@@ -585,12 +680,75 @@
                this.$message.error('操作失败')
            } finally {
                this.executeLoading = false
                this.globalLoading = false
            }
        },
        handleDialogClose() {
            if (!this.executeLoading) {
                this.confirmDialogVisible = false
            }
        },
        // æ•´å‡ºç¡®è®¤ç›¸å…³æ–¹æ³•
        showWholeOutConfirm() {
            // èŽ·å–éœ€è¦æ•´å‡ºçš„ç‰©æ–™ä¿¡æ¯
            const wholeOutItem = this.unpickedData.find(item => item.assignQuantity === item.originalQuantity);
            console.log('wholeOutItem:', wholeOutItem);
            if (wholeOutItem) {
                this.wholeOutInfo = {
                    palletCode: this.scanForm.palletCode,
                    locationCode: wholeOutItem.locationCode,
                    materielCode: wholeOutItem.materielCode,
                    materielName: wholeOutItem.materielName,
                    batchNo: wholeOutItem.batchNo,
                    assignQuantity: wholeOutItem.assignQuantity,
                    currentStock: wholeOutItem.currentStock,
                    originalQuantity: wholeOutItem.originalQuantity,
                    unit: wholeOutItem.unit
                };
                this.wholeOutDialogVisible = true;
            }
        },
        handleWholeOutDialogClose() {
            if (!this.executeLoading) {
                this.wholeOutDialogVisible = false;
                this.wholeOutInfo = null;
            }
        },
        executeWholeOut() {
            if (!this.wholeOutInfo) {
                this.$message.error('整出信息无效');
                return;
            }
            this.executeLoading = true;
            this.globalLoading = true;
            try {
                // è°ƒç”¨æ•´å‡ºæŽ¥å£ï¼Œè¿™é‡Œä½¿ç”¨ç‰©æ–™ç¼–码作为条码
                this.http.post('/api/Outbound/CompleteOutboundWithPallet', {
                    orderNo: this.orderNo,
                    palletCode: this.scanForm.palletCode,
                    operator: this.getUserName()
                }).then(response => {
                    if (response.status) {
                        this.$message.success('整出操作成功');
                        this.wholeOutDialogVisible = false;
                        this.wholeOutInfo = null;
                        this.loadPalletData();
                    } else {
                        this.$message.error(response.message || '整出操作失败');
                    }
                });
            } catch (error) {
                console.error('整出操作失败:', error);
                this.$message.error('整出操作失败');
            } finally {
                this.executeLoading = false;
                this.globalLoading = false;
            }
        },
@@ -636,7 +794,9 @@
        },
        refreshPickedTable() {
            this.loadPickedData()
            this.loadPickedData().catch(error => {
                console.error('刷新已拣选列表失败:', error)
            })
        },
        resetMaterialBarcode() {
@@ -704,7 +864,8 @@
        // æ£€æŸ¥æ˜¯å¦åŒ…含整出
        hasWholeOut() {
            return this.unpickedData.some(item => item.assignQuantity === item.originalQuantity) && this.matMixed;
            console.log('检查整出状态:', this.unpickedData.some(item => item.assignQuantity === item.originalQuantity));
            return this.unpickedData.some(item => item.assignQuantity === item.originalQuantity) && !this.matMixed;
        },
        // è®¡ç®—剩余库存
@@ -758,11 +919,19 @@
}
</script>
<style>
.el-dialog__header {
    margin: 0;
}
</style>
<style scoped>
.picking-container {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
    position: relative;
    overflow: hidden;
}
/* è®¢å•信息卡片 */
@@ -1071,4 +1240,154 @@
    height: 18px;
    line-height: 16px;
}
/* æ•´å‡ºç¡®è®¤å¼¹çª—样式 */
::v-deep .whole-out-dialog {
    border-radius: 8px;
}
::v-deep .whole-out-dialog .el-dialog__header {
    background: linear-gradient(135deg, #E6A23C 0%, #d9971a 100%);
    color: white;
    padding: 15px 20px;
    border-radius: 8px 8px 0 0;
}
::v-deep .whole-out-dialog .el-dialog__title {
    color: white;
    font-weight: bold;
    font-size: 16px;
}
::v-deep .whole-out-dialog .el-dialog__headerbtn .el-dialog__close {
    color: white;
    font-size: 18px;
}
.whole-out-content {
    padding: 0 10px;
}
.whole-out-alert {
    margin-bottom: 20px;
}
.whole-out-alert ::v-deep .el-alert__description {
    margin-top: 5px;
    font-size: 14px;
    color: #666;
}
.info-section {
    margin-bottom: 20px;
}
.section-title {
    color: #303133;
    font-size: 15px;
    font-weight: bold;
    margin-bottom: 10px;
    padding-bottom: 5px;
    border-bottom: 2px solid #E6A23C;
    display: flex;
    align-items: center;
}
.section-title i {
    margin-right: 8px;
    color: #E6A23C;
    font-size: 16px;
}
.info-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}
.info-item {
    display: flex;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px solid #f0f0f0;
}
.info-item label {
    font-weight: 500;
    color: #606266;
    min-width: 80px;
    font-size: 14px;
}
.info-item .info-value {
    color: #303133;
    font-size: 14px;
    flex: 1;
}
.info-item .info-value.highlight {
    color: #E6A23C;
    font-weight: bold;
    font-size: 15px;
}
.operation-tip {
    background-color: #fff7e6;
    border: 1px solid #ffd591;
    border-radius: 6px;
    padding: 12px 15px;
    margin-top: 20px;
    display: flex;
    align-items: center;
}
.operation-tip i {
    color: #E6A23C;
    margin-right: 8px;
    font-size: 16px;
    flex-shrink: 0;
}
.operation-tip span {
    color: #606266;
    font-size: 14px;
    line-height: 1.5;
}
/* å“åº”式调整 */
@media (max-width: 768px) {
    .info-grid {
        grid-template-columns: 1fr;
        gap: 8px;
    }
    .info-item {
        padding: 6px 0;
    }
    .info-item label {
        min-width: 70px;
        font-size: 13px;
    }
    .info-item .info-value {
        font-size: 13px;
    }
}
/* Element Plus Loading é®ç½©å±‚样式修复 */
::v-deep .el-loading-mask {
    background-color: rgba(255, 255, 255, 0.8);
    z-index: 2000;
}
::v-deep .el-loading-spinner {
    z-index: 2001;
}
::v-deep .el-loading-text {
    color: #409EFF;
    font-weight: bold;
    font-size: 14px;
}
</style>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/CodeChunks.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/SemanticSymbols.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/OutboundCompleteRequestDTO.cs
@@ -35,4 +35,27 @@
        /// </summary>
        public string Operator { get; set; }
    }
    /// <summary>
    ///
    /// </summary>
    public class OutboundCompletePalletRequestDTO
    {
        /// <summary>
        /// å‡ºåº“单编号
        /// </summary>
        [Required(ErrorMessage = "出库单编号不能为空")]
        public string OrderNo { get; set; }
        /// <summary>
        ///
        /// </summary>
        [Required(ErrorMessage = "托盘号不能为空")]
        public string PalletCode { get; set; }
        /// <summary>
        /// æ“ä½œè€…
        /// </summary>
        public string Operator { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundService.cs
@@ -30,6 +30,8 @@
        /// <returns>出库完成响应</returns>
        WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request);
        WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request);
        WebResponseContent QueryPickingTasks(string palletCode, string orderNo);
        WebResponseContent QueryPickedList(string orderNo, string palletCode);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs
@@ -153,7 +153,7 @@
        public async Task<List<Dt_OutStockLockInfo>> GetByOrderDetailId(int orderDetailId)
        {
            return await Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .Where(x => x.OrderDetailIds.Contains(orderDetailId.ToString()))
                .OrderBy(x => x.Id)
                .ToListAsync();
        }
@@ -255,7 +255,7 @@
        public List<Dt_OutStockLockInfo> GetByOrderDetailId(int orderDetailId, OutLockStockStatusEnum? outStockStatus)
        {
            return BaseDal.QueryData(x => x.OrderDetailId == orderDetailId );
            return BaseDal.QueryData(x => x.OrderDetailIds.Contains(orderDetailId.ToString()));
        }
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundQueryService.cs
@@ -19,8 +19,10 @@
            try
            {
                Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == palletCode).Includes(x => x.Details).First();
                bool isMatMixed = stockInfo.Details.GroupBy(x => new
                bool isMatMixed = false;
                if (stockInfo != null)
                {
                    isMatMixed = stockInfo.Details.GroupBy(x => new
                {
                    x.MaterielCode,
                    x.MaterielName,
@@ -28,6 +30,8 @@
                    x.SupplyCode,
                    x.WarehouseCode
                }).Count() > 1;
                }
                List<Dt_OutStockLockInfo> outStockLockInfos = _outboundLockInfoRepository.QueryData(x => x.PalletCode == palletCode && x.OrderNo == orderNo);
                return WebResponseContent.Instance.OK(data: new { outStockLockInfos, stockInfo, isMatMixed });
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -813,6 +813,332 @@
        }
        #endregion
        public WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
            OutboundCompleteResponseDTO response = new();
            try
            {
                // 1. æ ¹æ®æ‰˜ç›˜å·æŸ¥æ‰¾åº“存信息
                Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == request.PalletCode).Includes(x => x.Details).First();
                if (stockInfo == null)
                {
                    response.Success = false;
                    response.Message = $"托盘号 {request.PalletCode} å¯¹åº”的库存不存在";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                if (!stockInfo.Details.Any())
                {
                    response.Success = false;
                    response.Message = $"托盘 {request.PalletCode} å¯¹åº”的库存明细不存在";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                bool isMatMixed = stockInfo.Details.GroupBy(x => new
                {
                    x.MaterielCode,
                    x.MaterielName,
                    x.BatchNo,
                    x.SupplyCode,
                    x.WarehouseCode
                }).Count() > 1;
                if (isMatMixed)
                {
                    response.Success = false;
                    response.Message = $"混料托盘 {request.PalletCode} ä¸èƒ½æ•´ç®±å‡ºåº“";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                // 2. æŸ¥æ‰¾å‡ºåº“单信息
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
                if (outboundOrder == null)
                {
                    response.Success = false;
                    response.Message = $"出库单 {request.OrderNo} ä¸å­˜åœ¨";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                Dt_StockInfoDetail stockInfoDetail = stockInfo.Details.First();
                // 3. æŸ¥æ‰¾é”å®šè®°å½•
                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
                    x.OrderNo == request.OrderNo &&
                    x.StockId == stockInfo.Id &&
                    x.MaterielCode == stockInfoDetail.MaterielCode &&
                    x.PalletCode == stockInfo.PalletCode);
                if (lockInfo == null || lockInfo.AssignQuantity <= 0)
                {
                    response.Success = false;
                    response.Message = $"该库存没有分配出库量,托盘号:{request.PalletCode}";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                // æ‰¾å‡ºå·²åˆ†é…çš„订单明细Id
                List<int> detailIds = new List<int>();
                string[] ids = lockInfo.OrderDetailIds.Split(",");
                foreach (string id in ids)
                {
                    if (int.TryParse(id, out int detailId))
                    {
                        detailIds.Add(detailId);
                    }
                }
                // 4. æŸ¥æ‰¾å‡ºåº“单明细信息
                List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockInfoDetail, detailIds);
                if (!outboundOrderDetails.Any())
                {
                    response.Success = false;
                    response.Message = $"未找到匹配的出库单明细,物料:{stockInfoDetail.MaterielCode},批次:{stockInfoDetail.BatchNo}";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                decimal totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                // 5. è®¡ç®—实际出库量
                decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockInfo.Details, outboundOrderDetails, lockInfo);// éœ€å‡ºåº“量
                if (actualOutboundQuantity <= 0)
                {
                    decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity;
                    decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity;
                    decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);
                    response.Success = false;
                    response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                if (lockInfo.AssignQuantity != totalStockQuantity)
                {
                    response.Success = false;
                    response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},分配量:{lockInfo.AssignQuantity}";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                // 6. å¼€å¯äº‹åŠ¡
                _unitOfWorkManage.BeginTran();
                try
                {
                    // æ•´ç®±å‡ºåº“无需拆包
                    PerformFullOutboundOperation(stockInfo, request, lockInfo.TaskNum.GetValueOrDefault());
                    decimal allocatedQuantity = actualOutboundQuantity;
                    List<Dt_OutboundOrderDetail> updateDetails = new();
                    foreach (var item in outboundOrderDetails)
                    {
                        if (allocatedQuantity <= 0) break;
                        //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
                        //{
                        //    item.OverOutQuantity += allocatedQuantity;
                        //    allocatedQuantity = 0;
                        //}
                        //else
                        //{
                        //    allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
                        //    item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
                        //}
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        List<Dt_StockInfoDetail> stockInfoDetails = stockInfo.Details.Where((x => x.StockQuantity > x.OutboundQuantity)).ToList();
                        foreach (var stockDetail in stockInfoDetails)
                        {
                            if (item.LockQuantity - item.OverOutQuantity >= stockDetail.StockQuantity - stockInfoDetail.OutboundQuantity)
                            {
                                Barcodes barcodes = new Barcodes
                                {
                                    Barcode = stockDetail.Barcode,
                                    Qty = stockDetail.StockQuantity - stockInfoDetail.OutboundQuantity,
                                    SupplyCode = stockDetail?.SupplyCode ?? "",
                                    BatchNo = stockDetail?.BatchNo ?? "",
                                    Unit = stockDetail?.Unit ?? ""
                                };
                                stockDetail.StockQuantity = stockInfoDetail.OutboundQuantity;
                                barcodesList.Add(barcodes);
                            }
                            else
                            {
                                Barcodes barcodes = new Barcodes
                                {
                                    Barcode = stockDetail.Barcode,
                                    Qty = item.LockQuantity - item.OverOutQuantity,
                                    SupplyCode = stockDetail?.SupplyCode ?? "",
                                    BatchNo = stockDetail?.BatchNo ?? "",
                                    Unit = stockDetail?.Unit ?? ""
                                };
                                stockInfoDetail.OutboundQuantity += item.LockQuantity - item.OverOutQuantity;
                                barcodesList.Add(barcodes);
                            }
                        }
                        decimal barcodeQuantity = allocatedQuantity;
                        if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
                        {
                            item.OverOutQuantity += allocatedQuantity;
                            item.CurrentDeliveryQty += allocatedQuantity;
                            allocatedQuantity = 0;
                        }
                        else
                        {
                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
                            item.OverOutQuantity = item.LockQuantity;
                            item.CurrentDeliveryQty = item.LockQuantity;
                        }
                        updateDetails.Add(item);
                        if (!string.IsNullOrEmpty(item.ReturnJsonData))
                        {
                            barcodesList.AddRange(JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>());
                        }
                        JsonSerializerSettings settings = new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };
                        item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
                    }
                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
                    if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
                    {
                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
                    }
                    else
                    {
                        // æ›´æ–°é”å®šè®°å½•
                        _outboundLockInfoRepository.UpdateData(lockInfo);
                    }
                    // æ›´æ–°å‡ºåº“单明细的已出库数量
                    _detailRepository.UpdateData(updateDetails);
                    // æ›´æ–°é”å®šè®°å½•的累计已出库数量(需要更新该托盘该物料的所有相关记录)
                    //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
                    // æäº¤äº‹åŠ¡
                    _unitOfWorkManage.CommitTran();
                    response.Success = true;
                    response.Message = "出库完成";
                    response.UpdatedDetails = updateDetails;
                    // æ£€æŸ¥å‡ºåº“单是否完成
                    if (CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                        //todo: å›žä¼ MES
                    }
                }
                catch (Exception ex)
                {
                    _unitOfWorkManage.RollbackTran();
                    response.Success = false;
                    response.Message = $"出库处理失败:{ex.Message}";
                    return WebResponseContent.Instance.Error(ex.Message);
                }
                content = WebResponseContent.Instance.OK(data: response);
            }
            catch (Exception ex)
            {
                content = WebResponseContent.Instance.Error("处理出库完成失败:" + ex.Message);
            }
            return content;
        }
        /// <summary>
        /// è®¡ç®—实际出库数量
        /// </summary>
        private decimal CalculateActualOutboundQuantity(List<Dt_StockInfoDetail> stockDetails, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo)
        {
            decimal availableOutboundQuantity = lockInfo.AssignQuantity;
            decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity;
            return Math.Min(
                Math.Min(availableOutboundQuantity, detailRemainingQuantity),
                stockDetails.Sum(x => x.StockQuantity));
        }
        /// <summary>
        /// æ‰§è¡Œå®Œæ•´å‡ºåº“操作(不拆包)
        /// </summary>
        private void PerformFullOutboundOperation(Dt_StockInfo stockInfo, OutboundCompletePalletRequestDTO request, int taskNum)
        {
            List<Dt_StockInfoDetail_Hty> historyRecords = new List<Dt_StockInfoDetail_Hty>();
            List<Dt_StockQuantityChangeRecord> changeRecords = new List<Dt_StockQuantityChangeRecord>();
            foreach (var item in stockInfo.Details)
            {
                // ä¿å­˜åº“存明细到历史记录
                Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty
                {
                    SourceId = item.Id,
                    OperateType = "出库完成",
                    InsertTime = DateTime.Now,
                    StockId = item.StockId,
                    MaterielCode = item.MaterielCode,
                    MaterielName = item.MaterielName,
                    OrderNo = item.OrderNo,
                    BatchNo = item.BatchNo,
                    ProductionDate = item.ProductionDate,
                    EffectiveDate = item.EffectiveDate,
                    SerialNumber = item.SerialNumber,
                    StockQuantity = item.StockQuantity,
                    OutboundQuantity = item.StockQuantity,
                    Status = item.Status,
                    Unit = item.Unit,
                    InboundOrderRowNo = item.InboundOrderRowNo,
                    SupplyCode = item.SupplyCode,
                    FactoryArea = item.FactoryArea,
                    WarehouseCode = item.WarehouseCode,
                    Barcode = item.Barcode,
                    Remark = $"整箱出库完成删除,条码:{request.PalletCode},原数量:{item.StockQuantity},出库数量:{item.StockQuantity},操作者:{request.Operator}"
                };
                historyRecords.Add(historyRecord);
                // è®°å½•库存变动
                Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
                {
                    StockDetailId = item.Id,
                    PalleCode = stockInfo.PalletCode,
                    MaterielCode = item.MaterielCode,
                    MaterielName = item.MaterielName,
                    BatchNo = item.BatchNo,
                    OriginalSerilNumber = item.Barcode,
                    NewSerilNumber = "",
                    OrderNo = request.OrderNo,
                    TaskNum = taskNum,
                    ChangeType = (int)StockChangeTypeEnum.Outbound,
                    ChangeQuantity = -item.StockQuantity,
                    BeforeQuantity = item.StockQuantity,
                    AfterQuantity = 0,
                    SupplyCode = item.SupplyCode,
                    WarehouseCode = item.WarehouseCode,
                    Remark = $"整箱出库完成删除库存明细,条码:{request.PalletCode},出库数量:{item.StockQuantity},操作者:{request.Operator}"
                };
                changeRecords.Add(changeRecord);
            }
            _stockDetailHistoryRepository.AddData(historyRecords);
            // åˆ é™¤åº“存明细记录
            _stockDetailRepository.DeleteData(stockInfo.Details);
            _stockChangeRepository.AddData(changeRecords);
        }
        #region æ‹£é€‰
        /// <summary>
        /// å‡ºåº“完成处理(扫描条码扣减库存)
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundController.cs
@@ -63,6 +63,27 @@
            }
        }
        [HttpPost, Route("CompleteOutboundWithPallet"), AllowAnonymous]
        public WebResponseContent CompleteOutboundWithPallet([FromBody] OutboundCompletePalletRequestDTO request)
        {
            try
            {
                lock (lockObj)
                {
                    if (!ModelState.IsValid)
                        return WebResponseContent.Instance.Error(string.Join("; ", ModelState.Values
                            .SelectMany(v => v.Errors)
                            .Select(e => e.ErrorMessage)));
                    return _outboundService.CompleteOutboundWithPallet(request);
                }
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"整箱出库操作失败: {ex.Message}");
            }
        }
        [HttpPost, Route("QueryPickingTasks"), AllowAnonymous]
        public WebResponseContent QueryPickingTasks(string palletCode, string orderNo)
        {