647556386
2025-12-08 3fff7e8eb6fe78f3a512eff51bf16aee39cf9072
Merge branch 'master' of http://115.159.85.185:8098/r/ZhongRui/ALDbanyunxiangmu
已添加8个文件
已修改23个文件
5660 ■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/config/buttons.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/check/recheckOrder.js 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/allocateoutboundOrder.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/newAllocateOrderDetail.vue 797 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/stock/stockInfoDetailByMaterielSum.js 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/router/viewGird.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/BatchPickingConfirm.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/stock/stockInfoDetailByMaterielSum.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | 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_AllocateService/AllocateDetailService.cs 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/ErpApiService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Stock/StockDetailDtO.cs 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IAllocateService/IAllocateDetailService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IStockService/IStockDetailByMaterielService.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoDetailService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/StockDetailByMateriel.cs 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs 3443 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockDetailByMaterielService.cs 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoDetailService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderDetailController.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockDetailByMaterielController.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/config/buttons.js
@@ -208,6 +208,24 @@
    onClick: function () {
    }
},
{
    name: "分拣",
    // icon: 'el-icon-upload2',
    class: '',
    value: 'BatchOrder',
    type: 'primary',
    onClick: function () {
    }
},
{
    name: "组盘",
    // icon: 'el-icon-upload2',
    class: '',
    value: 'GroupPallet',
    type: 'primary',
    onClick: function () {
    }
},
]
export default buttons
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/check/recheckOrder.js
@@ -1,15 +1,8 @@
/*****************************************************************************************
**  Author:jxx 2022
**  QQ:283591387
**完整文档见:http://v2.volcore.xyz/document/api ã€ä»£ç ç”Ÿæˆé¡µé¢ViewGrid】
**常用示例见:http://v2.volcore.xyz/document/vueDev
**后台操作见:http://v2.volcore.xyz/document/netCoreDev
*****************************************************************************************/
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
import gridBody from './extend/StockSelect.vue'
import http from '@/api/http.js'
import { h,createVNode, render,reactive,ref  } from 'vue';
import { ElDialog , ElForm, ElFormItem, ElInput, ElButton, ElMessage ,ElSelect ,ElOption } from 'element-plus'; // å¼•å…¥ElMessage,解决提示无反应
import { h, createVNode, render, reactive, ref } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElInput, ElButton, ElMessage, ElSelect, ElOption } from 'element-plus'; // å¼•å…¥ElMessage,解决提示无反应
let extension = {
  components: {
    //查询界面扩展组件
@@ -22,36 +15,40 @@
    modelFooter: ''
  },
  tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [ {
          name: '组盘',
          type: 'primary',
          value: '组盘',
          onClick: function () { // ä¿®å¤1:移除无用row参数,加日志调试
            console.log('组盘按钮被点击,开始校验');
            const selectedRows = this.$refs.table.getSelected();
            // æ ¡éªŒ1:是否选中行
            if (selectedRows.length === 0) {
              console.log('校验不通过:未选中任何单据');
              ElMessage.warning('请选择一条单据');
              return;
            }
            // æ ¡éªŒ2:是否选中单行
            if (selectedRows.length > 1) {
              console.log('校验不通过:选中多行单据');
              ElMessage.warning('只能选择一条单据');
              return;
            }
            const targetRow = selectedRows[0];
            this.$emit('openPalletDialog', targetRow.orderNo);
          }
        },], box: [], detail: [] }, //扩展的按钮
  buttons: {
     view: [
      //{
    //   name: '组盘',
    //   type: 'primary',
    //   value: '组盘',
    //   onClick: function () { // ä¿®å¤1:移除无用row参数,加日志调试
    //     console.log('组盘按钮被点击,开始校验');
    //     const selectedRows = this.$refs.table.getSelected();
    //     // æ ¡éªŒ1:是否选中行
    //     if (selectedRows.length === 0) {
    //       console.log('校验不通过:未选中任何单据');
    //       ElMessage.warning('请选择一条单据');
    //       return;
    //     }
    //     // æ ¡éªŒ2:是否选中单行
    //     if (selectedRows.length > 1) {
    //       console.log('校验不通过:选中多行单据');
    //       ElMessage.warning('只能选择一条单据');
    //       return;
    //     }
    //     const targetRow = selectedRows[0];
    //     this.$emit('openPalletDialog', targetRow.orderNo);
    //   }
    // },
    ], box: [], detail: []
  }, //扩展的按钮
  methods: {
     //下面这些方法可以保留也可以删除
    //下面这些方法可以保留也可以删除
    onInit() {  //框架初始化配置前,
        this.columns.push({
      this.columns.push({
        field: '操作',
        title: '操作',
        width: 90,
@@ -71,6 +68,30 @@
          this.$refs.gridBody.open(row);
        }
      });
      let EmptyTrayOutboundBtn = this.buttons.find(x => x.value == 'BatchOrder');
      if (EmptyTrayOutboundBtn) {
        EmptyTrayOutboundBtn.onClick = function () {
          let rows = this.$refs.table.getSelected();
          if (rows.length == 0) return this.$error("请选择数据!");
          if (rows.length > 1) return this.$error("请选择一条数据!");
          this.$router.push({
            path: '/outbound/picking',
            query: { orderId: rows[0].id,orderNo:rows[0].orderNo}
          })
        }
      }
      let GroupPalletBtn = this.buttons.find(x=>x.value =='GroupPallet')
      if(GroupPalletBtn){
        GroupPalletBtn.onClick = function(){
          let rows  = this.$refs.table.getSelected();
          if(rows.length ==0 || rows.length>1) return ElMessage.warning('请选择一条单据');
          const targetRow = rows[0];
          this.$emit('openPalletDialog', targetRow.orderNo);
        }
      }
    },
    onInited() {
      //框架初始化配置后
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/allocateoutboundOrder.js
@@ -4,7 +4,7 @@
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'
import gridBody from './extend/newAllocateOrderDetail.vue'
let extension = {
    components: {
      //查询界面扩展组件
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/newAllocateOrderDetail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,797 @@
<template>
  <div>
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="75%"
      :padding="15"
      title="单据明细信息"
    >
      <div class="box-head">
        <el-alert :closable="false" style="width: 100%">
          <el-row>
            <el-col :span="16">
              <span>已选中 {{ selection.length }} é¡¹</span>
            </el-col>
            <el-col :span="8">
          <!--     <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px"
                @click="lockstocks"
                >锁定库存</el-link> -->
              <el-link
                type="primary"
                size="small"
                    v-if="isBatch === 0"
                style="float: right; height: 20px"
                @click="handleOpenPicking"
                >拣选</el-link>
                        <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                  v-if="isBatch === 1"
                @click="handleOpenBatchPicking"
                >分批拣选</el-link>
              <el-link
                type="primary"
                size="small"
                   v-if="isBatch === 0"
                style="float: right; height: 20px; margin-right: 10px"
                @click="outbound"
                >直接出库</el-link
              >
               <el-link
                type="primary"
                size="small"
                 v-if="isBatch === 1"
                style="float: right; height: 20px; margin-right: 10px"
                @click="outboundbatch"
                >分批出库</el-link
              >
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="getData"
                >刷新</el-link
              ></el-col
            >
          </el-row>
        </el-alert>
      </div>
      <div class="box-table" style="margin-top: 1%">
        <el-table
          ref="singleTable"
          :data="tableData"
          style="width: 100%; height: 100%"
          highlight-current-row
          @current-change="handleCurrentChange"
          height="500px"
          @row-click="handleRowClick"
          @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" width="55"> </el-table-column>
          <el-table-column
            label="序号"
            type="index"
            fixed="left"
            width="55"
            align="center"
          ></el-table-column>
          <el-table-column
            v-for="(item, index) in tableColumns.filter((x) => !x.hidden)"
            :key="index"
            :prop="item.prop"
            :label="item.title"
            :width="item.width"
            align="center"
          >
            <template #default="scoped">
              <div v-if="item.type == 'icon'">
                <el-tooltip
                  class="item"
                  effect="dark"
                  :content="item.title"
                  placement="bottom"
                  ><el-link
                    type="primary"
                    :disabled="getButtonEnable(item.prop, scoped.row)"
                    @click="tableButtonClick(scoped.row, item)"
                    ><i :class="item.icon" style="font-size: 22px"></i></el-link
                ></el-tooltip>
              </div>
              <div v-else-if="item.type == 'tag'">
                <el-tag size="small">
                  {{ getDictionary(scoped.row, item) }}
                </el-tag>
              </div>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </vol-box>
    <stock-select ref="child" @parentCall="parentCall"></stock-select>
    <selected-stock
      ref="selectedStock"
      @parentCall="parentCall"
    ></selected-stock>
    <NoStockOut ref="NoStockOut" @parentCall="parentCall"></NoStockOut>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import VolForm from "@/components/basic/VolForm.vue";
import StockSelect from "./StockSelect.vue";
import SelectedStock from "./SelectedStock.vue";
import NoStockOut from "./NoStockOut.vue";
import { h,createVNode, render,reactive  } from 'vue';
import { ElDialog , ElForm, ElFormItem, ElSelect,ElOption, ElButton, ElInput, ElMessage } from 'element-plus';
import { th } from 'element-plus/es/locale';
export default {
  components: { VolBox, VolForm, StockSelect, SelectedStock,NoStockOut},
  data() {
    return {
      row: null,
      isBatch :0,
      showDetialBox: false,
      flag: false,
      currentRow: null,
      selection: [],
      tableData: [],
      tableColumns: [
        {
          prop: "id",
          title: "Id",
          type: "int",
          width: 90,
          hidden: true,
        },
        {
          prop: "orderId",
          title: "出库单主键",
          type: "string",
          width: 90,
          hidden: true,
        },
        {
          prop: "materielCode",
          title: "物料编号",
          type: "string",
          width: 150,
        },
        {
          prop: "materielName",
          title: "物料名称",
          type: "string",
          width: 150,
        },
        {
          prop: "batchNo",
          title: "批次号",
          type: "string",
          width: 90,
        },
        {
          prop: "supplyCode",
          title: "供应商编号",
          type: "string",
          width: 150,
        },
        {
          prop: "orderQuantity",
          title: "单据数量",
          type: "string",
          width: 90,
        },
        {
          prop: "lockQuantity",
          title: "锁定数量",
          type: "int",
          width: 90,
        },
        {
          prop: "overOutQuantity",
          title: "已出数量",
          type: "string",
          width: 90,
        },
        {
          prop: "overOutQuantity",
          title: "挪料数量",
          type: "string",
          width: 90,
        },
        {
          prop: "unit",
          title: "单位",
          type: "string",
          width: 90,
        },
        {
          prop: "orderDetailStatus",
          title: "订单明细状态",
          type: "tag",
          width: 90,
          bindKey: "orderDetailStatusEnum",
        },
        {
          prop: "assignStock",
          title: "指定库存",
          type: "icon",
          width: 90,
          hidden:true,
          icon: "el-icon-s-grid",
        },
        {
          prop: "viewDetail",
          title: "出库详细",
          type: "icon",
          width: 90,
          icon: "el-icon-s-operation",
        },
        {
          prop: "creater",
          title: "创建人",
          type: "string",
          width: 90,
        },
        {
          prop: "createDate",
          title: "创建时间",
          type: "datetime",
          width: 160,
        },
        {
          prop: "modifier",
          title: "修改人",
          type: "string",
          width: 100,
        },
        {
          prop: "modifyDate",
          title: "修改时间",
          type: "datetime",
          width: 160,
        },
        {
          prop: "remark",
          title: "备注",
          type: "string",
        },
      ],
      paginations: {
        sort: "id",
        order: "desc",
        Foots: "",
        total: 0,
        // 2020.08.29增加自定义分页条大小
        sizes: [30, 60, 100, 120],
        size: 30, // é»˜è®¤åˆ†é¡µå¤§å°
        Wheres: [],
        page: 1,
        rows: 30,
      },
      searchFormOptions: [
        [
          {
            title: "单据编号",
            field: "allocation_code",
            type: "like",
          },
          {
            title: "单据类型",
            field: "allocation_type",
            type: "select",
            dataKey: "OrderType",
            data: [],
          },
          {
            title: "单据状态",
            field: "allocation_state",
            type: "select",
            dataKey: "OrderState",
            data: [],
          },
        ],
      ],
      searchFormFields: {
        allocation_code: "",
        allocation_type: "",
        allocation_state: "",
      },
      dictionaryList: null,
    };
  },
  methods: {
    open(row) {
      this.row = row;
      this.showDetialBox = true;
      console.log(this.row);
      this.isBatch = row.isBatch;
      this.getDictionaryData();
      this.getData();
    },
    getData() {
      var wheres = [{ name: "orderId", value: this.row.id }];
      var param = {
        page: this.paginations.page,
        rows: this.paginations.rows,
        sort: this.paginations.sort,
        order: this.paginations.order,
        wheres: JSON.stringify(wheres), // æŸ¥è¯¢æ¡ä»¶ï¼Œæ ¼å¼ä¸º[{ name: "字段", value: "xx" }]
      };
      this.http
        .post("api/AllocateOrderDetail/GetDetailPage", param, "查询中")
        .then((x) => {
          this.tableData = x.rows;
        });
    },
    tableButtonClick(row, column) {
      if (column.prop == "assignStock") {
        this.$refs.child.open(row);
      } else if (column.prop == "NoStockOut") {
        this.$refs.NoStockOut.open(row);
      }else{
          //点击打开出库详情
        this.$refs.selectedStock.open(row);
      }
    },
    lockstocks() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
      }
      var keys = this.selection.map((item) => item.id); // èŽ·å–é€‰ä¸­è¡Œçš„id
      this.http
        .post("api/OutboundOrderDetail/LockOutboundStocks", keys, "数据处理中")
        .then((x) => {
          if (!x.status) return this.$message.error(x.message);
          this.$message.success("操作成功");
          this.showDetialBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.getData();
          });
        });
    },
    // æ‰“开拣选页面
   handleOpenPicking() {
      this.$router.push({
        path: '/outbound/picking',
        query: { orderId: this.row.id ,orderNo:this.row.orderNo}
      })
    },
    handleOpenBatchPicking() {
      this.$router.push({
        path: '/outbound/batchpicking',
        query: { orderId: this.row.id ,orderNo:this.row.orderNo}})
    },
    outbound() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
      }
      const platformOptions = [{label:'站台2',value:'2-1'},{label:'站台3',value:'3-1'}];
      const mountNode = document.createElement('div');
      document.body.appendChild(mountNode);
      // 2. è¡¨å•数据(默认选中站台3)
      const formData = reactive({
        selectedPlatform: platformOptions[0].value // é»˜è®¤ç»‘定「站台3」的value
      });
      // 3. åŠ¨æ€åˆ›å»ºå¼¹çª—ç»„ä»¶
      const vnode = createVNode(ElDialog, {
        title: '出库操作 - é€‰æ‹©å‡ºåº“站台',
        width: '500px',
        modelValue: true,
        appendToBody: true,
        'onUpdate:modelValue': (isVisible) => {
          if (!isVisible) {
            render(null, mountNode);
            document.body.removeChild(mountNode);
          }
        },
        style: {
          padding: '20px 0',
          borderRadius: '8px'
        }
      }, {
        default: () => h(ElForm, {
          model: formData,
          rules: {
            selectedPlatform: [
              { required: true, message: '请选择出库站台', trigger: 'change' }
            ]
          },
          ref: 'outboundForm',
          labelWidth: '100px',
          style: {
            padding: '0 30px'
          }
        }, [
          // å‡ºåº“站台选择项(核心表单项)
          h(ElFormItem, {
            label: '出库站台',
            prop: 'selectedPlatform',
            style: {
              marginBottom: '24px'
            }
          }, [
            h(ElSelect, {
              placeholder: '请选择出库站台(3-12)',
              modelValue: formData.selectedPlatform,
              'onUpdate:modelValue': (val) => {
                formData.selectedPlatform = val;
              },
              style: {
                width: '100%',
                height: '40px',
                borderRadius: '4px',
                borderColor: '#dcdfe6'
              }
            }, platformOptions.map(platform =>
              h(ElOption, { label: platform.label, value: platform.value })
            ))
          ]),
          // åº•部按钮区
          h('div', {
            style: {
              textAlign: 'right',
              marginTop: '8px',
              paddingRight: '4px'
            }
          }, [
            h(ElButton, {
              type: 'text',
              onClick: () => {
                render(null, mountNode);
                document.body.removeChild(mountNode);
                ElMessage.info('取消出库操作');
              },
              style: {
                marginRight: '8px',
                color: '#606266'
              }
            }, '取消'),
            h(ElButton, {
              type: 'primary',
              onClick: async () => {
                const formRef = vnode.component.refs.outboundForm;
                try {
                  // è¡¨å•校验
                  await formRef.validate();
                } catch (err) {
                  return;
                }
                // 4. æž„造请求参数(选中单据ID + é€‰æ‹©çš„出库站台)
                const keys = this.selection.map((item) => item.id);
                const requestParams = {
                  taskIds: keys,
                  outboundPlatform: formData.selectedPlatform // å‡ºåº“站台
                };
                // 5. è°ƒç”¨å‡ºåº“接口
                this.http
                  .post("api/Task/GenerateOutboundTasks", requestParams, "数据处理中")
                  .then((x) => {
                    if (!x.status) return ElMessage.error(x.message);
                    ElMessage.success("操作成功");
                    this.showDetialBox = false; // å…³é—­è¯¦æƒ…框
                    this.$emit("parentCall", ($vue) => {
                      $vue.getData(); // é€šçŸ¥çˆ¶ç»„件刷新
                    });
                    // å…³é—­å¼¹çª—
                    render(null, mountNode);
                    document.body.removeChild(mountNode);
                  })
                  .catch(() => {
                    ElMessage.error('请求失败,请稍后重试');
                  });
              },
              style: {
                borderRadius: '4px',
                padding: '8px 20px'
              }
            }, '确定出库')
          ])
        ])
      });
      // ç»‘定app上下文,确保El组件正常工作
      vnode.appContext = this.$.appContext;
      render(vnode, mountNode);
    },
    outboundbatch() {
  if (this.selection.length === 0) {
    return this.$message.error("请选择单据明细");
  }
    if (this.selection.length>1) {
    return this.$message.error("只能选择一条单据明细进行分批出库");
  }
  const platformOptions = [{label:'站台2',value:'2-1'},{label:'站台3',value:'3-1'}];
  const mountNode = document.createElement('div');
  document.body.appendChild(mountNode);
  // 2. è¡¨å•数据(默认选中站台3,新增小数字段)
  const formData = reactive({
    selectedPlatform: platformOptions[0].value, // é»˜è®¤ç»‘定「站台3」的value
    outboundDecimal: '' // æ–°å¢žï¼šå°æ•°è¾“入框字段
  });
  // 3. åŠ¨æ€åˆ›å»ºå¼¹çª—ç»„ä»¶
  const vnode = createVNode(ElDialog, {
    title: '出库操作 - é€‰æ‹©å‡ºåº“站台',
    width: '500px',
    modelValue: true,
    appendToBody: true,
    'onUpdate:modelValue': (isVisible) => {
      if (!isVisible) {
        render(null, mountNode);
        document.body.removeChild(mountNode);
      }
    },
    style: {
      padding: '20px 0',
      borderRadius: '8px'
    }
  }, {
    default: () => h(ElForm, {
      model: formData,
      rules: {
        selectedPlatform: [
          { required: true, message: '请选择出库站台', trigger: 'change' }
        ],
        // æ–°å¢žï¼šå°æ•°å­—段验证规则(必填 + æœ‰æ•ˆå°æ•°æ ¼å¼ï¼‰
        outboundDecimal: [
          { required: true, message: '请输入小数数值', trigger: 'blur' },
          {
            validator: (rule, value, callback) => {
              // éªŒè¯è§„则:正数、支持小数点后最多2位(可根据需求调整小数位数)
              const decimalReg = /^(([1-9]\d*)|0)(\.\d{1,2})?$/;
              if (value && !decimalReg.test(value)) {
                callback(new Error('请输入有效的小数(正数,最多2位小数)'));
              } else {
                callback();
              }
            },
            trigger: 'blur'
          }
        ]
      },
      ref: 'outboundForm',
      labelWidth: '100px',
      style: {
        padding: '0 30px'
      }
    }, [
      // å‡ºåº“站台选择项(核心表单项)
      h(ElFormItem, {
        label: '出库站台',
        prop: 'selectedPlatform',
        style: {
          marginBottom: '24px'
        }
      }, [
        h(ElSelect, {
          placeholder: '请选择出库站台(3-12)',
          modelValue: formData.selectedPlatform,
          'onUpdate:modelValue': (val) => {
            formData.selectedPlatform = val;
          },
          style: {
            width: '100%',
            height: '40px',
            borderRadius: '4px',
            borderColor: '#dcdfe6'
          }
        }, platformOptions.map(platform =>
          h(ElOption, { label: platform.label, value: platform.value })
        ))
      ]),
      // æ–°å¢žï¼šå°æ•°è¾“入框表单项
      h(ElFormItem, {
        label: '出库数', // å¯æ ¹æ®ä¸šåŠ¡éœ€æ±‚ä¿®æ”¹æ ‡ç­¾åï¼ˆå¦‚â€œå‡ºåº“æ•°é‡â€â€œé‡é‡â€ç­‰ï¼‰
        prop: 'outboundDecimal',
        style: {
          marginBottom: '24px'
        }
      }, [
        h(ElInput, {
          type: 'number', // æ•°å­—类型,原生支持小数输入
          placeholder: '请输入小数数值(最多2位小数)',
          modelValue: formData.outboundDecimal,
          'onUpdate:modelValue': (val) => {
            formData.outboundDecimal = val;
          },
          style: {
            width: '100%',
            height: '40px',
            borderRadius: '4px',
            borderColor: '#dcdfe6'
          },
          step: '0.01', // æ­¥é•¿0.01,点击上下箭头时按0.01增减
          precision: 2, // é™åˆ¶æœ€å¤šè¾“å…¥2位小数(Element Plus属性)
          min: 0.01, // å¯é€‰ï¼šé™åˆ¶æœ€å°å€¼ä¸º0.01,避免输入0或负数
        })
      ]),
      // åº•部按钮区
      h('div', {
        style: {
          textAlign: 'right',
          marginTop: '8px',
          paddingRight: '4px'
        }
      }, [
        h(ElButton, {
          type: 'text',
          onClick: () => {
            render(null, mountNode);
            document.body.removeChild(mountNode);
            ElMessage.info('取消分批出库操作');
          },
          style: {
            marginRight: '8px',
            color: '#606266'
          }
        }, '取消'),
        h(ElButton, {
          type: 'primary',
          onClick: async () => {
            const formRef = vnode.component.refs.outboundForm;
            try {
              // è¡¨å•校验(会同时校验出库站台和小数字段)
              await formRef.validate();
            } catch (err) {
              return;
            }
console.log(this.selection);
            // 4. æž„造请求参数(新增小数字段)
            const keys = this.selection.map((item) => item.id);
            const requestParams = {
              orderDetailId: keys[0], // åˆ†æ‰¹å‡ºåº“仅支持单条明细
              outboundPlatform: formData.selectedPlatform, // å‡ºåº“站台
              batchQuantity: formData.outboundDecimal // æ–°å¢žï¼šå°æ•°å­—段传给后端
            };
            // 5. è°ƒç”¨å‡ºåº“接口
            this.http
              .post("api/Task/GenerateOutboundBatchTasks", requestParams, "数据处理中")
              .then((x) => {
                if (!x.status) return ElMessage.error(x.message);
                ElMessage.success("操作成功");
                this.showDetialBox = false; // å…³é—­è¯¦æƒ…框
                this.$emit("parentCall", ($vue) => {
                  $vue.getData(); // é€šçŸ¥çˆ¶ç»„件刷新
                });
                // å…³é—­å¼¹çª—
                render(null, mountNode);
                document.body.removeChild(mountNode);
              })
              .catch(() => {
                ElMessage.error('请求失败,请稍后重试');
              });
          },
          style: {
            borderRadius: '4px',
            padding: '8px 20px'
          }
        }, '确定分批出库')
      ])
    ])
  });
  // ç»‘定app上下文,确保El组件正常工作
  vnode.appContext = this.$.appContext;
  render(vnode, mountNode);
},
    setCurrent(row) {
      this.$refs.singleTable.setCurrentRow(row);
    },
    handleCurrentChange(val) {
      this.currentRow = val;
    },
    getButtonEnable(propName, row) {
      if (propName == "assignStock") {
        if (
          row.orderDetailStatus !== 0 &&
          row.orderDetailStatus !== 60 &&
          row.orderDetailStatus !== 70 &&
          row.orderDetailStatus !== 80
        ) {
          return true;
        } else {
          return false;
        }
      }
      return false;
    },
    parentCall(fun) {
      if (typeof fun != "function") {
        return console.log("扩展组件需要传入一个回调方法才能获取父级Vue对象");
      }
      fun(this);
    },
    handleRowClick(row) {
      this.$refs.singleTable.toggleRowSelection(row);
    },
    handleSelectionChange(val) {
      this.selection = val;
    },
    getDictionaryData() {
      if (this.dictionaryList) {
        return;
      }
      var param = [];
      this.tableColumns.forEach((x) => {
        if (x.type == "tag" && x.bindKey != "") {
          param.push(x.bindKey);
        }
      });
      this.http
        .post("api/Sys_Dictionary/GetVueDictionary", param, "查询中")
        .then((x) => {
          if (x.length > 0) {
            this.dictionaryList = x;
          }
        });
    },
    getDictionary(row, column) {
      if (this.dictionaryList) {
        var item = this.dictionaryList.find((x) => x.dicNo == column.bindKey);
        if (item) {
          var dicItem = item.data.find((x) => x.key == row[column.prop]);
          console.log(dicItem);
          if (dicItem) {
            return dicItem.value;
          } else {
            return row[column.prop];
          }
        } else {
          return row[column.prop];
        }
      }
    },
  },
};
</script>
<style scoped>
.text-button {
  border: 0px;
}
</style>
<style>
.text-button:hover {
  background-color: #f0f9eb !important;
}
.el-table .warning-row {
  background: oldlace;
}
.box-table .el-table tbody tr:hover > td {
  background-color: #d8e0d4 !important;
  /* color: #ffffff; */
}
.box-table .el-table tbody tr.current-row > td {
  background-color: #f0f9eb !important;
  /* color: #ffffff; */
}
.el-table .success-row {
  background: #f0f9eb;
}
.box-table .el-table {
  border: 1px solid #ebeef5;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js
@@ -383,8 +383,6 @@
    searchBefore(param) {
      //界面查询前,可以给param.wheres添加查询参数
      //返回false,则不会执行查询
      return true;
    },
    searchAfter(result) {
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/stock/stockInfoDetailByMaterielSum.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
let extension = {
    components: {
      //查询界面扩展组件
      gridHeader: '',
      gridBody: '',
      gridFooter: '',
      //新建、编辑弹出框扩展组件
      modelHeader: '',
      modelBody: '',
      modelFooter: ''
    },
    tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
    buttons: { view: [], box: [], detail: [] }, //扩展的按钮
    methods: {
       //下面这些方法可以保留也可以删除
      onInit() {
        console.log(this)
      },
      onInited() {
        //框架初始化配置后
        //如果要配置明细表,在此方法操作
        //this.detailOptions.columns.forEach(column=>{ });
      },
      searchBefore(param) {
        //界面查询前,可以给param.wheres添加查询参数
        //返回false,则不会执行查询
        return true;
      },
      searchAfter(result) {
        //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
        return true;
      },
      addBefore(formData) {
        //新建保存前formData为对象,包括明细表,可以给给表单设置值,自己输出看formData的值
        return true;
      },
      updateBefore(formData) {
        //编辑保存前formData为对象,包括明细表、删除行的Id
        return true;
      },
      rowClick({ row, column, event }) {
        //查询界面点击行事件
        this.$refs.table.$refs.table.toggleRowSelection(row); //单击行时选中当前行;
      },
      modelOpenAfter(row) {
        //点击编辑、新建按钮弹出框后,可以在此处写逻辑,如,从后台获取数据
        //(1)判断是编辑还是新建操作: this.currentAction=='Add';
        //(2)给弹出框设置默认值
        //(3)this.editFormFields.字段='xxx';
        //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
        //看不懂就把输出看:console.log(this.editFormOptions)
      }
    }
  };
  export default extension;
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -217,6 +217,11 @@
    path: '/reCheckOrder',
    name: 'Dt_ReCheckOrder',
    component: () => import('@/views/check/ReCheckOrder.vue')
  },
  {
    path: '/StockDetailByMateriel',
    name: 'StockDetailByMateriel',
    component: () => import('@/views/stock/stockInfoDetailByMaterielSum.vue')
  }
]
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/BatchPickingConfirm.vue
@@ -27,8 +27,8 @@
            @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="warning" @click="openSplitDialog">拆包</el-button>
          <el-button type="info" @click="openRevertSplitDialog">撤销拆包</el-button>  -->
          <el-button type="info" @click="handleEmptyPallet">取空箱</el-button>
          <el-button type="primary" @click="openBatchReturnDialog">回库</el-button>
        </div>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/stock/stockInfoDetailByMaterielSum.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
<template>
  <view-grid ref="grid" :columns="columns" :detail="detail" :editFormFields="editFormFields"
    :editFormOptions="editFormOptions" :searchFormFields="searchFormFields" :searchFormOptions="searchFormOptions"
    :table="table" :extend="extend">
  </view-grid>
</template>
<script>
import extend from "@/extension/stock/stockInfoDetailByMaterielSum.js";
import { ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const table = ref({
      key: "id",
      footer: "Foots",
      cnName: "库存汇总",
      name: "StockDetailByMateriel",
      url: "/StockDetailByMateriel/",
      sortName: "id",
    });
    const editFormFields = ref({
      deviceCode: "",
      deviceName: "",
      deviceType: "",
      deviceStatus: "",
      deviceIp: "",
      devicePort: "",
      devicePlcType: "",
      deviceRemark: "",
    });
    const editFormOptions = ref([
      [
        {
          title: "物料编号",
          required: true,
          field: "materielCode",
          type: "string",
        },
        {
          title: "单据编号",
          required: true,
          field: "materielName",
          type: "string",
        },
        {
          title: "批次号",
          required: true,
          field: "orderNo",
          type: "string",
        },
        {
          title: "序列号",
          required: true,
          field: "serialNumber",
          type: "string",
        },
      ],
    ]);
    const searchFormFields = ref({
      materielCode: "",
      materielName: "",
      orderNo: "",
    });
    const searchFormOptions = ref([
      [
        { title: "物料编号", field: "materielCode" ,type:'like'},
        { title: "物料名称", field: "materielName" ,type:'like'},
        { title: "单据编号", field: "orderNo" ,type:'like'},
        { title: "托盘号", field: "palletCode" ,type:'like'},
      ],
    ]);
    const columns = ref([
      {
        field: "id",
        title: "Id",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "materielCode",
        title: "物料编号",
        type: "string",
        width: 150,
        align: "left",
      },
      {
        field: "materielName",
        title: "物料名称",
        type: "string",
        width: 150,
        align: "left",
      },
      {
        field: "batchNo",
        title: "批次号",
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "supplyCode",
        title: "供应商编号",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "warehouseCode",
        title: "仓库号",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "stockQuantity",
        title: "库存数量",
        type: "string",
        width: 200,
        align: "left",
      },
      {
        field: "outboundQuantity",
        title: "出库数量",
        type: "string",
        width: 180,
        align: "left",
      },
      {
        field: "status",
        title: "库存明细状态",
        type: "string",
        width: 120,
        align: "left",
        bind: { key: "stockStatusEmun", data: [] },
      },
      {
        field: "creater",
        title: "创建人",
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "createDate",
        title: "创建时间",
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "modifier",
        title: "修改人",
        type: "string",
        width: 100,
        align: "left",
      },
      {
        field: "modifyDate",
        title: "修改时间",
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "remark",
        title: "备注",
        type: "string",
        width: 100,
        align: "left",
      },
    ]);
    const detail = ref({
      cnName: "#detailCnName",
      table: "",
      columns: [],
      sortName: "",
    });
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
    };
  },
});
</script>
ÏîÄ¿´úÂë/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_AllocateService/AllocateDetailService.cs
@@ -15,37 +15,72 @@
{
    public class AllocateDetailService : ServiceBase<Dt_AllocateOrderDetail, IRepository<Dt_AllocateOrderDetail>>, IAllocateDetailService
    {
        public AllocateDetailService(IRepository<Dt_AllocateOrderDetail> BaseDal) : base(BaseDal)
        public readonly IRepository<Dt_AllocateOrder> _allocateOrderRepository;
        public AllocateDetailService(IRepository<Dt_AllocateOrderDetail> BaseDal, IRepository<Dt_AllocateOrder> allocateOrderRepository) : base(BaseDal)
        {
            _allocateOrderRepository = allocateOrderRepository;
        }
        IRepository<Dt_AllocateOrderDetail> IAllocateDetailService.Repository => BaseDal;
        public override PageGridData<Dt_AllocateOrderDetail> GetPageData(PageDataOptions options)
        public override PageGridData<Dt_OutboundOrderDetail> GetDetailPage(PageDataOptions options)
        {
            string wheres = ValidatePageOptions(options);
            //获取排序字段
            Dictionary<string, SqlSugar.OrderByType> orderbyDic = GetPageDataSort(options, TProperties);
            List<OrderByModel> orderByModels = new List<OrderByModel>();
            foreach (var item in orderbyDic)
            {
                OrderByModel orderByModel = new()
                {
                    FieldName = item.Key,
                    OrderByType = item.Value
                };
                orderByModels.Add(orderByModel);
            }
            ISugarQueryable<Dt_AllocateOrderDetail> sugarQueryable1 = BaseDal.Db.Queryable<Dt_AllocateOrderDetail>();
            int totalCount = 0;
            List<SearchParameters> searchParametersList = new List<SearchParameters>();
            if (!string.IsNullOrEmpty(options.Wheres))
            {
                List<SearchParameters> searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
                int totalCount = 0;
                if (searchParametersList.Count > 0)
                try
                {
                    {
                        SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_AllocateOrderDetail.OrderId).FirstLetterToLower());
                        if (searchParameters != null)
                        {
                            sugarQueryable1 = sugarQueryable1.Where(x => x.OrderId == searchParameters.Value.ObjToInt());
                            var dataList = sugarQueryable1.ToPageList(options.Page, options.Rows, ref totalCount);
                            return new PageGridData<Dt_AllocateOrderDetail>(totalCount, dataList);
                        }
                    }
                    searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
                    options.Filter = searchParametersList;
                }
                catch { }
            }
            return new PageGridData<Dt_AllocateOrderDetail>();
            //var data = BaseDal.Db.Queryable<Dt_AllocateOrderDetail>()
            //    .WhereIF(!wheres.IsNullOrEmpty(), wheres)
            //    .OrderBy(orderByModels)
            //    .ToPageList(options.Page, options.Rows, ref totalCount);
            //Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.Id == (int)options.Value);
            //Dt_InboundOrder _InboundOrder = SqlSugarHelper.DbWMS.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == allocateOrder.UpperOrderNo).First();
            //var details = _inboundOrderDetailRepository.QueryData(x => x.OrderId == _InboundOrder.Id );
            //foreach (var item in data)
            //{
            //    var detail = details.Where(x => x.MaterielCode == item.MaterielCode).FirstOrDefault();
            //    if (detail != null)
            //    {
            //        item.OrderQuantity = detail.OrderQuantity;
            //        item.ReceiptQuantity = detail.ReceiptQuantity;
            //        item.OverInQuantity = detail.OverInQuantity;
            //        item.OrderDetailStatus = detail.OrderDetailStatus;
            //    }
            //}
            //return new PageGridData<Dt_AllocateOrderDetail>(totalCount, data);
            Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.Id == options.Filter.FirstOrDefault().Value.ObjToInt());
            Dt_OutboundOrder _InboundOrder = SqlSugarHelper.DbWMS.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == allocateOrder.UpperOrderNo).First();
            var data = BaseDal.Db.Queryable<Dt_OutboundOrderDetail>()
                .WhereIF(!_InboundOrder.IsNullOrEmpty(), x => x.OrderId == _InboundOrder.Id)
                .OrderBy(orderByModels)
                .ToPageList(options.Page, options.Rows, ref totalCount);
            return new PageGridData<Dt_OutboundOrderDetail>(totalCount, data);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/ErpApiService.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
@@ -355,11 +356,13 @@
                json = JsonConvert.SerializeObject(request);
            }
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var _client = _httpClientFactory.CreateClient("ERPUrl");
            _client.DefaultRequestHeaders.Clear();
            string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            string base64Header = Convert.ToBase64String(Encoding.UTF8.GetBytes(timestamp));
            _client.DefaultRequestHeaders.Add("X-Custom-Header", base64Header);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs
@@ -245,147 +245,145 @@
        public async Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout)
        {
            try
            {             // 1. ã€å†…存锁抢占】
                //if (MemoryLockManager.TryAcquireLock(orderNos[0]))
                //{
                        if (inout == 1)
            {
                if (inout == 1)
                {
                    foreach (var orderNo in orderNos)
                    {
                        try
                        {
                            foreach (var orderNo in orderNos)
                            var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
                                            .Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
                                            .ToList();
                            var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList();
                            var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList();
                            if (unreports != null && !unreports.Any())
                            {
                                try
                                return WebResponseContent.Instance.Error("没有需要回传的数据");
                            }
                            foreach (var item in unreports)
                            {
                                var lists = _stockInfoDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
                                if (lists.Any())
                                {
                                    var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
                                                    .Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
                                                    .ToList();
                                    var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList();
                                    var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList();
                                    if (unreports != null && !unreports.Any())
                                    var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
                                    if (inboundOrder != null)
                                    {
                                        return WebResponseContent.Instance.Error("没有需要回传的数据");
                                    }
                                    foreach (var item in unreports)
                                    {
                                        var lists = _stockInfoDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
                                        if (lists.Any())
                                        if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
                                        {
                                            var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
                                            if (inboundOrder != null)
                                            var allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                                            var allocatefeedmodel = new AllocateDto
                                            {
                                                if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
                                                {
                                                    var allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                                                    var allocatefeedmodel = new AllocateDto
                                                    {
                                                        ReqCode = Guid.NewGuid().ToString(),
                                                        ReqTime = DateTime.Now.ToString(),
                                                        BusinessType = "2",
                                                        FactoryArea = inboundOrder.FactoryArea,
                                                        OperationType = 1,
                                                        Operator = inboundOrder.Operator,
                                                        OrderNo = inboundOrder.UpperOrderNo,
                                                        fromWarehouse = allocate?.FromWarehouse ?? "",
                                                        toWarehouse = allocate?.ToWarehouse ?? "",
                                                        Details = new List<AllocateDtoDetail>()
                                                ReqCode = Guid.NewGuid().ToString(),
                                                ReqTime = DateTime.Now.ToString(),
                                                BusinessType = "2",
                                                FactoryArea = inboundOrder.FactoryArea,
                                                OperationType = 1,
                                                Operator = inboundOrder.Operator,
                                                OrderNo = inboundOrder.UpperOrderNo,
                                                fromWarehouse = allocate?.FromWarehouse ?? "",
                                                toWarehouse = allocate?.ToWarehouse ?? "",
                                                Details = new List<AllocateDtoDetail>()
                                                    };
                                            };
                                                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                                       .Select(group => new AllocateDtoDetail
                                                       {
                                                           MaterialCode = group.Key.MaterielCode,
                                                           LineNo = group.Key.InboundOrderRowNo,
                                                           WarehouseCode = group.Key.WarehouseCode,
                                                           Qty = group.Sum(x => x.BarcodeQty),
                                                           Unit = group.Key.BarcodeUnit,
                                                           Barcodes = group.Select(row => new BarcodeInfo
                                                           {
                                                               Barcode = row.Barcode,
                                                               Qty = row.BarcodeQty,
                                                               BatchNo = row.BatchNo,
                                                               SupplyCode = row.SupplyCode,
                                                               Unit = row.BarcodeUnit
                                                           }).ToList()
                                                       }).ToList();
                                                    allocatefeedmodel.Details = groupedData;
                                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                               .Select(group => new AllocateDtoDetail
                                               {
                                                   MaterialCode = group.Key.MaterielCode,
                                                   LineNo = group.Key.InboundOrderRowNo,
                                                   WarehouseCode = group.Key.WarehouseCode,
                                                   Qty = group.Sum(x => x.BarcodeQty),
                                                   Unit = group.Key.BarcodeUnit,
                                                   Barcodes = group.Select(row => new BarcodeInfo
                                                   {
                                                       Barcode = row.Barcode,
                                                       Qty = row.BarcodeQty,
                                                       BatchNo = row.BatchNo,
                                                       SupplyCode = row.SupplyCode,
                                                       Unit = row.BarcodeUnit
                                                   }).ToList()
                                               }).ToList();
                                            allocatefeedmodel.Details = groupedData;
                                                    var result = await FeedbackAllocate(allocatefeedmodel);
                                                    if (result != null && result.code == 200)
                                                    {
                                                        _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                                    }
                                                }
                                                else
                                                {
                                                    var feedmodel = new FeedbackInboundRequestModel
                                                    {
                                                        reqCode = Guid.NewGuid().ToString(),
                                                        reqTime = DateTime.Now.ToString(),
                                                        business_type = inboundOrder.BusinessType,
                                                        factoryArea = inboundOrder.FactoryArea,
                                                        operationType = 1,
                                                        Operator = inboundOrder.Operator,
                                                        orderNo = inboundOrder.UpperOrderNo,
                                                        status = inboundOrder.OrderStatus,
                                                        details = new List<FeedbackInboundDetailsModel>()
                                            var result = await FeedbackAllocate(allocatefeedmodel);
                                            if (result != null && result.code == 200)
                                            {
                                                _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                            }
                                        }
                                        else
                                        {
                                            var feedmodel = new FeedbackInboundRequestModel
                                            {
                                                reqCode = Guid.NewGuid().ToString(),
                                                reqTime = DateTime.Now.ToString(),
                                                business_type = inboundOrder.BusinessType,
                                                factoryArea = inboundOrder.FactoryArea,
                                                operationType = 1,
                                                Operator = inboundOrder.Operator,
                                                orderNo = inboundOrder.UpperOrderNo,
                                                status = inboundOrder.OrderStatus,
                                                details = new List<FeedbackInboundDetailsModel>()
                                                    };
                                            };
                                                    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,
                                                           supplyCode = group.Key.SupplyCode,
                                                           batchNo = group.Key.BatchNo,
                                                           lineNo = group.Key.InboundOrderRowNo,
                                                           qty = group.Sum(x => x.BarcodeQty),
                                                           // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                                           warehouseCode = group.Key.WarehouseCode,
                                                           unit = group.Key.BarcodeUnit,
                                                           barcodes = group.Select(row => new FeedbackBarcodesModel
                                                           {
                                                               barcode = row.Barcode,
                                                               qty = row.BarcodeQty
                                                           }).ToList()
                                                       }).ToList();
                                                    feedmodel.details = groupedData;
                                                    var result = await FeedbackInbound(feedmodel);
                                                    if (result != null && result.code == 200)
                                                    {
                                                        _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                                    }
                                                }
                                            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,
                                                   supplyCode = group.Key.SupplyCode,
                                                   batchNo = group.Key.BatchNo,
                                                   lineNo = group.Key.InboundOrderRowNo,
                                                   qty = group.Sum(x => x.BarcodeQty),
                                                   // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                                   warehouseCode = group.Key.WarehouseCode,
                                                   unit = group.Key.BarcodeUnit,
                                                   barcodes = group.Select(row => new FeedbackBarcodesModel
                                                   {
                                                       barcode = row.Barcode,
                                                       qty = row.BarcodeQty
                                                   }).ToList()
                                               }).ToList();
                                            feedmodel.details = groupedData;
                                            var result = await FeedbackInbound(feedmodel);
                                            if (result != null && result.code == 200)
                                            {
                                                _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                            }
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes å›žå†™MES失败:  " + ex.Message);
                                    return WebResponseContent.Instance.Error(ex.Message);
                                }
                            }
                        }
                        else if (inout == 2)
                        catch (Exception ex)
                        {
                            foreach (var orderNo in orderNos)
                            {
                                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
                                if (outboundOrder != null && outboundOrder.IsBatch == 0)
                                {
                                    var result = await HandleOutboundOrderToMESCompletion(outboundOrder, orderNo);
                                    return result;
                                }
                                else if (outboundOrder != null && outboundOrder.IsBatch == 1)
                                {
                                    var result = await HandleOutboundOrderBatchToMESCompletion(outboundOrder, orderNo);
                                    return result;
                                }
                            }
                            _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes å›žå†™MES失败:  " + ex.Message);
                            return WebResponseContent.Instance.Error(ex.Message);
                        }
                    }
                }
                else if (inout == 2)
                {
                    foreach (var orderNo in orderNos)
                    {
                        var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
                        if (outboundOrder != null && outboundOrder.IsBatch == 0)
                        {
                            var result = await HandleOutboundOrderToMESCompletion(outboundOrder, orderNo);
                            return result;
                        }
                        else if (outboundOrder != null && outboundOrder.IsBatch == 1)
                        {
                            var result = await HandleOutboundOrderBatchToMESCompletion(outboundOrder, orderNo);
                            return result;
                        }
                    }
                }
                //}
                //else
                //{
@@ -394,14 +392,14 @@
                //    return WebResponseContent.Instance.Error("WMS正在处理此回传任务,请勿重复操作。");
                //}
            }
            catch(Exception ex)
            catch (Exception ex)
            {
                _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes :  " +ex.Message);
                _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes :  " + ex.Message);
            }
            finally
            {
                // 2. ã€é‡Šæ”¾å†…存锁】无论成功失败,必须释放
               // MemoryLockManager.ReleaseLock(orderNos[0]);
                // MemoryLockManager.ReleaseLock(orderNos[0]);
            }
            return WebResponseContent.Instance.OK();
        }
@@ -913,52 +911,188 @@
    public static class MemoryLockManager
    {
        // å­˜å‚¨èµ„源ID及其对应的锁对象。使用 ConcurrentDictionary ç¡®ä¿å¯¹å­—典操作本身的线程安全。
        private static readonly ConcurrentDictionary<string, object> _resourceLocks = new ConcurrentDictionary<string, object>();
        // å­˜å‚¨èµ„源锁的元数据(锁对象、持有线程、占用时间、超时时间)
        private class LockMetadata
        {
            public object LockObject { get; } = new object();
            public int HoldingThreadId { get; set; } = -1; // æŒæœ‰é”çš„线程ID
            public DateTime AcquireTime { get; set; } // èŽ·å–é”çš„æ—¶é—´
            public TimeSpan Timeout { get; set; } // é”è¶…æ—¶æ—¶é—´
            public bool IsReleased { get; set; } // æ˜¯å¦å·²é‡Šæ”¾
        }
        // å…¨å±€é™æ€é”ï¼šç”¨äºŽä¿æŠ¤ _resourceLocks å­—典中 GetOrAdd æˆ– TryRemove æ—¶çš„竞争
        private static readonly object _globalLocker = new object();
        // èµ„源ID -> é”å…ƒæ•°æ®
        private static readonly ConcurrentDictionary<string, LockMetadata> _resourceLocks = new();
        // å…¨å±€é”ï¼ˆä¿æŠ¤é”å…ƒæ•°æ®çš„创建/删除)
        private static readonly object _globalLocker = new();
        // éšæœºæ•°ç”Ÿæˆå™¨ï¼ˆç”¨äºŽç”Ÿæˆ3-5秒随机超时)
        private static readonly Random _random = new Random();
        /// <summary>
        /// å°è¯•锁定一个资源ID。
        /// å°è¯•锁定资源(带超时自动释放)
        /// </summary>
        /// <param name="resourceId">要锁定的资源ID(例如 InboundRecord ID)</param>
        /// <param name="resourceId">资源ID</param>
        /// <param name="timeoutSeconds">超时时间(默认3-5秒随机)</param>
        /// <returns>是否成功获取锁</returns>
        public static bool TryAcquireLock(string resourceId)
        public static bool TryAcquireLock(string resourceId, int? timeoutSeconds = null)
        {
            object lockObject = null;
            if (string.IsNullOrEmpty(resourceId))
                throw new ArgumentNullException(nameof(resourceId));
            // æ ¸å¿ƒæ€è·¯ï¼šä¸ºæ¯ä¸ªèµ„源创建一个唯一的锁对象
            // ç¡®å®šè¶…时时间(3-5秒随机)
            var timeout = TimeSpan.FromSeconds(timeoutSeconds ?? _random.Next(3, 6));
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            LockMetadata lockMeta = null;
            lock (_globalLocker)
            {
                // å¦‚果资源ID不在字典中,则添加一个新的锁对象
                // å¦åˆ™ï¼Œä½¿ç”¨å·²å­˜åœ¨çš„锁对象
                lockObject = _resourceLocks.GetOrAdd(resourceId, new object());
                // èŽ·å–æˆ–åˆ›å»ºé”å…ƒæ•°æ®
                lockMeta = _resourceLocks.GetOrAdd(resourceId, key => new LockMetadata());
                // é˜²æ­¢é‡å¤èŽ·å–ï¼ˆå½“å‰çº¿ç¨‹å·²æŒæœ‰é”ï¼‰
                if (lockMeta.HoldingThreadId == currentThreadId && !lockMeta.IsReleased)
                    return true; // çº¿ç¨‹å¯é‡å…¥
            }
            // å°è¯•获取资源特定的锁
            // ä½¿ç”¨ Monitor.TryEnter é¿å…é˜»å¡žï¼Œå¹¶å®žçŽ°éžé˜»å¡žçš„æŠ¢é”
            return Monitor.TryEnter(lockObject);
            // å°è¯•获取锁(非阻塞)
            if (!Monitor.TryEnter(lockMeta.LockObject))
                return false;
            // æ ‡è®°é”æŒæœ‰çŠ¶æ€
            lockMeta.HoldingThreadId = currentThreadId;
            lockMeta.AcquireTime = DateTime.Now;
            lockMeta.Timeout = timeout;
            lockMeta.IsReleased = false;
            // å¯åŠ¨è¶…æ—¶è‡ªåŠ¨é‡Šæ”¾ä»»åŠ¡
            _ = Task.Delay(timeout).ContinueWith(_ =>
            {
                try
                {
                    ReleaseLock(resourceId, force: true);
                }
                catch (Exception ex)
                {
                    // è®°å½•超时释放异常
                    Console.WriteLine($"资源[{resourceId}]超时自动释放失败:{ex.Message}");
                }
            });
            return true;
        }
        /// <summary>
        /// é‡Šæ”¾èµ„源ID的锁定。
        /// é‡Šæ”¾èµ„源锁
        /// </summary>
        /// <param name="resourceId">要释放的资源ID</param>
        public static void ReleaseLock(string resourceId)
        /// <param name="resourceId">资源ID</param>
        /// <param name="force">是否强制释放(超时自动释放时使用)</param>
        public static void ReleaseLock(string resourceId, bool force = false)
        {
            if (_resourceLocks.TryGetValue(resourceId, out object lockObject))
            {
                // ç¡®ä¿é‡Šæ”¾çš„æ˜¯å½“前线程持有的锁
                if (Monitor.IsEntered(lockObject))
                {
                    Monitor.Exit(lockObject);
            if (string.IsNullOrEmpty(resourceId))
                throw new ArgumentNullException(nameof(resourceId));
                    // é‡Šæ”¾é”åŽï¼Œå°è¯•从字典中移除这个锁对象,清理内存。
                    // å¿…须在 Monitor.Exit ä¹‹åŽæ‰§è¡Œã€‚
            if (!_resourceLocks.TryGetValue(resourceId, out var lockMeta))
                return;
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            // æ ¡éªŒé‡Šæ”¾åˆæ³•性:仅持有锁的线程或强制释放可执行
            if (!force && lockMeta.HoldingThreadId != currentThreadId)
            {
                // éžæŒæœ‰çº¿ç¨‹å°è¯•释放,抛出异常或返回(根据业务选择)
                throw new InvalidOperationException($"线程[{currentThreadId}]无权释放资源[{resourceId}]的锁(当前持有线程:{lockMeta.HoldingThreadId})");
            }
            // åŒé‡æ ¡éªŒé”çŠ¶æ€
            lock (_globalLocker)
            {
                if (lockMeta.IsReleased)
                    return;
                // ç¡®ä¿é”è¢«å½“前线程持有(强制释放除外)
                if (Monitor.IsEntered(lockMeta.LockObject))
                {
                    try
                    {
                        Monitor.Exit(lockMeta.LockObject);
                    }
                    catch (SynchronizationLockException)
                    {
                        // å·²è¢«é‡Šæ”¾ï¼Œå¿½ç•¥
                        return;
                    }
                }
                // æ ‡è®°é”å·²é‡Šæ”¾
                lockMeta.IsReleased = true;
                lockMeta.HoldingThreadId = -1;
                // å»¶è¿Ÿæ¸…理锁元数据(避免并发创建)
                // ç­‰å¾…1秒后清理,防止刚释放就有新线程抢锁导致重复创建
                _ = Task.Delay(1000).ContinueWith(_ =>
                {
                    lock (_globalLocker)
                    {
                        _resourceLocks.TryRemove(resourceId, out _);
                        // ä»…当锁未被重新持有且已释放时清理
                        if (_resourceLocks.TryGetValue(resourceId, out var meta)
                            && meta.IsReleased
                            && meta.HoldingThreadId == -1)
                        {
                            _resourceLocks.TryRemove(resourceId, out var _resid);
                        }
                    }
                });
            }
        }
        /// <summary>
        /// æ£€æŸ¥èµ„源是否被锁定
        /// </summary>
        public static bool IsLocked(string resourceId)
        {
            if (!_resourceLocks.TryGetValue(resourceId, out var meta))
                return false;
            return !meta.IsReleased && meta.HoldingThreadId != -1;
        }
        public static void TestUsed()
        {
            string orderNo = "testt";
            bool lockAcquired = false;
            try
            {
                // å°è¯•获取锁(自动3-5秒超时)
                lockAcquired = MemoryLockManager.TryAcquireLock(orderNo);
                if (lockAcquired)
                {
                    // æ‰§è¡Œä¸šåŠ¡é€»è¾‘ï¼ˆå¦‚å¤„ç†è®¢å•ï¼‰
                    Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]获取锁:{orderNo}");
                    // æ¨¡æ‹Ÿä¸šåŠ¡è€—æ—¶ï¼ˆæµ‹è¯•è¶…æ—¶é‡Šæ”¾ï¼‰
                    // Thread.Sleep(6000);
                }
                else
                {
                    Console.WriteLine($"资源[{orderNo}]被占用,获取锁失败");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"业务处理异常:{ex.Message}");
            }
            finally
            {
                // é‡Šæ”¾é”ï¼ˆä»…当成功获取时)
                if (lockAcquired)
                {
                    try
                    {
                        MemoryLockManager.ReleaseLock(orderNo);
                        Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]释放锁:{orderNo}");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"释放锁失败:{ex.Message}");
                    }
                }
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Stock/StockDetailDtO.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WIDESEA_DTO.Stock
{
    public class StockDetailDtO
    {
        public int Id { get; set; }
        public int StockId { get; set; }
        public string MaterielCode { get; set; }
        public string MaterielName { get; set; }
        public string OrderNo { get; set; }
        public string BatchNo { get; set; }
        public string ProductionDate { get; set; }
        public string EffectiveDate { get; set; }
        public string SerialNumber { get; set; }
        public decimal StockQuantity { get; set; }
        public decimal OutboundQuantity { get; set; }
        public int Status { get; set; }
        public string Unit { get; set; }
        public string InboundOrderRowNo { get; set; }
        public string? SupplyCode { get; set; }
        public string FactoryArea { get; set; }
        public string? WarehouseCode { get; set; }
        public string? Barcode { get; set; }
        public string? BusinessType { get; set; }
        public decimal BarcodeQty { get; set; }
        public string BarcodeUnit { get; set; }
        public DateTime? ValidDate { get; set; }
        public string Remark { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IAllocateService/IAllocateDetailService.cs
@@ -12,5 +12,6 @@
    public interface IAllocateDetailService : IService<Dt_AllocateOrderDetail>
    {
        IRepository<Dt_AllocateOrderDetail> Repository { get; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IStockService/IStockDetailByMaterielService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Stock;
using WIDESEA_Model.Models;
namespace WIDESEA_IStockService
{
    public interface IStockDetailByMaterielService : IDependency
    {
        PageGridData<StockDetailByMateriel> GetPageGridData(PageDataOptions options);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoDetailService.cs
@@ -14,5 +14,6 @@
        bool ExistBarcodes(List<string> barcodes);
        PageGridData<StockInfoDetailWithPalletDto> GetPageData2(PageDataOptions options);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs
@@ -652,7 +652,7 @@
            {
                return WebResponseContent.Instance.Error("托盘号不能为空");
            }
            var stock = _stockRepository.Db.Queryable<Dt_StockInfo>().Includes(o => o.Details).First(x => x.PalletCode == palletCode && x.StockStatus == (int)StockStatusEmun.组盘暂存);
            var stock = _stockRepository.Db.Queryable<Dt_StockInfo>().Includes(o => o.Details).First(x => x.PalletCode == palletCode && (x.StockStatus == (int)StockStatusEmun.组盘暂存|| x.StockStatus == StockStatusEmun.入库确认.ObjToInt()));
            if (stock == null)
            {
                return WebResponseContent.Instance.Error($"未找到托盘号{palletCode}对应的库存记录");
@@ -705,7 +705,7 @@
                };
                orderByModels.Add(orderByModel);
            }
            ISugarQueryable<Dt_InboundOrder> sugarQueryable1 = BaseDal.Db.Queryable<Dt_InboundOrder>();
            int totalCount = 0;
            List<SearchParameters> searchParametersList = new List<SearchParameters>();
@@ -714,16 +714,68 @@
                try
                {
                    searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
                    if (searchParametersList.Count > 0)
                    {
                        SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_InboundOrder.InboundOrderNo).FirstLetterToLower());
                        if (searchParameters != null)
                        {
                            sugarQueryable1 = sugarQueryable1.Where(x => x.InboundOrderNo.Contains(searchParameters.Value.ToString())
                            || x.UpperOrderNo.Contains(searchParameters.Value.ToString()) ||
                            x.Details.Any(d => d.Barcode == searchParameters.Value.ToString()));
                        }
                        searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_InboundOrder.OrderType).FirstLetterToLower());
                        if (searchParameters != null)
                        {
                            sugarQueryable1 = sugarQueryable1.Where(x => x.OrderType.Equals(searchParameters.Value.ToString()));
                        }
                        //var dataList = sugarQueryable1.ToPageList(options.Page, options.Rows, ref totalCount);
                        //return new PageGridData<Dt_InboundOrder>(totalCount, dataList);
                    }
                    options.Filter = searchParametersList;
                }
                catch { }
            }
            var data = BaseDal.Db.Queryable<Dt_InboundOrder>()
            var data = sugarQueryable1
                .WhereIF(!wheres.IsNullOrEmpty(), wheres)
                .OrderBy(orderByModels).Includes(x=>x.Details)
                .Where(x => x.OrderType == 0)
                .OrderBy(orderByModels).Includes(x => x.Details)
                .ToPageList(options.Page, options.Rows, ref totalCount);
            return new PageGridData<Dt_InboundOrder>(totalCount, data);
        }
        //public override PageGridData<Dt_InboundOrder> GetPageData(PageDataOptions options)
        //{
        //    //var  pageGridData = base.GetPageData(options);
        //    ISugarQueryable<Dt_InboundOrder> sugarQueryable1 = BaseDal.Db.Queryable<Dt_InboundOrder>();
        //    if (!string.IsNullOrEmpty(options.Wheres))
        //    {
        //        List<SearchParameters> searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
        //        int totalCount = 0;
        //        if (searchParametersList.Count > 0)
        //        {
        //            SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_InboundOrder.InboundOrderNo).FirstLetterToLower());
        //            if (searchParameters != null)
        //            {
        //                sugarQueryable1 = sugarQueryable1.Where(x => x.InboundOrderNo.Contains(searchParameters.Value.ToString())
        //                || x.UpperOrderNo.Contains(searchParameters.Value.ToString()) ||
        //                x.Details.Any(d => d.Barcode == searchParameters.Value.ToString()));
        //            }
        //            searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_InboundOrder.OrderType).FirstLetterToLower());
        //            if (searchParameters != null)
        //            {
        //                sugarQueryable1 = sugarQueryable1.Where(x => x.OrderType.Equals(searchParameters.Value.ToString()));
        //            }
        //            var dataList = sugarQueryable1.ToPageList(options.Page, options.Rows, ref totalCount);
        //            return new PageGridData<Dt_InboundOrder>(totalCount, dataList);
        //        }
        //    }
        //    return new PageGridData<Dt_InboundOrder>();
        //}
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/StockDetailByMateriel.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WIDESEA_Model.Models
{
    public class StockDetailByMateriel
    {
        public int Id { get; set; }
        public int StockId { get; set; }
        public string MaterielCode { get; set; }
        public string MaterielName { get; set; }
        public string OrderNo { get; set; }
        public string BatchNo { get; set; }
        public string ProductionDate { get; set; }
        public string EffectiveDate { get; set; }
        public string SerialNumber { get; set; }
        public decimal StockQuantity { get; set; }
        public decimal OutboundQuantity { get; set; }
        public int Status { get; set; }
        public string Unit { get; set; }
        public string InboundOrderRowNo { get; set; }
        public string? SupplyCode { get; set; }
        public string FactoryArea { get; set; }
        public string? WarehouseCode { get; set; }
        public string? Barcode { get; set; }
        public string? BusinessType { get; set; }
        public decimal BarcodeQty { get; set; }
        public string BarcodeUnit { get; set; }
        public DateTime? ValidDate { get; set; }
        public string Remark { get; set; }
        public string Creater { get; set; }
        public DateTime? CreateDate { get; set; }
        public string Modifier { get; set; }
        public DateTime? ModifyDate { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs
@@ -1,6 +1,5 @@
using Microsoft.Extensions.Logging;
using SqlSugar;
using SqlSugar.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,6 +14,7 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Outbound;
using WIDESEA_IAllocateService;
@@ -249,27 +249,48 @@
        #region åˆ†æ‰¹åˆ†æ‹£
        /// <summary>
        /// åˆ†æ‰¹åˆ†æ‹£ç¡®è®¤
        /// åˆ†æ‰¹åˆ†æ‹£ç¡®è®¤ - ä¿®æ­£ç‰ˆï¼ˆåŒ…含拆包后验证)
        /// </summary>
        public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _logger.LogInformation($"【分拣开始】订单: {orderNo}, æ‰˜ç›˜: {palletCode}, æ¡ç : {barcode}");
                _unitOfWorkManage.BeginTran();
                // éªŒè¯åˆ†æ‹£è¯·æ±‚
                // 1. éªŒè¯åˆ†æ‹£è¯·æ±‚
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                }
                var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data;
                // æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…
                _logger.LogInformation($"验证通过 - é”å®šID: {lockInfo.Id}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å·²æ‹£é€‰: {lockInfo.PickedQty}");
                _logger.LogInformation($"库存信息 - æ¡ç : {stockDetail.Barcode}, åº“存数量: {stockDetail.StockQuantity}, å‡ºåº“数量: {stockDetail.OutboundQuantity}");
                decimal originalStockQtyBeforeSplit = stockDetail.StockQuantity;
                decimal originalOutboundQtyBeforeSplit = stockDetail.OutboundQuantity;
                // è®°å½•拆包前的关键数据(用于后续验证)
                decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
                decimal originalLockQty = orderDetail.LockQuantity;
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                // 2. æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…
                var autoSplitResult = await CheckAndAutoSplitIfNeeded(lockInfo, stockDetail, palletCode);
                if (autoSplitResult != null)
                {
                    // å¦‚果执行了自动拆包,重新获取最新的锁定信息和库存信息
                    _logger.LogInformation($"执行了自动拆包,重新获取数据");
                    // é‡æ–°èŽ·å–æœ€æ–°çš„é”å®šä¿¡æ¯å’Œåº“å­˜ä¿¡æ¯
                    var refreshedValidation = await ValidatePickingRequest(orderNo, palletCode, barcode);
                    if (!refreshedValidation.IsValid)
                    {
@@ -278,40 +299,56 @@
                    }
                    (lockInfo, orderDetail, stockDetail, batch) = refreshedValidation.Data;
                    // ã€é‡è¦ã€‘调用自动拆包后验证
                    decimal splitQuantity =  autoSplitResult.FirstOrDefault()?.quantityTotal.ObjToDecimal()??0 ;
                    bool autoSplitValid = await ValidateAfterAutoSplit(lockInfo, orderDetail, stockDetail, splitQuantity,originalStockQtyBeforeSplit);
                    var actualPickedQty = lockInfo.AssignQuantity;
                    var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                    if (!autoSplitValid)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error("自动拆包后数据验证失败,请检查系统日志");
                    }
                    // æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                    await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                    // è®°å½•拣选历史
                    await RecordPickingHistory(pickingResult, orderNo, palletCode);
                    _unitOfWorkManage.CommitTran();
                    return WebResponseContent.Instance.OK("自动拆包并分拣成功", autoSplitResult);
                    _logger.LogInformation($"自动拆包验证通过,继续执行分拣");
                }
                // æ­£å¸¸åˆ†æ‹£æµç¨‹ï¼ˆä¸éœ€è¦è‡ªåŠ¨æ‹†åŒ…ï¼‰
                var normalPickedQty = lockInfo.AssignQuantity;
                var normalPickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, normalPickedQty);
                // 3. è®¡ç®—实际拣选数量
                decimal actualPickedQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                // æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                await UpdateBatchAndOrderData(batch, orderDetail, normalPickedQty, orderNo);
                if (actualPickedQty <= 0)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error("该条码已拣选完成,无需重复拣选");
                }
                // è®°å½•拣选历史
                await RecordPickingHistory(normalPickingResult, orderNo, palletCode);
                _logger.LogInformation($"开始拣选 - æ•°é‡: {actualPickedQty}");
                // 4. æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                // 5. æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                // 6. è®°å½•拣选历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                // 7. æ‹£é€‰åŽéªŒè¯
                await ValidateAfterPicking(orderNo, palletCode, barcode, actualPickedQty);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分拣成功", new
                {
                    PickedQuantity = normalPickedQty,
                    PickedQuantity = actualPickedQty,
                    Barcode = barcode,
                    MaterialCode = lockInfo.MaterielCode,
                    AutoSplitted = false
                    AutoSplitted = autoSplitResult != null,
                    RemainingStock = stockDetail.StockQuantity,
                    CurrentOutbound = stockDetail.OutboundQuantity,
                    // å¦‚果是自动拆包,返回相关信息
                    UnallocatedCreated = autoSplitResult != null ? "是" : "否"
                });
            }
            catch (Exception ex)
@@ -321,6 +358,260 @@
                return WebResponseContent.Instance.Error($"分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// è‡ªåŠ¨æ‹†åŒ…åŽéªŒè¯æ•°æ®ä¸€è‡´æ€§ - ä¿®æ­£ç‰ˆ
        /// é‡ç‚¹ä¿®æ­£ï¼šæ­£ç¡®çš„原库存期望值计算
        /// </summary>
        private async Task<bool> ValidateAfterAutoSplit(Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail originalStockDetail, decimal splitQuantity, decimal originalStockQtyBeforeSplit)
        {
            try
            {
                _logger.LogInformation($"开始自动拆包后验证 - åŽŸæ¡ç : {originalStockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
                _logger.LogInformation($"拆包前原库存数量: {originalStockQtyBeforeSplit}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                bool allValid = true;
                List<string> validationErrors = new List<string>();
                // 1. é‡æ–°èŽ·å–æœ€æ–°çš„æ•°æ®ï¼ˆæ‹†åŒ…åŽçš„å½“å‰çŠ¶æ€ï¼‰
                var refreshedOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetail.Id);
                var refreshedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == lockInfo.Id);
                var refreshedStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Id == originalStockDetail.Id);
                // 2. ã€æ ¸å¿ƒä¿®æ­£ã€‘验证原库存明细数据
                // é‡è¦ï¼šæ‹†åŒ…后原库存的期望值 = åˆ†é…æ•°é‡ (因为自动拆包时,原库存应只保留分配数量)
                decimal expectedOriginalStockQty = lockInfo.AssignQuantity; // åº”该是120,而不是用计算
                _logger.LogInformation($"库存验证基准:");
                _logger.LogInformation($"  æ‹†åŒ…前原库存: {originalStockQtyBeforeSplit}");
                _logger.LogInformation($"  åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                _logger.LogInformation($"  æ‹†åŒ…数量: {splitQuantity}");
                _logger.LogInformation($"  æœŸæœ›åŽŸåº“å­˜: {expectedOriginalStockQty}");
                _logger.LogInformation($"  å®žé™…原库存: {refreshedStockDetail.StockQuantity}");
                // å…è®¸å°‘量误差的验证
                if (Math.Abs(refreshedStockDetail.StockQuantity - expectedOriginalStockQty) > 0.01m)
                {
                    // é¢å¤–检查:如果原库存数量不合理,可能是数据问题
                    if (refreshedStockDetail.StockQuantity < 0)
                    {
                        string error = $"原库存数量异常(负数)!实际: {refreshedStockDetail.StockQuantity}";
                        validationErrors.Add(error);
                        allValid = false;
                        _logger.LogError(error);
                    }
                    else if (refreshedStockDetail.StockQuantity > originalStockQtyBeforeSplit)
                    {
                        string error = $"原库存数量异常(大于拆包前)!拆包前: {originalStockQtyBeforeSplit}, å½“前: {refreshedStockDetail.StockQuantity}";
                        validationErrors.Add(error);
                        allValid = false;
                        _logger.LogError(error);
                    }
                    else
                    {
                        // å¯èƒ½æ˜¯åˆç†çš„误差,记录警告但不标记为失败
                        _logger.LogWarning($"原库存数量与期望值有差异,期望: {expectedOriginalStockQty}, å®žé™…: {refreshedStockDetail.StockQuantity}");
                    }
                }
                // éªŒè¯åŽŸåº“å­˜çš„å‡ºåº“æ•°é‡æ˜¯å¦ä¿æŒä¸å˜
                if (Math.Abs(refreshedStockDetail.OutboundQuantity - originalStockDetail.OutboundQuantity) > 0.01m)
                {
                    string error = $"原库存出库数量不应变化!拆包前: {originalStockDetail.OutboundQuantity}, æ‹†åŒ…后: {refreshedStockDetail.OutboundQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 3. éªŒè¯æ–°åº“存明细(拆包产生的)
                // æŸ¥æ‰¾æ–°æ¡ç ï¼ˆé€šè¿‡æ‹†åŒ…记录)
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OutStockLockInfoId == lockInfo.Id &&
                               !x.IsReverted &&
                               x.IsAutoSplit == true)
                    .OrderByDescending(x => x.SplitTime)
                    .ToListAsync();
                if (splitRecords.Any())
                {
                    var latestSplit = splitRecords.First();
                    if (!string.IsNullOrEmpty(latestSplit.NewBarcode))
                    {
                        var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .FirstAsync(x => x.Barcode == latestSplit.NewBarcode && x.StockId == originalStockDetail.StockId);
                        if (newStockDetail != null)
                        {
                            // æ–°åº“存的期望值 = æ‹†åŒ…数量
                            decimal expectedNewStockQty = splitQuantity;
                            _logger.LogInformation($"新库存验证:");
                            _logger.LogInformation($"  æ–°æ¡ç : {newStockDetail.Barcode}");
                            _logger.LogInformation($"  æœŸæœ›æ•°é‡: {expectedNewStockQty}");
                            _logger.LogInformation($"  å®žé™…数量: {newStockDetail.StockQuantity}");
                            _logger.LogInformation($"  å‡ºåº“数量: {newStockDetail.OutboundQuantity} (应为0)");
                            if (Math.Abs(newStockDetail.StockQuantity - expectedNewStockQty) > 0.01m)
                            {
                                string error = $"新库存数量不正确!期望: {expectedNewStockQty}, å®žé™…: {newStockDetail.StockQuantity}";
                                validationErrors.Add(error);
                                allValid = false;
                                _logger.LogError(error);
                            }
                            // æ–°åº“存出库数量应为0
                            if (Math.Abs(newStockDetail.OutboundQuantity - 0) > 0.01m)
                            {
                                string error = $"新库存出库数量不为0!实际: {newStockDetail.OutboundQuantity}";
                                validationErrors.Add(error);
                                allValid = false;
                                _logger.LogError(error);
                            }
                        }
                    }
                }
                // 4. éªŒè¯æœªåˆ†é…é”å®šè®°å½•
                var unallocatedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.ParentLockId == lockInfo.Id &&
                               x.IsUnallocated == 1 &&
                               x.OrderDetailId == 0)
                    .ToListAsync();
                if (!unallocatedLocks.Any())
                {
                    string error = $"未找到自动拆包创建的未分配锁定记录";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 5. éªŒè¯è®¢å•明细数据未改变
                if (Math.Abs(refreshedOrderDetail.AllocatedQuantity - orderDetail.AllocatedQuantity) > 0.01m)
                {
                    string error = $"订单明细分配数量异常变化!拆包前: {orderDetail.AllocatedQuantity}, æ‹†åŒ…后: {refreshedOrderDetail.AllocatedQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 6. éªŒè¯åŽŸé”å®šè®°å½•æ•°æ®æœªæ”¹å˜ï¼ˆåˆ†é…æ•°é‡ä¸å˜ï¼‰
                if (Math.Abs(refreshedLockInfo.AssignQuantity - lockInfo.AssignQuantity) > 0.01m)
                {
                    string error = $"锁定记录分配数量异常变化!拆包前: {lockInfo.AssignQuantity}, æ‹†åŒ…后: {refreshedLockInfo.AssignQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 7. ã€æ–°å¢žã€‘验证总库存守恒
                // æ‹†åŒ…前总库存 = åŽŸåº“å­˜æ•°é‡
                // æ‹†åŒ…后总库存 = åŽŸåº“å­˜çŽ°æœ‰æ•°é‡ + æ–°åº“存数量
                decimal totalStockAfterSplit = refreshedStockDetail.StockQuantity;
                if (splitRecords.Any() && !string.IsNullOrEmpty(splitRecords.First().NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .FirstAsync(x => x.Barcode == splitRecords.First().NewBarcode);
                    if (newStock != null)
                    {
                        totalStockAfterSplit += newStock.StockQuantity;
                    }
                }
                _logger.LogInformation($"库存守恒验证:");
                _logger.LogInformation($"  æ‹†åŒ…前总库存: {originalStockQtyBeforeSplit}");
                _logger.LogInformation($"  æ‹†åŒ…后总库存: {totalStockAfterSplit}");
                _logger.LogInformation($"  å·®å¼‚: {originalStockQtyBeforeSplit - totalStockAfterSplit}");
                // å…è®¸å¾ˆå°çš„æµ®ç‚¹æ•°è¯¯å·®
                if (Math.Abs(originalStockQtyBeforeSplit - totalStockAfterSplit) > 0.02m)
                {
                    string error = $"库存不守恒!拆包前: {originalStockQtyBeforeSplit}, æ‹†åŒ…后总库存: {totalStockAfterSplit}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // æ±‡æ€»éªŒè¯ç»“æžœ
                if (allValid)
                {
                    _logger.LogInformation($"✅ è‡ªåŠ¨æ‹†åŒ…åŽéªŒè¯å…¨éƒ¨é€šè¿‡");
                }
                else
                {
                    string errorSummary = $"自动拆包后验证失败,发现{validationErrors.Count}个问题:" +
                                         string.Join("; ", validationErrors.Take(3));
                    _logger.LogError(errorSummary);
                    // è®°å½•详细问题到日志
                    for (int i = 0; i < validationErrors.Count; i++)
                    {
                        _logger.LogError($"问题{i + 1}: {validationErrors[i]}");
                    }
                }
                return allValid;
            }
            catch (Exception ex)
            {
                _logger.LogError($"自动拆包后验证异常: {ex.Message}");
                return false;
            }
        }
        /// <summary>
        /// æ‹£é€‰åŽéªŒè¯
        /// </summary>
        private async Task ValidateAfterPicking(string orderNo, string palletCode, string barcode, decimal pickedQty)
        {
            try
            {
                _logger.LogInformation($"开始拣选后验证");
                // 1. éªŒè¯åº“存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == barcode);
                // 2. æŸ¥æ‰¾è¯¥æ¡ç çš„æ‰€æœ‰æ‹£é€‰è®°å½•
                var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && !x.IsCancelled)
                    .ToListAsync();
                decimal totalPickedFromRecords = pickingRecords.Sum(x => x.PickQuantity);
                _logger.LogInformation($"拣选验证 - æ¡ç : {barcode}");
                _logger.LogInformation($"  åº“存出库数量: {stockDetail.OutboundQuantity}");
                _logger.LogInformation($"  æ‹£é€‰è®°å½•总和: {totalPickedFromRecords}");
                if (Math.Abs(stockDetail.OutboundQuantity - totalPickedFromRecords) > 0.01m)
                {
                    _logger.LogError($"拣选数据不一致!库存出库数量({stockDetail.OutboundQuantity}) â‰  æ‹£é€‰è®°å½•总和({totalPickedFromRecords})");
                    // è‡ªåŠ¨ä¿®å¤ï¼šä»¥æ‹£é€‰è®°å½•æ€»å’Œä¸ºå‡†
                    decimal originalOutbound = stockDetail.OutboundQuantity;
                    stockDetail.OutboundQuantity = totalPickedFromRecords;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    _logger.LogWarning($"已自动修复出库数量: {originalOutbound} -> {totalPickedFromRecords}");
                }
                _logger.LogInformation($"拣选后验证完成");
            }
            catch (Exception ex)
            {
                _logger.LogError($"拣选后验证失败: {ex.Message}");
            }
        }
        /// <summary>
        /// å–消分拣
        /// </summary>
@@ -371,12 +662,13 @@
        #region å–走空箱逻辑
        /// <summary>
        /// éªŒè¯ç©ºç®±å–走条件
        /// éªŒè¯ç©ºç®±å–走条件
        /// </summary>
        private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
        {
            _logger.LogInformation($"开始验证空托盘取走 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            // èŽ·å–æ‰˜ç›˜çš„æ‰€æœ‰é”å®šè®°å½•
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
@@ -385,7 +677,33 @@
            if (!lockInfos.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
            // æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„锁定记录
            // èŽ·å–åº“å­˜ä¿¡æ¯
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
            if (stockInfo == null)
            {
                _logger.LogWarning($"未找到托盘的库存信息,可能已被清理");
                // å¦‚果找不到库存信息,只检查锁定记录
            }
            else
            {
                // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦è¿˜æœ‰åº“存货物
                var remainingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockInfo.Id &&
                               x.StockQuantity > 0)  // åªæ£€æŸ¥æ•°é‡å¤§äºŽ0的库存
                    .ToListAsync();
                if (remainingStock.Any())
                {
                    var remainingQty = remainingStock.Sum(x => x.StockQuantity);
                    var barcodes = string.Join(", ", remainingStock.Select(x => x.Barcode).Take(5)); // åªæ˜¾ç¤ºå‰5个
                    return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                        $"托盘上还有库存货物,数量{remainingQty},条码: {barcodes},不能取走空箱");
                }
            }
            // æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„锁定记录(状态为出库中或回库中)
            var unfinishedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.出库中 ||
                x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
@@ -398,35 +716,19 @@
                    $"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
            }
            // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦è¿˜æœ‰åº“存货物
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
            if (stockInfo != null)
            {
                var remainingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockInfo.Id &&
                               x.Status == (int)StockStatusEmun.出库锁定 &&
                               x.StockQuantity > 0)
                    .ToListAsync();
                if (remainingStock.Any())
                {
                    var remainingQty = remainingStock.Sum(x => x.StockQuantity);
                    return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                        $"托盘上还有库存货物,数量{remainingQty},不能取走空箱");
                }
            }
            // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•
            // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•ï¼ˆçŠ¶æ€ä¸ºæ‹£é€‰å®Œæˆæˆ–å·²å–èµ°ï¼‰
            var completedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
                x.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                x.Status == (int)OutLockStockStatusEnum.已取走).ToList();
            if (!completedLocks.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
            _logger.LogInformation($"空托盘验证通过 - æ‰¾åˆ° {completedLocks.Count} æ¡å·²å®Œæˆè®°å½•");
            return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
        }
        /// <summary>
        /// æ¸…理已完成的锁定记录
@@ -445,21 +747,86 @@
            }
        }
        /// <summary>
        /// æ¸…理库存信息
        /// æ¸…理库存信息 - å®Œæ•´ä¿®æ­£ç‰ˆ
        /// ç¡®ä¿OutboundQuantity正确清零
        /// </summary>
        private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null && stockDetail.Status == (int)StockStatusEmun.出库完成)
            try
            {
                stockDetail.Status = (int)StockStatusEmun.已清理;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"清理库存信息 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}");
                // æŸ¥æ‰¾é”å®šçš„库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                if (stockDetail != null)
                {
                    // è®°å½•清理前的状态
                    decimal originalStockQty = stockDetail.StockQuantity;
                    decimal originalOutboundQty = stockDetail.OutboundQuantity;
                    int originalStatus = stockDetail.Status;
                    _logger.LogInformation($"清理前状态 - åº“å­˜: {originalStockQty}, å‡ºåº“: {originalOutboundQty}, çŠ¶æ€: {GetStockStatusName(originalStatus)}");
                    // ã€é‡è¦ã€‘检查库存数量是否应该为0
                    // å¦‚果锁定状态是拣选完成,理论上库存应该为0
                    if (lockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
                    {
                        if (stockDetail.StockQuantity > 0)
                        {
                            _logger.LogWarning($"拣选完成但库存不为0 - æ¡ç : {stockDetail.Barcode}, åº“å­˜: {stockDetail.StockQuantity}");
                        }
                    }
                    // æ¸…理库存和出库数量
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;  // ã€ä¿®æ­£ã€‘确保出库数量清零
                    stockDetail.Status = (int)StockStatusEmun.已清理;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    _logger.LogInformation($"清理库存明细完成");
                    _logger.LogInformation($"  åº“存数量: {originalStockQty} -> 0");
                    _logger.LogInformation($"  å‡ºåº“数量: {originalOutboundQty} -> 0");
                    _logger.LogInformation($"  çŠ¶æ€: {GetStockStatusName(originalStatus)} -> å·²æ¸…理");
                }
                else
                {
                    _logger.LogWarning($"未找到条码对应的库存明细: {lockInfo.CurrentBarcode}");
                }
                // æ¸…理该托盘上的所有库存(避免遗漏)
                var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == lockInfo.StockId && x.StockQuantity > 0)
                    .ToListAsync();
                if (allStockDetails.Any())
                {
                    _logger.LogInformation($"清理托盘上其他库存 - å…± {allStockDetails.Count} æ¡è®°å½•");
                    foreach (var stock in allStockDetails)
                    {
                        decimal originalQty = stock.StockQuantity;
                        decimal originalOutbound = stock.OutboundQuantity;
                        stock.StockQuantity = 0;
                        stock.OutboundQuantity = 0;  // ã€ä¿®æ­£ã€‘确保出库数量清零
                        stock.Status = (int)StockStatusEmun.已清理;
                        await _stockInfoDetailService.Db.Updateable(stock).ExecuteCommandAsync();
                        _logger.LogInformation($"清理遗漏库存 - æ¡ç : {stock.Barcode}, åº“å­˜: {originalQty}->0, å‡ºåº“: {originalOutbound}->0");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"清理库存信息失败 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, Error: {ex.Message}");
                // ä¸æŠ›å‡ºå¼‚常,继续处理其他记录
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•状态
@@ -495,17 +862,17 @@
        /// </summary>
        private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
        {
            var removalRecord = new Dt_EmptyPalletRemoval
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                RemovalTime = DateTime.Now,
                Operator = App.User.UserName,
                CompletedItemsCount = completedLocks.Count,
                TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
            };
            //var removalRecord = new Dt_EmptyPalletRemoval
            //{
            //    OrderNo = orderNo,
            //    PalletCode = palletCode,
            //    RemovalTime = DateTime.Now,
            //    Operator = App.User.UserName,
            //    CompletedItemsCount = completedLocks.Count,
            //    TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
            //};
            await Db.Insertable(removalRecord).ExecuteCommandAsync();
            //await Db.Insertable(removalRecord).ExecuteCommandAsync();
        }
        #endregion
@@ -536,7 +903,6 @@
            }
        };
        }
        #region æ‰‹åŠ¨æ‹†åŒ…   
@@ -676,101 +1042,199 @@
            return lockInfos.Sum(x => x.AssignQuantity);
        }
        /// <summary>
        /// æ‰§è¡Œæ‰‹åŠ¨æ‹†åŒ…é€»è¾‘ - å¢žå¼ºåˆ†é…æ•°é‡æŽ§åˆ¶
        /// æ‰§è¡Œæ‰‹åŠ¨æ‹†åŒ…é€»è¾‘ - ä¿®å¤ç‰ˆæœ¬
        /// </summary>
        private async Task<List<SplitResult>> ExecuteManualSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string palletCode)
        {
            _logger.LogInformation($"开始执行手动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            // éªŒè¯æ‹†åŒ…数量
            if (lockInfo.AssignQuantity < splitQuantity)
            try
            {
                throw new InvalidOperationException($"拆包数量超过锁定信息分配数量,拆包数量: {splitQuantity}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                // éªŒè¯åº“存数量是否足够
                decimal availableStock = stockDetail.StockQuantity;
                if (availableStock < splitQuantity)
                {
                    throw new InvalidOperationException($"库存不足,当前库存: {availableStock}, éœ€è¦æ‹†åŒ…: {splitQuantity}");
                }
                // éªŒè¯æ‹†åŒ…数量不能等于或大于原锁定信息分配数量
                if (splitQuantity >= lockInfo.AssignQuantity)
                {
                    throw new InvalidOperationException($"拆包数量不能等于或大于原锁定信息分配数量,拆包数量: {splitQuantity}, åŽŸåˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                }
                // è®¡ç®—剩余数量
                decimal remainQty = lockInfo.AssignQuantity - splitQuantity;
                // éªŒè¯å‰©ä½™æ•°é‡æ˜¯å¦åˆç†
                if (remainQty <= 0)
                {
                    throw new InvalidOperationException($"拆包后剩余数量必须大于0,当前剩余: {remainQty}");
                }
                _logger.LogInformation($"拆包计算 - åŽŸåˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, æ‹†åŒ…数量: {splitQuantity}, å‰©ä½™æ•°é‡: {remainQty}");
                _logger.LogInformation($"原库存信息 - åº“存数量: {stockDetail.StockQuantity}, å‡ºåº“数量: {stockDetail.OutboundQuantity}");
                // ç”Ÿæˆæ–°æ¡ç 
                string newBarcode = await GenerateNewBarcode();
                _logger.LogInformation($"生成新条码: {newBarcode}");
                // è®°å½•拆包前的关键数据
                decimal originalLockAssignQty = lockInfo.AssignQuantity;
                decimal originalLockOrderQty = lockInfo.OrderQuantity;
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                // ä¿®å¤ï¼šè®¡ç®—新库存明细的正确数量
                // æ–°æ¡ç åº”该只有拆包数量,而不是2倍
                decimal newStockQuantity = splitQuantity;
                decimal originalRemainingStockQuantity = originalStockQty - splitQuantity;
                _logger.LogInformation($"数量分配 - æ–°æ¡ç æ•°é‡: {newStockQuantity}, åŽŸæ¡ç å‰©ä½™æ•°é‡: {originalRemainingStockQuantity}");
                // åˆ›å»ºæ–°åº“存明细 - ä¿®å¤æ•°é‡é—®é¢˜
                var newStockDetail = new Dt_StockInfoDetail
                {
                    StockId = stockDetail.StockId,
                    MaterielCode = stockDetail.MaterielCode,
                    OrderNo = stockDetail.OrderNo,
                    BatchNo = stockDetail.BatchNo,
                    StockQuantity = newStockQuantity,  // ä¿®å¤ï¼šä½¿ç”¨æ­£ç¡®çš„æ‹†åŒ…数量
                    OutboundQuantity = 0,  // æ–°æ¡ç åˆå§‹å‡ºåº“数量为0
                    Barcode = newBarcode,
                    Status = (int)StockStatusEmun.出库锁定,
                    SupplyCode = stockDetail.SupplyCode,
                    Unit = stockDetail.Unit,
                    BarcodeQty = stockDetail.BarcodeQty,
                    BarcodeUnit = stockDetail.BarcodeUnit,
                    BusinessType = stockDetail.BusinessType,
                    InboundOrderRowNo = stockDetail.InboundOrderRowNo,
                };
                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"创建新库存明细成功 - æ¡ç : {newBarcode}, åº“存数量: {newStockQuantity}");
                // ä¿®æ­£ï¼šæ›´æ–°åŽŸåº“å­˜æ˜Žç»† - ç¡®ä¿æ•°æ®ä¸€è‡´æ€§
                stockDetail.StockQuantity = originalRemainingStockQuantity;
                // ç¡®ä¿ä¸ä¼šä¸ºè´Ÿæ•°
                if (stockDetail.StockQuantity < 0)
                {
                    _logger.LogWarning($"原库存数量出现负数,重置为0");
                    stockDetail.StockQuantity = 0;
                }
                // å‡ºåº“数量保持不变,因为是拆包,不是实际出库
                // stockDetail.OutboundQuantity = stockDetail.OutboundQuantity;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"更新原库存明细 - æ¡ç : {stockDetail.Barcode}, " +
                                     $"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
                                     $"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
                // åˆ›å»ºæ–°é”å®šä¿¡æ¯
                var newLockInfo = new Dt_OutStockLockInfo
                {
                    OrderNo = lockInfo.OrderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    OutboundBatchNo = lockInfo.OutboundBatchNo,
                    MaterielCode = lockInfo.MaterielCode,
                    MaterielName = lockInfo.MaterielName,
                    StockId = lockInfo.StockId,
                    OrderQuantity = splitQuantity,
                    AssignQuantity = splitQuantity,
                    PickedQty = 0,
                    LocationCode = lockInfo.LocationCode,
                    PalletCode = lockInfo.PalletCode,
                    TaskNum = lockInfo.TaskNum,
                    Status = (int)OutLockStockStatusEnum.出库中,
                    Unit = lockInfo.Unit,
                    SupplyCode = lockInfo.SupplyCode,
                    OrderType = lockInfo.OrderType,
                    CurrentBarcode = newBarcode,
                    IsSplitted = 1,
                    ParentLockId = lockInfo.Id,
                    Operator = App.User.UserName,
                    FactoryArea = lockInfo.FactoryArea,
                    lineNo = lockInfo.lineNo,
                    WarehouseCode = lockInfo.WarehouseCode,
                    BarcodeQty = lockInfo.BarcodeQty,
                    BarcodeUnit = lockInfo.BarcodeUnit,
                };
                await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"创建新锁定信息成功 - æ¡ç : {newBarcode}, åˆ†é…æ•°é‡: {splitQuantity}");
                // æ›´æ–°åŽŸé”å®šä¿¡æ¯
                lockInfo.AssignQuantity = remainQty;
                lockInfo.OrderQuantity = remainQty;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"更新原锁定信息 - åˆ†é…æ•°é‡: {originalLockAssignQty} -> {lockInfo.AssignQuantity}, " +
                                     $"订单数量: {originalLockOrderQty} -> {lockInfo.OrderQuantity}");
                // éªŒè¯æ‹†åŒ…后数据一致性
                await ValidateDataConsistencyAfterSplit(lockInfo.OrderDetailId, originalLockAssignQty, originalLockOrderQty);
                // è®°å½•拆包历史
                await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false, originalStockQty);
                // åˆ›å»ºæ‹†åŒ…结果列表
                var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
                _logger.LogInformation($"手动拆包逻辑执行完成 - åŽŸæ¡ç : {stockDetail.Barcode}, æ–°æ¡ç : {newBarcode}, æ‹†åŒ…数量: {splitQuantity}");
                return splitResults;
            }
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // è®¡ç®—剩余数量
            decimal remainQty = lockInfo.AssignQuantity - splitQuantity;
            // åˆ›å»ºæ–°åº“存明细
            var newStockDetail = new Dt_StockInfoDetail
            catch (Exception ex)
            {
                StockId = stockDetail.StockId,
                MaterielCode = stockDetail.MaterielCode,
                OrderNo = stockDetail.OrderNo,
                BatchNo = stockDetail.BatchNo,
                StockQuantity = splitQuantity,
                OutboundQuantity = 0,
                Barcode = newBarcode,
                Status = (int)StockStatusEmun.出库锁定,
                SupplyCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit,
                BarcodeQty = stockDetail.BarcodeQty,
                BarcodeUnit = stockDetail.BarcodeUnit,
                BusinessType = stockDetail.BusinessType,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                _logger.LogError($"手动拆包逻辑执行失败 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}, Error: {ex.Message}");
                throw;
            }
        }
            // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            stockDetail.StockQuantity -= splitQuantity;
            if (stockDetail.StockQuantity < 0) stockDetail.StockQuantity = 0;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯
            var newLockInfo = new Dt_OutStockLockInfo
        /// <summary>
        /// éªŒè¯æ‹†åŒ…后订单明细的分配数量是否保持不变
        /// </summary>
        private async Task ValidateOrderDetailAllocationAfterSplit(long orderDetailId, decimal originalTotalAssignQty)
        {
            try
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId, // ç»‘定到同一个订单明细
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
                OrderQuantity = splitQuantity,
                AssignQuantity = splitQuantity,
                PickedQty = 0,
                LocationCode = lockInfo.LocationCode,
                PalletCode = lockInfo.PalletCode,
                TaskNum = lockInfo.TaskNum,
                Status = (int)OutLockStockStatusEnum.出库中,
                Unit = lockInfo.Unit,
                SupplyCode = lockInfo.SupplyCode,
                OrderType = lockInfo.OrderType,
                CurrentBarcode = newBarcode,
                IsSplitted = 1,
                ParentLockId = lockInfo.Id,
                Operator = App.User.UserName,
                FactoryArea = lockInfo.FactoryArea,
                lineNo = lockInfo.lineNo,
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
            };
                // èŽ·å–è®¢å•æ˜Žç»†çš„æ‰€æœ‰é”å®šä¿¡æ¯çš„æ€»åˆ†é…æ•°é‡
                var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderDetailId == orderDetailId)
                    .ToListAsync();
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity = remainQty;
            lockInfo.OrderQuantity = remainQty;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"拆包后分配数量验证 - è®¢å•明细ID: {orderDetailId}");
                _logger.LogInformation($"原始总分配数量: {originalTotalAssignQty}, å½“前总分配数量: {totalLockAssignQty}");
            // é‡è¦ï¼šæ‰‹åŠ¨æ‹†åŒ…ä¸æ”¹å˜è®¢å•æ˜Žç»†çš„æ€»åˆ†é…æ•°é‡
            // å› ä¸ºåˆ†é…æ•°é‡åªæ˜¯ä»Žä¸€ä¸ªé”å®šä¿¡æ¯è½¬ç§»åˆ°å¦ä¸€ä¸ªé”å®šä¿¡æ¯
            _logger.LogInformation($"手动拆包 - è®¢å•明细总分配数量保持不变");
                // æ‰‹åŠ¨æ‹†åŒ…åŽæ€»åˆ†é…æ•°é‡åº”è¯¥ä¿æŒä¸å˜
                if (Math.Abs(originalTotalAssignQty - totalLockAssignQty) > 0.01m)
                {
                    _logger.LogWarning($"拆包后总分配数量发生变化 - æœŸæœ›: {originalTotalAssignQty}, å®žé™…: {totalLockAssignQty}");
            // è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false);
            // åˆ›å»ºæ‹†åŒ…结果列表
            var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
            _logger.LogInformation($"手动拆包逻辑执行完成");
            return splitResults;
                    // å¦‚果变化很小,可能是精度问题,记录但不抛出异常
                    if (Math.Abs(originalTotalAssignQty - totalLockAssignQty) > 1.0m)
                    {
                        throw new InvalidOperationException($"拆包后总分配数量异常变化,期望: {originalTotalAssignQty}, å®žé™…: {totalLockAssignQty}");
                    }
                }
                else
                {
                    _logger.LogInformation($"拆包后分配数量验证通过 - æ€»åˆ†é…æ•°é‡ä¿æŒä¸å˜");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"验证拆包后分配数量失败 - OrderDetailId: {orderDetailId}, Error: {ex.Message}");
                throw;
            }
        }
        /// <summary>
        /// éªŒè¯æ‹†åŒ…后数据一致性
@@ -811,8 +1275,6 @@
            }
        }
        #endregion
        #region å–消拆包 
@@ -870,6 +1332,45 @@
        {
            _logger.LogInformation($"开始执行取消拆包逻辑 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}, æ‹†åŒ…数量: {splitRecord.SplitQty}");
            try
            {
                // æ ¹æ®æ‹†åŒ…类型调用不同的处理方法
                if (splitRecord.IsAutoSplit)
                {
                    await HandleAutoSplitCancel(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
                }
                else
                {
                    await HandleManualSplitCancel(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
                }
                // éªŒè¯å–消拆包后数据一致性
                await ValidateDataConsistencyAfterCancelSplit(originalLockInfo.OrderDetailId,
                    originalLockInfo.AssignQuantity, originalLockInfo.OrderQuantity,
                    splitRecord.IsAutoSplit, splitRecord.SplitQty);
                // æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•状态
                await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
                await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
                _logger.LogInformation($"取消拆包逻辑执行完成");
            }
            catch (Exception ex)
            {
                _logger.LogError($"取消拆包逻辑执行失败: {ex.Message}");
                throw;
            }
        }
        /// <summary>
        /// å¤„理自动拆包取消
        /// </summary>
        private async Task HandleAutoSplitCancel(Dt_SplitPackageRecord splitRecord,
            Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
            Dt_StockInfoDetail newStockDetail)
        {
            _logger.LogInformation($"处理自动拆包取消 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == originalLockInfo.OrderDetailId);
@@ -877,124 +1378,113 @@
            if (orderDetail == null)
                throw new InvalidOperationException("未找到订单明细");
            // è®°å½•取消拆包前的关键数据
            decimal originalOrderDetailAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalOrderDetailLockQty = orderDetail.LockQuantity;
            // 1. æ¢å¤è®¢å•明细的分配数量(自动拆包会增加分配数量)
            decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalLockQty = orderDetail.LockQuantity;
            _logger.LogInformation($"取消拆包前数据 - è®¢å•明细分配数量: {originalOrderDetailAllocatedQty}, é”å®šæ•°é‡: {originalOrderDetailLockQty}");
            orderDetail.AllocatedQuantity -= splitRecord.SplitQty;
            orderDetail.LockQuantity -= splitRecord.SplitQty;
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            decimal originalAssignQtyBefore = originalLockInfo.AssignQuantity;
            decimal originalOrderQtyBefore = originalLockInfo.OrderQuantity;
            // è¾¹ç•Œæ£€æŸ¥
            if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
            if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
            // æ ¹æ®æ‹†åŒ…类型决定如何恢复
            if (splitRecord.IsAutoSplit)
            {
                // è‡ªåŠ¨æ‹†åŒ…ï¼šåŽŸé”å®šä¿¡æ¯ä¿æŒä¸å˜ï¼Œåªéœ€è¦åˆ é™¤æ–°é”å®šä¿¡æ¯
                _logger.LogInformation($"取消自动拆包 - åŽŸé”å®šä¿¡æ¯ä¿æŒä¸å˜");
            }
            else
            {
                // æ‰‹åŠ¨æ‹†åŒ…ï¼šæ¢å¤åŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡
                originalLockInfo.AssignQuantity += splitRecord.SplitQty;
                originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            _logger.LogInformation($"自动拆包取消恢复订单明细 - åˆ†é…æ•°é‡: {originalAllocatedQty} -> {orderDetail.AllocatedQuantity}");
                _logger.LogInformation($"取消手动拆包 - æ¢å¤åŽŸé”å®šä¿¡æ¯åˆ†é…æ•°é‡ä»Ž {originalAssignQtyBefore} å¢žåŠ åˆ° {originalLockInfo.AssignQuantity}");
            }
            // å¦‚果原锁定信息的状态是拣选完成,需要重新设置为出库中
            if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
            {
                originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                _logger.LogInformation($"原锁定信息状态从拣选完成恢复为出库中");
            }
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            // æ¢å¤åŽŸåº“å­˜æ˜Žç»†
            // 2. æ¢å¤åŽŸåº“å­˜ï¼ˆå°†æ‹†åŒ…çš„æ•°é‡åŠ å›žåŽŸåº“å­˜ï¼‰
            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            if (originalStock != null)
            {
                if (splitRecord.IsAutoSplit)
                decimal originalStockQty = originalStock.StockQuantity;
                originalStock.StockQuantity += splitRecord.SplitQty;
                // å¦‚果原库存状态是出库完成,恢复为出库锁定
                if (originalStock.Status == (int)StockStatusEmun.出库完成)
                {
                    // è‡ªåŠ¨æ‹†åŒ…ï¼šåŽŸåº“å­˜æ˜Žç»†ä¿æŒä¸å˜
                    _logger.LogInformation($"取消自动拆包 - åŽŸåº“å­˜æ˜Žç»†ä¿æŒä¸å˜");
                    originalStock.Status = (int)StockStatusEmun.出库锁定;
                }
                else
                {
                    // æ‰‹åŠ¨æ‹†åŒ…ï¼šæ¢å¤åŽŸåº“å­˜æ•°é‡
                    decimal originalStockQtyBefore = originalStock.StockQuantity;
                    originalStock.StockQuantity += splitRecord.SplitQty;
                    _logger.LogInformation($"取消手动拆包 - æ¢å¤åŽŸåº“å­˜æ˜Žç»†æ•°é‡ä»Ž {originalStockQtyBefore} å¢žåŠ åˆ° {originalStock.StockQuantity}");
                    // å¦‚果原库存状态是出库完成,需要重新设置为出库锁定
                    if (originalStock.Status == (int)StockStatusEmun.出库完成)
                    {
                        originalStock.Status = (int)StockStatusEmun.出库锁定;
                        _logger.LogInformation($"原库存状态从出库完成恢复为出库锁定");
                    }
                    await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                }
                await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                _logger.LogInformation($"自动拆包取消恢复原库存 - æ¡ç : {originalStock.Barcode}, æ•°é‡: {originalStockQty} -> {originalStock.StockQuantity}");
            }
            // 3. åˆ é™¤æ–°é”å®šä¿¡æ¯å’Œåº“存明细
            await DeleteNewSplitRecords(newLockInfo, newStockDetail);
        }
        /// <summary>
        /// å¤„理手动拆包取消
        /// </summary>
        private async Task HandleManualSplitCancel(Dt_SplitPackageRecord splitRecord,
            Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
            Dt_StockInfoDetail newStockDetail)
        {
            _logger.LogInformation($"处理手动拆包取消 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
            // 1. æ¢å¤åŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡
            decimal originalAssignQty = originalLockInfo.AssignQuantity;
            decimal originalOrderQty = originalLockInfo.OrderQuantity;
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            // æ¢å¤çŠ¶æ€
            if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
            {
                originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            _logger.LogInformation($"手动拆包取消恢复原锁定信息 - åˆ†é…æ•°é‡: {originalAssignQty} -> {originalLockInfo.AssignQuantity}");
            // 2. æ¢å¤åŽŸåº“å­˜æ˜Žç»†
            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            if (originalStock != null)
            {
                decimal originalStockQty = originalStock.StockQuantity;
                originalStock.StockQuantity += splitRecord.SplitQty;
                // æ¢å¤åº“存状态
                if (originalStock.Status == (int)StockStatusEmun.出库完成)
                {
                    originalStock.Status = (int)StockStatusEmun.出库锁定;
                }
                await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                _logger.LogInformation($"手动拆包取消恢复原库存 - æ¡ç : {originalStock.Barcode}, æ•°é‡: {originalStockQty} -> {originalStock.StockQuantity}");
            }
            // 3. åˆ é™¤æ–°é”å®šä¿¡æ¯å’Œåº“存明细
            await DeleteNewSplitRecords(newLockInfo, newStockDetail);
        }
        /// <summary>
        /// åˆ é™¤æ–°æ‹†åŒ…记录相关的数据
        /// </summary>
        private async Task DeleteNewSplitRecords(Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
        {
            // åˆ é™¤æ–°é”å®šä¿¡æ¯
            _logger.LogInformation($"删除新锁定信息 - æ¡ç : {newLockInfo.CurrentBarcode}, åˆ†é…æ•°é‡: {newLockInfo.AssignQuantity}");
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
            if (newLockInfo != null)
            {
                _logger.LogInformation($"删除新锁定信息 - æ¡ç : {newLockInfo.CurrentBarcode}, åˆ†é…æ•°é‡: {newLockInfo.AssignQuantity}");
                await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                    .Where(x => x.Id == newLockInfo.Id)
                    .ExecuteCommandAsync();
            }
            // åˆ é™¤æ–°åº“存明细
            _logger.LogInformation($"删除新库存明细 - æ¡ç : {newStockDetail.Barcode}, åº“存数量: {newStockDetail.StockQuantity}");
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
            // å¦‚果是自动拆包,需要减少订单明细的分配数量和锁定数量
            if (splitRecord.IsAutoSplit)
            if (newStockDetail != null)
            {
                decimal originalAllocatedBefore = orderDetail.AllocatedQuantity;
                decimal originalLockBefore = orderDetail.LockQuantity;
                orderDetail.AllocatedQuantity -= splitRecord.SplitQty;
                orderDetail.LockQuantity -= splitRecord.SplitQty;
                // è¾¹ç•Œæ£€æŸ¥ï¼šç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
                if (orderDetail.AllocatedQuantity < 0)
                {
                    _logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + splitRecord.SplitQty}, å‡å°‘: {splitRecord.SplitQty}");
                    orderDetail.AllocatedQuantity = 0;
                }
                if (orderDetail.LockQuantity < 0)
                {
                    _logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + splitRecord.SplitQty}, å‡å°‘: {splitRecord.SplitQty}");
                    orderDetail.LockQuantity = 0;
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                _logger.LogInformation($"取消自动拆包减少订单明细数量 - åˆ†é…æ•°é‡ä»Ž {originalAllocatedBefore} å‡å°‘到 {orderDetail.AllocatedQuantity}");
                _logger.LogInformation($"取消自动拆包减少订单明细数量 - é”å®šæ•°é‡ä»Ž {originalLockBefore} å‡å°‘到 {orderDetail.LockQuantity}");
                _logger.LogInformation($"删除新库存明细 - æ¡ç : {newStockDetail.Barcode}, åº“存数量: {newStockDetail.StockQuantity}");
                await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == newStockDetail.Barcode)
                    .ExecuteCommandAsync();
            }
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            _logger.LogInformation($"标记拆包记录为已撤销");
            // éªŒè¯å–消拆包后数据一致性
            await ValidateDataConsistencyAfterCancelSplit(orderDetail.Id, originalOrderDetailAllocatedQty, originalOrderDetailLockQty, splitRecord.IsAutoSplit, splitRecord.SplitQty);
            // æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•状态
            await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
            await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
            _logger.LogInformation($"取消拆包逻辑执行完成");
        }
        /// <summary>
        /// éªŒè¯å–消拆包后数据一致性 - æœ€æ–°ç‰ˆæœ¬
@@ -1682,45 +2172,93 @@
            return totalQty;
        }
        /// <summary>
        /// ç»Ÿä¸€å›žåº“方法 - å¤„理托盘上所有剩余货物
        /// ç»Ÿä¸€å›žåº“方法
        /// </summary>
        public async Task<WebResponseContent> ExecutePalletReturn(string orderNo, string palletCode, string returnReason = "分批回库")
        {
            try
            {
                _logger.LogInformation($"【增强回库开始】订单: {orderNo}, æ‰˜ç›˜: {palletCode}");
                _unitOfWorkManage.BeginTran();
                //  åŸºç¡€éªŒè¯
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                // èŽ·å–åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                int stockId = stockInfo.Id;
                //  æ‰§è¡Œå›žåº“前数据验证
                var validationResult = await ValidateDataBeforeReturn(orderNo, palletCode, stockId);
                if (!validationResult.IsValid)
                {
                    _logger.LogWarning($"回库前数据验证失败: {validationResult.ErrorMessage}");
                    // å¯ä»¥æ ¹æ®å®žé™…情况决定是否继续
                }
                // åˆ†æžæ‰˜ç›˜çŠ¶æ€
                var statusAnalysis = await AnalyzePalletStatusForReturn(orderNo, palletCode, stockInfo.Id);
                if (!statusAnalysis.HasItemsToReturn)
                    return await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
                {
                    try
                    {
                        _logger.LogInformation($"【无回库物品】处理空托盘");
                        var result = await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
                        _unitOfWorkManage.CommitTran();
                        return result;
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        _logger.LogError($"空箱回库失败: {ex.Message}");
                        return WebResponseContent.Instance.Error($"空箱回库失败:{ex.Message}");
                    }
                }
                _logger.LogInformation($"开始回库操作 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, å›žåº“数量: {statusAnalysis.TotalReturnQty}");
                _logger.LogInformation($"【开始回库】总回库数量: {statusAnalysis.TotalReturnQty}, æ¡ç æ•°: {statusAnalysis.AllBarcodes.Count}");
                // æ‰§è¡Œå›žåº“数据操作
                await ExecuteReturnDataOperations(statusAnalysis);
                //  æ‰§è¡Œå›žåº“操作
                try
                {
                    await ExecuteEnhancedReturnOperations(statusAnalysis);
                }
                catch (Exception ex)
                {
                    _logger.LogError($"主回库方法失败: {ex.Message}");
                    // å°è¯•简化方法
                    await ExecuteSimpleReturnDataOperations(statusAnalysis);
                }
                // æ›´æ–°è®¢å•状态
                //  æ›´æ–°è®¢å•状态
                await UpdateOrderStatusAfterReturn(orderNo);
                //  åˆ›å»ºå›žåº“任务
                try
                {
                    await CreateReturnTask(orderNo, palletCode, stockInfo);
                }
                catch (Exception taskEx)
                {
                    _logger.LogError($"回库任务创建失败: {taskEx.Message}");
                    // ä»»åŠ¡åˆ›å»ºå¤±è´¥ä¸å½±å“æ•°æ®å›žåº“
                }
                _unitOfWorkManage.CommitTran();
                // 8. å›žåº“后验证
                await ValidateDataAfterReturn(orderNo, palletCode, stockId);
                // åˆ›å»ºå›žåº“任务(AGV)
                await CreateReturnTask(orderNo, palletCode, stockInfo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}", new
                return WebResponseContent.Instance.OK($"回库成功,回库数量:{statusAnalysis.TotalReturnQty}", new
                {
                    ReturnQuantity = statusAnalysis.TotalReturnQty,
                    ReturnBarcodes = statusAnalysis.AllBarcodes,
@@ -1732,13 +2270,910 @@
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"回库操作失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
                _logger.LogError($"回库失败: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å¢žå¼ºçš„回库前数据验证
        /// </summary>
        private async Task<ValidationResult<bool>> ValidateDataBeforeReturn(string orderNo, string palletCode, int stockId)
        {
            var errors = new List<string>();
            try
            {
                // 1. éªŒè¯åº“存数据
                var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId)
                    .ToListAsync();
                // æ£€æŸ¥åº“存数量是否为负数
                var negativeStocks = stockDetails.Where(x => x.StockQuantity < 0).ToList();
                if (negativeStocks.Any())
                {
                    errors.Add($"发现负数库存: {string.Join(", ", negativeStocks.Select(x => $"{x.Barcode}:{x.StockQuantity}"))}");
                }
                // 2. éªŒè¯é”å®šè®°å½•
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                // æ£€æŸ¥å·²æ‹£é€‰æ•°é‡æ˜¯å¦å¤§äºŽåˆ†é…æ•°é‡
                var invalidLocks = lockInfos.Where(x => x.PickedQty > x.AssignQuantity).ToList();
                if (invalidLocks.Any())
                {
                    errors.Add($"发现已拣选数量大于分配数量的锁定记录");
                }
                // 3. éªŒè¯æ‹†åŒ…记录
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                // æ£€æŸ¥å¾ªçŽ¯æ‹†åŒ…
                var barcodeMap = new Dictionary<string, string>();
                foreach (var record in splitRecords.Where(x => !x.IsReverted))
                {
                    if (!barcodeMap.ContainsKey(record.OriginalBarcode))
                        barcodeMap[record.OriginalBarcode] = record.NewBarcode;
                }
                // æ£€æŸ¥å¾ªçŽ¯å¼•ç”¨
                foreach (var record in splitRecords)
                {
                    var current = record.OriginalBarcode;
                    var visited = new HashSet<string>();
                    while (barcodeMap.ContainsKey(current))
                    {
                        if (visited.Contains(current))
                        {
                            errors.Add($"发现循环拆包引用: {record.OriginalBarcode}");
                            break;
                        }
                        visited.Add(current);
                        current = barcodeMap[current];
                    }
                }
                if (errors.Any())
                {
                    return ValidationResult<bool>.Error($"回库前数据验证发现问题: {string.Join("; ", errors)}");
                }
                return ValidationResult<bool>.Success(true);
            }
            catch (Exception ex)
            {
                _logger.LogError($"数据验证失败: {ex.Message}");
                return ValidationResult<bool>.Error($"数据验证异常: {ex.Message}");
            }
        }
        // <summary>
        /// æ‰§è¡Œå¢žå¼ºçš„回库操作 - ä¿®æ­£ç‰ˆï¼ˆé¿å…é‡å¤å¤„理)
        /// </summary>
        private async Task ExecuteEnhancedReturnOperations(PalletStatusAnalysis statusAnalysis)
        {
            _logger.LogInformation($"【开始执行回库操作】订单: {statusAnalysis.OrderNo}, æ‰˜ç›˜: {statusAnalysis.PalletCode}");
            // ä½¿ç”¨æœ¬åœ°å·²å¤„理集合,避免重复
            var locallyProcessedBarcodes = new HashSet<string>();
            decimal totalProcessedQty = 0;
            // 1. å¤„理已分配的锁定记录
            if (statusAnalysis.HasRemainingLocks)
            {
                _logger.LogInformation($"处理已分配锁定记录 - {statusAnalysis.RemainingLocks.Count} æ¡");
                foreach (var lockInfo in statusAnalysis.RemainingLocks)
                {
                    if (string.IsNullOrEmpty(lockInfo.CurrentBarcode) ||
                        locallyProcessedBarcodes.Contains(lockInfo.CurrentBarcode))
                    {
                        _logger.LogInformation($"跳过已处理条码的锁定记录: {lockInfo.CurrentBarcode}");
                        continue;
                    }
                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    if (returnQty > 0)
                    {
                        // ã€ä¿®å¤ã€‘传递 isUnallocated = false
                        await ProcessSingleBarcodeReturn(lockInfo.CurrentBarcode, statusAnalysis.StockId, returnQty, false);
                        locallyProcessedBarcodes.Add(lockInfo.CurrentBarcode);
                        totalProcessedQty += returnQty;
                        _logger.LogInformation($"已处理锁定记录 - æ¡ç : {lockInfo.CurrentBarcode}, æ•°é‡: {returnQty}");
                    }
                }
            }
            // 2. å¤„理未分配的锁定记录
            if (statusAnalysis.HasUnallocatedLocks)
            {
                _logger.LogInformation($"处理未分配锁定记录 - {statusAnalysis.UnallocatedLocks.Count} æ¡");
                foreach (var lockInfo in statusAnalysis.UnallocatedLocks)
                {
                    if (string.IsNullOrEmpty(lockInfo.CurrentBarcode) ||
                        locallyProcessedBarcodes.Contains(lockInfo.CurrentBarcode))
                    {
                        _logger.LogInformation($"跳过已处理条码的未分配锁定: {lockInfo.CurrentBarcode}");
                        continue;
                    }
                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    if (returnQty > 0)
                    {
                        // ã€ä¿®å¤ã€‘使用专门的未分配锁定处理方法
                        await ProcessUnallocatedLockReturn(lockInfo, returnQty);
                        locallyProcessedBarcodes.Add(lockInfo.CurrentBarcode);
                        totalProcessedQty += returnQty;
                        _logger.LogInformation($"已处理未分配锁定 - æ¡ç : {lockInfo.CurrentBarcode}, æ•°é‡: {returnQty}");
                    }
                }
            }
            // 3. å¤„理未分配的库存货物
            if (statusAnalysis.HasPalletStockGoods)
            {
                _logger.LogInformation($"处理未分配库存货物 - {statusAnalysis.PalletStockGoods.Count} ä¸ª");
                foreach (var stockDetail in statusAnalysis.PalletStockGoods)
                {
                    if (string.IsNullOrEmpty(stockDetail.Barcode) ||
                        locallyProcessedBarcodes.Contains(stockDetail.Barcode))
                    {
                        _logger.LogInformation($"跳过已处理条码的库存: {stockDetail.Barcode}");
                        continue;
                    }
                    decimal returnQty = stockDetail.StockQuantity;
                    if (returnQty > 0)
                    {
                        await ProcessUnallocatedStockReturn(stockDetail);
                        locallyProcessedBarcodes.Add(stockDetail.Barcode);
                        totalProcessedQty += returnQty;
                        _logger.LogInformation($"已处理未分配库存 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {returnQty}");
                    }
                }
            }
            // 4. ã€ä¿®æ­£ã€‘处理拆包记录 - åªå¤„理未被其他逻辑覆盖的条码
            if (statusAnalysis.HasSplitRecords && statusAnalysis.SplitReturnQty > 0)
            {
                _logger.LogInformation($"处理拆包记录相关库存 - æ–°å¢žæ•°é‡: {statusAnalysis.SplitReturnQty}");
                // åªå¤„理在SplitRecords中但不在已处理集合中的条码
                var splitBarcodes = statusAnalysis.SplitRecords
                    .SelectMany(r => new[] { r.OriginalBarcode, r.NewBarcode })
                    .Where(b => !string.IsNullOrEmpty(b))
                    .Distinct()
                    .ToList();
                foreach (var barcode in splitBarcodes)
                {
                    if (locallyProcessedBarcodes.Contains(barcode))
                    {
                        _logger.LogInformation($"跳过已处理的拆包条码: {barcode}");
                        continue;
                    }
                    var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .FirstAsync(x => x.Barcode == barcode && x.StockId == statusAnalysis.StockId);
                    if (stockDetail != null && stockDetail.StockQuantity > 0)
                    {
                        decimal returnQty = stockDetail.StockQuantity;
                        await ProcessSingleBarcodeReturn(barcode, statusAnalysis.StockId, returnQty);
                        locallyProcessedBarcodes.Add(barcode);
                        totalProcessedQty += returnQty;
                        _logger.LogInformation($"处理拆包条码 - {barcode}, æ•°é‡: {returnQty}");
                    }
                }
            }
            _logger.LogInformation($"【回库操作完成】总处理数量: {totalProcessedQty}, å¤„理条码数: {locallyProcessedBarcodes.Count}");
            // éªŒè¯å¤„理数量与预期一致
            if (Math.Abs(totalProcessedQty - statusAnalysis.TotalReturnQty) > 0.01m)
            {
                _logger.LogWarning($"处理数量({totalProcessedQty})与预期({statusAnalysis.TotalReturnQty})不一致,但继续执行");
            }
        }
        /// <summary>
        /// å¤„理已分配锁定记录的回库
        /// å·²åˆ†é…é”å®šè®°å½•需要:1.恢复库存 2.更新锁定状态 3.减少订单明细分配数量
        /// </summary>
        private async Task ProcessSingleLockReturn(Dt_OutStockLockInfo lockInfo, decimal returnQty)
        {
            try
            {
                _logger.LogInformation($"开始处理已分配锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                if (returnQty <= 0)
                {
                    _logger.LogInformation($"回库数量无效({returnQty}),跳过处理");
                    return;
                }
                // 1. éªŒè¯é”å®šè®°å½•状态
                if (lockInfo.Status != (int)OutLockStockStatusEnum.出库中 &&
                    lockInfo.Status != (int)OutLockStockStatusEnum.回库中)
                {
                    _logger.LogWarning($"锁定记录状态不是出库中或回库中,跳过处理 - çŠ¶æ€: {lockInfo.Status}");
                    return;
                }
                // 2. èŽ·å–å…³è”çš„åº“å­˜æ˜Žç»†
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                if (stockDetail == null)
                {
                    _logger.LogError($"未找到库存明细 - æ¡ç : {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
                    throw new InvalidOperationException($"库存明细不存在: {lockInfo.CurrentBarcode}");
                }
                // 3. èŽ·å–å…³è”çš„è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                if (orderDetail == null)
                {
                    _logger.LogError($"未找到订单明细 - OrderDetailId: {lockInfo.OrderDetailId}");
                    throw new InvalidOperationException($"订单明细不存在: {lockInfo.OrderDetailId}");
                }
                // è®°å½•原始值(用于日志和回滚检查)
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                int originalStockStatus = stockDetail.Status;
                decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
                decimal originalLockQty = orderDetail.LockQuantity;
                decimal originalOverOutQty = orderDetail.OverOutQuantity;
                decimal originalLockPickedQty = lockInfo.PickedQty;
                decimal originalLockAssignQty = lockInfo.AssignQuantity;
                int originalLockStatus = lockInfo.Status;
                _logger.LogInformation($"回库前数据状态:");
                _logger.LogInformation($"  åº“å­˜ - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {originalStockQty}, å‡ºåº“: {originalOutboundQty}, çŠ¶æ€: {GetStockStatusName(originalStockStatus)}");
                _logger.LogInformation($"  è®¢å•明细 - åˆ†é…: {originalAllocatedQty}, é”å®š: {originalLockQty}, å·²å‡ºåº“: {originalOverOutQty}");
                _logger.LogInformation($"  é”å®šè®°å½• - åˆ†é…: {originalLockAssignQty}, å·²æ‹£é€‰: {originalLockPickedQty}, çŠ¶æ€: {GetLockStatusName(originalLockStatus)}");
                // 4. ã€æ ¸å¿ƒé€»è¾‘】恢复库存数据
                // 4.1 å¢žåŠ åº“å­˜æ•°é‡
                stockDetail.StockQuantity += returnQty;
                // 4.2 å‡å°‘出库数量(但需确保不会出现负数)
                if (stockDetail.OutboundQuantity >= returnQty)
                {
                    stockDetail.OutboundQuantity -= returnQty;
                }
                else
                {
                    // å¦‚果出库数量小于回库数量,说明数据异常
                    _logger.LogWarning($"出库数量({stockDetail.OutboundQuantity})小于回库数量({returnQty}),数据异常");
                    stockDetail.OutboundQuantity = 0;
                }
                // 4.3 æ›´æ–°åº“存状态
                if (stockDetail.OutboundQuantity <= 0 && stockDetail.StockQuantity > 0)
                {
                    stockDetail.Status = (int)StockStatusEmun.入库完成;
                    _logger.LogInformation($"库存状态更新为: å…¥åº“完成");
                }
                else if (stockDetail.StockQuantity > 0)
                {
                    stockDetail.Status = (int)StockStatusEmun.出库锁定;
                    _logger.LogInformation($"库存状态保持为: å‡ºåº“锁定");
                }
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"库存更新完成 - æ¡ç : {stockDetail.Barcode}");
                _logger.LogInformation($"  åº“存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
                _logger.LogInformation($"  å‡ºåº“数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
                _logger.LogInformation($"  çŠ¶æ€: {GetStockStatusName(originalStockStatus)} -> {GetStockStatusName(stockDetail.Status)}");
                // 5. æ›´æ–°é”å®šè®°å½•状态
                lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                lockInfo.Operator = App.User.UserName;
                // ã€é‡è¦ã€‘如果锁定记录是部分拣选后回库,需要调整已拣选数量
                // ä½†ä¸€èˆ¬æƒ…况下,回库的是未拣选的部分,所以PickedQty保持不变
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"锁定记录更新完成 - ID: {lockInfo.Id}");
                _logger.LogInformation($"  çŠ¶æ€: {GetLockStatusName(originalLockStatus)} -> {GetLockStatusName(lockInfo.Status)}");
                _logger.LogInformation($"  åˆ†é…æ•°é‡: {originalLockAssignQty} (不变)");
                _logger.LogInformation($"  å·²æ‹£é€‰æ•°é‡: {originalLockPickedQty} (不变)");
                // 6. æ›´æ–°è®¢å•明细数据
                // 6.1 å‡å°‘已分配数量
                if (orderDetail.AllocatedQuantity >= returnQty)
                {
                    orderDetail.AllocatedQuantity -= returnQty;
                }
                else
                {
                    // å¦‚果分配数量小于回库数量,说明数据异常
                    _logger.LogWarning($"分配数量({orderDetail.AllocatedQuantity})小于回库数量({returnQty}),重置为0");
                    orderDetail.AllocatedQuantity = 0;
                }
                // 6.2 å‡å°‘锁定数量(应与分配数量保持同步)
                if (orderDetail.LockQuantity >= returnQty)
                {
                    orderDetail.LockQuantity -= returnQty;
                }
                else
                {
                    _logger.LogWarning($"锁定数量({orderDetail.LockQuantity})小于回库数量({returnQty}),重置为0");
                    orderDetail.LockQuantity = 0;
                }
                // 6.3 å·²å‡ºåº“数量保持不变(回库不影响已出库数量)
                // orderDetail.OverOutQuantity = orderDetail.OverOutQuantity;
                // 6.4 æ›´æ–°æ‰¹æ¬¡åˆ†é…çŠ¶æ€
                await UpdateBatchAllocateStatus(orderDetail);
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                _logger.LogInformation($"订单明细更新完成 - ID: {orderDetail.Id}");
                _logger.LogInformation($"  åˆ†é…æ•°é‡: {originalAllocatedQty} -> {orderDetail.AllocatedQuantity}");
                _logger.LogInformation($"  é”å®šæ•°é‡: {originalLockQty} -> {orderDetail.LockQuantity}");
                _logger.LogInformation($"  å·²å‡ºåº“数量: {originalOverOutQty} (保持不变)");
                // 7. éªŒè¯å›žåº“后的数据一致性
                await ValidateAfterLockReturn(lockInfo, stockDetail, orderDetail, returnQty);
                _logger.LogInformation($"已分配锁定记录回库处理完成 - é”å®šID: {lockInfo.Id}, å›žåº“数量: {returnQty}");
            }
            catch (Exception ex)
            {
                _logger.LogError($"处理已分配锁定记录回库失败 - é”å®šID: {lockInfo.Id}, Error: {ex.Message}");
                throw new InvalidOperationException($"处理锁定记录回库失败: {ex.Message}", ex);
            }
        }
        /// <summary>
        /// é”å®šè®°å½•回库后验证数据一致性
        /// </summary>
        private async Task ValidateAfterLockReturn(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            Dt_OutboundOrderDetail orderDetail, decimal returnQty)
        {
            try
            {
                _logger.LogInformation($"开始回库后数据验证 - é”å®šID: {lockInfo.Id}");
                bool allValid = true;
                List<string> validationErrors = new List<string>();
                // 1. é‡æ–°èŽ·å–æœ€æ–°æ•°æ®
                var refreshedStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Id == stockDetail.Id);
                var refreshedOrder = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetail.Id);
                var refreshedLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == lockInfo.Id);
                // 2. éªŒè¯åº“存数据
                // è®¡ç®—库存数量应该增加回库数量
                decimal expectedStockQty = stockDetail.StockQuantity + returnQty;
                if (Math.Abs(refreshedStock.StockQuantity - expectedStockQty) > 0.01m)
                {
                    string error = $"库存数量不正确!期望: {expectedStockQty}, å®žé™…: {refreshedStock.StockQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // éªŒè¯åº“存状态应为入库完成或出库锁定
                if (refreshedStock.Status != (int)StockStatusEmun.入库完成 &&
                    refreshedStock.Status != (int)StockStatusEmun.出库锁定)
                {
                    string error = $"库存状态异常!期望:入库完成或出库锁定, å®žé™…:{GetStockStatusName(refreshedStock.Status)}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 3. éªŒè¯é”å®šè®°å½•状态
                if (refreshedLock.Status != (int)OutLockStockStatusEnum.已回库)
                {
                    string error = $"锁定记录状态异常!期望:已回库, å®žé™…:{GetLockStatusName(refreshedLock.Status)}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 4. éªŒè¯è®¢å•明细数据
                // è®¡ç®—分配数量应该减少回库数量
                decimal expectedAllocatedQty = Math.Max(0, orderDetail.AllocatedQuantity - returnQty);
                if (Math.Abs(refreshedOrder.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
                {
                    string error = $"订单分配数量不正确!期望: {expectedAllocatedQty}, å®žé™…: {refreshedOrder.AllocatedQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // åˆ†é…æ•°é‡åº”与锁定数量一致
                if (Math.Abs(refreshedOrder.AllocatedQuantity - refreshedOrder.LockQuantity) > 0.01m)
                {
                    string error = $"订单分配数量与锁定数量不一致!分配: {refreshedOrder.AllocatedQuantity}, é”å®š: {refreshedOrder.LockQuantity}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 5. æ•°æ®å…³è”性验证
                // é”å®šè®°å½•的已拣选数量 + å½“前库存的出库数量应等于原始出库数量
                decimal totalOutboundFromLock = refreshedLock.PickedQty + refreshedStock.OutboundQuantity;
                // æŸ¥æ‰¾è¯¥æ¡ç çš„æ‰€æœ‰æœªå–消拣选记录
                var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Barcode == stockDetail.Barcode && !x.IsCancelled)
                    .ToListAsync();
                decimal totalPickedFromRecords = pickingRecords.Sum(x => x.PickQuantity);
                if (Math.Abs(totalOutboundFromLock - totalPickedFromRecords) > 0.01m)
                {
                    string error = $"数据关联性异常!锁定拣选({refreshedLock.PickedQty})+库存出库({refreshedStock.OutboundQuantity})={totalOutboundFromLock}, ä½†æ‹£é€‰è®°å½•总和={totalPickedFromRecords}";
                    validationErrors.Add(error);
                    allValid = false;
                    _logger.LogError(error);
                }
                // 6. è¾“出验证结果
                if (allValid)
                {
                    _logger.LogInformation($"回库后数据验证全部通过");
                }
                else
                {
                    _logger.LogError($"回库后数据验证失败,发现{validationErrors.Count}个问题");
                    foreach (var error in validationErrors.Take(3)) // åªæ˜¾ç¤ºå‰3个问题
                    {
                        _logger.LogError($"验证问题: {error}");
                    }
                    // å¦‚果问题严重,可以抛出异常
                    if (validationErrors.Any(e => e.Contains("异常")))
                    {
                        throw new InvalidOperationException($"回库后数据验证失败: {string.Join("; ", validationErrors.Take(2))}");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"回库后验证异常: {ex.Message}");
                // ä¸é‡æ–°æŠ›å‡ºï¼Œé¿å…å½±å“ä¸»æµç¨‹
            }
        }
        /// <summary>
        /// å¤„理单个条码回库(通用方法)- åŒºåˆ†å·²åˆ†é…å’Œæœªåˆ†é…
        /// </summary>
        private async Task ProcessSingleBarcodeReturn(string barcode, int stockId, decimal returnQty, bool isUnallocated = false)
        {
            try
            {
                _logger.LogInformation($"处理单个条码回库 - {barcode}, æ•°é‡: {returnQty}, æ˜¯å¦æœªåˆ†é…: {isUnallocated}");
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
                if (stockDetail == null)
                {
                    _logger.LogWarning($"未找到库存明细: {barcode}");
                    return;
                }
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                int originalStatus = stockDetail.Status;
                _logger.LogInformation($"回库前状态 - åº“å­˜: {originalStockQty}, å‡ºåº“: {originalOutboundQty}, çŠ¶æ€: {GetStockStatusName(originalStatus)}");
                // ã€ä¿®å¤ã€‘根据是否未分配决定处理逻辑
                if (isUnallocated)
                {
                    // æœªåˆ†é…é”å®šï¼šåªæ¢å¤çŠ¶æ€ï¼Œä¸æ”¹å˜åº“å­˜æ•°é‡
                    // å‡ºåº“数量应为0
                    if (stockDetail.OutboundQuantity > 0)
                    {
                        _logger.LogWarning($"未分配锁定的库存出库数量不为0,重置为0 - æ¡ç : {stockDetail.Barcode}, å½“前出库: {stockDetail.OutboundQuantity}");
                        stockDetail.OutboundQuantity = 0;
                    }
                    // åº“存数量保持不变
                    _logger.LogInformation($"未分配锁定回库 - åº“存数量保持不变: {stockDetail.StockQuantity}");
                }
                else
                {
                    // å·²åˆ†é…é”å®šï¼šæ¢å¤åº“存数量
                    stockDetail.StockQuantity += returnQty;
                    // å‡å°‘出库数量(如果出库数量大于0)
                    if (stockDetail.OutboundQuantity >= returnQty)
                    {
                        stockDetail.OutboundQuantity -= returnQty;
                    }
                    else
                    {
                        // å¦‚果出库数量小于回库数量,说明数据异常,出库数量清零
                        _logger.LogWarning($"出库数量({stockDetail.OutboundQuantity})小于回库数量({returnQty}),清零");
                        stockDetail.OutboundQuantity = 0;
                    }
                    _logger.LogInformation($"已分配锁定回库 - åº“存数量增加: {originalStockQty} -> {stockDetail.StockQuantity}");
                }
                // æ›´æ–°çŠ¶æ€
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"条码回库完成 - {barcode}: åº“å­˜ {originalStockQty}->{stockDetail.StockQuantity}, å‡ºåº“ {originalOutboundQty}->{stockDetail.OutboundQuantity}");
            }
            catch (Exception ex)
            {
                _logger.LogError($"处理条码回库失败 - {barcode}: {ex.Message}");
                throw;
            }
        }
        /// <summary>
        /// æ‰§è¡Œå›žåº“数据操作
        /// ç¡®ä¿ä¸ä¼šå°†ç”Ÿæˆçš„æ¡ç æ•°é‡é”™è¯¯ç»‘定到锁定数量
        /// å¤„理未分配锁定记录回库 - ä¿®å¤ç‰ˆæœ¬
        /// æœªåˆ†é…é”å®šè®°å½•:只恢复状态,不增加库存数量
        /// </summary>
        private async Task ProcessUnallocatedLockReturn(Dt_OutStockLockInfo lockInfo, decimal returnQty)
        {
            _logger.LogInformation($"处理未分配锁定回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, æ•°é‡: {returnQty}");
            try
            {
                // éªŒè¯é”å®šè®°å½•状态
                if (lockInfo.Status != (int)OutLockStockStatusEnum.出库中 &&
                    lockInfo.Status != (int)OutLockStockStatusEnum.回库中)
                {
                    _logger.LogWarning($"锁定记录状态不是出库中或回库中,跳过处理 - çŠ¶æ€: {lockInfo.Status}");
                    return;
                }
                // ã€ä¿®å¤ã€‘获取库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                if (stockDetail == null)
                {
                    _logger.LogError($"未找到库存明细 - æ¡ç : {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
                    throw new InvalidOperationException($"库存明细不存在: {lockInfo.CurrentBarcode}");
                }
                // è®°å½•原始状态
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                int originalStatus = stockDetail.Status;
                _logger.LogInformation($"未分配锁定回库前状态:");
                _logger.LogInformation($"  åº“å­˜ - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {originalStockQty}, å‡ºåº“: {originalOutboundQty}, çŠ¶æ€: {GetStockStatusName(originalStatus)}");
                // ã€ä¿®å¤å…³é”®ã€‘对于未分配锁定记录,库存数量不应改变!
                // å› ä¸ºåº“存本来就存在,只是状态被锁定
                // stockDetail.StockQuantity ä¿æŒä¸å˜
                // å‡ºåº“数量应为0(未分配锁定不应该有出库)
                if (stockDetail.OutboundQuantity > 0)
                {
                    _logger.LogWarning($"未分配锁定的库存出库数量不为0,重置为0 - æ¡ç : {stockDetail.Barcode}, å½“前出库: {stockDetail.OutboundQuantity}");
                    stockDetail.OutboundQuantity = 0;
                }
                // æ›´æ–°åº“存状态为入库完成(恢复为可用状态)
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"更新未分配库存状态 - æ¡ç : {stockDetail.Barcode}");
                _logger.LogInformation($"  åº“存数量: {originalStockQty} -> {stockDetail.StockQuantity} (保持不变)");
                _logger.LogInformation($"  å‡ºåº“数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
                _logger.LogInformation($"  çŠ¶æ€: {GetStockStatusName(originalStatus)} -> {GetStockStatusName(stockDetail.Status)}");
                // æ›´æ–°é”å®šè®°å½•状态为已回库
                lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                lockInfo.Operator = App.User.UserName;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"更新未分配锁定状态 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: å‡ºåº“中 -> å·²å›žåº“");
            }
            catch (Exception ex)
            {
                _logger.LogError($"处理未分配锁定回库失败 - é”å®šID: {lockInfo.Id}, Error: {ex.Message}");
                throw new InvalidOperationException($"处理未分配锁定回库失败: {ex.Message}", ex);
            }
        }
        /// <summary>
        /// å¤„理未分配库存回库
        /// </summary>
        private async Task ProcessUnallocatedStockReturn(Dt_StockInfoDetail stockDetail)
        {
            _logger.LogInformation($"处理未分配库存回库 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
            // ç›´æŽ¥æ›´æ–°åº“存状态
            stockDetail.Status = (int)StockStatusEmun.入库完成;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
        }
        /// <summary>
        /// å›žåº“后数据验证 - å¢žå¼ºç‰ˆ
        /// </summary>
        private async Task ValidateDataAfterReturn(string orderNo, string palletCode, int stockId)
        {
            try
            {
                _logger.LogInformation($"开始回库后数据验证");
                // 1. éªŒè¯åº“存状态和数量
                var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId)
                    .ToListAsync();
                decimal totalStock = stockDetails.Sum(x => x.StockQuantity);
                // éªŒè¯åº“存数量是否合理
                var unreasonableStocks = stockDetails.Where(x => x.StockQuantity < 0).ToList();
                if (unreasonableStocks.Any())
                {
                    _logger.LogError($"发现负数库存数量!条码: {string.Join(", ", unreasonableStocks.Select(x => x.Barcode))}");
                }
                // 2. éªŒè¯é”å®šè®°å½•状态
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                var notReturnedLocks = lockInfos.Where(x =>
                    x.Status != (int)OutLockStockStatusEnum.已回库 &&
                    x.Status != (int)OutLockStockStatusEnum.已取走 &&
                    x.Status != (int)OutLockStockStatusEnum.拣选完成).ToList();
                if (notReturnedLocks.Any())
                {
                    _logger.LogWarning($"回库后仍有未回库状态的锁定记录: {notReturnedLocks.Count}条");
                    foreach (var lockInfo in notReturnedLocks)
                    {
                        _logger.LogWarning($"未回库锁定 - ID: {lockInfo.Id}, çŠ¶æ€: {GetLockStatusName(lockInfo.Status)}, æ¡ç : {lockInfo.CurrentBarcode}");
                    }
                }
                // 3. æ•°æ®ä¸€è‡´æ€§éªŒè¯
                decimal totalExpectedReturnQty = lockInfos
                    .Where(x => x.Status == (int)OutLockStockStatusEnum.已回库)
                    .Sum(x => x.AssignQuantity - x.PickedQty);
                _logger.LogInformation($"回库验证汇总:");
                _logger.LogInformation($"  å›žåº“后库存总量: {totalStock}");
                _logger.LogInformation($"  å·²å›žåº“锁定记录数量: {lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.已回库)}");
                _logger.LogInformation($"  æ€»å›žåº“数量(锁定记录计算): {totalExpectedReturnQty}");
                // 4. éªŒè¯åº“存数量与锁定记录的一致性
                foreach (var lockInfo in lockInfos.Where(x => !string.IsNullOrEmpty(x.CurrentBarcode)))
                {
                    var stock = stockDetails.FirstOrDefault(x => x.Barcode == lockInfo.CurrentBarcode);
                    if (stock != null)
                    {
                        // å¦‚果锁定记录是已回库状态,对应的库存应该是入库完成状态
                        if (lockInfo.Status == (int)OutLockStockStatusEnum.已回库 &&
                            stock.Status != (int)StockStatusEmun.入库完成)
                        {
                            _logger.LogWarning($"锁定记录已回库但库存状态不正确 - æ¡ç : {lockInfo.CurrentBarcode}, åº“存状态: {GetStockStatusName(stock.Status)}");
                        }
                    }
                }
                _logger.LogInformation($"回库后数据验证完成");
            }
            catch (Exception ex)
            {
                _logger.LogError($"回库后验证失败: {ex.Message}");
            }
        }
        /// <summary>
        /// éªŒè¯å›žåº“前后数据一致性
        /// </summary>
        private async Task<bool> ValidateReturnData(string orderNo, string palletCode, int stockId, bool isBefore = true)
        {
            string phase = isBefore ? "回库前" : "回库后";
            try
            {
                _logger.LogInformation($"【{phase}数据验证】开始 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                // 1. æ£€æŸ¥åº“存明细
                var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId)
                    .ToListAsync();
                decimal totalStockQty = stockDetails.Sum(x => x.StockQuantity);
                _logger.LogInformation($"{phase}库存总量: {totalStockQty}");
                // 2. æ£€æŸ¥é”å®šè®°å½•
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                // æ£€æŸ¥é”å®šè®°å½•状态分布
                var statusGroups = lockInfos.GroupBy(x => x.Status)
                    .Select(g => new { Status = g.Key, Count = g.Count() })
                    .ToList();
                foreach (var group in statusGroups)
                {
                    _logger.LogInformation($"{phase}锁定状态 {GetLockStatusName(group.Status)}: {group.Count} æ¡");
                }
                // 3. åŸºæœ¬éªŒè¯
                bool isValid = true;
                // éªŒè¯1: å¦‚果锁定记录状态为"拣选完成",对应库存应该为0
                var completedLocks = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
                foreach (var lockInfo in completedLocks)
                {
                    if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                    {
                        var stock = stockDetails.FirstOrDefault(x => x.Barcode == lockInfo.CurrentBarcode);
                        if (stock != null && stock.StockQuantity > 0)
                        {
                            _logger.LogWarning($"{phase}验证警告 - é”å®šID {lockInfo.Id} æ‹£é€‰å®Œæˆä½†åº“存不为0: {stock.StockQuantity}");
                        }
                    }
                }
                // éªŒè¯2: åº“存状态一致性
                foreach (var stock in stockDetails)
                {
                    if (stock.Status == (int)StockStatusEmun.出库锁定 && stock.StockQuantity == 0)
                    {
                        _logger.LogWarning($"{phase}验证警告 - æ¡ç  {stock.Barcode} çŠ¶æ€ä¸ºå‡ºåº“é”å®šä½†åº“å­˜ä¸º0");
                    }
                }
                _logger.LogInformation($"【{phase}数据验证】完成 - çŠ¶æ€: {(isValid ? "通过" : "有警告")}");
                return isValid;
            }
            catch (Exception ex)
            {
                _logger.LogError($"{phase} æ•°æ®éªŒè¯å¤±è´¥: {ex.Message}");
                return false;
            }
        }
        private string GetLockStatusName(int status)
        {
            return status switch
            {
                1 => "出库中",
                2 => "拣选完成",
                3 => "已回库",
                4 => "已取走",
                _ => $"未知({status})"
            };
        }
        /// <summary>
        /// ç®€åŒ–回库数据操作(当主方法失败时使用)
        /// </summary>
        private async Task ExecuteSimpleReturnDataOperations(PalletStatusAnalysis statusAnalysis)
        {
            _logger.LogInformation($"【简化回库】开始执行简化回库操作");
            try
            {
                // èŽ·å–è¯¥æ‰˜ç›˜çš„æ‰€æœ‰æ¡ç ï¼ˆåŒ…æ‹¬æ‰€æœ‰çŠ¶æ€çš„åº“å­˜ï¼‰
                var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == statusAnalysis.StockId)
                    .ToListAsync();
                _logger.LogInformation($"找到 {allStockDetails.Count} ä¸ªåº“存明细记录");
                foreach (var stockDetail in allStockDetails)
                {
                    // è®°å½•原始状态
                    int originalStatus = stockDetail.Status;
                    decimal originalStockQty = stockDetail.StockQuantity;
                    // å°†æ‰€æœ‰å‡ºåº“相关的状态恢复为入库完成
                    if (stockDetail.Status == (int)StockStatusEmun.出库锁定 ||
                        //stockDetail.Status == (int)StockStatusEmun.出库中 ||
                        stockDetail.Status == (int)StockStatusEmun.出库完成)
                    {
                        // å¦‚果是出库完成状态且库存为0,可能需要特殊处理
                        if (stockDetail.Status == (int)StockStatusEmun.出库完成 && stockDetail.StockQuantity == 0)
                        {
                            _logger.LogInformation($"跳过已出库完成的零库存条码: {stockDetail.Barcode}");
                            continue;
                        }
                        stockDetail.Status = (int)StockStatusEmun.入库完成;
                        // å¦‚果是出库锁定状态但库存为0,重置库存为1(避免零库存问题)
                        if (stockDetail.Status == (int)StockStatusEmun.出库锁定 && stockDetail.StockQuantity == 0)
                        {
                            // æŸ¥æ‰¾æ˜¯å¦æœ‰å¯¹åº”的锁定记录来确定应该恢复的数量
                            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                                .Where(x => x.CurrentBarcode == stockDetail.Barcode &&
                                           x.StockId == stockDetail.StockId)
                                .FirstAsync();
                            if (lockInfo != null)
                            {
                                stockDetail.StockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
                                _logger.LogInformation($"恢复零库存条码的数量 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
                            }
                        }
                        await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        _logger.LogInformation($"简化回库 - æ¡ç : {stockDetail.Barcode}, çŠ¶æ€: {GetStockStatusName(originalStatus)} -> å…¥åº“完成, æ•°é‡: {originalStockQty} -> {stockDetail.StockQuantity}");
                    }
                }
                // æ›´æ–°æ‰€æœ‰é”å®šè®°å½•为已回库
                var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == statusAnalysis.OrderNo &&
                               x.PalletCode == statusAnalysis.PalletCode &&
                               (x.Status == (int)OutLockStockStatusEnum.出库中 ||
                                x.Status == (int)OutLockStockStatusEnum.拣选完成))
                    .ToListAsync();
                foreach (var lockInfo in allLocks)
                {
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                    lockInfo.Operator = App.User.UserName;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    _logger.LogInformation($"简化回库 - é”å®šè®°å½•: {lockInfo.Id}, çŠ¶æ€: å·²å›žåº“");
                }
                _logger.LogInformation($"【简化回库】完成 - å¤„理 {allStockDetails.Count} ä¸ªæ¡ç , {allLocks.Count} æ¡é”å®šè®°å½•");
            }
            catch (Exception ex)
            {
                _logger.LogError($"简化回库失败: {ex.Message}");
                throw;
            }
        }
        /// <summary>
        /// èŽ·å–åº“å­˜çŠ¶æ€åç§°
        /// </summary>
        private string GetStockStatusName(int status)
        {
            return status switch
            {
                1 => "入库确认",
                2 => "入库完成",
                3 => "出库锁定",
                4 => "出库中",
                5 => "出库完成",
                6 => "已清理",
                _ => $"未知({status})"
            };
        }
        /// <summary>
        /// æ‰§è¡Œå›žåº“数据操作 - ç®€åŒ–版本
        /// </summary>
        private async Task ExecuteReturnDataOperations(PalletStatusAnalysis statusAnalysis)
        {
@@ -1746,28 +3181,142 @@
            try
            {
                // ä½¿ç”¨ HashSet é¿å…é‡å¤å¤„理条码
                var processedBarcodes = new HashSet<string>();
                decimal totalReturnedQty = 0;
                // 1. å¤„理已分配的未分拣锁定记录
                if (statusAnalysis.HasRemainingLocks)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} æ¡å·²åˆ†é…æœªåˆ†æ‹£é”å®šè®°å½•");
                    await HandleAllocatedLocksReturn(statusAnalysis.RemainingLocks);
                    foreach (var lockInfo in statusAnalysis.RemainingLocks)
                    {
                        if (string.IsNullOrEmpty(lockInfo.CurrentBarcode) || processedBarcodes.Contains(lockInfo.CurrentBarcode))
                        {
                            _logger.LogInformation($"跳过重复或空条码的锁定记录 - ID: {lockInfo.Id}");
                            continue;
                        }
                        // è®¡ç®—回库数量(未拣选的部分)
                        decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                        if (returnQty > 0)
                        {
                            _logger.LogInformation($"处理锁定记录回库 - ID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                            // å¤„理库存
                            await ProcessStockForReturn(lockInfo.CurrentBarcode, statusAnalysis.StockId, returnQty);
                            // æ ‡è®°ä¸ºå·²å›žåº“
                            lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                            lockInfo.Operator = App.User.UserName;
                            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                            // å‡å°‘订单明细的分配数量
                            if (lockInfo.OrderDetailId > 0)
                            {
                                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                                await ReduceOrderDetailAllocation(orderDetail, returnQty);
                            }
                            processedBarcodes.Add(lockInfo.CurrentBarcode);
                            totalReturnedQty += returnQty;
                            _logger.LogInformation($"锁定记录回库完成 - ID: {lockInfo.Id}, å›žåº“数量: {returnQty}");
                        }
                        else
                        {
                            _logger.LogInformation($"锁定记录无需回库 - ID: {lockInfo.Id}, å·²æ‹£é€‰å®Œæˆ");
                        }
                    }
                }
                // 2. å¤„理未分配的锁定记录(如自动拆包产生的)
                if (statusAnalysis.HasUnallocatedLocks)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.UnallocatedLocks.Count} æ¡æœªåˆ†é…é”å®šè®°å½•");
                    await HandleUnallocatedLocksReturn(statusAnalysis.UnallocatedLocks);
                }
                // 3. å¤„理未分配的库存货物
                // 2. å¤„理未分配的库存货物
                if (statusAnalysis.HasPalletStockGoods)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} ä¸ªæœªåˆ†é…åº“存货物");
                    await HandleUnallocatedStockReturn(statusAnalysis.PalletStockGoods);
                    foreach (var stockDetail in statusAnalysis.PalletStockGoods)
                    {
                        if (string.IsNullOrEmpty(stockDetail.Barcode) || processedBarcodes.Contains(stockDetail.Barcode))
                        {
                            _logger.LogInformation($"跳过重复或空条码的库存 - åº“å­˜ID: {stockDetail.Id}");
                            continue;
                        }
                        if (stockDetail.StockQuantity > 0)
                        {
                            decimal returnQty = stockDetail.StockQuantity;
                            _logger.LogInformation($"处理未分配库存回库 - æ¡ç : {stockDetail.Barcode}, å›žåº“数量: {returnQty}");
                            // ç›´æŽ¥æ¢å¤åº“存状态
                            stockDetail.Status = (int)StockStatusEmun.入库完成;
                            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                            processedBarcodes.Add(stockDetail.Barcode);
                            totalReturnedQty += returnQty;
                            _logger.LogInformation($"未分配库存回库完成 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {returnQty}");
                        }
                    }
                }
                _logger.LogInformation($"回库数据操作完成 - æ€»å›žåº“数量: {statusAnalysis.TotalReturnQty}");
                // 3. å¤„理拆包记录相关的条码
                if (statusAnalysis.HasSplitRecords)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.SplitRecords.Count} æ¡æ‹†åŒ…记录");
                    // æ”¶é›†æ‹†åŒ…相关的所有条码
                    var splitBarcodes = new List<string>();
                    foreach (var splitRecord in statusAnalysis.SplitRecords)
                    {
                        if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode))
                            splitBarcodes.Add(splitRecord.OriginalBarcode);
                        if (!string.IsNullOrEmpty(splitRecord.NewBarcode))
                            splitBarcodes.Add(splitRecord.NewBarcode);
                    }
                    // åŽ»é‡
                    splitBarcodes = splitBarcodes.Distinct().ToList();
                    foreach (var barcode in splitBarcodes)
                    {
                        if (processedBarcodes.Contains(barcode))
                        {
                            _logger.LogInformation($"拆包条码已处理: {barcode}");
                            continue;
                        }
                        // æŸ¥æ‰¾åº“å­˜
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .FirstAsync(x => x.Barcode == barcode && x.StockId == statusAnalysis.StockId);
                        if (stockDetail != null && stockDetail.StockQuantity > 0)
                        {
                            decimal returnQty = stockDetail.StockQuantity;
                            _logger.LogInformation($"处理拆包相关库存回库 - æ¡ç : {barcode}, å›žåº“数量: {returnQty}");
                            // æ¢å¤åº“存状态
                            if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
                            {
                                stockDetail.Status = (int)StockStatusEmun.入库完成;
                                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                                processedBarcodes.Add(barcode);
                                totalReturnedQty += returnQty;
                                _logger.LogInformation($"拆包库存回库完成 - æ¡ç : {barcode}, æ•°é‡: {returnQty}");
                            }
                        }
                    }
                }
                _logger.LogInformation($"回库数据操作完成 - æ€»å›žåº“数量: {totalReturnedQty}, å¤„理条码数: {processedBarcodes.Count}");
            }
            catch (Exception ex)
            {
@@ -1775,9 +3324,154 @@
                throw;
            }
        }
        // <summary>
        /// å¤„理未分配的锁定记录回库
        /// ä¸éœ€è¦å‡å°‘订单明细的分配数量
        /// <summary>
        /// å¤„理库存回库 - å®Œæ•´ä¿®æ­£ç‰ˆ
        /// ç¡®ä¿OutboundQuantity正确减少
        /// </summary>
        private async Task ProcessStockForReturn(string barcode, int stockId, decimal returnQty)
        {
            try
            {
                _logger.LogInformation($"处理库存回库 - æ¡ç : {barcode}, å›žåº“数量: {returnQty}");
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
                if (stockDetail == null)
                {
                    _logger.LogWarning($"未找到库存明细 - æ¡ç : {barcode}");
                    return;
                }
                // è®°å½•原始值
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                int originalStatus = stockDetail.Status;
                _logger.LogInformation($"回库前状态 - åº“å­˜: {originalStockQty}, å‡ºåº“: {originalOutboundQty}, çŠ¶æ€: {GetStockStatusName(originalStatus)}");
                // ã€æ ¸å¿ƒä¿®æ­£ã€‘确保回库操作正确处理
                // 1. åº“存数量增加(回库的货物回到库存)
                stockDetail.StockQuantity += returnQty;
                // 2. å‡ºåº“数量减少(因为货物没有出库,而是回库了)
                // ä½†éœ€è¦ç¡®ä¿ä¸ä¼šå‡ºçŽ°è´Ÿæ•°
                if (stockDetail.OutboundQuantity >= returnQty)
                {
                    stockDetail.OutboundQuantity -= returnQty;
                }
                else
                {
                    // å¦‚果出库数量小于回库数量,说明数据异常
                    _logger.LogWarning($"出库数量({stockDetail.OutboundQuantity})小于回库数量({returnQty}),重置出库数量为0");
                    stockDetail.OutboundQuantity = 0;
                }
                // 3. æ›´æ–°çŠ¶æ€ä¸ºå…¥åº“å®Œæˆ
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"库存回库完成 - æ¡ç : {barcode}");
                _logger.LogInformation($"  åº“存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
                _logger.LogInformation($"  å‡ºåº“数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
                _logger.LogInformation($"  çŠ¶æ€: {GetStockStatusName(originalStatus)} -> {GetStockStatusName(stockDetail.Status)}");
                // éªŒè¯å›žåº“后的数据
                await ValidateStockAfterReturn(barcode, stockId, returnQty);
            }
            catch (Exception ex)
            {
                _logger.LogError($"处理库存回库失败 - æ¡ç : {barcode}, Error: {ex.Message}");
                throw;
            }
        }
        /// <summary>
        /// éªŒè¯å›žåº“后库存数据
        /// </summary>
        private async Task ValidateStockAfterReturn(string barcode, int stockId, decimal returnQty)
        {
            try
            {
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
                // æ£€æŸ¥å‡ºåº“数量是否为0或合理
                if (stockDetail.OutboundQuantity > stockDetail.StockQuantity)
                {
                    _logger.LogWarning($"回库后数据异常 - å‡ºåº“数量({stockDetail.OutboundQuantity})大于库存数量({stockDetail.StockQuantity})");
                }
                // æ£€æŸ¥çŠ¶æ€æ˜¯å¦æ­£ç¡®
                if (stockDetail.Status != (int)StockStatusEmun.入库完成)
                {
                    _logger.LogWarning($"回库后状态异常 - æœŸæœ›:入库完成, å®žé™…:{GetStockStatusName(stockDetail.Status)}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"验证回库后数据失败: {ex.Message}");
            }
        }
        /// <summary>
        /// ä¸ºé”å®šè®°å½•减少订单明细的分配数量
        /// </summary>
        private async Task ReduceOrderDetailAllocationForLock(long orderDetailId, decimal reduceQty)
        {
            if (orderDetailId <= 0)
                return;
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return;
            decimal originalAllocated = orderDetail.AllocatedQuantity;
            decimal originalLock = orderDetail.LockQuantity;
            // éªŒè¯å‡å°‘数量不会导致负数
            if (orderDetail.AllocatedQuantity < reduceQty)
            {
                _logger.LogWarning($"分配数量不足,调整减少数量 - åŽŸè®¡åˆ’å‡å°‘: {reduceQty}, å®žé™…可用: {orderDetail.AllocatedQuantity}");
                reduceQty = orderDetail.AllocatedQuantity;
            }
            // å‡å°‘分配数量和锁定数量
            orderDetail.AllocatedQuantity -= reduceQty;
            orderDetail.LockQuantity -= reduceQty;
            // ç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < 0)
            {
                _logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + reduceQty}, å‡å°‘: {reduceQty}");
                orderDetail.AllocatedQuantity = 0;
            }
            if (orderDetail.LockQuantity < 0)
            {
                _logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + reduceQty}, å‡å°‘: {reduceQty}");
                orderDetail.LockQuantity = 0;
            }
            // æ›´æ–°æ‰¹æ¬¡åˆ†é…çŠ¶æ€
            await UpdateBatchAllocateStatus(orderDetail);
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            _logger.LogInformation($"减少订单明细分配 - OrderDetailId: {orderDetail.Id}, " +
                                  $"分配数量: {originalAllocated} -> {orderDetail.AllocatedQuantity}, " +
                                  $"锁定数量: {originalLock} -> {orderDetail.LockQuantity}, " +
                                  $"减少数量: {reduceQty}");
        }
        /// <summary>
        /// å¤„理未分配的锁定记录回库 - ä¿®æ­£ç‰ˆ
        /// æ³¨æ„ï¼šæœªåˆ†é…é”å®šè®°å½•没有绑定订单明细,不需要减少订单明细的分配数量
        /// </summary>
        private async Task HandleUnallocatedLocksReturn(List<Dt_OutStockLockInfo> unallocatedLocks)
        {
@@ -1797,7 +3491,27 @@
                _logger.LogInformation($"处理未分配锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                // æ¢å¤åº“存状态
                await RestoreStockForLockInfo(lockInfo, returnQty);
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                if (stockDetail != null)
                {
                    // ã€ä¿®æ­£ã€‘恢复库存数量,出库数量保持不变(因为未分配锁定的出库数量本来就是0)
                    decimal originalStockQty = stockDetail.StockQuantity;
                    stockDetail.StockQuantity += returnQty;
                    // å‡ºåº“数量保持为0不变!
                    // stockDetail.OutboundQuantity = stockDetail.OutboundQuantity; // ä¿æŒä¸å˜
                    // æ¢å¤åº“存状态为可用状态
                    if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
                    {
                        stockDetail.Status = (int)StockStatusEmun.入库完成;
                    }
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    _logger.LogInformation($"恢复未分配库存 - æ¡ç : {stockDetail.Barcode}, åº“存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
                }
                // æ›´æ–°é”å®šè®°å½•状态为已回库
                lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
@@ -1806,13 +3520,11 @@
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"更新未分配锁定状态 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: å‡ºåº“中 -> å·²å›žåº“");
                // é‡è¦ï¼šæœªåˆ†é…é”å®šè®°å½•不需要减少订单明细的分配数量
                _logger.LogInformation($"未分配锁定记录回库完成 - é”å®šID: {lockInfo.Id}, å›žåº“数量: {returnQty}, æ— éœ€æ›´æ–°è®¢å•明细");
            }
            _logger.LogInformation($"未分配锁定记录回库处理完成 - å…±å¤„理 {unallocatedLocks.Count} æ¡è®°å½•");
        }
        private async Task HandleAllocatedLocksReturn(List<Dt_OutStockLockInfo> allocatedLocks)
        {
            _logger.LogInformation($"开始处理已分配锁定记录回库 - å…± {allocatedLocks.Count} æ¡è®°å½•");
@@ -1953,7 +3665,7 @@
                try
                {
                    await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
                    _logger.LogInformation($"CreateReturnTaskAndHandleESS  åˆ†æ‰¹åˆ é™¤åŽ†å²ä»»åŠ¡: {orderNo} ï¼Œ {currentTask.TaskNum}");
                    // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
                    //_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
@@ -1971,7 +3683,7 @@
                {
                    _logger.LogInformation($"创建回库任务失败 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                    throw new Exception($"创建回库任务失败 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                }
                // å‘送ESS命令
                await SendESSCommands(palletCode, targetAddress, returnTask);
@@ -2044,114 +3756,231 @@
        /// åˆ†æžæ‰˜ç›˜çŠ¶æ€ç”¨äºŽå›žåº“
        /// ç¡®ä¿ä¸ä¼šé”™è¯¯è¯†åˆ«éœ€è¦å›žåº“的物品
        /// </summary>
        /// <summary>
        /// åˆ†æžæ‰˜ç›˜çŠ¶æ€ç”¨äºŽå›žåº“ - ä¿®æ­£ç‰ˆï¼ˆè§£å†³æ‹†åŒ…记录重复计算问题)
        /// </summary>
        private async Task<PalletStatusAnalysis> AnalyzePalletStatusForReturn(string orderNo, string palletCode, int stockId)
        {
            var result = new PalletStatusAnalysis
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                StockId = stockId
                StockId = stockId,
                AllBarcodes = new List<string>(),
                ProcessedBarcodes = new HashSet<string>()
            };
            // 1. åˆ†æžæœªåˆ†æ‹£çš„锁定记录(状态为出库中)
            var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
            _logger.LogInformation($"【回库分析】开始分析托盘状态 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, StockId: {stockId}");
            if (unfinishedLocks.Any())
            try
            {
                //  åŒºåˆ†å·²åˆ†é…å’Œæœªåˆ†é…çš„锁定记录
                var allocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated != 1 && x.OrderDetailId > 0).ToList();
                var unallocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated == 1 || x.OrderDetailId == 0).ToList();
                // 1. é¦–先获取托盘上所有的库存明细(基础数据)
                var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId && x.StockQuantity > 0)
                    .ToListAsync();
                // å¤„理已分配的锁定记录
                _logger.LogInformation($"找到 {allStockDetails.Count} ä¸ªæœ‰åº“存的明细记录");
                // 2. åˆ†æžæ‰€æœ‰é”å®šè®°å½•(已分配和未分配)
                var allLockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                // åŒºåˆ†å·²åˆ†é…å’Œæœªåˆ†é…é”å®šè®°å½•
                var allocatedLocks = allLockInfos
                    .Where(x => x.IsUnallocated != 1 && x.OrderDetailId > 0 &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToList();
                var unallocatedLocks = allLockInfos
                    .Where(x => (x.IsUnallocated == 1 || x.OrderDetailId == 0) &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToList();
                // å¤„理已分配锁定记录
                if (allocatedLocks.Any())
                {
                    result.HasRemainingLocks = true;
                    result.RemainingLocks = allocatedLocks;
                    result.RemainingLocksReturnQty = allocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    // ã€ä¿®å¤ã€‘对于已分配锁定记录,回库数量是未拣选的部分
                    result.RemainingLocksReturnQty = allocatedLocks.Sum(x =>
                    {
                        var returnQty = x.AssignQuantity - x.PickedQty;
                        return returnQty > 0 ? returnQty : 0;
                    });
                    foreach (var lockInfo in allocatedLocks)
                    {
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                        {
                            result.AllBarcodes.Add(lockInfo.CurrentBarcode);
                            result.ProcessedBarcodes.Add(lockInfo.CurrentBarcode);
                        }
                    }
                    _logger.LogInformation($"发现{allocatedLocks.Count}条已分配未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
                    _logger.LogInformation($"发现 {allocatedLocks.Count} æ¡å·²åˆ†é…é”å®šè®°å½•,回库数量: {result.RemainingLocksReturnQty}");
                }
                // å¤„理未分配的锁定记录(如自动拆包产生的)
                // å¤„理未分配锁定记录(自动拆包产生的)
                if (unallocatedLocks.Any())
                {
                    result.HasUnallocatedLocks = true;
                    result.UnallocatedLocks = unallocatedLocks;
                    result.UnallocatedLocksReturnQty = unallocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    // ã€ä¿®å¤ã€‘对于未分配锁定记录,回库数量是它的分配数量(因为未拣选过)
                    // ä½†å®žé™…上,库存本来就存在,只是状态需要恢复
                    result.UnallocatedLocksReturnQty = unallocatedLocks.Sum(x => x.AssignQuantity);
                    foreach (var lockInfo in unallocatedLocks)
                    {
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode) &&
                            !result.ProcessedBarcodes.Contains(lockInfo.CurrentBarcode))
                        {
                            result.AllBarcodes.Add(lockInfo.CurrentBarcode);
                            result.ProcessedBarcodes.Add(lockInfo.CurrentBarcode);
                        }
                    }
                    _logger.LogInformation($"发现{unallocatedLocks.Count}条未分配锁定记录,总数量: {result.UnallocatedLocksReturnQty}");
                    _logger.LogInformation($"发现 {unallocatedLocks.Count} æ¡æœªåˆ†é…é”å®šè®°å½•,回库数量(状态恢复): {result.UnallocatedLocksReturnQty}");
                }
                // 3. ã€é‡è¦ä¿®å¤ã€‘重新计算总回库数量
                // å¯¹äºŽå·²åˆ†é…é”å®šï¼šå›žåº“数量 = æœªæ‹£é€‰æ•°é‡
                // å¯¹äºŽæœªåˆ†é…é”å®šï¼šæ²¡æœ‰å®žé™…的库存数量变化,只是状态恢复
                result.TotalReturnQty = result.RemainingLocksReturnQty; // åªè®¡ç®—已分配锁定的回库数量
                // è®°å½•库存数量(用于验证)
                decimal totalStockOnPallet = allStockDetails.Sum(x => x.StockQuantity);
                _logger.LogInformation($"回库分析完成:");
                _logger.LogInformation($"  æ‰˜ç›˜æ€»åº“å­˜: {totalStockOnPallet}");
                _logger.LogInformation($"  å·²åˆ†é…é”å®šå›žåº“数量: {result.RemainingLocksReturnQty}");
                _logger.LogInformation($"  æœªåˆ†é…é”å®šçŠ¶æ€æ¢å¤æ•°é‡: {result.UnallocatedLocksReturnQty}");
                _logger.LogInformation($"  å®žé™…物理回库数量: {result.TotalReturnQty}");
                result.HasItemsToReturn = result.TotalReturnQty > 0 || result.UnallocatedLocksReturnQty > 0;
                result.IsEmptyPallet = !result.HasItemsToReturn;
                return result;
            }
            // 2. åˆ†æžæ‰˜ç›˜ä¸Šçš„剩余库存货物(状态为出库锁定但未分配)
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.StockId == stockId &&
                           x.Status == (int)StockStatusEmun.出库锁定 &&
                           x.StockQuantity > 0)
                .ToListAsync();
            // è¿‡æ»¤æŽ‰å·²ç»è¢«é”å®šè®°å½•占用的库存
            var lockedBarcodes = unfinishedLocks.Select(x => x.CurrentBarcode).ToList();
            var unlockedStockGoods = palletStockGoods.Where(x => !lockedBarcodes.Contains(x.Barcode)).ToList();
            // è¿›ä¸€æ­¥è¿‡æ»¤ï¼šæ£€æŸ¥è¿™äº›åº“存是否有关联的锁定记录
            var trulyUnallocatedGoods = new List<Dt_StockInfoDetail>();
            foreach (var stock in unlockedStockGoods)
            catch (Exception ex)
            {
                var hasLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == stock.Barcode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .AnyAsync();
                if (!hasLock)
                {
                    trulyUnallocatedGoods.Add(stock);
                }
                _logger.LogError($"回库分析失败 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, Error: {ex.Message}");
                throw;
            }
            if (trulyUnallocatedGoods.Any())
            {
                result.HasPalletStockGoods = true;
                result.PalletStockGoods = trulyUnallocatedGoods;
                result.PalletStockReturnQty = trulyUnallocatedGoods.Sum(x => x.StockQuantity);
                foreach (var stock in trulyUnallocatedGoods)
                {
                    result.AllBarcodes.Add(stock.Barcode);
                }
                _logger.LogInformation($"发现{trulyUnallocatedGoods.Count}个真正未分配库存货物,总数量: {result.PalletStockReturnQty}");
            }
            // 3. è®¡ç®—总回库数量
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.UnallocatedLocksReturnQty + result.PalletStockReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            result.IsEmptyPallet = !result.HasItemsToReturn;
            _logger.LogInformation($"托盘状态分析完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, æ€»å›žåº“数量: {result.TotalReturnQty}");
            return result;
        }
        /// <summary>
        /// éªŒè¯æ²¡æœ‰é‡å¤æ¡ç 
        /// </summary>
        private async Task ValidateNoDuplicateBarcodes(PalletStatusAnalysis analysis, List<Dt_StockInfoDetail> allStockDetails)
        {
            try
            {
                // æ£€æŸ¥AllBarcodes中是否有重复
                var duplicateBarcodes = analysis.AllBarcodes
                    .GroupBy(b => b)
                    .Where(g => g.Count() > 1)
                    .Select(g => g.Key)
                    .ToList();
                if (duplicateBarcodes.Any())
                {
                    _logger.LogError($"发现重复条码: {string.Join(", ", duplicateBarcodes)}");
                    // è‡ªåŠ¨åŽ»é‡
                    analysis.AllBarcodes = analysis.AllBarcodes.Distinct().ToList();
                    _logger.LogWarning($"已自动去重,条码数: {analysis.AllBarcodes.Count}");
                }
                // æ£€æŸ¥æ¯ä¸ªæ¡ç çš„实际库存
                decimal totalStockFromBarcodes = 0;
                foreach (var barcode in analysis.AllBarcodes)
                {
                    var stock = allStockDetails.FirstOrDefault(x => x.Barcode == barcode);
                    if (stock != null)
                    {
                        totalStockFromBarcodes += stock.StockQuantity;
                        _logger.LogInformation($"条码库存 - {barcode}: {stock.StockQuantity}");
                    }
                }
                _logger.LogInformation($"回库分析总库存: {analysis.TotalReturnQty}, æ¡ç å®žé™…总库存: {totalStockFromBarcodes}");
                // å¦‚果分析的数量大于实际库存,说明有重复计算
                if (analysis.TotalReturnQty > totalStockFromBarcodes * 1.1m) // å…è®¸10%的误差
                {
                    _logger.LogError($"回库数量({analysis.TotalReturnQty})明显大于实际库存({totalStockFromBarcodes}),可能存在重复计算!");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"验证重复条码失败: {ex.Message}");
            }
        }
        /// <summary>
        /// éªŒè¯åˆ†æžç»“果,确保数据一致性
        /// </summary>
        private async Task ValidateAnalysisResults(PalletStatusAnalysis analysis, int stockId)
        {
            _logger.LogInformation($"开始验证分析结果 - è®¢å•: {analysis.OrderNo}, æ‰˜ç›˜: {analysis.PalletCode}");
            try
            {
                // 1. éªŒè¯é”å®šè®°å½•和库存明细的匹配
                foreach (var lockInfo in analysis.RemainingLocks.Concat(analysis.UnallocatedLocks))
                {
                    if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                    {
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                        if (stockDetail == null)
                        {
                            _logger.LogWarning($"锁定记录 {lockInfo.Id} çš„æ¡ç  {lockInfo.CurrentBarcode} åœ¨åº“存明细中不存在");
                        }
                        else if (stockDetail.StockQuantity <= 0)
                        {
                            _logger.LogWarning($"锁定记录 {lockInfo.Id} çš„æ¡ç  {lockInfo.CurrentBarcode} åº“存数量为0或负数");
                        }
                    }
                }
                // 2. éªŒè¯æ€»å›žåº“数量的合理性
                // èŽ·å–æ‰˜ç›˜ä¸Šçš„æ€»åº“å­˜æ•°é‡
                var totalStockOnPallet = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId)
                    .SumAsync(x => x.StockQuantity);
                if (analysis.TotalReturnQty > totalStockOnPallet)
                {
                    _logger.LogWarning($"总回库数量 {analysis.TotalReturnQty} å¤§äºŽæ‰˜ç›˜æ€»åº“å­˜ {totalStockOnPallet},可能存在计算错误");
                }
                // 3. éªŒè¯æ¡ç çš„唯一性
                var duplicateBarcodes = analysis.AllBarcodes
                    .GroupBy(x => x)
                    .Where(g => g.Count() > 1)
                    .Select(g => g.Key)
                    .ToList();
                if (duplicateBarcodes.Any())
                {
                    _logger.LogWarning($"发现重复的条码: {string.Join(", ", duplicateBarcodes)}");
                }
                _logger.LogInformation($"分析结果验证完成");
            }
            catch (Exception ex)
            {
                _logger.LogError($"分析结果验证失败 - Error: {ex.Message}");
                // ä¸æŠ›å‡ºå¼‚常,只记录错误
            }
        }
        /// <summary>
        /// å¤„理未分拣的锁定记录回库
        /// ç¡®ä¿ä¸ä¼šé”™è¯¯ç»‘定条码数量到锁定数量
@@ -2408,44 +4237,117 @@
        }
        /// <summary>
        /// å–走空箱 - å…ˆæ‰§è¡Œå›žåº“再清理 - å¢žå¼ºç‰ˆæœ¬
        /// å–走空箱 - ä¿®æ­£ç‰ˆï¼Œæ­£ç¡®å¤„理未分配锁定记录
        /// </summary>
        public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
        {
            try
            {
                _logger.LogInformation($"【取走空箱开始】订单: {orderNo}, æ‰˜ç›˜: {palletCode}");
                _unitOfWorkManage.BeginTran();
                _logger.LogInformation($"开始取走空箱 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                // 1. å…ˆå°è¯•执行回库操作,确保所有物品都回库
                _logger.LogInformation($"步骤1: å…ˆæ‰§è¡Œå›žåº“操作");
                var returnResult = await ExecutePalletReturn(orderNo, palletCode, "取走空箱前回库");
                // 1. éªŒè¯ç©ºç®±å–走条件(必须全部完成拣选)
                var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
                if (!validationResult.IsValid)
                // å³ä½¿å›žåº“失败,继续验证空箱条件(可能是真的空托盘)
                if (!returnResult.Status)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                    _logger.LogWarning($"回库操作可能失败或无物品: {returnResult.Message}");
                }
                var completedLocks = validationResult.Data;
                // 2. éªŒè¯ç©ºç®±å–走条件(必须全部完成拣选或已回库)
                _logger.LogInformation($"步骤2: éªŒè¯ç©ºç®±å–走条件");
                // 2. æ¸…理已完成的锁定记录(标记为已取走)
                await CleanupCompletedLocks(completedLocks);
                // èŽ·å–æ‰˜ç›˜çš„æ‰€æœ‰é”å®šè®°å½•ï¼ˆåŒ…æ‹¬å·²å›žåº“å’Œå·²å–èµ°çš„ï¼‰
                var allLockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .ToListAsync();
                // 3. æ¸…理对应的库存记录状态
                if (!allLockInfos.Any())
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error("该托盘没有锁定记录");
                }
                // ã€ä¿®æ­£ã€‘检查是否有未完成的锁定记录
                var unfinishedLocks = allLockInfos.Where(x =>
                    x.Status == (int)OutLockStockStatusEnum.出库中 ||
                    x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
                if (unfinishedLocks.Any())
                {
                    var unfinishedCount = unfinishedLocks.Count;
                    // åŒºåˆ†å·²åˆ†é…å’Œæœªåˆ†é…
                    var allocatedUnfinished = unfinishedLocks.Where(x => x.IsUnallocated != 1).ToList();
                    var unallocatedUnfinished = unfinishedLocks.Where(x => x.IsUnallocated == 1).ToList();
                    string errorMsg = $"托盘还有{unfinishedCount}条未完成记录";
                    if (allocatedUnfinished.Any()) errorMsg += $",其中已分配{allocatedUnfinished.Count}条";
                    if (unallocatedUnfinished.Any()) errorMsg += $",未分配{unallocatedUnfinished.Count}条";
                    errorMsg += ",不能取走空箱";
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error(errorMsg);
                }
                // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•ï¼ˆçŠ¶æ€ä¸ºæ‹£é€‰å®Œæˆæˆ–å·²å–èµ°ï¼‰
                var completedLocks = allLockInfos.Where(x =>
                    x.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                    x.Status == (int)OutLockStockStatusEnum.已取走).ToList();
                if (!completedLocks.Any())
                {
                    // æ£€æŸ¥æ˜¯å¦éƒ½æ˜¯å·²å›žåº“状态
                    var returnedLocks = allLockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.已回库).ToList();
                    if (returnedLocks.Any())
                    {
                        _logger.LogInformation($"所有锁定记录都已回库,可以取走空箱");
                        completedLocks = returnedLocks;
                    }
                    else
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error("该托盘没有已完成拣选或已回库的记录");
                    }
                }
                _logger.LogInformation($"验证通过,找到 {completedLocks.Count} æ¡å·²å®Œæˆè®°å½•");
                // 3. æ¸…理已完成的锁定记录(标记为已取走)
                _logger.LogInformation($"步骤3: æ¸…理锁定记录");
                foreach (var lockInfo in completedLocks)
                {
                    // åªå¤„理状态不是已取走的记录
                    if (lockInfo.Status != (int)OutLockStockStatusEnum.已取走)
                    {
                        lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
                        lockInfo.Operator = App.User.UserName;
                        await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                        _logger.LogInformation($"锁定记录标记为已取走 - ID: {lockInfo.Id}");
                    }
                }
                // 4. æ¸…理对应的库存记录状态
                _logger.LogInformation($"步骤4: æ¸…理库存记录");
                foreach (var lockInfo in completedLocks)
                {
                    await CleanupStockInfo(lockInfo);
                }
                // 4. æ›´æ–°ç›¸å…³è®¢å•状态
                // 5. æ›´æ–°ç›¸å…³è®¢å•状态
                _logger.LogInformation($"步骤5: æ›´æ–°è®¢å•状态");
                await UpdateOrderStatusAfterPalletRemoval(orderNo);
                // 5. è®°å½•操作历史
                // 6. è®°å½•操作历史
                _logger.LogInformation($"步骤6: è®°å½•操作历史");
                await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
                _unitOfWorkManage.CommitTran();
                _logger.LogInformation($"取走空箱成功 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                _logger.LogInformation($"【取走空箱成功】订单: {orderNo}, æ‰˜ç›˜: {palletCode}");
                return WebResponseContent.Instance.OK("取走空箱成功");
            }
@@ -2456,6 +4358,8 @@
                return WebResponseContent.Instance.Error($"取走空箱失败:{ex.Message}");
            }
        }
        /// <summary>
        /// æ”¶é›†éœ€è¦å›žåº“的条码
        /// </summary>
@@ -2718,6 +4622,359 @@
            }
        }
        /// <summary>
        /// æ”¶é›†éœ€è¦å›žåº“的条码(避免重复)
        /// </summary>
        private async Task<HashSet<string>> CollectBarcodesForReturn(string orderNo, string palletCode, int stockId)
        {
            var barcodes = new HashSet<string>();
            try
            {
                _logger.LogInformation($"开始收集回库条码 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, StockId: {stockId}");
                // 1. ä»Žé”å®šè®°å½•收集
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               (x.Status == (int)OutLockStockStatusEnum.出库中
                              // || x.Status == (int)OutLockStockStatusEnum.出库锁定)
                              ))
                    .ToListAsync();
                foreach (var lockInfo in lockInfos)
                {
                    if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                    {
                        barcodes.Add(lockInfo.CurrentBarcode);
                        _logger.LogInformation($"从锁定记录添加条码: {lockInfo.CurrentBarcode}");
                    }
                }
                // 2. ä»Žåº“存明细收集(状态为出库锁定的)
                var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId &&
                               x.Status == (int)StockStatusEmun.出库锁定 &&
                               x.StockQuantity > 0)
                    .ToListAsync();
                foreach (var stockDetail in stockDetails)
                {
                    if (!barcodes.Contains(stockDetail.Barcode) && !string.IsNullOrEmpty(stockDetail.Barcode))
                    {
                        barcodes.Add(stockDetail.Barcode);
                        _logger.LogInformation($"从库存明细添加条码: {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
                    }
                }
                // 3. ä»Žæ‹†åŒ…记录收集
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               !x.IsReverted &&
                               x.Status != (int)SplitPackageStatusEnum.已拣选)
                    .ToListAsync();
                foreach (var splitRecord in splitRecords)
                {
                    // æ·»åŠ åŽŸæ¡ç 
                    if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode) && !barcodes.Contains(splitRecord.OriginalBarcode))
                    {
                        barcodes.Add(splitRecord.OriginalBarcode);
                        _logger.LogInformation($"从拆包记录添加原条码: {splitRecord.OriginalBarcode}");
                    }
                    // æ·»åŠ æ–°æ¡ç 
                    if (!string.IsNullOrEmpty(splitRecord.NewBarcode) && !barcodes.Contains(splitRecord.NewBarcode))
                    {
                        barcodes.Add(splitRecord.NewBarcode);
                        _logger.LogInformation($"从拆包记录添加新条码: {splitRecord.NewBarcode}");
                    }
                }
                _logger.LogInformation($"条码收集完成 - å…± {barcodes.Count} ä¸ªæ¡ç : {string.Join(", ", barcodes)}");
                return barcodes;
            }
            catch (Exception ex)
            {
                _logger.LogError($"收集回库条码失败 - Error: {ex.Message}");
                return barcodes;
            }
        }
        /// <summary>
        /// ç»Ÿä¸€å¤„理条码回库(避免重复处理)
        /// </summary>
        private async Task ProcessBarcodeReturn(string barcode, int stockId, decimal returnQty, HashSet<string> processedBarcodes)
        {
            if (returnQty <= 0)
                return;
            // æ£€æŸ¥æ˜¯å¦å·²å¤„理过
            if (processedBarcodes.Contains(barcode))
            {
                _logger.LogInformation($"跳过已处理的条码: {barcode}");
                return;
            }
            // èŽ·å–åº“å­˜æ˜Žç»†
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
            if (stockDetail == null)
            {
                _logger.LogWarning($"未找到条码对应的库存明细: {barcode}");
                return;
            }
            // è®°å½•原始值
            decimal originalStockQty = stockDetail.StockQuantity;
            decimal originalOutboundQty = stockDetail.OutboundQuantity;
            int originalStatus = stockDetail.Status;
            _logger.LogInformation($"处理条码回库 - {barcode}: åŽŸå§‹åº“å­˜={originalStockQty}, åŽŸå§‹å‡ºåº“={originalOutboundQty}, çŠ¶æ€={originalStatus}");
            // éªŒè¯æ•°æ®ä¸€è‡´æ€§
            if (originalOutboundQty < returnQty)
            {
                _logger.LogWarning($"出库数量小于回库数量,调整回库数量 - æ¡ç : {barcode}, å‡ºåº“数量: {originalOutboundQty}, å›žåº“数量: {returnQty}");
                returnQty = originalOutboundQty;
            }
            // æ›´æ–°åº“存:出库数量减少,库存数量增加
            stockDetail.OutboundQuantity -= returnQty;
            stockDetail.StockQuantity += returnQty;
            // ç¡®ä¿ä¸ä¼šå‡ºçŽ°è´Ÿæ•°
            if (stockDetail.OutboundQuantity < 0)
            {
                _logger.LogWarning($"出库数量出现负数,重置为0 - æ¡ç : {barcode}");
                stockDetail.OutboundQuantity = 0;
            }
            // æ›´æ–°çŠ¶æ€
            if (stockDetail.OutboundQuantity <= 0 && stockDetail.StockQuantity > 0)
            {
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                _logger.LogInformation($"库存状态更新为入库完成 - æ¡ç : {barcode}");
            }
            else if (stockDetail.StockQuantity > 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
                _logger.LogInformation($"库存状态保持为出库锁定 - æ¡ç : {barcode}");
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // æ ‡è®°ä¸ºå·²å¤„理
            processedBarcodes.Add(barcode);
            _logger.LogInformation($"条码回库完成 - {barcode}: " +
                                  $"库存 {originalStockQty} -> {stockDetail.StockQuantity}, " +
                                  $"出库 {originalOutboundQty} -> {stockDetail.OutboundQuantity}, " +
                                  $"状态 {originalStatus} -> {stockDetail.Status}");
        }
        /// <summary>
        /// å¤„理拆包记录回库 - é¿å…é‡å¤
        /// </summary>
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, int stockId, HashSet<string> processedBarcodes)
        {
            if (!splitRecords.Any())
                return;
            _logger.LogInformation($"开始处理拆包记录回库 - å…± {splitRecords.Count} æ¡è®°å½•");
            foreach (var splitRecord in splitRecords)
            {
                // åªå¤„理未撤销的拆包记录
                if (splitRecord.IsReverted)
                {
                    _logger.LogInformation($"跳过已撤销的拆包记录 - ID: {splitRecord.Id}");
                    continue;
                }
                // å¤„理新条码
                if (!string.IsNullOrEmpty(splitRecord.NewBarcode) && !processedBarcodes.Contains(splitRecord.NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .FirstAsync(x => x.Barcode == splitRecord.NewBarcode && x.StockId == stockId);
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        // æ‹†åŒ…的新条码回库数量应该是其库存数量
                        await ProcessBarcodeReturn(splitRecord.NewBarcode, stockId, newStock.StockQuantity, processedBarcodes);
                    }
                }
                // å¤„理原条码
                if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode) && !processedBarcodes.Contains(splitRecord.OriginalBarcode))
                {
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == stockId);
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        // åŽŸæ¡ç çš„å›žåº“æ•°é‡åº”è¯¥æ˜¯æ‹†åŒ…åŽå‰©ä½™çš„æ•°é‡
                        await ProcessBarcodeReturn(splitRecord.OriginalBarcode, stockId, originalStock.StockQuantity, processedBarcodes);
                    }
                }
                // æ›´æ–°æ‹†åŒ…记录状态为已回库
                splitRecord.Status = (int)SplitPackageStatusEnum.已回库;
                await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
                _logger.LogInformation($"拆包记录状态更新为已回库 - è®°å½•ID: {splitRecord.Id}");
            }
            _logger.LogInformation($"拆包记录回库处理完成");
        }
        /// <summary>
        /// ç®€åŒ–版回库方法 - ç»•过复杂验证
        /// </summary>
        public async Task<WebResponseContent> SimplePalletReturn(string orderNo, string palletCode, string returnReason = "简化回库")
        {
            try
            {
                _logger.LogInformation($"【简化回库开始】订单: {orderNo}, æ‰˜ç›˜: {palletCode}");
                _unitOfWorkManage.BeginTran();
                // 1. èŽ·å–åº“å­˜ä¿¡æ¯ï¼ˆè·³è¿‡å¤æ‚éªŒè¯ï¼‰
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                }
                // 2. ç›´æŽ¥æŸ¥æ‰¾éœ€è¦å›žåº“的条码(简化逻辑)
                var barcodesToReturn = await GetBarcodesForSimpleReturn(orderNo, palletCode, stockInfo.Id);
                if (!barcodesToReturn.Any())
                {
                    try
                    {
                        _logger.LogInformation($"【无回库物品】处理空托盘");
                        var result = await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
                        _unitOfWorkManage.CommitTran();
                        return result;
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        _logger.LogError($"空箱回库失败: {ex.Message}");
                        return WebResponseContent.Instance.Error($"空箱回库失败:{ex.Message}");
                    }
                }
                // 3. ç®€åŒ–处理每个条码
                foreach (var barcode in barcodesToReturn)
                {
                    await ProcessSimpleBarcodeReturn(barcode, stockInfo.Id);
                }
                // 4. æ›´æ–°è®¢å•状态(简化)
                await UpdateOrderStatusAfterReturn(orderNo);
                // 5. åˆ›å»ºå›žåº“任务
                await CreateReturnTask(orderNo, palletCode, stockInfo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"简化回库成功,处理 {barcodesToReturn.Count} ä¸ªæ¡ç ");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"简化回库失败: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库失败: {ex.Message}");
            }
        }
        /// <summary>
        /// ç®€åŒ–获取回库条码
        /// </summary>
        private async Task<List<string>> GetBarcodesForSimpleReturn(string orderNo, string palletCode, int stockId)
        {
            var barcodes = new List<string>();
            try
            {
                // 1. ä»Žé”å®šè®°å½•获取
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                    .Select(x => x.CurrentBarcode)
                    .ToListAsync();
                barcodes.AddRange(lockInfos.Where(b => !string.IsNullOrEmpty(b)));
                // 2. ä»Žåº“存明细获取
                var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId && x.StockQuantity > 0)
                    .Select(x => x.Barcode)
                    .ToListAsync();
                barcodes.AddRange(stockDetails.Where(b => !string.IsNullOrEmpty(b)));
                // åŽ»é‡
                return barcodes.Distinct().ToList();
            }
            catch (Exception ex)
            {
                _logger.LogError($"获取回库条码失败: {ex.Message}");
                return barcodes;
            }
        }
        /// <summary>
        /// ç®€åŒ–处理条码回库
        /// </summary>
        private async Task ProcessSimpleBarcodeReturn(string barcode, int stockId)
        {
            try
            {
                // 1. èŽ·å–åº“å­˜æ˜Žç»†
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
                if (stockDetail == null)
                {
                    _logger.LogWarning($"未找到条码对应的库存明细: {barcode}");
                    return;
                }
                // 2. å¦‚果是出库锁定状态,恢复为入库完成
                if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
                {
                    stockDetail.Status = (int)StockStatusEmun.入库完成;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    _logger.LogInformation($"条码状态恢复 - {barcode}: å‡ºåº“锁定 -> å…¥åº“完成");
                }
                // 3. æ›´æ–°ç›¸å…³çš„锁定记录
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == barcode &&
                               (x.Status == (int)OutLockStockStatusEnum.出库中 ||
                                x.Status == (int)OutLockStockStatusEnum.拣选完成))
                    .ToListAsync();
                foreach (var lockInfo in lockInfos)
                {
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                    lockInfo.Operator = App.User.UserName;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    _logger.LogInformation($"锁定记录状态更新 - ID: {lockInfo.Id}: å·²å›žåº“");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"处理条码回库失败 - æ¡ç : {barcode}, Error: {ex.Message}");
            }
        }
        #endregion
        #region è¾…助方法
@@ -2831,9 +5088,6 @@
        #endregion
        #region éªŒè¯æ–¹æ³•
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest(
       string orderNo, string palletCode, string barcode)
@@ -2937,6 +5191,7 @@
                _logger.LogInformation($"跳过未分配锁定记录的自动拆包检查 - é”å®šID: {lockInfo.Id}");
                return null;
            }
            // æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…çš„æ¡ä»¶ï¼š
            // 1. åº“存数量大于分配数量
            // 2. é”å®šä¿¡æ¯çŠ¶æ€ä¸ºå‡ºåº“ä¸­
@@ -2951,109 +5206,134 @@
            // è®¡ç®—拆包数量 = åº“存数量 - åˆ†é…æ•°é‡
            decimal splitQuantity = stockDetail.StockQuantity - lockInfo.AssignQuantity;
            _logger.LogInformation($"需要自动拆包 - åº“å­˜: {stockDetail.StockQuantity}, åˆ†é…: {lockInfo.AssignQuantity}, æ‹†åŒ…数量: {splitQuantity}");
            // æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…
            var splitResult = await ExecuteAutoSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
            // å°†æ‹†åŒ…数量传递给调用方,用于验证
            if (splitResult != null && splitResult.Any())
            {
                // åœ¨è¿”回结果中携带拆包数量信息
                foreach (var result in splitResult)
                {
                    result.quantityTotal = splitQuantity.ToString("F2");
                }
            }
            return splitResult;
        }
        /// <summary>
        /// æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…é€»è¾‘
        /// ç¡®ä¿è‡ªåŠ¨æ‹†åŒ…ä¸ä¼šå½±å“å›žåº“é€»è¾‘
        /// æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…é€»è¾‘ - å®Œå…¨ä¿®æ­£ç‰ˆ
        /// åŽŸåˆ™ï¼šåªåˆ†ç¦»ç‰©ç†åº“å­˜ï¼Œä¸æ”¹å˜åŽŸè®¢å•çš„ä»»ä½•åˆ†é…å’Œå‡ºåº“æ•°é‡
        /// </summary>
        private async Task<List<SplitResult>> ExecuteAutoSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
     decimal splitQuantity, string palletCode)
            decimal splitQuantity, string palletCode)
        {
            _logger.LogInformation($"开始执行自动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            // éªŒè¯æ‹†åŒ…数量合理性
            if (splitQuantity <= 0)
            try
            {
                throw new InvalidOperationException($"拆包数量必须大于0,当前值: {splitQuantity}");
                // 1. éªŒè¯æ‹†åŒ…数量合理性
                if (splitQuantity <= 0)
                    throw new InvalidOperationException($"拆包数量必须大于0,当前值: {splitQuantity}");
                if (stockDetail.StockQuantity < lockInfo.AssignQuantity + splitQuantity)
                    throw new InvalidOperationException($"库存数量不足以进行自动拆包,库存: {stockDetail.StockQuantity}, éœ€è¦: {lockInfo.AssignQuantity + splitQuantity}");
                // 2. ç”Ÿæˆæ–°æ¡ç 
                string newBarcode = await GenerateNewBarcode();
                _logger.LogInformation($"生成新条码: {newBarcode}");
                // 3. ã€æ ¸å¿ƒä¿®æ­£ã€‘更新原库存明细:只减少物理库存,不影响出库数量
                decimal originalStockQty = stockDetail.StockQuantity;
                stockDetail.StockQuantity -= splitQuantity; // ä»…库存减少
                                                            // stockDetail.OutboundQuantity ä¿æŒä¸å˜ï¼
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"更新原库存明细:条码 {stockDetail.Barcode} åº“å­˜ {originalStockQty} -> {stockDetail.StockQuantity},出库数量不变({stockDetail.OutboundQuantity})");
                // 4. åˆ›å»ºæ–°åº“存明细(多余部分)- å‡ºåº“数量为0
                var newStockDetail = new Dt_StockInfoDetail
                {
                    StockId = stockDetail.StockId,
                    MaterielCode = stockDetail.MaterielCode,
                    OrderNo = stockDetail.OrderNo,
                    BatchNo = stockDetail.BatchNo,
                    StockQuantity = splitQuantity, // æ–°åº“存数量
                    OutboundQuantity = 0, // ã€é‡ç‚¹ã€‘初始出库数量为0
                    Barcode = newBarcode,
                    Status = (int)StockStatusEmun.出库锁定, // ä»ä¸ºé”å®šçŠ¶æ€ï¼Œä½†æœªç»‘å®šè®¢å•
                    SupplyCode = stockDetail.SupplyCode,
                    Unit = stockDetail.Unit,
                    BarcodeQty = stockDetail.BarcodeQty,
                    BarcodeUnit = stockDetail.BarcodeUnit,
                    BusinessType = stockDetail.BusinessType,
                    InboundOrderRowNo = stockDetail.InboundOrderRowNo,
                };
                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"创建新库存明细:条码 {newBarcode},库存 {splitQuantity},出库 0");
                // 5. åˆ›å»ºæ–°é”å®šä¿¡æ¯ - æ ‡è®°ä¸ºæœªåˆ†é…
                var newLockInfo = new Dt_OutStockLockInfo
                {
                    OrderNo = lockInfo.OrderNo,
                    OrderDetailId = 0, // ã€é‡ç‚¹ã€‘不绑定到具体订单明细,表示未分配
                    OutboundBatchNo = lockInfo.OutboundBatchNo,
                    MaterielCode = lockInfo.MaterielCode,
                    MaterielName = lockInfo.MaterielName,
                    StockId = lockInfo.StockId,
                    OrderQuantity = splitQuantity,
                    AssignQuantity = splitQuantity,
                    PickedQty = 0,
                    LocationCode = lockInfo.LocationCode,
                    PalletCode = lockInfo.PalletCode,
                    TaskNum = lockInfo.TaskNum,
                    Status = (int)OutLockStockStatusEnum.出库中,
                    Unit = lockInfo.Unit,
                    SupplyCode = lockInfo.SupplyCode,
                    OrderType = lockInfo.OrderType,
                    CurrentBarcode = newBarcode,
                    IsSplitted = 1,
                    ParentLockId = lockInfo.Id,
                    Operator = App.User.UserName,
                    FactoryArea = lockInfo.FactoryArea,
                    lineNo = lockInfo.lineNo,
                    WarehouseCode = lockInfo.WarehouseCode,
                    BarcodeQty = lockInfo.BarcodeQty,
                    BarcodeUnit = lockInfo.BarcodeUnit,
                    IsUnallocated = 1 // ã€é‡ç‚¹ã€‘明确标记为"未分配"的锁定记录
                };
                await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"创建未分配锁定记录:ID {newLockInfo.Id},条码 {newBarcode},数量 {splitQuantity}");
                // 6. ã€å…³é”®ä¿®æ­£ã€‘原锁定记录和原订单明细数据完全保持不变!
                // - ä¸ä¿®æ”¹ lockInfo çš„任何字段
                // - ä¸ä¿®æ”¹å…³è”çš„ Dt_OutboundOrderDetail çš„ AllocatedQuantity å’Œ LockQuantity
                // è®°å½•拆包历史
                await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, true, originalStockQty);
                // åˆ›å»ºæ‹†åŒ…结果列表
                var splitResults = CreateSplitResults(lockInfo, splitQuantity, lockInfo.AssignQuantity, newBarcode, stockDetail.Barcode);
                _logger.LogInformation($"自动拆包逻辑执行完成 - åˆ›å»ºäº†æœªåˆ†é…çš„库存和锁定记录");
                return splitResults;
            }
            if (stockDetail.StockQuantity < lockInfo.AssignQuantity + splitQuantity)
            catch (Exception ex)
            {
                throw new InvalidOperationException($"库存数量不足以进行自动拆包");
                _logger.LogError($"自动拆包逻辑执行失败 - åŽŸæ¡ç : {stockDetail.Barcode}, Error: {ex.Message}");
                throw;
            }
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // è®°å½•拆包前的分配数量
            decimal originalAssignQty = lockInfo.AssignQuantity;
            decimal remainQty = originalAssignQty; // åŽŸé”å®šä¿¡æ¯åˆ†é…æ•°é‡ä¿æŒä¸å˜
            // åˆ›å»ºæ–°åº“存明细(多余部分)
            var newStockDetail = new Dt_StockInfoDetail
            {
                StockId = stockDetail.StockId,
                MaterielCode = stockDetail.MaterielCode,
                OrderNo = stockDetail.OrderNo,
                BatchNo = stockDetail.BatchNo,
                StockQuantity = splitQuantity,
                OutboundQuantity = 0,
                Barcode = newBarcode,
                Status = (int)StockStatusEmun.出库锁定,
                SupplyCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit,
                BarcodeQty = stockDetail.BarcodeQty,
                BarcodeUnit = stockDetail.BarcodeUnit,
                BusinessType = stockDetail.BusinessType,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"创建新库存明细 - æ¡ç : {newBarcode}, åº“存数量: {splitQuantity}");
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯ï¼ˆå¤šä½™éƒ¨åˆ†ï¼‰- æ ‡è®°ä¸ºæœªåˆ†é…
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = 0, // é‡è¦ï¼šä¸ç»‘定到具体订单明细
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
                OrderQuantity = splitQuantity,
                AssignQuantity = splitQuantity,
                PickedQty = 0,
                LocationCode = lockInfo.LocationCode,
                PalletCode = lockInfo.PalletCode,
                TaskNum = lockInfo.TaskNum,
                Status = (int)OutLockStockStatusEnum.出库中,
                Unit = lockInfo.Unit,
                SupplyCode = lockInfo.SupplyCode,
                OrderType = lockInfo.OrderType,
                CurrentBarcode = newBarcode,
                IsSplitted = 1,
                ParentLockId = lockInfo.Id,
                Operator = App.User.UserName,
                FactoryArea = lockInfo.FactoryArea,
                lineNo = lockInfo.lineNo,
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
                IsUnallocated = 1 // æ ‡è®°ä¸ºæœªåˆ†é…
            };
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
            _logger.LogInformation($"创建新锁定信息 - æ¡ç : {newBarcode}, åˆ†é…æ•°é‡: {splitQuantity}, æ ‡è®°ä¸ºæœªåˆ†é…");
            // è‡ªåŠ¨æ‹†åŒ…ä¸æ”¹å˜è®¢å•æ˜Žç»†çš„åˆ†é…æ•°é‡
            _logger.LogInformation($"自动拆包 - è®¢å•明细分配数量保持不变");
            // è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, true, stockDetail.StockQuantity);
            // åˆ›å»ºæ‹†åŒ…结果列表
            var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
            _logger.LogInformation($"自动拆包逻辑执行完成");
            return splitResults;
        }
        /// <summary>
        /// éªŒè¯è‡ªåŠ¨æ‹†åŒ…åŽæ•°æ®ä¸€è‡´æ€§
        /// éªŒè¯è‡ªåŠ¨æ‹†åŒ…åŽæ•°æ®ä¸€è‡´æ€§ - ä¿®æ­£ç‰ˆ
        /// åŽŸåˆ™ï¼šéªŒè¯æœªåˆ†é…é”å®šè®°å½•çš„åˆ›å»ºï¼Œè€Œä¸æ˜¯è®¢å•æ˜Žç»†æ•°é‡çš„å˜åŒ–
        /// </summary>
        private async Task ValidateDataConsistencyAfterAutoSplit(long orderDetailId, decimal originalAllocatedQty, decimal originalLockQty, decimal splitQuantity)
        {
@@ -3064,60 +5344,104 @@
            if (orderDetail == null)
                return;
            // è®¡ç®—所有锁定信息的总分配数量
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .ToListAsync();
            decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
            _logger.LogInformation($"自动拆包后数据一致性验证 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            // éªŒè¯è‡ªåŠ¨æ‹†åŒ…åŽçš„æ•°æ®ä¸€è‡´æ€§
            decimal expectedAllocatedQty = originalAllocatedQty + splitQuantity;
            decimal expectedLockQty = originalLockQty + splitQuantity;
            if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
            // ã€ä¿®æ­£ã€‘自动拆包后,订单明细分配数量应该保持不变!
            if (Math.Abs(orderDetail.AllocatedQuantity - originalAllocatedQty) > 0.01m)
            {
                _logger.LogWarning($"自动拆包后分配数量异常 - æœŸæœ›: {expectedAllocatedQty}, å®žé™…: {orderDetail.AllocatedQuantity}");
                _logger.LogError($"自动拆包后订单明细分配数量异常变化!期望保持不变: {originalAllocatedQty}, å®žé™…: {orderDetail.AllocatedQuantity}");
                // è®°å½•严重错误,但不抛出异常(生产环境可能需要告警)
            }
            if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
            if (Math.Abs(orderDetail.LockQuantity - originalLockQty) > 0.01m)
            {
                _logger.LogWarning($"自动拆包后锁定数量异常 - æœŸæœ›: {expectedLockQty}, å®žé™…: {orderDetail.LockQuantity}");
                _logger.LogError($"自动拆包后订单明细锁定数量异常变化!期望保持不变: {originalLockQty}, å®žé™…: {orderDetail.LockQuantity}");
            }
            if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
            // éªŒè¯æœªåˆ†é…é”å®šè®°å½•的创建
            // æŸ¥æ‰¾çˆ¶é”å®šè®°å½•
            var parentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.OrderDetailId == orderDetailId);
            if (parentLockInfo != null)
            {
                _logger.LogWarning($"自动拆包后数据不一致 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
                // æŸ¥æ‰¾æœªåˆ†é…çš„子锁定记录(自动拆包生成的)
                var unallocatedChildLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.ParentLockId == parentLockInfo.Id &&
                               x.IsUnallocated == 1 &&
                               x.OrderDetailId == 0)
                    .ToListAsync();
                if (unallocatedChildLocks.Any())
                {
                    decimal totalUnallocatedQty = unallocatedChildLocks.Sum(x => x.AssignQuantity);
                    _logger.LogInformation($"验证通过:创建了{unallocatedChildLocks.Count}条未分配锁定记录,总数量: {totalUnallocatedQty}");
                    if (Math.Abs(totalUnallocatedQty - splitQuantity) > 0.01m)
                    {
                        _logger.LogWarning($"未分配锁定记录总数量与拆包数量不匹配,拆包数量: {splitQuantity}, æœªåˆ†é…æ€»æ•°: {totalUnallocatedQty}");
                    }
                }
                else
                {
                    _logger.LogWarning($"未找到自动拆包生成的未分配锁定记录");
                }
            }
            _logger.LogInformation($"自动拆包数据一致性验证完成");
        }
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        /// <summary>
        /// æ‰§è¡Œåˆ†æ‹£é€»è¾‘ - å®Œå…¨ä¿®æ­£ç‰ˆ
        /// ç¡®ä¿OutboundQuantity准确累加,不包含拆包数量
        /// </summary>
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
            _logger.LogInformation($"开始执行分拣逻辑 - æ¡ç : {stockDetail.Barcode}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å®žé™…拣选: {actualPickedQty}");
            // å†æ¬¡éªŒè¯è®¢å•明细的分配数量(防止并发操作)
            if (orderDetail.AllocatedQuantity < actualPickedQty)
            // å†æ¬¡éªŒè¯åº“存数量
            if (stockDetail.StockQuantity < actualPickedQty)
            {
                throw new InvalidOperationException($"订单明细分配数量不足,需要拣选 {actualPickedQty},可用分配数量 {orderDetail.AllocatedQuantity}");
                throw new InvalidOperationException($"库存数量不足,需要拣选 {actualPickedQty},实际库存 {stockDetail.StockQuantity}");
            }
            if (orderDetail.LockQuantity < actualPickedQty)
            // è®°å½•拣选前的值
            decimal originalStockQty = stockDetail.StockQuantity;
            decimal originalOutboundQty = stockDetail.OutboundQuantity;
            int originalStatus = stockDetail.Status;
            // ã€æ ¸å¿ƒä¿®æ­£ã€‘确保OutboundQuantity只增加本次拣选数量,不包含其他
            stockDetail.StockQuantity -= actualPickedQty;
            stockDetail.OutboundQuantity += actualPickedQty;  // åªå¢žåŠ æœ¬æ¬¡æ‹£é€‰æ•°é‡
            _logger.LogInformation($"更新库存信息 - æ¡ç : {stockDetail.Barcode}");
            _logger.LogInformation($"  åº“存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
            _logger.LogInformation($"  å‡ºåº“数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
            // æ›´æ–°åº“存状态
            if (stockDetail.StockQuantity <= 0)
            {
                throw new InvalidOperationException($"订单明细锁定数量不足,需要拣选 {actualPickedQty},可用锁定数量 {orderDetail.LockQuantity}");
                stockDetail.Status = (int)StockStatusEmun.出库完成;
                _logger.LogInformation($"库存状态更新为出库完成");
            }
            else
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
                _logger.LogInformation($"库存状态保持为出库锁定");
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // æ›´æ–°é”å®šä¿¡æ¯
            decimal originalPickedQty = lockInfo.PickedQty;
            lockInfo.PickedQty += actualPickedQty;
            _logger.LogInformation($"更新锁定信息 - å·²æ‹£é€‰æ•°é‡ä»Ž {lockInfo.PickedQty - actualPickedQty} å¢žåŠ åˆ° {lockInfo.PickedQty}");
            _logger.LogInformation($"更新锁定信息 - å·²æ‹£é€‰æ•°é‡ä»Ž {originalPickedQty} å¢žåŠ åˆ° {lockInfo.PickedQty}");
            // å‡†ç¡®åˆ¤æ–­æ‹£é€‰å®ŒæˆçŠ¶æ€
            // åˆ¤æ–­æ‹£é€‰å®ŒæˆçŠ¶æ€
            if (Math.Abs(lockInfo.PickedQty - lockInfo.AssignQuantity) < 0.001m)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
@@ -3132,29 +5456,8 @@
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ›´æ–°åº“存信息
            decimal originalStockQty = stockDetail.StockQuantity;
            decimal originalOutboundQty = stockDetail.OutboundQuantity;
            stockDetail.StockQuantity -= actualPickedQty;
            stockDetail.OutboundQuantity += actualPickedQty;
            _logger.LogInformation($"更新库存信息 - åº“存数量从 {originalStockQty} å‡å°‘到 {stockDetail.StockQuantity}");
            _logger.LogInformation($"更新库存信息 - å‡ºåº“数量从 {originalOutboundQty} å¢žåŠ åˆ° {stockDetail.OutboundQuantity}");
            // å‡†ç¡®åˆ¤æ–­åº“存状态
            if (stockDetail.StockQuantity <= 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库完成;
                _logger.LogInformation($"库存状态更新为出库完成");
            }
            else
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
                _logger.LogInformation($"库存状态保持为出库锁定");
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // éªŒè¯æ‹£é€‰åŽçš„æ•°æ®ä¸€è‡´æ€§
            await ValidatePickingDataConsistency(lockInfo, stockDetail, actualPickedQty);
            _logger.LogInformation($"分拣逻辑执行完成 - æ¡ç : {stockDetail.Barcode}");
@@ -3163,6 +5466,40 @@
                FinalLockInfo = lockInfo,
                ActualPickedQty = actualPickedQty
            };
        }
        /// <summary>
        /// éªŒè¯æ‹£é€‰åŽæ•°æ®ä¸€è‡´æ€§
        /// </summary>
        private async Task ValidatePickingDataConsistency(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal pickedQty)
        {
            _logger.LogInformation($"验证拣选数据一致性 - æ¡ç : {stockDetail.Barcode}");
            // 1. éªŒè¯åº“存明细的OutboundQuantity增加量等于拣选数量
            var refreshedStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Id == stockDetail.Id);
            decimal outboundIncrease = refreshedStockDetail.OutboundQuantity - stockDetail.OutboundQuantity;
            if (Math.Abs(outboundIncrease - pickedQty) > 0.01m)
            {
                _logger.LogError($"拣选数据不一致:出库数量增加 {outboundIncrease},但拣选数量是 {pickedQty}");
                // ä¿®å¤ï¼šç¡®ä¿OutboundQuantity正确
                refreshedStockDetail.OutboundQuantity = stockDetail.OutboundQuantity + pickedQty;
                await _stockInfoDetailService.Db.Updateable(refreshedStockDetail).ExecuteCommandAsync();
                _logger.LogWarning($"已修复出库数量:{stockDetail.OutboundQuantity} -> {refreshedStockDetail.OutboundQuantity}");
            }
            // 2. éªŒè¯é”å®šè®°å½•的已拣选数量
            var refreshedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == lockInfo.Id);
            if (Math.Abs(refreshedLockInfo.PickedQty - (lockInfo.PickedQty - pickedQty) - pickedQty) > 0.01m)
            {
                _logger.LogError($"锁定记录已拣选数量不一致");
            }
            _logger.LogInformation($"拣选数据一致性验证通过");
        }
        private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
@@ -3255,59 +5592,85 @@
        #region æ•°æ®æ›´æ–°æ–¹æ³•
        /// <summary>
        /// æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据 - ä¿®æ­£ç‰ˆ
        /// ç¡®ä¿åªæ›´æ–°å®žé™…的拣选数量
        /// </summary>
        private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
        {
            _logger.LogInformation($"开始更新批次和订单数据 - æ‹£é€‰æ•°é‡: {pickedQty}");
            var latestOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == orderDetail.Id);
            // é‡æ–°èŽ·å–æœ€æ–°æ•°æ®ï¼ˆé˜²æ­¢å¹¶å‘é—®é¢˜ï¼‰
            var latestOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetail.Id);
            if (latestOrderDetail == null)
                throw new InvalidOperationException("未找到订单明细");
            orderDetail = latestOrderDetail;
            // éªŒè¯åˆ†é…æ•°é‡ä¸ä¼šå˜æˆè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < pickedQty)
            // éªŒè¯æ‹£é€‰æ•°é‡åˆç†æ€§
            if (pickedQty <= 0)
            {
                decimal actualPickedQty = orderDetail.AllocatedQuantity;
                _logger.LogWarning($"分配数量不足,调整拣选数量 - åŽŸéœ€è¦: {pickedQty}, å®žé™…可用: {actualPickedQty}");
                pickedQty = actualPickedQty;
                _logger.LogWarning($"拣选数量无效: {pickedQty}");
                return;
            }
            if (orderDetail.LockQuantity < pickedQty)
            {
                decimal actualPickedQty = orderDetail.LockQuantity;
                _logger.LogWarning($"锁定数量不足,调整拣选数量 - åŽŸéœ€è¦: {pickedQty}, å®žé™…可用: {actualPickedQty}");
                pickedQty = actualPickedQty;
            }
            // 1. æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            decimal originalBatchCompletedQty = batch.CompletedQuantity;
            batch.CompletedQuantity += pickedQty;
            _logger.LogInformation($"更新批次完成数量 - ä»Ž {originalBatchCompletedQty} å¢žåŠ åˆ° {batch.CompletedQuantity}");
            if (batch.CompletedQuantity >= batch.BatchQuantity)
            {
                batch.BatchStatus = (int)BatchStatusEnum.已完成;
                _logger.LogInformation($"批次状态更新为已完成");
            }
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            // æ›´æ–°è®¢å•明细
            // è®°å½•原始值
            decimal originalOverOutQty = orderDetail.OverOutQuantity;
            decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalLockQty = orderDetail.LockQuantity;
            orderDetail.OverOutQuantity += pickedQty;
            orderDetail.AllocatedQuantity -= pickedQty;
            // LockQuantity åŒæ­¥å‡å°‘
            orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
            if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
            if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
            _logger.LogInformation($"更新订单明细 - å·²å‡ºåº“数量从 {originalOverOutQty} å¢žåŠ åˆ° {orderDetail.OverOutQuantity}");
            _logger.LogInformation($"更新订单明细 - å·²åˆ†é…æ•°é‡ä»Ž {originalAllocatedQty} å‡å°‘到 {orderDetail.AllocatedQuantity}");
            _logger.LogInformation($"更新订单明细 - é”å®šæ•°é‡ä»Ž {originalLockQty} å‡å°‘到 {orderDetail.LockQuantity}");
            // æ›´æ–°è®¢å•明细
            orderDetail.OverOutQuantity += pickedQty;  // å·²å‡ºåº“数量增加
            orderDetail.AllocatedQuantity -= pickedQty;  // å·²åˆ†é…æ•°é‡å‡å°‘
            orderDetail.LockQuantity -= pickedQty;  // é”å®šæ•°é‡å‡å°‘
            // ç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < 0)
            {
                _logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + pickedQty}");
                orderDetail.AllocatedQuantity = 0;
            }
            if (orderDetail.LockQuantity < 0)
            {
                _logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + pickedQty}");
                orderDetail.LockQuantity = 0;
            }
            // æ›´æ–°æ‰¹æ¬¡åˆ†é…çŠ¶æ€
            await UpdateBatchAllocateStatus(orderDetail);
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            _logger.LogInformation($"更新订单明细成功");
            _logger.LogInformation($"  å·²å‡ºåº“数量: {originalOverOutQty} -> {orderDetail.OverOutQuantity}");
            _logger.LogInformation($"  å·²åˆ†é…æ•°é‡: {originalAllocatedQty} -> {orderDetail.AllocatedQuantity}");
            _logger.LogInformation($"  é”å®šæ•°é‡: {originalLockQty} -> {orderDetail.LockQuantity}");
            // æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            if (batch != null)
            {
                decimal originalBatchCompletedQty = batch.CompletedQuantity;
                batch.CompletedQuantity += pickedQty;
                _logger.LogInformation($"更新批次完成数量 - ä»Ž {originalBatchCompletedQty} å¢žåŠ åˆ° {batch.CompletedQuantity}");
                // æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                if (batch.CompletedQuantity >= batch.BatchQuantity)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.已完成;
                    _logger.LogInformation($"批次状态更新为已完成");
                }
                else if (batch.CompletedQuantity > 0)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.执行中;
                    _logger.LogInformation($"批次状态更新为执行中");
                }
                await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            }
            // æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
@@ -3513,7 +5876,7 @@
            _logger.LogInformation($"CreateReturnTaskAndHandleESS  åˆ†æ‰¹åˆ é™¤åŽ†å²ä»»åŠ¡: {orderNo} ï¼Œ {originalTask.TaskNum}");
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            //_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
           var result= _task_HtyService.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.人工删除);
            var result = _task_HtyService.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.人工删除);
            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            if (!result)
@@ -3579,7 +5942,6 @@
                throw new Exception($"ESS系统通信失败: {ex.Message}");
            }
        }
        #region è¾…助方法
@@ -3668,7 +6030,6 @@
            };
        }
        #endregion
        #region DTOç±»
        /// <summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderService.cs
@@ -334,7 +334,7 @@
            }
            var data = BaseDal.Db.Queryable<Dt_OutboundOrder>()
                .WhereIF(!wheres.IsNullOrEmpty(), wheres)
                .Where(x => x.OrderType == 0 || x.OrderType == 116)
                .Where(x => x.OrderType == 0)
                .OrderBy(orderByModels)
                .ToPageList(options.Page, options.Rows, ref totalCount);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -87,7 +87,7 @@
        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, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_InboundOrder> inboundOrderRepository, IInboundOrderDetailService inboundOrderDetailService, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IReCheckOrderService reCheckOrderService, ITask_HtyService task_HtyService,IRepository<Dt_InterfaceLog> interfaceLog) : base(BaseDal)
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_InboundOrder> inboundOrderRepository, IInboundOrderDetailService inboundOrderDetailService, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IReCheckOrderService reCheckOrderService, ITask_HtyService task_HtyService, IRepository<Dt_InterfaceLog> interfaceLog) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -304,7 +304,27 @@
                var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
                if (!statusAnalysis.HasItemsToReturn)
                    return await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id);
                {
                    try
                    {
                        var result = await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id);
                        _unitOfWorkManage.CommitTran();
                        if (result.Status)
                        {
                            task.PalletType = PalletTypeEnum.Empty.ObjToInt();
                            await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
                        }
                        return result;
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        _logger.LogError($"ReturnRemaining å›žåº“空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                        return WebResponseContent.Instance.Error($"回库空箱操作失败: {ex.Message}");
                    }
                }
                // 4. æ£€æŸ¥æ˜¯å¦æœ‰è¿›è¡Œä¸­çš„任务
                if (statusAnalysis.HasActiveTasks)
@@ -1314,8 +1334,6 @@
                //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
                originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
            }
            catch (Exception ex)
            {
@@ -1700,7 +1718,7 @@
                    throw new Exception("创建任务失败!");
                }
            }
            catch (Exception ex)
            catch (Exception ex)
            {
                _logger.LogInformation($"CreateReturnTaskAndHandleESS åˆ›å»ºä»»åŠ¡å¤±è´¥: {orderNo} ï¼Œ {palletCode}");
                throw new Exception("创建任务失败!");
@@ -2119,8 +2137,7 @@
                    .Where(x => x.StockId == stockId && x.StockQuantity == 0)
                    .ExecuteCommandAsync();
                await _stockInfoService.Db.Deleteable<Dt_StockInfo>()
                   .Where(x => x.Id == stockId).ExecuteCommandAsync();
                _stockInfoService.DeleteData(stockId);
                _logger.LogInformation($"清理零库存明细记录 - StockId: {stockId}, åˆ é™¤è®°å½•æ•°: {deleteDetailCount}");
@@ -2937,7 +2954,7 @@
                    await _interfaceLog.Db.Updateable<Dt_InterfaceLog>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x=>x.DocumentNo == documentNo)
                        .Where(x => x.DocumentNo == documentNo)
                        .ExecuteCommandAsync();
                }
                return WebResponseContent.Instance.OK();
@@ -3040,7 +3057,7 @@
                if (materielGroupDTO.orderTypes == InOrderTypeEnum.ReCheck.ObjToInt())
                {
                    var dborder = _reCheckOrderService.Db.Queryable<Dt_ReCheckOrder>().First(x => x.OrderNo == materielGroupDTO.OrderNo);
                    if (dborder != null && dborder.SignSeq == 0)
                    if (dborder != null && (dborder.SignSeq == 0 || dborder.SignSeq == 1))
                    {
                        return content.Error("只有拿到重检结果才能入库!");
                    }
@@ -3285,6 +3302,8 @@
        public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
        public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
        // ã€æ–°å¢žã€‘已处理的条码集合(用于避免重复)
        public HashSet<string> ProcessedBarcodes { get; set; } = new HashSet<string>();
        public List<string> AllBarcodes { get; set; } = new List<string>();
        // ç©ºæ‰˜ç›˜ç›¸å…³å±žæ€§
        public bool IsEmptyPallet { get; set; }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockDetailByMaterielService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,164 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Stock;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_StockService
{
    public class StockDetailByMaterielService :IStockDetailByMaterielService
    {
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        private readonly SqlSugarClient _dbBase;
        public StockDetailByMaterielService(IUnitOfWorkManage unitOfWorkManage)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _dbBase = unitOfWorkManage.GetDbClient();
        }
        public virtual PageGridData<StockDetailByMateriel> GetPageGridData(PageDataOptions options)
        {
            try
            {
                List<StockDetailByMateriel> materielnfoStatistics = new List<StockDetailByMateriel>();
                ISugarQueryable<Dt_StockInfoDetail> sugarQueryable1 = _dbBase.Queryable<Dt_StockInfoDetail>();
                ISugarQueryable<Dt_StockInfo> sugarQueryable = _dbBase.Queryable<Dt_StockInfo>();
                if (!string.IsNullOrEmpty(options.Wheres))
                {
                    try
                    {
                        List<SearchParameters> searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
                        if (searchParametersList.Count > 0)
                        {
                            {
                                SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_StockInfoDetail.MaterielCode).FirstLetterToLower());
                                if (searchParameters != null)
                                {
                                    sugarQueryable1 = sugarQueryable1.Where(x => x.MaterielCode.Contains(searchParameters.Value));
                                }
                            }
                            {
                                SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_StockInfo.WarehouseId).FirstLetterToLower());
                                if (searchParameters != null)
                                {
                                    sugarQueryable = sugarQueryable.Where(x => x.WarehouseId.Equals(searchParameters.Value));
                                }
                            }
                            {
                                SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_StockInfo.StockStatus).FirstLetterToLower());
                                if (searchParameters != null)
                                {
                                    sugarQueryable = sugarQueryable.Where(x => x.StockStatus.Equals(searchParameters.Value));
                                }
                            }
                            {
                                SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_StockInfoDetail.MaterielName).FirstLetterToLower());
                                if (searchParameters != null)
                                {
                                    sugarQueryable1 = sugarQueryable1.Where(x => x.MaterielName.Contains(searchParameters.Value));
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                }
                ISugarQueryable<StockDetailByMateriel> sugarQueryable2 = sugarQueryable1.InnerJoin(sugarQueryable, (b, a) => b.StockId == a.Id).Where((b, a) => a.LocationCode != "" && a.LocationCode != null).Select((b, a)
                    => new StockDetailByMateriel
                    {
                       StockId = b.StockId,
                        MaterielCode = b.MaterielCode,
                        MaterielName = b.MaterielName,
                        StockQuantity = (decimal)b.StockQuantity,
                        OutboundQuantity = (decimal)b.OutboundQuantity,
                        OrderNo = b.OrderNo,
                        BatchNo = b.BatchNo,
                        SerialNumber = b.SerialNumber,
                        SupplyCode = b.SupplyCode,
                        WarehouseCode = b.WarehouseCode,
                        Barcode = b.Barcode,
                        Unit = b.Unit,
                        Creater = b.Creater,
                        CreateDate= b.CreateDate,
                        Modifier = b.Modifier,
                        ModifyDate = b.ModifyDate,
                        Remark = a.Remark,
                    });
                // èŽ·å–æ‰€æœ‰æ•°æ®
                var allData = sugarQueryable2.ToList();
                // ä½¿ç”¨å­—典进行分组汇总
                var groupedData = new Dictionary<string, StockDetailByMateriel>();
                foreach (var item in allData)
                {
                    // ç¡®ä¿åˆå§‹å€¼ä¸ºdecimal类型
                    if (groupedData.ContainsKey(item.MaterielCode))
                    {
                        groupedData[item.MaterielCode].StockQuantity += Convert.ToDecimal(item.StockQuantity);
                        groupedData[item.MaterielCode].OutboundQuantity += Convert.ToDecimal(item.OutboundQuantity);
                    }
                    else
                    {
                        groupedData[item.MaterielCode] = new StockDetailByMateriel
                        {
                            StockId = item.StockId,
                            MaterielCode = item.MaterielCode,
                            MaterielName = item.MaterielName,
                            StockQuantity = Convert.ToDecimal(item.StockQuantity),
                            OutboundQuantity = Convert.ToDecimal(item.OutboundQuantity),
                            OrderNo = item.OrderNo,
                            BatchNo = item.BatchNo,
                            SerialNumber = item.SerialNumber,
                            SupplyCode = item.SupplyCode,
                            WarehouseCode = item.WarehouseCode,
                            Barcode = item.Barcode,
                            Creater = item.Creater,
                            CreateDate = item.CreateDate,
                            Modifier = item.Modifier,
                            ModifyDate = item.ModifyDate,
                            Unit = item.Unit,
                            Remark = item.Remark
                        };
                    }
                }
                // è½¬æ¢ä¸ºåˆ—表
                materielnfoStatistics = groupedData.Values.ToList();
                // åˆ†é¡µå¤„理
                int startIndex = (options.Page - 1) * options.Rows;
                int endIndex = Math.Min(startIndex + options.Rows, materielnfoStatistics.Count);
                materielnfoStatistics = materielnfoStatistics.GetRange(startIndex, endIndex - startIndex);
                int count = groupedData.Count;
                return new PageGridData<StockDetailByMateriel>(count, materielnfoStatistics);
            }
            catch (Exception ex)
            {
            }
            return new PageGridData<StockDetailByMateriel>();
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoDetailService.cs
@@ -114,6 +114,7 @@
                Summary = pageData.Summary
            };
        }
    }
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -627,8 +627,7 @@
                // æ›´æ–°å‡ºåº“锁定记录状态为回库完成
                foreach (var lockInfo in returnLocks)
                {
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                    lockInfo.Operator = App.User.UserName;
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                }
                if (returnLocks.Any())
@@ -965,8 +964,7 @@
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => new Dt_OutboundOrder
                        {
                            OrderStatus = newStatus,
                            Operator = App.User.UserName,
                            OrderStatus = newStatus,
                        })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
@@ -1120,8 +1118,7 @@
                                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                                      .SetColumns(x => new Dt_OutboundOrder
                                      {
                                          ReturnToMESStatus = 1,
                                          Operator = App.User.UserName,
                                          ReturnToMESStatus = 1,
                                      }).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync();
                            }
                        }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderDetailController.cs
@@ -1,5 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_Core.Attributes;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.Inbound;
using WIDESEA_IAllocateService;
using WIDESEA_Model.Models;
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs
@@ -93,21 +93,21 @@
            erpApiService.GetMaterialUnitAsync();
            var sss = await _invokeMESService.NewMaterielToMes(new WIDESEA_DTO.Basic.MaterielToMesDTO
            {
                reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                reqCode = Guid.NewGuid().ToString(),
                oldmaterialCode = "b001",
                newmaterialCode = "Bar01",
                unit = "A01",
                operationType = 1,
                supplyCode = "A0001",
                batchNo = "A0002",
                materialCode = "FC00001",
                warehouseCode = "",
                factoryArea = "A01",
                qty =20,
            });
            //var sss = await _invokeMESService.NewMaterielToMes(new WIDESEA_DTO.Basic.MaterielToMesDTO
            //{
            //    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
            //    reqCode = Guid.NewGuid().ToString(),
            //    oldmaterialCode = "b001",
            //    newmaterialCode = "Bar01",
            //    unit = "A01",
            //    operationType = 1,
            //    supplyCode = "A0001",
            //    batchNo = "A0002",
            //    materialCode = "FC00001",
            //    warehouseCode = "",
            //    factoryArea = "A01",
            //    qty =20,
            //});
            //await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
            //{
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockDetailByMaterielController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_DTO.Stock;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_StockService;
namespace WIDESEA_WMSServer.Controllers.Stock
{
    [Route("api/StockDetailByMateriel")]
    [Authorize, ApiController]
    public class StockDetailByMaterielController:Controller
    {
        public readonly IStockDetailByMaterielService _stockDetailByMaterielService;
        public StockDetailByMaterielController(IStockDetailByMaterielService stockDetailByMaterielService)
        {
            _stockDetailByMaterielService = stockDetailByMaterielService;
        }
        [HttpPost, Route("GetPageData")]
        public PageGridData<StockDetailByMateriel> GetPageData([FromBody] PageDataOptions options)
        {
            return _stockDetailByMaterielService.GetPageGridData(options);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.Stock;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_StockService;
namespace WIDESEA_WMSServer.Controllers.Stock
{
@@ -28,5 +30,6 @@
            return Json(result);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
@@ -1,5 +1,3 @@
using System.Reflection;
using System.Text;
using Autofac;
using Autofac.Core;
using Autofac.Extensions.DependencyInjection;
@@ -11,7 +9,12 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Quartz;
using Serilog;
using System.Net.Security;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Text;
using WIDESEA_Core;
using WIDESEA_Core.Authorization;
using WIDESEA_Core.BaseServices;
@@ -22,7 +25,6 @@
//using WIDESEA_Core.HostedService;
using WIDESEA_Core.Middlewares;
using WIDESEA_WMSServer.Filter;
using Quartz;
using WIDESEA_WMSServer.Jobs;
var builder = WebApplication.CreateBuilder(args);
@@ -147,7 +149,7 @@
    client.BaseAddress = new Uri(erpurl!);
    client.Timeout = TimeSpan.FromSeconds(30);
});
})  ;
 
builder.Services.AddQuartz(q =>
{