d88884b7072dff4269626c600ef11f9bb42dd9e3..cf83e0828b286b61b69a15005e6247d8b03f4cd8
2025-11-16 pan
Merge branch 'master' of http://115.159.85.185:8098/r/ZhongRui/ALDbanyunxia...
cf83e0 对比 | 目录
2025-11-16 pan
提交
e31ca4 对比 | 目录
2025-11-16 heshaofeng
提交
c2cbdb 对比 | 目录
已修改29个文件
2218 ■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/inbound/inboundOrder.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue 1458 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/outboundOrder.vue 15 ●●●● 补丁 | 查看 | 原始文档 | 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/InvokeMESService.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/OrderEnum/OrderCreateTypeEnum.cs 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Core/BaseRepository/RepositoryBase.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Inbound/MaterielGroupDTO.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Inbound.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/inboundOrder.js
@@ -113,19 +113,19 @@
    const getWarehouseList = async () => {
      isLoadingWarehouses.value = true;
      try {
        const { data, status } = await http.post('/api/Warehouse/GetWarehouseTypes');
        const { data, status } = await http.post('/api/LocationInfo/GetLocationTypes');
        if (status && Array.isArray(data)) {
          // æ ¼å¼åŒ–仓库选项:适配ElSelect的label-value格式
          warehouses.value = data.map(item => ({
            label: item.warehouseTypeDesc,
            value: item.warehouseType
            label: item.locationTypeDesc,
            value: item.locationType
          }));
        } else {
          ElMessage.error('获取仓库列表失败');
          ElMessage.error('获取区域列表失败');
          warehouses.value = [];
        }
      } catch (err) {
        ElMessage.error('仓库数据请求异常,请稍后重试');
        ElMessage.error('区域数据请求异常,请稍后重试');
        warehouses.value = [];
      } finally {
        isLoadingWarehouses.value = false;
@@ -202,19 +202,19 @@
            { required: true, message: '请输入料箱码', trigger: ['blur', 'enter'] }
          ],
          warehouseCode:[
            { required: true, message: '请选择仓库', trigger: ['change', 'blur'] }
            { required: true, message: '请选择区域', trigger: ['change', 'blur'] }
          ]
        },
        ref: 'batchInForm'
      }, [
        //仓库数据
        h(ElFormItem, { label: '仓库', prop: 'warehouseCode', required: true }, [
        h(ElFormItem, { label: '区域', prop: 'warehouseCode', required: true }, [
          h(ElSelect, {
            modelValue: formData.warehouseCode,
            'onUpdate:modelValue': (val) => {
              formData.warehouseCode = val;
            },
            placeholder: '请选择入库仓库',
            placeholder: '请选择入库区域',
            filterable: true, // æ”¯æŒæœç´¢ä»“库
            loading: isLoadingWarehouses.value, // åŠ è½½çŠ¶æ€
            style: { width: '100%' }
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js
@@ -1,7 +1,7 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
import http from '@/api/http.js'
import { h,createVNode, render,reactive  } from 'vue';
import { h,createVNode, render,reactive ,ref } from 'vue';
import { ElDialog , ElForm, ElFormItem, ElInput, ElButton, ElMessage ,ElSelect, ElOption} from 'element-plus';
import gridBody from './extend/outOrderDetail.vue'
@@ -55,13 +55,45 @@
      return { label: `站台${num}`, value: `1-2` };
    });
    const quantityOptions = Array.from({ length: 6 }, (_, i) => ({
      label: (i + 1).toString(),
      value: i + 1
    }));
    const warehouseOptions = ref([]);
    const isLoadingWarehouses = ref(false);
    const getWarehouseList = async () => {
      isLoadingWarehouses.value = true;
      try {
        const { data, status } = await http.post('/api/LocationInfo/GetLocationTypes');
        if (status && Array.isArray(data)) {
          // æ ¼å¼åŒ–仓库选项:适配ElSelect的label-value格式
          warehouseOptions.value = data.map(item => ({
            label: item.locationTypeDesc,
            value: item.locationType
          }));
        } else {
          ElMessage.error('获取区域列表失败');
          warehouseOptions.value = [];
        }
      } catch (err) {
        ElMessage.error('区域数据请求异常,请稍后重试');
        warehouseOptions.value = [];
      } finally {
        isLoadingWarehouses.value = false;
      }
    };
    const mountNode = document.createElement('div');
    document.body.appendChild(mountNode);
    
    const formData = reactive({
      warehouseCode:'',
      palletCode: '',
      selectedPlatform: platformOptions[0].value
      selectedPlatform: platformOptions[0].value,
      quantity:1
    });
    const vnode = createVNode(ElDialog, {
@@ -69,6 +101,11 @@
      width: '500px', 
      modelValue: true,
      appendToBody: true,
      onOpened: async () => {
        await getWarehouseList();
        const inputRef = vnode.component.refs.boxCodeInput;
        inputRef?.focus();
      },
      'onUpdate:modelValue': (isVisible) => {
        if (!isVisible) {
          render(null, mountNode);
@@ -83,20 +120,49 @@
      default: () => h(ElForm, {
        model: formData,
        rules: {
          warehouseCode:[
            { required: true, message: '请选择区域', trigger: ['change', 'blur'] }
          ],
          palletCode: [
            { type: 'string', message: '料箱号必须为字符串', trigger: 'blur' }
          ],
          selectedPlatform: [
            { required: true, message: '请选择出库站台', trigger: 'change' }
          ]
          ],
          quantity:[
            { required: true, message: '请选择空箱数量', trigger: 'change'}
          ]
        },
        ref: 'batchOutForm',
        labelWidth: '100px', 
        style: {
          padding: '0 30px', 
        }
      }, [
       },
       [
      //   h(ElFormItem, {
      //     label: '仓库区域',
      //     prop: 'warehouseCode',
      //     style: {
      //       marginBottom: '24px'
      //     }
      //   }, [
      //     h(ElSelect, {
      //       placeholder: '请选择仓库区域',
      //       modelValue: formData.warehouseCode,
      //       'onUpdate:modelValue': (val) => {
      //         formData.warehouseCode = val;
      //       },
      //       style: {
      //         width: '100%',
      //         height: '40px',
      //         borderRadius: '4px',
      //         borderColor: '#dcdfe6'
      //       }
      //     }, warehouseOptions.value.map(platform =>
      //       h(ElOption, { label: platform.label, value: platform.value })
      //     ))
      //   ]),
        h(ElFormItem, {
          label: '出库站台',
          prop: 'selectedPlatform',
@@ -120,7 +186,33 @@
            h(ElOption, { label: platform.label, value: platform.value })
          ))
        ]),
      //   h(ElFormItem,{
      //     label:'出库数量',
      //     prop:'quantity',
      //     style:{
      //       marginBottom:'24px'
      //     }
      //   },[h(ElSelect,{
      //     placeholder:'请选择空箱数量',
      //     modelValue:formData.quantity,
      //     'onUpdate:modelValue':(val)=>{
      //       formData.quantity=val;
      //     },
      //     style:{
      //       width:'100%',
      //       height:'40px',
      //       borderRadius:'4px',
      //       borderColor:'#dcdfe6'
      //     },
      //     filterable:false
      //   },
      //   quantityOptions.map(option=>
      //     h(ElOption,{
      //       label:option.label,
      //       value:option.value
      //     })
      //   )
      // )]),
        h(ElFormItem, {
          label: '料箱号',
          prop: 'palletCode',
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue
@@ -142,14 +142,6 @@
        align: "left",
      },
      {
          field: "warehouseId",
          title: "仓库",
          type: "string",
          width: 90,
          align: "left",
          bind:{key: "warehouses", data: []}
      },
      {
        field: "orderType",
        title: "单据类型",
        type: "string",
@@ -182,6 +174,13 @@
        bind: { key: "createType", data: [] },
      },
      {
        field: "factoryArea",
        title: "厂区",
        type: "string",
        width: 120,
        align: "left"
      },
      {
        field: "creater",
        title: "创建人",
        type: "string",
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue
@@ -1,5 +1,5 @@
<template>
  <div class="picking-confirm">
  <div class="OutboundPicking-container">
    <div class="page-header">
      <el-page-header @back="goBack">
        <template #content>
@@ -7,90 +7,288 @@
        </template>
      </el-page-header>
    </div>
    <!-- æ‰«ç åŒºåŸŸ -->
    <div class="scanner-area">
      <el-card>
        <div class="scanner-form">
          <el-input
            ref="palletInput"
            v-model="scanData.palletCode"
            placeholder="扫描托盘码"
            @change="onPalletScan"
            @keyup.enter.native="onPalletScan">
          </el-input>
          <el-input
           ref="barcodeInput"
            v-model="scanData.barcode"
            placeholder="扫描物料条码"
            @change="onBarcodeScan"
            @keyup.enter.native="onBarcodeScan">
          </el-input>
          <el-button type="success" @click="confirmPicking">确认拣选</el-button>
          <el-button type="warning" @click="openSplitDialog">拆包</el-button>
          <el-button type="info" @click="openRevertSplitDialog">撤销拆包</el-button>
          <el-button type="primary" @click="openBatchReturnDialog">回库</el-button>
          <el-button type="danger" @click="handleDirectOutbound">直接出库</el-button>
        </div>
      </el-card>
    </div>
    <!-- æ±‡æ€»ä¿¡æ¯ -->
    <div class="summary-area">
      <el-card>
        <div class="summary-info">
          <el-tag type="warning">未拣选条数: {{summary.unpickedCount}}</el-tag>
          <el-tag type="danger">未拣选数量: {{summary.unpickedQuantity}}</el-tag>
          <el-tag type="success">已拣选条数: {{summary.pickedCount}}</el-tag>
          <el-tag type="info">托盘状态: {{palletStatus}}</el-tag>
        </div>
      </el-card>
    </div>
    <!-- æ•°æ®åˆ—表 -->
    <div class="content-area">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card header="未拣选列表">
            <el-table :data="unpickedList" border height="440">
              <el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
    
    <div class="content-layout">
      <!-- å·¦ä¾§ï¼šæ‰«ç åŒºåŸŸ -->
      <div class="left-section">
        <div class="scan-section">
          <el-alert
            title="请使用扫码枪扫描托盘码和物料条码,扫码枪带回车功能,扫完物料条码自动确认"
            type="info"
            :closable="false"
            class="scan-alert"
          />
              <el-table-column prop="assignQuantity" label="分配数量" width="100"></el-table-column>
              <el-table-column prop="remainQuantity" label="剩余数量" width="100"></el-table-column>
              <el-table-column prop="locationCode" label="货位" width="100"></el-table-column>
              <el-table-column prop="currentBarcode" label="条码"></el-table-column>
             <!--  <el-table-column label="操作" width="100">
                <template slot-scope="scope">
                  <el-button
                    size="mini"
                    type="primary"
                    @click="handleSingleReturn(scope.row)">
                    å›žåº“
                  </el-button>
                </template>
              </el-table-column> -->
            </el-table>
          </el-card>
        </el-col>
        <el-col :span="12">
          <el-card header="已拣选列表">
             <div class="table-actions">
              <el-button
                size="mini"
                type="danger"
                :disabled="selectedPickedRows.length === 0"
                @click="batchCancelSelected">
                å–消拣选
              </el-button>
              <span class="selection-count">已选择 {{selectedPickedRows.length}} é¡¹</span>
            </div>
            <el-table :data="pickedList" border height="400"  style="width: 100%"    @selection-change="handlePickedSelectionChange">
              <el-table-column type="selection" width="55"></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
              <el-table-column prop="pickedQty" label="已拣数量" width="100"></el-table-column>
              <el-table-column prop="locationCode" label="货位" width="100"></el-table-column>
              <el-table-column prop="currentBarcode" label="条码"></el-table-column>
          <el-form :model="scanForm" label-width="100px" class="scan-form">
            <el-form-item label="托盘码" required>
              <el-input
                ref="palletInput"
                v-model="scanForm.palletCode"
                placeholder="请扫描托盘码"
                @keyup.enter="handlePalletScan"
                @blur="loadPalletSummary"
                clearable
              />
            </el-form-item>
            </el-table>
          </el-card>
        </el-col>
      </el-row>
    </div>
            <el-form-item label="物料条码" required>
              <el-input
                ref="materialInput"
                v-model="scanForm.materialBarcode"
                placeholder="请扫描物料条码"
                :disabled="!scanForm.palletCode"
                @keyup.enter="handleMaterialScan"
                clearable
              />
            </el-form-item>
          </el-form>
    <!-- æ‹†åŒ…弹窗 -->
<!-- æ‹†åŒ…弹窗 -->
     <div v-if="showCustomSplitDialog" class="custom-dialog-overlay">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>拆包操作</h3>
             <!--   <el-button
              type="text"
              icon="el-icon-close"
              @click="closeCustomSplitDialog"
              class="close-button">
            </el-button> -->
                  <el-button
              type="text"
              @click="closeCustomSplitDialog"
              class="close-button">
              X
            </el-button>
          <!-- æ‰˜ç›˜æ‹£è´§ç»Ÿè®¡ -->
          <div v-if="palletSummary" class="pallet-summary">
            <el-card header="托盘拣货统计">
              <el-descriptions :column="3" border>
                <el-descriptions-item label="托盘号">
                  {{ scanForm.palletCode }}
                </el-descriptions-item>
                <el-descriptions-item label="未拣货条数">
                  <el-text type="warning">{{ palletSummary.unpickedCount }}</el-text>
                </el-descriptions-item>
                <el-descriptions-item label="未拣货总数">
                  <el-text type="danger">{{ palletSummary.unpickedTotal }}</el-text>
                </el-descriptions-item>
              </el-descriptions>
            </el-card>
          </div>
          <div class="action-buttons">
            <el-button type="primary" @click="handleConfirm" :loading="confirmLoading">
              æ‰‹åŠ¨ç¡®è®¤
            </el-button>
            <el-button @click="handleReset">重置</el-button>
            <el-button @click="$emit('close')">取消</el-button>
          <div class="custom-dialog-body">
            <el-form :model="splitForm"  :rules="splitFormRules" ref="splitFormRef" label-width="100px">
              <el-form-item label="订单编号">
                <el-input v-model="splitForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="托盘编号">
                <el-input v-model="splitForm.palletCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="原条码"  prop="originalBarcode">
                <el-input
                  v-model="splitForm.originalBarcode"
                  placeholder="扫描原条码"
                  @keyup.enter.native="onSplitBarcodeScan"
                  @change="onSplitBarcodeScan"
                  clearable>
                </el-input>
              </el-form-item>
              <el-form-item label="物料编码">
                <el-input v-model="splitForm.materielCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="剩余数量">
                <el-input v-model="splitForm.maxQuantity" disabled></el-input>
              </el-form-item>
              <el-form-item label="拆包数量" prop="splitQuantity">
                <el-input-number
                  v-model="splitForm.splitQuantity"
                  :min="1"
                  :max="splitForm.maxQuantity"
                  :precision="2"
                  :step="1"
                  style="width: 100%">
                </el-input-number>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeCustomSplitDialog">取消</el-button>
            <el-button type="primary" @click="handleSplitPackage" :loading="splitLoading">确认拆包</el-button>
          </div>
        </div>
      </div>
    </div>
      <!-- å³ä¾§ï¼šå‡ºåº“详情列表 -->
      <div class="right-section">
        <el-card class="outbound-details-card" header="出库详情">
          <vol-table
            ref="outboundTable"
            :table-config="outboundTableConfig"
            :height="300"
          />
        </el-card>
    <!-- æ’¤é”€æ‹†åŒ…弹窗 -->
    <div v-if="showRevertSplitDialog" class="custom-dialog-overlay">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>撤销拆包</h3>
            <el-button
              type="text"
              @click="closeRevertSplitDialog"
              class="close-button">
              Ã—
            </el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form
              :model="revertSplitForm"
              :rules="revertSplitFormRules"
              ref="revertSplitFormRef"
              label-width="100px">
              <el-form-item label="原条码" prop="originalBarcode">
                <el-input
                  v-model="revertSplitForm.originalBarcode"
                  placeholder="扫描原条码"
                  @keyup.enter.native="onRevertSplitBarcodeScan"
                  @change="onRevertSplitBarcodeScan"
                  clearable>
                </el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeRevertSplitDialog">取消</el-button>
            <el-button type="primary" @click="handleRevertSplit" :loading="revertSplitLoading">确认撤销</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- å·²åˆ†æ‹£è®°å½•列表 -->
    <div class="picked-records">
      <el-card header="已分拣记录">
        <vol-table
          ref="pickedTable"
          :table-config="pickedTableConfig"
          :height="300"
        />
      </el-card>
     <!-- æ‰¹é‡å›žåº“弹窗 -->
    <div v-if="showBatchReturnDialog" class="custom-dialog-overlay">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>回库</h3>
            <el-button
              type="text"
              @click="closeBatchReturnDialog"
              class="close-button">
              Ã—
            </el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form
              :model="batchReturnForm"
              :rules="batchReturnFormRules"
              ref="batchReturnFormRef"
              label-width="100px">
              <el-form-item label="订单编号">
                <el-input v-model="batchReturnForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="托盘编号" prop="palletCode">
                <el-input
                  v-model="batchReturnForm.palletCode"
                  placeholder="扫描托盘码"
                  @keyup.enter.native="onBatchReturnPalletScan"
                  @change="onBatchReturnPalletScan"
                  clearable>
                </el-input>
              </el-form-item>
              <el-form-item label="未拣选数量">
                <el-input v-model="batchReturnForm.unpickedCount" disabled></el-input>
              </el-form-item>
              <el-form-item label="未拣选条数">
                <el-input v-model="batchReturnForm.unpickedQuantity" disabled></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeBatchReturnDialog">取消</el-button>
            <el-button type="primary" @click="handleBatchReturnConfirm" :loading="batchReturnLoading">确认回库</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- ç›´æŽ¥å‡ºåº“弹窗 -->
    <div v-if="showDirectOutDialog" class="custom-dialog-overlay">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>直接出库</h3>
            <el-button
              type="text"
              @click="closeDirectOutDialog"
              class="close-button">
              Ã—
            </el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form
              :model="directOutForm"
              :rules="directOutFormRules"
              ref="directOutFormRef"
              label-width="100px">
              <el-form-item label="订单编号">
                <el-input v-model="directOutForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="托盘编号" prop="palletCode">
                <el-input
                  v-model="directOutForm.palletCode"
                  placeholder="扫描托盘码"
                  @keyup.enter.native="onDirectOutPalletScan"
                  @change="onDirectOutPalletScan"
                  clearable>
                </el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeDirectOutDialog">取消</el-button>
            <el-button type="primary" @click="handleDirectOutConfirm" :loading="directOutLoading">确认出库</el-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
@@ -98,8 +296,8 @@
<script>
import http from '@/api/http.js'
import { ref, defineComponent } from "vue";
import { ElMessage } from "element-plus";
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
export default defineComponent({
  name: 'PickingConfirm',
@@ -114,264 +312,968 @@
  },
  emits: ['confirm', 'close'],
  data() {
        // å®šä¹‰æ‹†åŒ…表单验证规则
    const validateOriginalBarcode = (rule, value, callback) => {
      if (!value || value.trim() === '') {
        callback(new Error('请输入原条码'));
      } else {
        callback();
      }
    };
    const validateSplitQuantity = (rule, value, callback) => {
      if (value === null || value === undefined || value === '') {
        callback(new Error('请输入拆包数量'));
      } else if (value <= 0) {
        callback(new Error('拆包数量必须大于0'));
      } else if (this.splitForm.maxQuantity && value > this.splitForm.maxQuantity) {
        callback(new Error('拆包数量不能大于剩余数量'));
      } else {
        callback();
      }
    };
    // å®šä¹‰æ’¤é”€æ‹†åŒ…表单验证规则
    const validateRevertOriginalBarcode = (rule, value, callback) => {
      if (!value || value.trim() === '') {
        callback(new Error('请输入原条码'));
      } else {
        callback();
      }
    };
        // å®šä¹‰æ‰¹é‡å›žåº“表单验证规则
    const validateBatchReturnPalletCode = (rule, value, callback) => {
      if (!value || value.trim() === '') {
        callback(new Error('请输入托盘码'));
      } else {
        callback();
      }
    };
     // å®šä¹‰ç›´æŽ¥å‡ºåº“表单验证规则
    const validateDirectOutPalletCode = (rule, value, callback) => {
      if (!value || value.trim() === '') {
        callback(new Error('请输入托盘码'));
      } else {
        callback();
      }
    };
    return {
      scanForm: {
      scanData: {
        orderNo: '',
        palletCode: '',
        materialBarcode: ''
        barcode: ''
      },
      palletSummary: null,
      confirmLoading: false,
      pickedTableConfig: {
        url: '/api/outbound/getPickingRecords',
        query: { orderNo: this.orderNo },
        columns: [
          { prop: 'TaskNo', label: '任务号', width: 150 },
          { prop: 'Barcode', label: '物料条码', width: 150 },
          { prop: 'MaterielName', label: '物料名称', width: 150 },
          { prop: 'PickQuantity', label: '拣货数量', width: 100 },
          { prop: 'LocationCode', label: '货位', width: 120 },
          { prop: 'CreateTime', label: '拣货时间', width: 180 }
      unpickedList: [],
      pickedList: [],
       selectedUnpickedRows: [], // æœªæ‹£é€‰åˆ—表选中的行
      selectedPickedRows: [], // å·²æ‹£é€‰åˆ—表选中的行
      summary: {
        unpickedCount: 0,
        unpickedQuantity: 0,
        pickedCount: 0
      },
      palletStatus: '未知',
      showSplitDialog: false,
      showRevertSplitDialog: false,
      showCustomSplitDialog: false, // è‡ªå®šä¹‰æ‹†åŒ…弹窗显示状态
      showBatchReturnDialog: false, // æ‰¹é‡å›žåº“弹窗显示状态
      showReturnDialog: false,
      splitLoading: false,
       revertSplitLoading: false,
      batchReturnLoading: false, // æ‰¹é‡å›žåº“加载状态
      splitForm: {
        orderNo: '',
        palletCode: '',
        originalBarcode: '',
        materielCode: '',
        splitQuantity: 0,
        maxQuantity: 0
      },
            // æ‹†åŒ…表单验证规则
      splitFormRules: {
        originalBarcode: [
          { required: true, validator: validateOriginalBarcode, trigger: 'blur' }
        ],
        splitQuantity: [
          { required: true, validator: validateSplitQuantity, trigger: 'blur' }
        ]
      },
      // å‡ºåº“详情表格配置
      outboundTableConfig: {
        url: '/api/outbound/getOutboundDetails',
        query: { orderNo: this.orderNo },
        columns: [
          { prop: 'OrderNo', label: '出库单号', width: 150 },
          { prop: 'MaterialCode', label: '物料编号', width: 120 },
          { prop: 'MaterialBarcode', label: '物料条码', width: 150 },
          { prop: 'BatchNo', label: '批次号', width: 120 },
          { prop: 'AssignQuantity', label: '分配出库量', width: 100 },
          { prop: 'PalletCode', label: '托盘编号', width: 120 },
          { prop: 'Unit', label: '单位', width: 80 }
      revertSplitForm: {
        originalBarcode: ''
      },
          // æ’¤é”€æ‹†åŒ…表单验证规则
      revertSplitFormRules: {
        originalBarcode: [
          { required: true, validator: validateRevertOriginalBarcode, trigger: 'blur' }
        ]
      },
      orderInfo: {orderNo:''}
         // æ‰¹é‡å›žåº“表单
      batchReturnForm: {
        orderNo: '',
        palletCode: '',
        unpickedCount: 0,
        unpickedQuantity: 0
      },
      // æ‰¹é‡å›žåº“表单验证规则
      batchReturnFormRules: {
        palletCode: [
          { required: true, validator: validateBatchReturnPalletCode, trigger: 'blur' }
        ]
      },
       showDirectOutDialog: false, // ç›´æŽ¥å‡ºåº“弹窗显示状态
      directOutLoading: false, // ç›´æŽ¥å‡ºåº“加载状态
      directOutForm: {
        orderNo: '',
        palletCode: ''
      },
      directOutFormRules: {
        palletCode: [
          { required: true, validator: validateDirectOutPalletCode, trigger: 'blur' }
        ]
      },
      returnForm: {
        orderNo: '',
        palletCode: '',
        barcode: '',
        materielCode: '',
        returnQuantity: 0
      },
      isProcessing: false // é˜²æ­¢é‡å¤æäº¤
    }
  },
  mounted() {
    this.loadOrderInfo();
    // ä»Žè·¯ç”±å‚数获取订单编号
    if (this.$route.query.orderNo) {
      this.scanData.orderNo = this.$route.query.orderNo;
      this.splitForm.orderNo = this.$route.query.orderNo;
      this.returnForm.orderNo = this.$route.query.orderNo;
    }
        // é¡µé¢åŠ è½½åŽè‡ªåŠ¨èšç„¦åˆ°æ‰˜ç›˜ç è¾“å…¥æ¡†
    this.$nextTick(() => {
      if (this.$refs.palletInput) {
        this.$refs.palletInput.focus()
      }
    })
      this.$refs.palletInput.focus();
    });
  },
  methods: {
    loadOrderInfo() {
      const orderId = this.$route.query.orderId
      if (!orderId) return
    goBack(){
       this.$router.back()
    },
     openSplitDialog() {
      console.log('打开自定义拆包弹窗');
         if (!this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      this.showCustomSplitDialog = true;
      // é‡ç½®è¡¨å•
      this.resetSplitForm();
      // è®¾ç½®è®¢å•和托盘信息
      this.splitForm.orderNo = this.scanData.orderNo;
      this.splitForm.palletCode = this.scanData.palletCode;
        // æ¸…除表单验证
      this.$nextTick(() => {
        if (this.$refs.splitFormRef) {
          this.$refs.splitFormRef.clearValidate();
        }
      });
    },
    // å…³é—­è‡ªå®šä¹‰æ‹†åŒ…弹窗
    closeCustomSplitDialog() {
      this.showCustomSplitDialog = false;
      this.resetSplitForm();
         // æ¸…除表单验证
      if (this.$refs.splitFormRef) {
        this.$refs.splitFormRef.clearValidate();
      }
    },
    // æ‰“开撤销拆包弹窗
    openRevertSplitDialog() {
      console.log('打开撤销拆包弹窗');
      this.showRevertSplitDialog = true;
      // é‡ç½®è¡¨å•
      this.resetRevertSplitForm();
      // æ¸…除表单验证
      this.$nextTick(() => {
        if (this.$refs.revertSplitFormRef) {
          this.$refs.revertSplitFormRef.clearValidate();
        }
      });
    },
    // å…³é—­æ’¤é”€æ‹†åŒ…弹窗
    closeRevertSplitDialog() {
      this.showRevertSplitDialog = false;
      this.resetRevertSplitForm();
      // æ¸…除表单验证
      if (this.$refs.revertSplitFormRef) {
        this.$refs.revertSplitFormRef.clearValidate();
      }
    },
     // æ‰“开批量回库弹窗
    openBatchReturnDialog() {
      console.log('打开批量回库弹窗');
      this.showBatchReturnDialog = true;
      // é‡ç½®è¡¨å•
      this.resetBatchReturnForm();
      // è®¾ç½®è®¢å•信息
      this.batchReturnForm.orderNo = this.scanData.orderNo;
      // æ›´æ–°æœªæ‹£é€‰ä¿¡æ¯
      this.batchReturnForm.unpickedCount = this.summary.unpickedCount || 0;
      this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity || 0;
      // æ¸…除表单验证
      this.$nextTick(() => {
        if (this.$refs.batchReturnFormRef) {
          this.$refs.batchReturnFormRef.clearValidate();
        }
      });
    },
    // å…³é—­æ‰¹é‡å›žåº“弹窗
    closeBatchReturnDialog() {
      this.showBatchReturnDialog = false;
      this.resetBatchReturnForm();
      // æ¸…除表单验证
      if (this.$refs.batchReturnFormRef) {
        this.$refs.batchReturnFormRef.clearValidate();
      }
    },
    // æ‰¹é‡å›žåº“托盘码扫码
    onBatchReturnPalletScan() {
      if (!this.batchReturnForm.palletCode) return;
      this.batchReturnForm.palletCode = this.batchReturnForm.palletCode.replace(/\n/g, '').trim();
      // æ¸…除验证状态
      if (this.$refs.batchReturnFormRef) {
        this.$refs.batchReturnFormRef.clearValidate(['palletCode']);
      }
    },
    // æ‰¹é‡å›žåº“确认
    async handleBatchReturnConfirm() {
      // è¡¨å•验证
      if (this.$refs.batchReturnFormRef) {
        this.$refs.batchReturnFormRef.validate((valid) => {
          if (valid) {
            this.submitBatchReturn();
          } else {
            this.$message.warning('请扫描托盘码');
            return false;
          }
        });
      } else {
        // å¦‚果没有表单引用,使用原有的验证
        if (!this.batchReturnForm.palletCode) {
          this.$message.warning('请扫描托盘码');
          return;
        }
        this.submitBatchReturn();
      }
    },
    // æäº¤æ‰¹é‡å›žåº“请求
    async submitBatchReturn() {
      this.batchReturnLoading = true;
      try {
        const res = await this.http.post('/api/OutboundPicking/batch-return-to-stock', {
          orderNo: this.batchReturnForm.orderNo,
          palletCode: this.batchReturnForm.palletCode
        });
        if (res.status) {
          this.$message.success('批量回库成功');
          this.showBatchReturnDialog = false;
          this.loadData();
        } else {
          this.$message.error(res.message || '批量回库失败');
        }
      } catch (error) {
        this.$message.error('批量回库失败');
      } finally {
        this.batchReturnLoading = false;
      }
    },
    // æ‰“开直接出库弹窗
    openDirectOutDialog() {
      console.log('打开直接出库弹窗');
      this.showDirectOutDialog = true;
      // é‡ç½®è¡¨å•
      this.resetDirectOutForm();
      // è®¾ç½®è®¢å•信息
      this.directOutForm.orderNo = this.scanData.orderNo;
      // æ¸…除表单验证
      this.$nextTick(() => {
        if (this.$refs.directOutFormRef) {
          this.$refs.directOutFormRef.clearValidate();
        }
      });
    },
    // å…³é—­ç›´æŽ¥å‡ºåº“弹窗
    closeDirectOutDialog() {
      this.showDirectOutDialog = false;
      this.resetDirectOutForm();
      // æ¸…除表单验证
      if (this.$refs.directOutFormRef) {
        this.$refs.directOutFormRef.clearValidate();
      }
    },
    // ç›´æŽ¥å‡ºåº“托盘码扫码
    onDirectOutPalletScan() {
      if (!this.directOutForm.palletCode) return;
      this.directOutForm.palletCode = this.directOutForm.palletCode.replace(/\n/g, '').trim();
      // æ¸…除验证状态
      if (this.$refs.directOutFormRef) {
        this.$refs.directOutFormRef.clearValidate(['palletCode']);
      }
    },
    // ç›´æŽ¥å‡ºåº“确认
    async handleDirectOutConfirm() {
      // è¡¨å•验证
      if (this.$refs.directOutFormRef) {
        this.$refs.directOutFormRef.validate((valid) => {
          if (valid) {
            this.submitDirectOut();
          } else {
            this.$message.warning('请扫描托盘码');
            return false;
          }
        });
      } else {
        // å¦‚果没有表单引用,使用原有的验证
        if (!this.directOutForm.palletCode) {
          this.$message.warning('请扫描托盘码');
          return;
        }
        this.submitDirectOut();
      }
    },
    // æäº¤ç›´æŽ¥å‡ºåº“请求
    async submitDirectOut() {
      this.directOutLoading = true;
      try {
        const res = await this.http.post('/api/OutboundPicking/direct-outbound', {
          orderNo: this.directOutForm.orderNo,
          palletCode: this.directOutForm.palletCode
        });
        debugger;
        if (res.status) {
          this.$message.success('直接出库成功');
          this.showDirectOutDialog = false;
          this.loadData();
        } else {
          this.$message.error(res.message || '直接出库失败');
        }
      } catch (error) {
        this.$message.error('直接出库失败');
      } finally {
        this.directOutLoading = false;
      }
    },
    // é‡ç½®ç›´æŽ¥å‡ºåº“表单
    resetDirectOutForm() {
      this.directOutForm.palletCode = '';
    },
    // ä¿®æ”¹åŽŸæœ‰çš„ç›´æŽ¥å‡ºåº“æŒ‰é’®ç‚¹å‡»äº‹ä»¶
    handleDirectOutbound() {
      this.openDirectOutDialog();
    },
    async loadData() {
      if (!this.scanData.orderNo || !this.scanData.palletCode) {
        return;
      }
      try {
        this.http.get(`/api/OutboundOrder/GetById?id=${orderId}`).then(response => {debugger;
          if (response.status) {
            this.orderInfo = response.data
        // åŠ è½½æœªæ‹£é€‰åˆ—è¡¨
        const unpickedRes = await this.http.post('/api/OutboundPicking/unpicked-list', this.scanData);
        this.unpickedList = unpickedRes.data || [];
        // åŠ è½½å·²æ‹£é€‰åˆ—è¡¨
        const pickedRes = await this.http.post('/api/OutboundPicking/picked-list', this.scanData);
        this.pickedList = pickedRes.data || [];
        // åŠ è½½æ±‡æ€»ä¿¡æ¯
        const summaryRes = await this.http.post('/api/OutboundPicking/picking-summary',this.scanData);
        this.summary = summaryRes.data || {};
        // æ›´æ–°æ‰˜ç›˜çŠ¶æ€
        this.updatePalletStatus();
      } catch (error) {
        this.$message.error('加载数据失败');
      }
    },
    updatePalletStatus() {
      if (this.unpickedList.length === 0 && this.pickedList.length > 0) {
        this.palletStatus = '已全部拣选';
      } else if (this.unpickedList.length > 0 && this.pickedList.length === 0) {
        this.palletStatus = '待拣选';
      } else if (this.unpickedList.length > 0 && this.pickedList.length > 0) {
        this.palletStatus = '部分拣选';
      } else {
        this.palletStatus = '无数据';
      }
    },
    // å·²æ‹£é€‰åˆ—表选择变化
    handlePickedSelectionChange(selection) {
      this.selectedPickedRows = selection;
    },
       // æ‰¹é‡å–消选中的已拣选项
    async batchCancelSelected() {
      if (this.selectedPickedRows.length === 0) {
        this.$message.warning('请先选择要取消的项');
        return;
      }
      this.$confirm(`确定要取消选中的 ${this.selectedPickedRows.length} é¡¹å—?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          let successCount = 0;
          let errorCount = 0;
          for (const row of this.selectedPickedRows) {
            try {
              const res = await this.http.post('/api/OutboundPicking/CancelPicking', {
                orderNo: this.scanData.orderNo,
                palletCode: this.scanData.palletCode,
                barcode: row.currentBarcode
              });
              
          }
        })
      } catch (error) {
        ElMessage.error('加载出库单信息失败')
      }
    },
     goBack() {
      this.$router.back()
    },
    async handlePalletScan() {
      if (this.scanForm.palletCode) {
        ElMessage.success(`已扫描托盘: ${this.scanForm.palletCode}`)
        await this.loadPalletSummary()
        this.$nextTick(() => {
          if (this.$refs.materialInput) {
            this.$refs.materialInput.focus()
          }
        })
      }
    },
    async handleMaterialScan() {
      if (!this.scanForm.palletCode) {
        ElMessage.warning('请先扫描托盘码')
        this.$refs.palletInput.focus()
        return
      }
      if (!this.scanForm.materialBarcode) {
        ElMessage.warning('请扫描物料条码')
        return
      }
      await this.executePickingConfirm()
    },
    async loadPalletSummary() {
      if (!this.scanForm.palletCode) {
        this.palletSummary = null
        return
      }
      try {
        const result = await http.get('/api/outbound/getPalletPickingSummary', {
          params: {
            orderNo: this.orderNo,
            palletCode: this.scanForm.palletCode
          }
        })
        if (result.success) {
          // å¤„理统计信息
          const summary = result.data
          const assigned = summary.find(x => x.Status === '已分配') || { TotalAssignQty: 0, TotalPickedQty: 0 }
          const picked = summary.find(x => x.Status === '已拣选') || { TotalPickedQty: 0 }
          this.palletSummary = {
            unpickedCount: assigned.TotalAssignQty > 0 ? 1 : 0, // ç®€åŒ–计算
            unpickedTotal: assigned.TotalAssignQty - assigned.TotalPickedQty
          }
        }
      } catch (error) {
        console.error('加载托盘统计失败:', error)
      }
    },
    async handleConfirm() {
      if (!this.scanForm.palletCode || !this.scanForm.materialBarcode) {
        ElMessage.warning('请填写完整的扫码信息')
        return
      }
      await this.executePickingConfirm()
    },
    async executePickingConfirm() {
      this.confirmLoading = true
      try {
        // å…ˆæ‰¾åˆ°å¯¹åº”的出库锁定信息
        const lockInfoResult = await this.http.get('/api/outbound/getOutStockLockInfo', {
          params: {
            orderNo: this.orderNo,
            palletCode: this.scanForm.palletCode,
            materialBarcode: this.scanForm.materialBarcode
          }
        })
        if (!lockInfoResult.success || !lockInfoResult.data || lockInfoResult.data.length === 0) {
          ElMessage.error('未找到对应的出库锁定信息')
          return
        }
        const lockInfo = lockInfoResult.data[0]
        const request = {
          outStockLockId: lockInfo.Id,
          taskNo: `TASK_${Date.now()}`,
          palletCode: this.scanForm.palletCode,
          materialBarcode: this.scanForm.materialBarcode,
          locationCode: lockInfo.LocationCode
        }
        const result = await this.http.post('/api/outbound/pickingConfirm', request)
        if (result.success) {
          ElMessage.success('分拣确认成功')
          this.handleReset()
          this.$emit('confirm')
          // åˆ·æ–°è¡¨æ ¼
          if (this.$refs.pickedTable) {
            this.$refs.pickedTable.refresh()
              if (res.status) {
                successCount++;
              } else {
                errorCount++;
                console.error(`取消拣选失败: ${row.Barcode}`, res.message);
              }
            } catch (error) {
              errorCount++;
              console.error(`取消拣选失败: ${row.Barcode}`, error);
            }
          }
          
          // åˆ·æ–°å‡ºåº“详情表格
          if (this.$refs.outboundTable) {
            this.$refs.outboundTable.refresh()
          if (errorCount === 0) {
            this.$message.success(`成功取消 ${successCount} é¡¹`);
          } else {
            this.$message.warning(`成功取消 ${successCount} é¡¹ï¼Œå¤±è´¥ ${errorCount} é¡¹`);
          }
          this.loadData();
          this.selectedPickedRows = [];
        } catch (error) {
          this.$message.error('批量取消操作失败');
        }
      }).catch(() => {
        this.$message.info('已取消批量操作');
      });
    },
    // æ‰˜ç›˜ç æ‰«ç 
    onPalletScan() {
     // åŽ»é™¤å›žè½¦ç¬¦å’Œå‰åŽç©ºæ ¼
      this.scanData.palletCode = this.scanData.palletCode.replace(/\n/g, '').trim();
      if (!this.scanData.palletCode) return;
      this.splitForm.palletCode = this.scanData.palletCode;
      this.returnForm.palletCode = this.scanData.palletCode;
      // åŠ è½½æ•°æ®
      this.loadData();
      // è‡ªåŠ¨è·³è½¬åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†
      this.$nextTick(() => {
        this.$refs.barcodeInput.focus();
      });
    },
          // é‡æ–°åŠ è½½æ‰˜ç›˜ç»Ÿè®¡
          await this.loadPalletSummary()
    onBarcodeScan() {
       // åŽ»é™¤å›žè½¦ç¬¦å’Œå‰åŽç©ºæ ¼
      this.scanData.barcode = this.scanData.barcode.replace(/\n/g, '').trim();
      if (!this.scanData.barcode) return;
      // è‡ªåŠ¨ç¡®è®¤æ‹£é€‰
      this.confirmPicking();
    },
    async confirmPicking() {
       if (this.isProcessing) return;
      if (!this.scanData.orderNo || !this.scanData.palletCode || !this.scanData.barcode) {
        this.$message.warning('请先扫描托盘码和物料条码');
        this.focusBarcodeInput();
        return;
      }
      this.isProcessing = true;
      try {
        const res = await this.http.post('/api/OutboundPicking/confirm-picking', this.scanData);
        if (res.status) {
          this.$message.success('拣选确认成功');
          this.scanData.barcode = ''; // æ¸…空物料条码
          this.loadData();
          // æˆåŠŸåŽç»§ç»­èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†ï¼Œå‡†å¤‡ä¸‹ä¸€ä¸ªæ‰«ç 
          this.$nextTick(() => {
            this.$refs.barcodeInput.focus();
          });
        } else {
          ElMessage.error(result.ElMessage)
          // æ˜¾ç¤ºåŽç«¯è¿”回的错误信息
          this.$message.error(res.message || '拣选确认失败');
          // å¤±è´¥æ—¶èšç„¦å¹¶é€‰ä¸­ç‰©æ–™æ¡ç è¾“入框内容
          this.focusBarcodeInput(true);
        }
      } catch (error) {
        ElMessage.error('分拣确认失败')
        this.$message.error('拣选确认失败: ' + (error.message || '网络错误'));
        // å¤±è´¥æ—¶èšç„¦å¹¶é€‰ä¸­ç‰©æ–™æ¡ç è¾“入框内容
        this.focusBarcodeInput(true);
      } finally {
        this.confirmLoading = false
        this.isProcessing = false;
      }
    },
    handleReset() {
      this.scanForm.materialBarcode = ''
    // èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“入框
    focusBarcodeInput(selectText = false) {
      this.$nextTick(() => {
        if (this.$refs.materialInput) {
          this.$refs.materialInput.focus()
        const input = this.$refs.barcodeInput;
        if (input && input.$el && input.$el.querySelector('input')) {
          const inputEl = input.$el.querySelector('input');
          inputEl.focus();
          if (selectText) {
            inputEl.select();
          }
        }
      })
      });
    },
    async cancelPicking(row) {
      try {
        const res = await this.http.post('/api/OutboundPicking/CancelPicking', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          barcode: row.Barcode
        });
        if (res.status) {
          this.$message.success('取消拣选成功');
          this.loadData();
        } else {
          this.$message.error(res.message || '取消拣选失败');
        }
      } catch (error) {
        this.$message.error('取消拣选失败');
      }
    },
   /*  // å›žåº“操作 - å›žåº“整个托盘未拣选的货物
    async handleBatchReturn() {
      if (!this.scanData.orderNo || !this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      if (this.unpickedList.length === 0) {
        this.$message.warning('该托盘没有可回库的货物');
        return;
      }
      this.$confirm(`确定要回库整个托盘的未拣选货物吗?共 ${this.unpickedList.length} æ¡è®°å½•`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          const res = await this.http.post('/api/OutboundPicking/batch-return-to-stock', {
            orderNo: this.scanData.orderNo,
            palletCode: this.scanData.palletCode
          });
          if (res.success) {
            this.$message.success('批量回库成功');
            this.loadData();
          } else {
            this.$message.error(res.message || '批量回库失败');
          }
        } catch (error) {
          this.$message.error('批量回库失败');
        }
      }).catch(() => {
        this.$message.info('已取消批量回库');
      });
    }, */
   /*  // ç›´æŽ¥å‡ºåº“操作
    async handleDirectOutbound() {
      if (!this.scanData.orderNo || !this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      this.$confirm('确定要直接出库整个托盘吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          const res = await this.http.post('/api/OutboundPicking/direct-outbound', {
            orderNo: this.scanData.orderNo,
            palletCode: this.scanData.palletCode
          });
          if (res.success) {
            this.$message.success('直接出库成功');
            this.loadData();
          } else {
            this.$message.error(res.message || '直接出库失败');
          }
        } catch (error) {
          this.$message.error('直接出库失败');
        }
      }).catch(() => {
        this.$message.info('已取消直接出库');
      });
    }, */
    // ç¡®è®¤å›žåº“(通过弹窗)
    async handleReturnConfirm() {
      if (!this.returnForm.barcode) {
        this.$message.warning('请扫描回库条码');
        return;
      }
      try {
        const res = await this.http.post('/api/OutboundPicking/return-to-stock', {
          orderNo: this.returnForm.orderNo,
          palletCode: this.returnForm.palletCode,
          barcode: this.returnForm.barcode
        });
        if (res.success) {
          this.$message.success('回库成功');
          this.showReturnDialog = false;
          this.resetReturnForm();
          this.loadData();
        } else {
          this.$message.error(res.message || '回库失败');
        }
      } catch (error) {
        this.$message.error('回库失败');
      }
    },
    // æ‹†åŒ…扫码
    async onSplitBarcodeScan() {
      if (!this.splitForm.originalBarcode) return;
      // åŽ»é™¤å›žè½¦ç¬¦å’Œå‰åŽç©ºæ ¼
      this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim();
      try {
        const res = await this.http.post('/api/OutboundPicking/split-package-info', {
            orderNo: this.splitForm.orderNo,
            palletCode: this.splitForm.palletCode,
            barcode: this.splitForm.originalBarcode
        });
        if (res.status) {
          this.splitForm.materielCode = res.data.materielCode;
          this.splitForm.maxQuantity = res.data.remainQuantity;
          this.splitForm.splitQuantity = Math.min(1, this.splitForm.maxQuantity);
           // æ¸…除验证状态
          if (this.$refs.splitFormRef) {
            this.$refs.splitFormRef.clearValidate(['originalBarcode']);
          }
        } else {
          this.$message.error(res.message || '获取拆包信息失败');
            // éªŒè¯å¤±è´¥ï¼Œè®¾ç½®é”™è¯¯çŠ¶æ€
          if (this.$refs.splitFormRef) {
            this.$refs.splitFormRef.validateField('originalBarcode');
          }
        }
      } catch (error) {
        this.$message.error('获取拆包信息失败');
         // éªŒè¯å¤±è´¥ï¼Œè®¾ç½®é”™è¯¯çŠ¶æ€
        if (this.$refs.splitFormRef) {
          this.$refs.splitFormRef.validateField('originalBarcode');
        }
      }
    },
    async handleSplitPackage() {
       // è¡¨å•验证
      if (this.$refs.splitFormRef) {
        this.$refs.splitFormRef.validate((valid) => {
          if (valid) {
            this.submitSplitPackage();
          } else {
            this.$message.warning('请填写完整的拆包信息');
            return false;
          }
        });
      } else {
        // å¦‚果没有表单引用,使用原有的验证
        if (!this.splitForm.originalBarcode || this.splitForm.splitQuantity <= 0) {
          this.$message.warning('请填写完整的拆包信息');
          return;
        }
        if (this.splitForm.splitQuantity > this.splitForm.maxQuantity) {
          this.$message.warning('拆包数量不能大于剩余数量');
          return;
        }
        this.submitSplitPackage();
      }
    },
    // æäº¤æ‹†åŒ…请求
    async submitSplitPackage() {
    this.splitLoading = true;
     try {
        const res = await this.http.post('/api/OutboundPicking/split-package', this.splitForm);
        if (res.status) {
          this.$message.success('拆包成功');
          this.showSplitDialog = false;
          this.resetSplitForm();
          this.loadData();
        } else {
          this.$message.error(res.message || '拆包失败');
        }
      } catch (error) {
        this.$message.error('拆包失败');
      }
    },
        // æ’¤é”€æ‹†åŒ…扫码
    onRevertSplitBarcodeScan() {
      if (!this.revertSplitForm.originalBarcode) return;
      this.revertSplitForm.originalBarcode = this.revertSplitForm.originalBarcode.replace(/\n/g, '').trim();
      // æ¸…除验证状态
      if (this.$refs.revertSplitFormRef) {
        this.$refs.revertSplitFormRef.clearValidate(['originalBarcode']);
      }
    },
    async handleRevertSplit() {
      // è¡¨å•验证
      if (this.$refs.revertSplitFormRef) {
        this.$refs.revertSplitFormRef.validate((valid) => {
          if (valid) {
            this.submitRevertSplit();
          } else {
            this.$message.warning('请输入原条码');
            return false;
          }
        });
      } else {
        // å¦‚果没有表单引用,使用原有的验证
        if (!this.revertSplitForm.originalBarcode) {
          this.$message.warning('请输入原条码');
          return;
        }
        this.submitRevertSplit();
      }
    },
    // æäº¤æ’¤é”€æ‹†åŒ…请求
    async submitRevertSplit() {
      this.revertSplitLoading = true;
      try {
        const res = await this.http.post('/api/OutboundPicking/revert-split-package', {
          originalBarcode: this.revertSplitForm.originalBarcode
        });
        if (res.status) {
          this.$message.success('撤销拆包成功');
          this.showRevertSplitDialog = false;
          this.revertSplitForm.originalBarcode = '';
          this.loadData();
        } else {
          this.$message.error(res.message || '撤销拆包失败');
        }
      } catch (error) {
        this.$message.error('撤销拆包失败');
      }
    },
    resetSplitForm() {
      this.splitForm.originalBarcode = '';
      this.splitForm.materielCode = '';
      this.splitForm.splitQuantity = 0;
      this.splitForm.maxQuantity = 0;
    },
    // é‡ç½®æ‰¹é‡å›žåº“表单
    resetBatchReturnForm() {
      this.batchReturnForm.palletCode = '';
      this.batchReturnForm.unpickedCount = 0;
      this.batchReturnForm.unpickedQuantity = 0;
    },
    resetReturnForm() {
      this.returnForm.barcode = '';
      this.returnForm.materielCode = '';
      this.returnForm.returnQuantity = 0;
    }
  }
})
</script>
<style scoped>
.picking-confirm {
.picking-container {
  padding: 20px;
  position: relative; /* ä¸ºå¼¹çª—定位提供上下文 */
}
.scanner-form {
  display: flex;
  flex-direction: column;
  height: 70vh;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.content-layout {
.scanner-form .el-input {
  width: 200px;
}
.summary-info {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
  flex: 1;
  min-height: 0; /* é‡è¦ï¼šé˜²æ­¢flex子元素溢出 */
  gap: 20px;
  flex-wrap: wrap;
}
.left-section {
  flex: 1;
/* è¡¨æ ¼æ“ä½œåŒºåŸŸæ ·å¼ */
.table-actions {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  padding: 0 10px;
}
.right-section {
  flex: 1;
.selection-count {
  font-size: 12px;
  color: #909399;
}
/* è¡¨æ ¼æ ·å¼è°ƒæ•´ */
.content-area .el-table {
  margin-top: 0;
}
/* ç¡®ä¿è¡¨æ ¼é«˜åº¦é€‚应 */
.content-area .el-card__body {
  padding: 15px;
}
/* è‡ªå®šä¹‰å¼¹çª—样式 */
.custom-dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}
.scan-section {
  flex-shrink: 0;
.custom-dialog-wrapper {
  position: relative;
  z-index: 10000;
}
.scan-alert {
  margin-bottom: 16px;
.custom-dialog {
  background: white;
  border-radius: 4px;
  width: 500px;
  max-width: 90vw;
  max-height: 90vh;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  overflow: auto;
}
.scan-form {
  max-width: 500px;
}
.pallet-summary {
  margin: 16px 0;
}
.action-buttons {
  margin-top: 16px;
}
.outbound-details-card {
  height: 100%;
.custom-dialog-header {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: 20px 20px 10px;
  border-bottom: 1px solid #ebeef5;
}
.outbound-details-card :deep(.el-card__body) {
  flex: 1;
.custom-dialog-header h3 {
  margin: 0;
  color: #303133;
}
/* å…³é—­æŒ‰é’®æ ·å¼ */
.close-button {
  font-size: 18px;
  color: #909399;
  padding: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.picked-records {
  flex-shrink: 0;
  height: 300px;
.close-button:hover {
  color: #409EFF;
  background-color: transparent;
}
.picked-records :deep(.el-card__body) {
  padding: 0;
.custom-dialog-body {
  padding: 20px;
}
.custom-dialog-footer {
  padding: 10px 20px 20px;
  text-align: right;
  border-top: 1px solid #ebeef5;
}
.custom-dialog-footer .el-button {
  margin-left: 10px;
}
/* ç¡®ä¿å¼¹çª—在移动设备上也能正常显示 */
@media (max-width: 768px) {
  .custom-dialog {
    width: 95vw;
    margin: 10px;
  }
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/outboundOrder.vue
@@ -147,14 +147,6 @@
        align: "left",
      },
      {
        field: "warehouseId",
        title: "仓库",
        type: "string",
        width: 90,
        align: "left",
        bind:{key: "warehouses", data: []}
      },
      {
        field: "orderNo",
        title: "单据编号",
        type: "string",
@@ -202,6 +194,13 @@
        bind: { key: "createType", data: [] },
      },
      {
        field: "factoryArea",
        title: "厂区",
        type: "string",
        width: 120,
        align: "left"
      },
      {
        field: "departmentCode",
        title: "修改时间",
        type: "string",
ÏîÄ¿´úÂë/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/InvokeMESService.cs
@@ -207,7 +207,7 @@
                                    };
                                    
                                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.Unit, item.WarehouseCode })
                                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                       .Select(group => new FeedbackInboundDetailsModel
                                       {
                                           materialCode = group.Key.MaterielCode,
@@ -216,11 +216,11 @@
                                           lineNo = group.Key.InboundOrderRowNo,
                                           // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                           warehouseCode =group.Key.WarehouseCode,
                                           unit = group.Key.Unit,
                                           unit = group.Key.BarcodeUnit,
                                           barcodes = group.Select(row => new FeedbackBarcodesModel
                                           {
                                               barcode = row.Barcode,
                                               qty = row.StockQuantity
                                               qty = row.BarcodeQty
                                           }).ToList()
                                       }).ToList();
                                    feedmodel.details = groupedData;
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/OrderEnum/OrderCreateTypeEnum.cs
@@ -10,15 +10,16 @@
    public enum OrderCreateTypeEnum
    {
        /// <summary>
        /// ç³»ç»Ÿå†…创建
        /// </summary>
        [Description("系统内创建")]
        CreateInSystem,
        /// <summary>
        /// ä¸Šæ¸¸ç³»ç»ŸæŽ¨é€
        /// </summary>
        [Description("上游系统推送")]
        UpperSystemPush
        UpperSystemPush,
        /// <summary>
        /// ç³»ç»Ÿå†…创建
        /// </summary>
        [Description("系统内创建")]
        CreateInSystem
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Core/BaseRepository/RepositoryBase.cs
@@ -912,7 +912,8 @@
                        _db.InsertableByObject(obj).AS(type.Name + "_Hty").ExecuteCommand();
                }
            }
            return DeleteData(entity);
            // return DeleteData(entity);
            return true;
        }
        public bool DeleteAndMoveIntoHty(List<TEntity> entities, OperateTypeEnum operateType)
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Inbound/MaterielGroupDTO.cs
@@ -46,6 +46,6 @@
        [PropertyValidate("托盘编号", NotNullAndEmpty = true)]
        public string PalletCode { get; set; }
        public string WarehouseCode { get; set; }
        public int WarehouseCode { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Forms;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -25,11 +26,23 @@
    }
    public class ConfirmPickingDto
    {
        [JsonProperty("orderNo")]
        public string OrderNo { get; set; }
        [JsonProperty("palletCode")]
        public string PalletCode { get; set; }
        [JsonProperty("barcode")]
        public string Barcode { get; set; }
    }
    public class SummaryPickingDto
    {
       public string PalletCode { get; set; }
        public string MaterielCode { get; set; }
        public int UnpickedCount { get; set; }
        public decimal UnpickedQuantity { get; set; }
        public int pickedCount { get; set; }
    }
    public class PickingConfirmRequest
    {
        public int OrderDetailId { get; set; }
@@ -45,6 +58,7 @@
    public class DirectOutboundRequest
    {
        public string PalletCode { get; set; }
        public string OrderNo { get; set; }
    }
    public class CancelPickingRequest
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
@@ -20,12 +20,14 @@
        Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request);
        Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
        Task GetPalletPickingSummary(string orderNo, string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
        Task<object> GetPickingSummary(string orderNo);
        Task<object> GetPickingSummary(ConfirmPickingDto dto);
        Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode);
        Task<WebResponseContent> ValidateBarcode(string barcode);
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -45,5 +45,6 @@
        Task<WebResponseContent> TaskCompleted(string taskNum);
        Task<WebResponseContent> GenerateOutboundTasksAsync(int[] keys);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs
@@ -367,10 +367,13 @@
                        SupplyCode = item.SupplyCode,
                        WarehouseCode = materielGroupDTO.WarehouseType,
                        StockQuantity = item.OrderQuantity,
                        BarcodeQty=item.BarcodeQty,
                        BarcodeUnit=item.BarcodeUnit,
                        FactoryArea= inboundOrder.FactoryArea,
                        Status = 0,                         
                        OrderNo = inboundOrder.InboundOrderNo,
                        BusinessType = inboundOrder.BusinessType,
                        ProductionDate = DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")
                    });
                   
                    item.ReceiptQuantity = item.BarcodeQty;
@@ -419,12 +422,12 @@
            WebResponseContent content = new WebResponseContent();
            try
            {
            {
                (bool, string, object?) result2 = ModelValidate.ValidateModelData(materielGroupDTO);
                if (!result2.Item1) return content = WebResponseContent.Instance.Error(result2.Item2);
                var code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>().Where(x => x.Code == materielGroupDTO.WarehouseCode).Select(x => x.Code).First();
                if (string.IsNullOrEmpty(code))
                bool code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>().Where(x => x.Id == materielGroupDTO.WarehouseCode).Any();
                if (!code)
                {
                    return content = WebResponseContent.Instance.Error($"仓库中没有该{materielGroupDTO.WarehouseCode}编号。");
                }
@@ -448,7 +451,7 @@
                {
                    if (stockInfo == null)
                    {
                        stockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = materielGroupDTO.PalletCode,LocationType=1 };
                        stockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = materielGroupDTO.PalletCode,LocationType= materielGroupDTO.WarehouseCode.ObjToInt() };
                        stockInfo.Details = new List<Dt_StockInfoDetail>();
                    }
                    else
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -111,7 +111,7 @@
        public string SupplyCode { get; set; }
        public string WarehouseCode { get; set; }
        /// <summary>
        /// çŠ¶æ€ çŠ¶æ€ï¼š0-已分配 1-部分拣选 2-已拣选 3-已完成
        /// çŠ¶æ€ çŠ¶æ€ï¼š0-已分配  1-出库中 2-部分拣选  3已拣选
        /// </summary>
        [SugarColumn(IsNullable = false, ColumnDescription = "状态")]
        public int Status { get; set; }
@@ -127,7 +127,10 @@
        [Navigate(NavigateType.OneToOne, nameof(StockInfo))]//一对一 SchoolId是StudentA类里面的
        public Dt_StockInfo StockInfo { get; set; } //不能赋值只能是null
        [SugarColumn(IsIgnore = true)]
        public string FactoryArea { get; set; }
        [SqlSugar.SugarColumn(IsIgnore = true)]
        public decimal RemainQuantity => AssignQuantity - PickedQty;
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs
@@ -19,6 +19,8 @@
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public int TaskNo { get; set; }
        public string OrderNo { get; set; }
        public int OrderDetailId { get; set; }
@@ -41,6 +43,9 @@
        public string LocationCode { get; set; }
        public int StockId { get; set; }
        public string FactoryArea { get; set; }
    }
 
@@ -79,13 +84,15 @@
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string OrderNo { get; set; }
        public int? TaskNum { get; set; }
        public string PalletCode { get; set; }
        public string StockId { get; set; }
        public int StockId { get; set; }
        public bool IsReverted { get; set; } = false;
        public int OutStockLockInfoId { get; set; } // å…³è”的出库锁定信息
        public string OriginalBarcode { get; set; } // åŽŸæ¡ç 
        public string NewBarcode { get; set; } // æ–°æ¡ç 
 
        public string FactoryArea { get; set; }
        /// <summary>
        /// æ‹†åˆ†æ•°é‡ï¼ˆæ–°æ¡ç æ•°é‡ï¼‰
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs
@@ -104,6 +104,7 @@
        [SugarColumn(ColumnName = "supplyCode", ColumnDescription = "供应商编号")]
        public string? SupplyCode { get; set; }
        public string FactoryArea { get; set; }
        /// <summary>
        /// ä»“库
        /// é»˜è®¤å€¼:
@@ -126,6 +127,11 @@
        [SugarColumn(ColumnName = "businessType", ColumnDescription = "业务类型")]
        public string? BusinessType { get; set; }
        public decimal BarcodeQty { get; set; }
        public string BarcodeUnit { get; set; }
        /// <summary>
        /// å¤‡æ³¨
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs
@@ -34,9 +34,9 @@
            _recordService = recordService;
        }
        /// <summary>
        /// åˆ›å»ºå‡ºåº“锁定信息 - ä¿®å¤ç‰ˆæœ¬
        /// åˆ›å»ºå‡ºåº“锁定
        /// </summary>
        public Dt_OutStockLockInfo GetOutStockLockInfo(
            Dt_OutboundOrder outboundOrder,
@@ -72,7 +72,7 @@
                // ä½¿ç”¨ç¬¬ä¸€ä¸ªå¯ç”¨æ¡ç 
                var firstAvailableDetail = stockDetails
                    .Where(x => x.StockQuantity > x.OutboundQuantity)
                    .OrderBy(x => x.ProductionDate)
                    .OrderBy(x => x.CreateDate)
                    .FirstOrDefault();
                if (firstAvailableDetail == null)
@@ -84,6 +84,7 @@
            return new Dt_OutStockLockInfo()
            {
                PalletCode = outStock.PalletCode,
                AssignQuantity = assignQuantity,
                MaterielCode = outboundOrderDetail.MaterielCode,
@@ -99,8 +100,9 @@
                Status = (int)OutLockStockStatusEnum.已分配,
                StockId = outStock.Id,
                Unit = outboundOrderDetail.Unit,
                SupplyCode     = outboundOrderDetail.SupplyCode,
                FactoryArea = outboundOrder.FactoryArea,
                OrderType=outboundOrder.OrderType,
                SupplyCode = outboundOrderDetail.SupplyCode,
                WarehouseCode = outboundOrderDetail.WarehouseCode,
                // æ–°å¢žå­—段
                CurrentBarcode = targetBarcode,
@@ -141,7 +143,7 @@
                    Status = lockInfo.Status,
                    IsSplitted = lockInfo.IsSplitted,
                    ParentLockId = lockInfo.ParentLockId,
                    MaterielName = detail.MaterielName,
                    Unit = detail.Unit
                })
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -70,7 +70,7 @@
                    MaterielCode = x.Key.MaterielCode,
                    BatchNo = x.Key.BatchNo,
                    Details = x.ToList(),
                    TotalNeedQuantity = x.Sum(v => v.OrderQuantity - v.OverOutQuantity - v.LockQuantity)
                    TotalNeedQuantity = x.Sum(v => v.OrderQuantity - v.OverOutQuantity - v.LockQuantity-v.MoveQty)
                })
                .Where(x => x.TotalNeedQuantity > 0)
                .ToList();
@@ -107,7 +107,7 @@
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»† - ä¿®å¤ç‰ˆæœ¬
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
        /// </summary>
        private void DistributeLockQuantityByFIFO(
            List<Dt_OutboundOrderDetail> details,
@@ -118,7 +118,7 @@
        {
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序出库单明细(假设先创建的明细需要优先满足)
            var sortedDetails = details
                .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity > 0) // åªå¤„理还需要分配的数量
                .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity -d.MoveQty> 0) // åªå¤„理还需要分配的数量
                .OrderBy(x => x.Id)
                .ToList();
@@ -128,7 +128,7 @@
            var allocatedStockDetails = assignStocks
                .SelectMany(x => x.Details)
                .Where(x => stockAllocations.ContainsKey(x.Id))
                .OrderBy(x => x.ProductionDate)
                .OrderBy(x => x.CreateDate)
                .ThenBy(x => x.StockId)
                .ToList();
@@ -201,123 +201,6 @@
            }
        }
/// <summary>
    /// åˆ›å»ºå‡ºåº“锁定信息
    /// </summary>
    private void CreateOutStockLockInfos(Dt_OutboundOrder outboundOrder,List<Dt_OutboundOrderDetail> details,
        List<Dt_StockInfo> assignStocks,List<Dt_OutStockLockInfo> outStockLockInfos)
        {
            foreach (var stock in assignStocks)
            {
                // èŽ·å–è¯¥åº“å­˜ä¸­ç›¸å…³ç‰©æ–™çš„å¯ç”¨æ¡ç ä¿¡æ¯
                var stockDetails = stock.Details
                    .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode) &&
                               x.StockQuantity > x.OutboundQuantity)
                    .OrderBy(x => x.ProductionDate) // å…ˆè¿›å…ˆå‡º
                    .ToList();
                if (!stockDetails.Any()) continue;
                var stockAssignQuantity = stockDetails.Sum(x => x.OutboundQuantity);
                // æŸ¥æ‰¾è¿™ä¸ªåº“存已经分配的数量
                var existingAssign = outStockLockInfos
                    .Where(x => x.StockId == stock.Id &&
                               details.Any(d => d.Id == x.OrderDetailId))
                    .Sum(x => x.AssignQuantity);
                var availableAssign = stockAssignQuantity - existingAssign;
                if (availableAssign <= 0) continue;
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…æ¡ç 
                var barcodeAllocation = AllocateBarcodes(stockDetails, availableAssign);
                // åˆ†é…ç»™å„个明细
                foreach (var detail in details.Where(d => d.LockQuantity > 0))
                {
                    var alreadyAssigned = outStockLockInfos
                        .Where(x => x.OrderDetailId == detail.Id && x.StockId == stock.Id)
                        .Sum(x => x.AssignQuantity);
                    var canAssign = Math.Min(detail.LockQuantity - alreadyAssigned, availableAssign);
                    if (canAssign > 0)
                    {
                        // ä¸ºè¿™ä¸ªåˆ†é…ç¡®å®šæ¡ç 
                        var (barcode, barcodeQuantity) = GetBarcodeForAllocation(barcodeAllocation, canAssign);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stock, canAssign, barcode);
                        outStockLockInfos.Add(lockInfo);
                        availableAssign -= canAssign;
                        // æ›´æ–°æ¡ç åˆ†é…è®°å½•
                        UpdateBarcodeAllocation(barcodeAllocation, barcode, barcodeQuantity);
                    }
                    if (availableAssign <= 0) break;
                }
            }
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…æ¡ç 
        /// </summary>
        private Dictionary<string, decimal> AllocateBarcodes(List<Dt_StockInfoDetail> stockDetails, decimal totalQuantity)
        {
            var allocation = new Dictionary<string, decimal>();
            decimal remainingQuantity = totalQuantity;
            foreach (var detail in stockDetails.OrderBy(x => x.ProductionDate))
            {
                if (remainingQuantity <= 0) break;
                decimal available = detail.StockQuantity - detail.OutboundQuantity;
                decimal allocate = Math.Min(available, remainingQuantity);
                allocation[detail.Barcode] = allocate;
                remainingQuantity -= allocate;
            }
            return allocation;
        }
        /// <summary>
        /// ä¸ºåˆ†é…èŽ·å–åˆé€‚çš„æ¡ç 
        /// </summary>
        private (string barcode, decimal quantity) GetBarcodeForAllocation(Dictionary<string, decimal> barcodeAllocation, decimal requiredQuantity)
        {
            foreach (var (barcode, quantity) in barcodeAllocation)
            {
                if (quantity >= requiredQuantity)
                {
                    return (barcode, requiredQuantity);
                }
            }
            // å¦‚果单个条码数量不足,使用第一个条码
            var first = barcodeAllocation.First();
            return (first.Key, Math.Min(first.Value, requiredQuantity));
        }
        /// <summary>
        /// æ›´æ–°æ¡ç åˆ†é…è®°å½•
        /// </summary>
        private void UpdateBarcodeAllocation(Dictionary<string, decimal> barcodeAllocation, string barcode, decimal usedQuantity)
        {
            if (barcodeAllocation.ContainsKey(barcode))
            {
                barcodeAllocation[barcode] -= usedQuantity;
                if (barcodeAllocation[barcode] <= 0)
                {
                    barcodeAllocation.Remove(barcode);
                }
            }
        }
        /// <summary>
        /// å‡ºåº“库存分配后,更新数据库数据
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Http;
using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using System;
using System.Collections.Generic;
@@ -36,9 +38,10 @@
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly ISplitPackageService _splitPackageService;
        private readonly IRepository<Dt_Task> _taskRepository;
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService) : base(BaseDal)
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService, IRepository<Dt_Task> taskRepository) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -49,6 +52,7 @@
            _outboundOrderDetailService = outboundOrderDetailService;
            _splitPackageService = splitPackageService;
            _outboundOrderService = outboundOrderService;
            _taskRepository = taskRepository;
        }
@@ -114,8 +118,7 @@
        }
        /// <summary>
        /// æ‰«ç æ‹£é€‰ç¡®è®¤ - ç®€åŒ–版本
        /// åªå¤„理实际拣选的库存扣减
        /// æ‰«ç æ‹£é€‰ç¡®è®¤
        /// </summary>
        public async Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request)
        {
@@ -221,19 +224,20 @@
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                // 2. æ£€æŸ¥åº“存可用数量
                var availableQty = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                if (availableQty <= 0)
                    return WebResponseContent.Instance.Error("库存数量不足");
                //// 2. æ£€æŸ¥åº“存可用数量
                //var availableQty = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                //if (availableQty <= 0)
                //    return WebResponseContent.Instance.Error("库存数量不足");
                // 3. æŸ¥æ‰¾ç›¸å…³çš„出库详情信息
                var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.CurrentBarcode == barcode &&
                               x.Status == 0 &&
                               x.RemainQuantity > 0)
                               x.Status == (int)OutLockStockStatusEnum.出库中 &&
                               x.AssignQuantity - x.PickedQty > 0)
                    .FirstAsync();
                if (outStockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选信息或已拣选完成");
@@ -244,36 +248,45 @@
                // 5. æ›´æ–°å‡ºåº“详情的已拣选数量
                outStockInfo.PickedQty = outStockInfo.AssignQuantity;
                outStockInfo.Status = 1;
                outStockInfo.Status = (int)OutLockStockStatusEnum.已拣选;
                await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
                // 6. æ›´æ–°åº“存出库数量
                await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                    .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity + outStockInfo.AssignQuantity)
                    .Where(x => x.Id == stockDetail.Id)
                    .ExecuteCommandAsync();
                //// 6. æ›´æ–°åº“存出库数量
                //await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                //    .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity + outStockInfo.AssignQuantity)
                //    .Where(x => x.Id == stockDetail.Id)
                //    .ExecuteCommandAsync();
                // 7. æ›´æ–°å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == outStockInfo.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity += outStockInfo.AssignQuantity;
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                if (orderDetail != null)
                {
                    orderDetail.OverOutQuantity += outStockInfo.AssignQuantity;
                    orderDetail.LockQuantity -= outStockInfo.AssignQuantity;
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                }
                // 8. æ£€æŸ¥æ˜¯å¦å®Œæˆå‡ºåº“
                await CheckAndUpdateOrderStatus(orderNo);
                //查询任务表
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                // 9. è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    FactoryArea = outStockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = stockDetail.Id,
                    OrderNo = orderNo,
                    OrderDetailId = orderDetail?.Id ?? 0,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = outStockInfo.MaterielCode,
                    PickQuantity = outStockInfo.AssignQuantity,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = outStockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
@@ -325,7 +338,7 @@
                        .Where(x => x.OrderNo == orderNo &&
                                   x.PalletCode == palletCode &&
                                   x.CurrentBarcode == barcode &&
                                   x.Status == 1)
                                   x.Status == 2)
                        .FirstAsync();
                if (outStockInfo == null)
@@ -333,14 +346,14 @@
                // è¿˜åŽŸå‡ºåº“è¯¦æƒ…çŠ¶æ€
                outStockInfo.PickedQty = 0;
                outStockInfo.Status = 0;
                outStockInfo.Status = 1;
                await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
                // è¿˜åŽŸåº“å­˜å‡ºåº“æ•°é‡
                await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                        .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity - outStockInfo.AssignQuantity)
                        .Where(x => x.Barcode == barcode)
                        .ExecuteCommandAsync();
                //// è¿˜åŽŸåº“å­˜å‡ºåº“æ•°é‡
                //await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                //        .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity - outStockInfo.AssignQuantity)
                //        .Where(x => x.Barcode == barcode)
                //        .ExecuteCommandAsync();
                // è¿˜åŽŸå‡ºåº“å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
@@ -384,10 +397,9 @@
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 0 &&
                       x.RemainQuantity > 0)
                           x.Status == 1)
                .ToListAsync();
            return list;
            return list.Where(x => x.RemainQuantity > 0).ToList();
        }
        // èŽ·å–å·²æ‹£é€‰åˆ—è¡¨
@@ -396,26 +408,68 @@
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 1)
                           x.Status == 2)
                .ToListAsync();
            return list;
        }
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(string orderNo)
        public async Task<object> GetPickingSummary(string orderNo, string palletCode)
        {
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.Status == 0)
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode && x.Status == 1)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.RemainQuantity)
                    UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty)
                })
                .ToListAsync();
                .FirstAsync();
            return  summary;
            return summary;
        }
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(ConfirmPickingDto dto)
        {
            var picked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
             .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
             .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
             .Where(x => x.Status == 2)
             .GroupBy(x => new { x.PalletCode, x.MaterielCode })
             .Select(x => new SummaryPickingDto
             {
                 PalletCode = x.PalletCode,
                 MaterielCode = x.MaterielCode,
                 pickedCount = SqlFunc.AggregateCount(x.Id)
             }).FirstAsync();
            if (picked == null)
            {
                picked = new SummaryPickingDto { pickedCount = 0 };
            }
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
                .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
                .Where(x => x.Status == 1)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new SummaryPickingDto
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty),
                }).FirstAsync();
            if (summary == null)
            {
                summary = new SummaryPickingDto { pickedCount = 0 };
            }
            summary.pickedCount = picked.pickedCount;
            return summary;
        }
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
@@ -587,50 +641,59 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == request.PalletCode)
                    .FirstAsync();
                    .Where(x => x.PalletCode == request.PalletCode).FirstAsync();
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到托盘库存信息");
                // 2. èŽ·å–ç›¸å…³çš„å‡ºåº“é”å®šä¿¡æ¯
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.PalletCode == request.PalletCode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode)
                    .ToListAsync();
                // 3. æ•´ä¸ªæ‰˜ç›˜å‡ºåº“ - è®¾ç½®å‡ºåº“数量等于库存数量
                foreach (var detail in stockInfo.Details)
                {
                    decimal outboundQuantity = detail.StockQuantity - detail.OutboundQuantity;
                    detail.OutboundQuantity = detail.StockQuantity; // å…¨éƒ¨å‡ºåº“
                    await _stockInfoDetailService.Db.Updateable(detail).ExecuteCommandAsync();
                }
                // 4. æ›´æ–°å‡ºåº“锁定信息
                foreach (var lockInfo in lockInfos)
                {
                    decimal unpicked = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    lockInfo.PickedQty += unpicked; // æ ‡è®°ä¸ºå…¨éƒ¨æ‹£é€‰
                    if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中)
                    {
                        lockInfo.PickedQty = lockInfo.AssignQuantity;
                    }
                    lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    // æ›´æ–°å‡ºåº“单明细
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .Where(x => x.Id == lockInfo.OrderDetailId)
                        .FirstAsync();
                    orderDetail.OverOutQuantity += unpicked;
                    orderDetail.LockQuantity -= unpicked;
                    orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                    orderDetail.LockQuantity = 0;
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    .Where(x => x.Id == lockInfo.OrderDetailId)
                    .FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity += lockInfo.PickedQty;
                        orderDetail.LockQuantity -= lockInfo.PickedQty;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        orderDetail.LockQuantity = 0;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new
                {
                    OrderDetailId = x.Key,
                    TotalQuantity = x.Sum(o => o.PickedQty)
                }).ToList();
                foreach (var item in groupDetails)
                {
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.Id == item.OrderDetailId).FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity = item.TotalQuantity;
                        orderDetail.LockQuantity = 0;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                // 5. æ›´æ–°æ‹†åŒ…记录状态
                await CheckAndUpdateOrderStatus(request.OrderNo);
                var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
@@ -643,7 +706,7 @@
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // 6. æ¸…空货位
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == stockInfo.LocationCode)
                    .FirstAsync();
@@ -653,6 +716,14 @@
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                foreach (var detail in stockInfo.Details)
                {
                    await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync();
                }
                await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("直接出库成功");
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs
@@ -9,6 +9,7 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
@@ -23,13 +24,15 @@
        private readonly IStockInfoService _stockInfoService;
        private readonly IStockInfoDetailService _stockInfoDetailService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        private readonly IDailySequenceService _dailySequenceService;
        public SplitPackageService(IRepository<Dt_SplitPackageRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService) : base(BaseDal)
        public SplitPackageService(IRepository<Dt_SplitPackageRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, IDailySequenceService dailySequenceService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
            _outStockLockInfoService = outStockLockInfoService;
            _stockInfoDetailService = stockInfoDetailService;
            _dailySequenceService = dailySequenceService;
        }
        /// <summary>
@@ -42,16 +45,16 @@
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯å‡ºåº“锁定信息
                var lockInfo = await _stockInfoDetailService.Db.Queryable<Dt_OutStockLockInfo>()
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                          .Where(x => x.OrderNo == request.OrderNo &&
                           x.PalletCode == request.PalletCode &&
                           x.CurrentBarcode == request.OriginalBarcode &&
                           x.Status == 0)
                           x.Status == 1)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到有效的出库锁定信息");
                // 2. æ£€æŸ¥å‰©ä½™é”å®šæ•°é‡
                decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (request.SplitQuantity > remainingLockQuantity)
@@ -61,12 +64,14 @@
               .Where(x => x.Barcode == request.OriginalBarcode)
               .FirstAsync();
                var seq = await _dailySequenceService.GetNextSequenceAsync();
                // 3. ç”Ÿæˆæ–°æ¡ç 
                string newBarcode = "";
                string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
                // 4. åˆ›å»ºæ–°çš„出库锁定信息(新条码)
                var newLockInfo = new Dt_OutStockLockInfo
                {
                    OrderNo = lockInfo.OrderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    BatchNo = lockInfo.BatchNo,
@@ -82,22 +87,28 @@
                    TaskNum = lockInfo.TaskNum,
                    Status = (int)OutLockStockStatusEnum.出库中,
                    Unit = lockInfo.Unit,
                    SupplyCode = lockInfo.SupplyCode,
                    OrderType=lockInfo.OrderType,
                    CurrentBarcode = newBarcode, // æ–°æ¡ç 
                    OriginalLockQuantity = request.SplitQuantity,
                    IsSplitted = 1,
                    ParentLockId = lockInfo.Id // è®°å½•父级锁定ID
                };
                await Db.Insertable(newLockInfo).ExecuteCommandAsync();
                await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                // 5. æ›´æ–°åŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡ï¼ˆå‡å°‘æ‹†åŒ…æ•°é‡ï¼‰
                lockInfo.AssignQuantity -= request.SplitQuantity;
                await Db.Updateable(lockInfo).ExecuteCommandAsync();
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // 6. è®°å½•拆包历史(用于追踪)
                var splitHistory = new Dt_SplitPackageRecord
                {
                    FactoryArea = lockInfo.FactoryArea,
                    TaskNum=lockInfo.TaskNum,
                    OutStockLockInfoId = lockInfo.Id,
                    StockId = stockDetail?.StockId ?? 0,
                    Operator = App.User.UserName,
                    IsReverted = false,
                    OriginalBarcode = lockInfo.CurrentBarcode,
                    NewBarcode = newBarcode,
                    SplitQty = request.SplitQuantity,
@@ -110,10 +121,10 @@
                };
                await Db.Insertable(splitHistory).ExecuteCommandAsync();
                Db.Ado.CommitTran();
                _unitOfWorkManage.CommitTran();
                // 7. å›žä¼ æ–°æ¡ç ç»™MES
               // await SendBarcodeToMES(newBarcode, request.MaterielCode, request.SplitQuantity);
                // await SendBarcodeToMES(newBarcode, request.MaterielCode, request.SplitQuantity);
                return WebResponseContent.Instance.OK("拆包成功", new
                {
@@ -123,7 +134,7 @@
            }
            catch (Exception ex)
            {
                Db.Ado.RollbackTran();
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"拆包失败: {ex.Message}");
            }
        }
@@ -140,66 +151,67 @@
                        .OrderByDescending(x => x.CreateDate)
                        .FirstAsync();
                    if (splitPackage == null)
                        return  WebResponseContent.Instance.Error("未找到拆包记录");
                if (splitPackage == null)
                    return WebResponseContent.Instance.Error("未找到拆包记录");
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦å·²æ‹£é€‰
                    var newOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                        .FirstAsync();
                // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦å·²æ‹£é€‰
                var newOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                    .FirstAsync();
                    if (newOutStockInfo.Status == 1)
                        return  WebResponseContent.Instance.Error("新条码已拣选,无法撤销拆包");
                if (newOutStockInfo.Status == 2)
                    return WebResponseContent.Instance.Error("新条码已拣选,无法撤销拆包");
                    // è¿˜åŽŸåŽŸå‡ºåº“è¯¦æƒ…æ•°é‡
                    var originalOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == originalBarcode)
                        .FirstAsync();
                // è¿˜åŽŸåŽŸå‡ºåº“è¯¦æƒ…æ•°é‡
                var originalOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == originalBarcode)
                    .FirstAsync();
                    originalOutStockInfo.AssignQuantity += splitPackage.SplitQty;
                    await _outStockLockInfoService.Db.Updateable(originalOutStockInfo).ExecuteCommandAsync();
                originalOutStockInfo.AssignQuantity += splitPackage.SplitQty;
                await _outStockLockInfoService.Db.Updateable(originalOutStockInfo).ExecuteCommandAsync();
                    // åˆ é™¤æ–°å‡ºåº“详情记录
                    await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                        .ExecuteCommandAsync();
                // åˆ é™¤æ–°å‡ºåº“详情记录
                await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                    .ExecuteCommandAsync();
                    // æ ‡è®°æ‹†åŒ…记录为已撤销
                    splitPackage.IsReverted = true;
                    await Db.Updateable(splitPackage).ExecuteCommandAsync();
                // æ ‡è®°æ‹†åŒ…记录为已撤销
                splitPackage.IsReverted = true;
                await Db.Updateable(splitPackage).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                    return  WebResponseContent.Instance.OK("撤销拆包成功");
                return WebResponseContent.Instance.OK("撤销拆包成功");
            }
            catch (Exception ex)
            {
                return  WebResponseContent.Instance.Error($"撤销拆包失败:{ex.Message}");
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"撤销拆包失败:{ex.Message}");
            }
        }
        // èŽ·å–æ‹†åŒ…ä¿¡æ¯
        public async Task< WebResponseContent > GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
        public async Task<WebResponseContent> GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
        {
            var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == barcode &&
                           x.Status == 0)
                           x.Status == 1)
                .FirstAsync();
            if (outStockInfo == null)
                return  WebResponseContent.Instance .Error("未找到对应的出库信息");
                return WebResponseContent.Instance.Error("未找到对应的出库信息");
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode)
                .FirstAsync();
            //var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
            //    .Where(x => x.Barcode == barcode)
            //    .FirstAsync();
            return  WebResponseContent.Instance .OK("",new
            return WebResponseContent.Instance.OK("", new
            {
                MaterielCode = outStockInfo.MaterielCode,
                RemainQuantity = outStockInfo.RemainQuantity,
                Unit = "个" // æ ¹æ®å®žé™…情况获取单位
                Unit = outStockInfo.Unit
            });
        }
        /// <summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -113,7 +113,7 @@
            if (stockTotalQuantity < needQuantity)
            {
                residueQuantity = needQuantity - stockTotalQuantity;
                // ä¸æŠ›å‡ºå¼‚常,允许部分分配
            }
            else
            {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -108,6 +108,7 @@
            _outStockLockInfoService = outStockLockInfoService;
        }
        /// <summary>
        /// 
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Inbound.cs
@@ -70,7 +70,8 @@
                    TaskStatus = TaskStatusEnum.New.ObjToInt(),
                    WarehouseId = stockInfo.WarehouseId,
                    PalletType = stockInfo.PalletType,
                    OrderNo= stockInfo.Details.FirstOrDefault()?.OrderNo
                };
                //空箱
                if (stockInfo.PalletType == PalletTypeEnum.Empty.ObjToInt())
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -164,7 +164,7 @@
                    tasks = GetTasks(result.Item1, typeEnum);
                    tasks.ForEach(x =>
                    {
                        x.OrderNo = outboundOrder.UpperOrderNo;
                        x.OrderNo = outboundOrder.OrderNo;
                    });
                    result.Item2.ForEach(x =>
                    {
@@ -331,7 +331,7 @@
                            // TaskNum = BaseDal.GetTaskNum(nameof(SequenceEnum.SeqTaskNum)),
                            PalletType = stockInfo.PalletType,
                            WarehouseId = stockInfo.WarehouseId,
                        };
                        //if (taskType != TaskTypeEnum.OutEmpty)
                        //{
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs
@@ -50,13 +50,14 @@
        [HttpPost, Route("Test"), AllowAnonymous, MethodParamsValidate]
        public async Task<WebResponseContent> Test()
        {
            var purchaseToStockResult = await _materialUnitService.ConvertPurchaseToStockAsync("101001-00002", 10);
           // var purchaseToStockResult = await _materialUnitService.ConvertPurchaseToStockAsync("101001-00002", 10);
            var pdddurchaseToStockResult = await _materialUnitService.ConvertPurchaseToStockAsync("100513-00210", 10);
           // var pdddurchaseToStockResult = await _materialUnitService.ConvertPurchaseToStockAsync("100513-00210", 10);
            var sddd = _locationInfoService.AssignLocation();
            var code = sddd.LocationCode;
            // await _dailySequenceService.GetNextSequenceAsync();
            //var ssss=await _dailySequenceService.GetNextSequenceAsync();
            //var  ddddssss = "WSLOT" + DateTime.Now.ToString("yyyyMMddHHmmss") + ssss.ToString().PadLeft(5, '0');
            //erpApiService.GetSuppliersAsync();
            //erpApiService.GetMaterialUnitAsync();
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
@@ -23,7 +23,7 @@
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€
        /// </summary>
        [HttpGet("GetPalletOutboundStatus")]
        [HttpPost("GetPalletOutboundStatus")]
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            return await Service.GetPalletOutboundStatus(palletCode);
@@ -32,7 +32,7 @@
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
        /// </summary>
        [HttpGet("GetPalletLockInfos")]
        [HttpPost("GetPalletLockInfos")]
        public async Task<WebResponseContent> GetPalletLockInfos(string palletCode)
        {
            var lockInfos = await _outStockLockInfoService.GetPalletLockInfos(palletCode);
@@ -41,17 +41,17 @@
 
        [HttpGet("unpicked-list")]
        public async Task<WebResponseContent> GetUnpickedList(string orderNo, string palletCode)
        [HttpPost("unpicked-list")]
        public async Task<WebResponseContent> GetUnpickedList([FromBody] ConfirmPickingDto dto)
        {
            var lists= await Service.GetUnpickedList(orderNo, palletCode);
            var lists= await Service.GetUnpickedList(dto.OrderNo, dto.PalletCode);
            return WebResponseContent.Instance.OK("", lists);
        }
        [HttpGet("picked-list")]
        public async Task<WebResponseContent> GetPickedList(string orderNo, string palletCode)
        [HttpPost("picked-list")]
        public async Task<WebResponseContent> GetPickedList([FromBody] ConfirmPickingDto dto)
        {
            var lists = await Service.GetPickedList(orderNo, palletCode);
            var lists = await Service.GetPickedList(dto.OrderNo, dto.PalletCode);
            return WebResponseContent.Instance.OK("", lists);
        }
@@ -61,11 +61,48 @@
            return await Service.ConfirmPicking(dto.OrderNo, dto.PalletCode, dto.Barcode);
        }
        [HttpGet("picking-summary")]
        public async Task<WebResponseContent> GetPickingSummary(string orderNo)
        [HttpPost("picking-summary")]
        public async Task<WebResponseContent> GetPickingSummary([FromBody] ConfirmPickingDto dto)
        {
            var data = await Service.GetPickingSummary(orderNo);
            var data = await Service.GetPickingSummary(dto);
            return WebResponseContent.Instance.OK("", data);
        }
        [HttpPost("split-package")]
        public async Task<WebResponseContent> SplitPackage([FromBody] SplitPackageDto dto)
        {
            return  await _splitPackageService.SplitPackage(dto);
        }
        [HttpPost("revert-split-package")]
        public async Task<WebResponseContent> RevertSplitPackage([FromBody] RevertSplitDto dto)
        {
           return  await _splitPackageService.RevertSplitPackage(dto.OriginalBarcode);
        }
        [HttpPost("split-package-info")]
        public async Task<WebResponseContent> GetSplitPackageInfo([FromBody] ConfirmPickingDto dto)
        {
          return  await _splitPackageService.GetSplitPackageInfo(dto.OrderNo, dto.PalletCode, dto.Barcode);
        }
        [HttpPost("return-to-stock")]
        public async Task<WebResponseContent> ReturnToStock( )
        {
            var data= "";
            return WebResponseContent.Instance.OK("", data);
        }
        [HttpPost("direct-outbound")]
        public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest dto)
        {
           return await Service.DirectOutbound(dto);
        }
        ///// <summary>
@@ -84,14 +121,7 @@
        {
            return await Service.ValidateBarcode(barcode);
        }
        /// <summary>
        /// æ‹†åŒ…操作
        /// </summary>
        [HttpPost("SplitPackage")]
        public async Task<WebResponseContent> SplitPackage([FromBody] SplitPackageDto request)
        {
            return await _splitPackageService.SplitPackage(request);
        }
        ///// <summary>
        ///// ç›´æŽ¥å‡ºåº“
@@ -116,9 +146,9 @@
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
        [HttpPost("CancelPicking")]
        public async Task<WebResponseContent> CancelPicking([FromBody] CancelPickingRequest request)
        public async Task<WebResponseContent> CancelPicking([FromBody] ConfirmPickingDto dto)
        {
            return await Service.CancelPicking(request);
            return await Service.CancelPicking(dto.OrderNo,dto.PalletCode,dto.Barcode);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -24,8 +24,9 @@
        {
        }
        [HttpPost, Route("PalletOutboundTask"), AllowAnonymous, MethodParamsValidate]
        [HttpPost, Route("PalletOutboundTask"), AllowAnonymous, MethodParamsValidate]
        public async Task<WebResponseContent> PalletOutboundTask(string endStation, string palletCode = "")
        {