647556386
2025-11-22 68628c6cc163cddfcc745c225a9f3f34767261ef
Merge branch 'master' of http://115.159.85.185:8098/r/ZhongRui/ALDbanyunxiangmu
已添加5个文件
已修改35个文件
5943 ■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/inbound/allocateinboundOrder.js 456 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/inboundOrder.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/allocateoutboundOrder.js 374 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/printView.vue 588 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/router/viewGird.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/inbound/allocateinboundOrder.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/allocateoutboundOrder.vue 443 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/MaterielToMesService.cs 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/StockStatusEmun.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Allocate/AllocateDto.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IBasicService/IInvokeMESService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IBasicService/IMaterielToMesService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundOrderDetailService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_MaterielToMes.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 2670 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs 209 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WIDESEA_TaskInfoService.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderController.cs 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/allocateinboundOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,456 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
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,解决提示无反应
let extension = {
    components: {
      //查询界面扩展组件
      gridHeader: '',
      gridBody: '',
      gridFooter: '',
      //新建、编辑弹出框扩展组件
      modelHeader: '',
      modelBody: '',
      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.inboundOrderNo);
        }
      },
      {
                name: '撤销组盘',
                type: 'primary',
                value: '撤销组盘',
                onClick: function () {
                    console.log('撤销组盘按钮被点击');
                    const mountNode = document.createElement('div');
                    document.body.appendChild(mountNode);
                    // å“åº”式表单数据:托盘号(必填)
                    const formData = reactive({
                        palletCode: '' // æ‰˜ç›˜å·è¾“入框
                    });
                    // æäº¤è¡¨å•的统一逻辑
                    const submitForm = async () => {
                        const formRef = vnode.component.refs.cancelPalletForm;
                        try {
                            // æ‰§è¡Œè¡¨å•校验(托盘号必填)
                            await formRef.validate();
                        } catch (err) {
                            ElMessage.warning('请输入有效的托盘号');
                            return;
                        }
                        // å‘起撤销组盘请求
                        try {
                            //console.log('发起撤销组盘请求,托盘号:', formData.palletCode.trim());
                            const response = await http.post('/api/InboundOrder/CancelPalletGroup', {
                                palletCode: formData.palletCode.trim()
                            });
                            const { status, message, data } = response;
                            if (status) {
                                ElMessage.success(`撤销组盘成功,托盘号:${formData.palletCode.trim()}`);
                                this.refresh(); // æˆåŠŸåŽåˆ·æ–°åˆ—è¡¨
                                // å…³é—­å¯¹è¯æ¡†
                                render(null, mountNode);
                                document.body.removeChild(mountNode);
                            } else {
                                console.log('撤销组盘失败,后端提示:', message);
                                ElMessage.error(message || data?.message || '撤销组盘失败');
                                selectPalletCodeInput(); // é€‰ä¸­è¾“入框方便重新输入
                            }
                        } catch (error) {
                            console.error('撤销组盘请求异常:', error);
                            ElMessage.error('网络异常或接口错误,请稍后重试');
                            selectPalletCodeInput();
                        }
                    };
                    // é€‰ä¸­è¾“入框文本(方便重新输入)
                    const selectPalletCodeInput = () => {
                        setTimeout(() => {
                            const inputRef = vnode.component.refs.palletCodeInput;
                            if (inputRef) {
                                const targetInput = inputRef.$el?.querySelector('input') || inputRef;
                                targetInput?.focus();
                                targetInput?.select();
                            }
                        }, 100);
                    };
                    // åˆ›å»ºå¯¹è¯æ¡†VNode
                    const vnode = createVNode(ElDialog, {
                        title: '撤销组盘',
                        width: '400px',
                        modelValue: true,
                        appendToBody: true,
                        onOpened: () => {
                            // å¯¹è¯æ¡†æ‰“开后自动聚焦输入框
                            setTimeout(() => {
                                const inputRef = vnode.component.refs.palletCodeInput;
                                inputRef?.focus();
                            }, 100);
                        },
                        'onUpdate:modelValue': (isVisible) => {
                            if (!isVisible) {
                                render(null, mountNode);
                                document.body.removeChild(mountNode);
                            }
                        }
                    }, {
                        default: () => h(ElForm, {
                            model: formData,
                            rules: {
                                palletCode: [
                                    { required: true, message: '请输入托盘号', trigger: ['blur', 'enter'] },
                                    { min: 1, max: 50, message: '托盘号长度不能超过50个字符', trigger: ['blur', 'input'] }
                                ]
                            },
                            ref: 'cancelPalletForm'
                        }, [
                            // æ‰˜ç›˜å·è¾“入项
                            h(ElFormItem, { label: '托盘号', prop: 'palletCode', required: true }, [
                                h(ElInput, {
                                    type: 'text',
                                    modelValue: formData.palletCode,
                                    'onUpdate:modelValue': (val) => {
                                        formData.palletCode = val;
                                    },
                                    ref: 'palletCodeInput',
                                    placeholder: '扫码输入或手动输入托盘号',
                                    maxLength: 50,
                                    // ç›‘听回车事件(扫码枪默认会发送回车)
                                    onKeydown: (e) => {
                                        if (e.key === 'Enter') {
                                            e.preventDefault();
                                            submitForm();
                                        }
                                    }
                                })
                            ]),
                            // åº•部按钮区
                            h('div', { style: { textAlign: 'right', marginTop: '16px' } }, [
                                h(ElButton, {
                                    type: 'text',
                                    onClick: () => {
                                        render(null, mountNode);
                                        document.body.removeChild(mountNode);
                                        ElMessage.info('取消撤销组盘');
                                    }
                                }, '取消'),
                                h(ElButton, {
                                    type: 'primary',
                                    onClick: submitForm.bind(this) // ç»‘定this上下文
                                }, '确认撤销')
                            ])
                        ])
                    });
                    vnode.appContext = this.$.appContext;
                    render(vnode, mountNode);
                }
            },
      {
  name: '分批入库',
  type: 'primary',
  value: '分批入库',
  onClick: async function () {
    console.log('分批入库按钮被点击,开始校验');
    const selectedRows = this.$refs.table.getSelected();
    // æ ¡éªŒ1:是否选中行(至少选择一条)
    if (selectedRows.length === 0) {
      console.log('校验不通过:未选中任何单据');
      ElMessage.warning('请选择至少一条单据');
      return;
    }
    // æ”¶é›†æ‰€æœ‰é€‰ä¸­å•据的编号(过滤无单据号的异常行)
    const inboundOrderNos = selectedRows
      .filter(row => row.inboundOrderNo)
      .map(row => row.inboundOrderNo);
    // æ ¡éªŒ2:是否有有效单据号
    if (inboundOrderNos.length === 0) {
      console.log('校验不通过:选中单据无有效编号');
      ElMessage.warning('选中的单据中无有效编号,请重新选择');
      return;
    }
    try {
      console.log('发起分批入库请求,参数:', { inboundOrderNos});
      const response = await http.post('/api/InboundOrder/BatchOrderFeedbackToMes', {
        orderNos: inboundOrderNos,
        inout:1
      });
      const { status, message, data } = response;
      if (status) {
        console.log('分批入库成功,后端返回:', data);
        ElMessage.success(`分批入库成功!共处理${inboundOrderNos.length}条单据`);
        this.refresh(); // å…¥åº“成功后刷新列表(复用原有逻辑)
      } else {
        console.log('分批入库失败,后端提示:', message);
        ElMessage.error(message || data?.message || '分批入库失败');
      }
    } catch (error) {
      console.error('分批入库请求异常:', error);
      ElMessage.error('网络异常或接口错误,请稍后重试');
    }
  }
},
     {
  name: '空托盘入库',
  type: 'primary',
  value: '空托盘入库',
  onClick: function () {
    const mountNode = document.createElement('div');
    document.body.appendChild(mountNode);
    // å“åº”式表单数据:料箱码(必填,扫码枪/手动输入)
    const formData = reactive({
      boxCode: '',
      warehouseCode:''
    });
    const warehouses = ref([]);
    const isLoadingWarehouses = ref(false);
    const getWarehouseList = async () => {
      isLoadingWarehouses.value = true;
      try {
        const { data, status } = await http.post('/api/LocationInfo/GetLocationTypes');
        if (status && Array.isArray(data)) {
          // æ ¼å¼åŒ–仓库选项:适配ElSelect的label-value格式
          warehouses.value = data.map(item => ({
            label: item.locationTypeDesc,
            value: item.locationType
          }));
        } else {
          ElMessage.error('获取区域列表失败');
          warehouses.value = [];
        }
      } catch (err) {
        ElMessage.error('区域数据请求异常,请稍后重试');
        warehouses.value = [];
      } finally {
        isLoadingWarehouses.value = false;
      }
    };
    // æäº¤è¡¨å•的统一逻辑(供回车触发和按钮点击共用)
    const submitForm = async () => {
      const formRef = vnode.component.refs.batchInForm;
      try {
        // æ‰§è¡Œè¡¨å•校验(料箱码必填)
        await formRef.validate();
      } catch (err) {
        ElMessage.warning('请输入有效的料箱码');
        return;
      }
      http.post('/api/InboundOrder/EmptyMaterielGroup', {
        palletCode: formData.boxCode.trim(),
        warehouseCode:formData.warehouseCode
      }).then(({ data, status, message }) => {
        if (status) {
          ElMessage.success(`入库成功,料箱码:${formData.boxCode.trim()}`);
          this.refresh();
          formData.boxCode = '';
          setTimeout(() => {
            const inputRef = vnode.component.refs.boxCodeInput;
            inputRef?.focus();
          }, 100);
        } else {
          ElMessage.error(message || data?.message || '入库失败');
          selectBoxCodeInput();
        }
      }).catch(() => {
        ElMessage.error('请求失败,请稍后重试');
        selectBoxCodeInput();
      });
    };
    const selectBoxCodeInput = () => {
      setTimeout(() => {
        const inputRef = vnode.component.refs.boxCodeInput;
        if (inputRef) {
          const targetInput = inputRef.$el?.querySelector('input') || inputRef;
          targetInput?.focus();
          targetInput?.select();
        }
  }, 100);
}
    const vnode = createVNode(ElDialog, {
      title: '空托盘入库',
      width: '400px',
      modelValue: true,
      appendToBody: true,
      onOpened: async () => {
        await getWarehouseList();
        const inputRef = vnode.component.refs.boxCodeInput;
        inputRef?.focus();
      },
      'onUpdate:modelValue': (isVisible) => {
        if (!isVisible) {
          render(null, mountNode);
          document.body.removeChild(mountNode);
        }
      }
    }, {
      default: () => h(ElForm, {
        model: formData,
        rules: {
          boxCode: [
            { required: true, message: '请输入料箱码', trigger: ['blur', 'enter'] }
          ],
          warehouseCode:[
            { required: true, message: '请选择区域', trigger: ['change', 'blur'] }
          ]
        },
        ref: 'batchInForm'
      }, [
        //仓库数据
        h(ElFormItem, { label: '区域', prop: 'warehouseCode', required: true }, [
          h(ElSelect, {
            modelValue: formData.warehouseCode,
            'onUpdate:modelValue': (val) => {
              formData.warehouseCode = val;
            },
            placeholder: '请选择入库区域',
            filterable: true, // æ”¯æŒæœç´¢ä»“库
            loading: isLoadingWarehouses.value, // åŠ è½½çŠ¶æ€
            style: { width: '100%' }
          }, [
            // æ¸²æŸ“仓库下拉选项
            warehouses.value.map(item => h(ElOption, {
              label: item.label,
              value: item.value
            }))
          ])
        ]),
        // æ–™ç®±ç è¾“入项(支持聚焦、回车提交)
        h(ElFormItem, { label: '料箱码', prop: 'boxCode', required: true }, [
          h(ElInput, {
            type: 'text',
            modelValue: formData.boxCode,
            'onUpdate:modelValue': (val) => {
              formData.boxCode = val;
            },
            ref: 'boxCodeInput',
            placeholder: '扫码输入或手动输入料箱码',
            // ç›‘听回车事件(扫码枪默认会发送回车)
            onKeydown: (e) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                submitForm();
              }
            }
          })
        ]),
        // åº•部按钮区
        h('div', { style: { textAlign: 'right', marginTop: '16px' } }, [
          h(ElButton, {
            type: 'text',
            onClick: () => {
              render(null, mountNode);
              document.body.removeChild(mountNode);
              ElMessage.info('取消入库任务');
            }
          }, '取消'),
          h(ElButton, {
            type: 'primary',
            onClick: submitForm
          }, '确定')
        ])
      ])
    });
    vnode.appContext = this.$.appContext;
    render(vnode, mountNode);
  }
}
    ], box: [], detail: [] },
    methods: {
       //下面这些方法可以保留也可以删除
      onInit() {
      },
      onInited() {
        //框架初始化配置后
        //如果要配置明细表,在此方法操作
        //this.detailOptions.columns.forEach(column=>{ });
      },
      searchBefore(param) {
        //界面查询前,可以给param.wheres添加查询参数
        //返回false,则不会执行查询
        let wheres = [{
            'name': 'orderType',
            'value': '115',
            'displayType': 'text'}];
          param.wheres.push(...wheres);
    //   this.searchFormFields.orderType=[115]
        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/extension/inbound/inboundOrder.js
@@ -416,6 +416,7 @@
      searchBefore(param) {
        //界面查询前,可以给param.wheres添加查询参数
        //返回false,则不会执行查询
       this.searchFormFields.orderType=[0]
        return true;
      },
      searchAfter(result) {
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/allocateoutboundOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,374 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
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';
import gridBody from './extend/outOrderDetail.vue'
let extension = {
    components: {
      //查询界面扩展组件
      gridHeader: '',
      gridBody: gridBody,
      gridFooter: '',
      //新建、编辑弹出框扩展组件
      modelHeader: '',
      modelBody: '',
      modelFooter: ''
    },
    tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
    buttons: { view: [
       /* {
        name: '出库',
        type: 'primary',
        value: '出库',
        onClick: function () { // ä¿®å¤ï¼šç”¨ElMessage替代this.$message
          const selectedRows = this.$refs.table.getSelected();
          if (selectedRows.length === 0) {
            ElMessage.warning('请先选择要生成任务的行');
            return;
          }
          if (selectedRows.length > 1) {
            ElMessage.warning('只能选择一行');
            return;
          }
          // æ‰€æœ‰æ ¡éªŒé€šè¿‡ï¼Œè§¦å‘主组件打开出库弹窗
          console.log('所有校验通过,触发openOutboundDialog事件,单据数据:', selectedRows[0]);
          this.$emit('openOutboundDialog', {
            transNo: selectedRows[0].transNo,       // å‡ºåº“单编号
            createDate: selectedRows[0].createDate || new Date().toLocaleDateString()  // å‡ºåº“日期
          });
        }
      }, */
      {
  name: '空托盘出库',
  type: 'primary',
  value: '空托盘出库',
  onClick: function () {
    const platformOptions = Array.from({ length: 1 }, (_, i) => {
      const num = 1;
      return { label: `站台${num}`, value: `1-2` };
    });
    const quantityOptions = Array.from({ length: 6 }, (_, i) => ({
      label: (i + 1).toString(),
      value: i + 1
    }));
    const warehouseOptions = ref([]);
    const isLoadingWarehouses = ref(false);
    const getWarehouseList = async () => {
      isLoadingWarehouses.value = true;
      try {
        const { data, status } = await http.post('/api/LocationInfo/GetLocationTypes');
        if (status && Array.isArray(data)) {
          // æ ¼å¼åŒ–仓库选项:适配ElSelect的label-value格式
          warehouseOptions.value = data.map(item => ({
            label: item.locationTypeDesc,
            value: item.locationType
          }));
        } else {
          ElMessage.error('获取区域列表失败');
          warehouseOptions.value = [];
        }
      } catch (err) {
        ElMessage.error('区域数据请求异常,请稍后重试');
        warehouseOptions.value = [];
      } finally {
        isLoadingWarehouses.value = false;
      }
    };
    const mountNode = document.createElement('div');
    document.body.appendChild(mountNode);
    const formData = reactive({
      warehouseCode:'',
      palletCode: '',
      selectedPlatform: platformOptions[0].value,
      quantity:1
    });
    const vnode = createVNode(ElDialog, {
      title: '空托盘出库',
      width: '500px',
      modelValue: true,
      appendToBody: true,
      onOpened: async () => {
        await getWarehouseList();
        const inputRef = vnode.component.refs.boxCodeInput;
        inputRef?.focus();
      },
      'onUpdate:modelValue': (isVisible) => {
        if (!isVisible) {
          render(null, mountNode);
          document.body.removeChild(mountNode);
        }
      },
      style: {
        padding: '20px 0',
        borderRadius: '8px'
      }
    }, {
      default: () => h(ElForm, {
        model: formData,
        rules: {
          warehouseCode:[
            { required: true, message: '请选择区域', trigger: ['change', 'blur'] }
          ],
          palletCode: [
            { type: 'string', message: '料箱号必须为字符串', trigger: 'blur' }
          ],
          selectedPlatform: [
            { required: true, message: '请选择出库站台', trigger: 'change' }
          ],
          quantity:[
            { required: true, message: '请选择空箱数量', trigger: 'change'}
          ]
        },
        ref: 'batchOutForm',
        labelWidth: '100px',
        style: {
          padding: '0 30px',
        }
       },
       [
      //   h(ElFormItem, {
      //     label: '仓库区域',
      //     prop: 'warehouseCode',
      //     style: {
      //       marginBottom: '24px'
      //     }
      //   }, [
      //     h(ElSelect, {
      //       placeholder: '请选择仓库区域',
      //       modelValue: formData.warehouseCode,
      //       'onUpdate:modelValue': (val) => {
      //         formData.warehouseCode = val;
      //       },
      //       style: {
      //         width: '100%',
      //         height: '40px',
      //         borderRadius: '4px',
      //         borderColor: '#dcdfe6'
      //       }
      //     }, warehouseOptions.value.map(platform =>
      //       h(ElOption, { label: platform.label, value: platform.value })
      //     ))
      //   ]),
        h(ElFormItem, {
          label: '出库站台',
          prop: 'selectedPlatform',
          style: {
            marginBottom: '24px'
          }
        }, [
          h(ElSelect, {
            placeholder: '请选择出库站台',
            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:'quantity',
      //     style:{
      //       marginBottom:'24px'
      //     }
      //   },[h(ElSelect,{
      //     placeholder:'请选择空箱数量',
      //     modelValue:formData.quantity,
      //     'onUpdate:modelValue':(val)=>{
      //       formData.quantity=val;
      //     },
      //     style:{
      //       width:'100%',
      //       height:'40px',
      //       borderRadius:'4px',
      //       borderColor:'#dcdfe6'
      //     },
      //     filterable:false
      //   },
      //   quantityOptions.map(option=>
      //     h(ElOption,{
      //       label:option.label,
      //       value:option.value
      //     })
      //   )
      // )]),
        h(ElFormItem, {
          label: '料箱号',
          prop: 'palletCode',
          style: {
            marginBottom: '16px'
          }
        }, [
          h(ElInput, {
            type: 'text',
            placeholder: '可选输入料箱号,不填则自动分配空料箱',
            modelValue: formData.palletCode,
            'onUpdate:modelValue': (val) => {
              formData.palletCode = val;
            },
            style: {
              width: '100%',
              height: '40px',
              borderRadius: '4px',
              borderColor: '#dcdfe6'
            },
            attrs: {
              placeholderStyle: 'color: #909399;'
            }
          })
        ]),
        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.batchOutForm;
              try {
                await formRef.validate();
              } catch (err) {
                return;
              }
              http.post('/api/Task/PalletOutboundTask?palletCode='+formData.palletCode+'&endStation='+formData.selectedPlatform, {
              }).then(({ data, status, message }) => {
                if (status) {
                  ElMessage.success(`出库成功`);
                  this.refresh();
                  render(null, mountNode);
                  document.body.removeChild(mountNode);
                } else {
                  ElMessage.error(message || data?.message || '出库失败');
                }
              }).catch(() => {
                ElMessage.error('请求失败,请稍后重试');
              });
            },
            style: {
              borderRadius: '4px',
              padding: '8px 20px'
            }
          }, '确定')
        ])
      ])
    });
    vnode.appContext = this.$.appContext;
    render(vnode, mountNode);
  }
}
    ], box: [], detail: [] }, //扩展的按钮
    methods: {
       //下面这些方法可以保留也可以删除
      onInit() {
        //扩展页面初始化操作
        this.columns.push({
          field: '操作',
          title: '操作',
          width: 90,
          fixed: 'right',
          align: 'center',
          formatter: (row) => {
              return (
                  '<i style="cursor: pointer;color: #2d8cf0;"class="el-icon-view">查看明细</i>'
              );
          },
          click: (row) => {
            const table = this.$refs.table.$refs.table;
            if(table){
              table.clearSelection();
              table.toggleRowSelection(row,true);
            }
              const rowId =row.id;
              console.log(rowId);
              this.$refs.gridBody.open(row);
          }
      });
      },
      onInited() {
        //框架初始化配置后
        //如果要配置明细表,在此方法操作
        //this.detailOptions.columns.forEach(column=>{ });
      },
      searchBefore(param) {
        //界面查询前,可以给param.wheres添加查询参数
        //返回false,则不会执行查询
       //   this.searchFormFields.orderType=[215]
        let wheres = [{
            'name': 'orderType',
            'value': '215',
            'displayType': 'text'}];
          param.wheres.push(...wheres);
        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/extension/outbound/extend/printView.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,588 @@
<template>
  <div>
    <vol-box v-model="showDetialBox" :lazy="true" width="500px" :padding="15" title="">
      <!-- æ‰“印专用容器 -->
      <div id="printContainer" style="position: absolute; left: -9999px;">
        <div class="print-page" v-for="(item, index) in printData" :key="index">
          <div class="material-card">
            <!-- <div class="header">卓力能物料标识卡(小包)</div> -->
            <div class="dual-column preview-card-header-qrcode">
              <div class="preview-card-header">物料卡 {{ idx + 1 }}</div>
              <VueQrcode :value="generateQr(item)" :size="20" id="qrcode" />
            </div>
            <div class="content">
              <!-- äºŒç»´ç æ”¾åœ¨å³ä¸Šè§’ -->
              <!-- <div class="qr-section"> -->
              <!-- <VueQrcode :value="generateQr(item)" id="qrcode" /> -->
              <!-- </div> -->
              <div class="row dual-column">
                <div class="column">
                  <span class="label">料号</span>
                  <span class="value code-value">{{ item.materialCode }}</span>
                </div>
                <div class="column">
                  <span class="label">供应商编码</span>
                  <span class="value">{{ item.supplierCode }}</span>
                </div>
              </div>
              <div class="row dual-column">
                <div class="column">
                  <span class="label">品名</span>
                  <span class="value">{{ item.materialName }}</span>
                </div>
                <div class="column">
                  <span class="label">采购单号</span>
                  <span class="value">{{ item.purchaseOrderNo }}</span>
                </div>
              </div>
              <div class="row dual-column">
                <div class="column">
                  <span class="label">规格</span>
                  <span class="value full-width">{{ item.specification }}</span>
                </div>
                <div class="column">
                  <span class="label">数量/总数</span>
                  <span class="value">{{ item.quantityTotal }}</span>
                </div>
              </div>
              <div class="row dual-column">
                <div class="column">
                  <span class="label">批号</span>
                  <span class="value">{{ item.batchNumber }}</span>
                </div>
                <div class="column">
                  <span class="label">批次</span>
                  <span class="value">{{ item.batch }}</span>
                </div>
              </div>
              <div class="row dual-column">
                <div class="column">
                  <span class="label">厂区</span>
                  <span class="value">{{ item.factory }}</span>
                </div>
                <div class="column">
                  <span class="label">日期</span>
                  <span class="value">{{ item.date }}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <!-- é¢„览区域 -->
      <div id="previewContent" class="preview-container">
        <div class="preview-header">卓力能物料标识卡(小包) - é¢„览 (共{{ printData.length }}个)</div>
        <div class="preview-scroll">
          <div v-for="(item, idx) in printData" :key="idx" class="preview-card">
            <div class="dual-column preview-card-header-qrcode">
              <div class="preview-card-header">物料卡 {{ idx + 1 }}</div>
              <VueQrcode :value="generateQr(item)" :size="20" id="qrcode" />
            </div>
            <div class="preview-wrapper">
              <div class="content-preview">
                <div class="row-preview dual-column">
                  <div class="column-preview">
                    <span class="label-preview">料号</span>
                    <span class="value-preview code-value">{{ item.materialCode }}</span>
                  </div>
                  <div class="column-preview">
                    <span class="label-preview">供应商编码</span>
                    <span class="value-preview">{{ item.supplierCode }}</span>
                  </div>
                </div>
                <div class="row-preview dual-column">
                  <div class="column-preview">
                    <span class="label-preview">品名</span>
                    <span class="value-preview">{{ item.materialName }}</span>
                  </div>
                  <div class="column-preview">
                    <span class="label-preview">采购单号</span>
                    <span class="value-preview">{{ item.purchaseOrderNo }}</span>
                  </div>
                </div>
                <div class="row-preview dual-column">
                  <div class="column-preview">
                    <span class="label-preview">规格</span>
                    <span class="value-preview full-width">{{ item.specification }}</span>
                  </div>
                  <div class="column-preview">
                    <span class="label-preview">数量/总数</span>
                    <span class="value-preview">{{ item.quantityTotal }}</span>
                  </div>
                </div>
                <div class="row-preview dual-column">
                  <div class="column-preview">
                    <span class="label-preview">批号</span>
                    <span class="value-preview">{{ item.batchNumber }}</span>
                  </div>
                  <div class="column-preview">
                    <span class="label-preview">批次</span>
                    <span class="value-preview">{{ item.batch }}</span>
                  </div>
                </div>
                <div class="row-preview dual-column">
                  <div class="column-preview">
                    <span class="label-preview">厂区</span>
                    <span class="value-preview">{{ item.factory }}</span>
                  </div>
                  <div class="column-preview">
                    <span class="label-preview">日期</span>
                    <span class="value-preview">{{ item.date }}</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="footer-actions">
          <span class="print-count">共 {{ printData.length }} ä¸ªç‰©æ–™å¡å¾…打印</span>
          <div>
            <el-button type="primary" size="small" @click="print">打印全部</el-button>
            <el-button type="danger" size="small" @click="showDetialBox = false">关闭</el-button>
          </div>
        </div>
      </template>
    </vol-box>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import VueQrcode from "vue-qrcode";
export default {
  components: { VolBox, VueQrcode },
  data() {
    return {
      showDetialBox: false,
      printData: [], // å­˜å‚¨æ‰“印数据集合
    };
  },
  methods: {
    generateQr(item) {
      return `${item.materialCode}_${item.supplierCode}_${item.purchaseOrderNo}_${item.materialName}_${item.batch}_${item.batchNumber}_${item.factory}_${item.date}`;
    },
    open(rows) {
      this.showDetialBox = true;
      this.printData = Array.isArray(rows) ? rows : [rows];
      console.log('打印数据:', this.printData);
    },
    async print() {
      if (this.printData.length === 0) {
        this.$message.warning('没有可打印的数据');
        return;
      }
      this.$nextTick(() => {
        try {
          const printContent = document.getElementById("printContainer").innerHTML;
          const printWindow = window.open("", "_blank", "width=800,height=600,scrollbars=yes");
          if (!printWindow) {
            this.$message.error('无法打开打印窗口,请检查浏览器弹窗设置');
            return;
          }
          printWindow.document.write(`
            <!DOCTYPE html>
            <html>
            <head>
              <meta charset="UTF-8">
              <title>物料标识卡打印 - å…±${this.printData.length}个</title>
              <style>
                /* é‡ç½®æ‰€æœ‰æ ·å¼ */
                * {
                  margin: 0;
                  padding: 0;
                  box-sizing: border-box;
                }
                body {
                  font-family: SimSun, "宋体", sans-serif;
                  -webkit-print-color-adjust: exact !important;
                  print-color-adjust: exact !important;
                  color-adjust: exact !important;
                }
                #qrcode {
                  width: 60px;
                  height: 60px;
                  display: block;
                  margin: 0, 15px
                }
                @media print {
                  @page {
                    size: 80mm 60mm;
                    margin: 0;
                    padding: 0;
                  }
                  body {
                    margin: 0 !important;
                    padding: 0 !important;
                    width: 80mm !important;
                    background: white !important;
                  }
                  .print-page {
                    width: 80mm !important;
                    height: 60mm !important;
                    page-break-after: always !important;
                    break-after: page !important;
                    margin: 0 !important;
                    padding: 2mm !important;
                    display: block !important;
                  }
                  .print-page:last-child {
                    page-break-after: auto !important;
                  }
                  .material-card {
                    width: 80mm !important;
                    height: 56mm !important;
                    border: 0.3mm solid #000 !important;
                    padding: 1mm !important;
                    box-sizing: border-box !important;
                    position: relative !important;
                    background: white !important;
                  }
                  .header {
                    font-size: 3.5mm !important;
                    font-weight: bold !important;
                    text-align: center !important;
                    margin-bottom: 1mm !important;
                    padding-bottom: 0.5mm !important;
                    border-bottom: 0.2mm solid #000 !important;
                  }
                  .content {
                    height: calc(100% - 6mm) !important;
                    position: relative !important;
                  }
                  .row {
                    display: flex !important;
                    margin-bottom: 0.8mm !important;
                    min-height: 6mm !important;
                  }
                  .dual-column {
                    display: flex !important;
                    justify-content: space-between !important;
                  }
                  .column {
                    flex: 1 !important;
                    display: flex !important;
                    align-items: center !important;
                  }
                  .column:first-child {
                    margin-right: 2mm !important;
                  }
                  .label {
                    font-weight: bold !important;
                    flex: 0 0 15mm !important;
                    white-space: nowrap !important;
                    font-size: 2.2mm !important;
                  }
                  .value {
                    flex: 1 !important;
                    overflow: hidden !important;
                    text-overflow: ellipsis !important;
                    white-space: nowrap !important;
                    border-bottom: 0.1mm dashed #666 !important;
                    padding-bottom: 0.2mm !important;
                    min-height: 2.5mm !important;
                    font-size: 2.2mm !important;
                  }
                  .code-value {
                    font-weight: bold !important;
                  }
                  .full-width {
                    white-space: normal !important;
                    word-break: break-all !important;
                    line-height: 1.2 !important;
                  }
                  /* äºŒç»´ç æ ·å¼ */
                  .qr-section {
                    width: 10mm !important;
                    height: 12mm !important;
                    font-siz:10px;
                  }
                  .qr-section canvas {
                    width: 10mm !important;
                    height: 10mm !important;
                    max-width: 100% !important;
                    max-height: 100% !important;
                  }
                }
                /* å±å¹•预览样式 */
                @media screen {
                  body {
                    background: #f5f5f5;
                    padding: 10px;
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                  }
                  .print-page {
                    width: 80mm;
                    height: 60mm;
                    margin: 10px 0;
                    padding: 2mm;
                    background: white;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
                    border: 1px dashed #ccc;
                  }
                  .material-card {
                    width: 80mm;
                    height: 60mm;
                    border: 0.3mm solid #000;
                    padding: 1.5mm;
                    background: white;
                    position: relative;
                  }
                  .qr-section {
                    position: absolute;
                    top: 4mm;
                    right: 2mm;
                    width: 12mm;
                    height: 12mm;
                    border: 0.2mm solid #ccc;
                    padding: 0.5mm;
                    background: white;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                  }
                  .qr-section canvas {
                    width: 10mm !important;
                    height: 10mm !important;
                  }
                }
              </style>
            </head>
            <body>
              ${printContent}
              <script>
                window.onload = function() {
                  console.log('打印页面加载完成,共${this.printData.length}个物料卡');
                  // ç­‰å¾…二维码渲染完成
                  setTimeout(() => {
                    // å¼ºåˆ¶è®¾ç½®äºŒç»´ç canvas尺寸
                    const canvases = document.querySelectorAll('canvas');
                    canvases.forEach(canvas => {
                      canvas.style.width = '10mm !important';
                      canvas.style.height = '10mm !important';
                    });
                    window.print();
                  }, 800);
                };
                window.onafterprint = function() {
                  setTimeout(() => {
                    window.close();
                  }, 500);
                };
              <\/script>
            </body>
            </html>
          `);
          printWindow.document.close();
        } catch (error) {
          console.error('打印错误:', error);
          this.$message.error('打印失败: ' + error.message);
        }
      });
    }
  }
};
</script>
<style scoped>
#qrcode {
  width: 60px;
  height: 60px;
  display: block;
  margin: 0, 15px
}
.preview-container {
  max-height: 500px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.preview-header {
  text-align: center;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 10px;
  padding: 8px;
  background: #f0f0f0;
  border-radius: 4px;
}
.preview-scroll {
  max-height: 400px;
  overflow-y: auto;
  padding: 10px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  background: #f9f9f9;
}
.preview-card {
  width: 320px;
  height: 240px;
  margin: 0 auto 15px auto;
  padding: 8px;
  border: 1px solid #ccc;
  font-family: SimSun;
  box-sizing: border-box;
  border: 2px solid #000;
  background: white;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  position: relative;
}
.preview-card:last-child {
  margin-bottom: 0;
}
.preview-card-header {
  text-align: center;
  font-size: 14px;
  /* font-weight: bold;
  margin-bottom: 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid #000;
  background: #f5f5f5;
  padding: 4px; */
  height: 60px;
  width: 60px;
}
.preview-wrapper {
  position: relative;
  height: calc(100% - 40px);
}
.content-preview {
  width: 100%;
  height: 100%;
}
.row-preview {
  /* margin-bottom: px; */
  min-height: 32px;
}
.dual-column {
  display: flex;
  justify-content: space-between;
}
.column-preview {
  flex: 1;
  display: flex;
  align-items: center;
}
.column-preview:first-child {
  margin-right: 10px;
}
.label-preview {
  font-weight: bold;
  flex: 0 0 60px;
  white-space: nowrap;
  font-size: 12px;
}
.value-preview {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  border-bottom: 1px dashed #666;
  padding-bottom: 2px;
}
.code-value {
  font-weight: bold;
}
.full-width {
  display: block;
  margin-top: 2px;
  white-space: normal;
  word-break: break-all;
  line-height: 1.2;
  font-size: 12px;
}
/* é¢„览区域二维码样式 */
.qr-section-preview {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 150px;
  height: 175px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #ddd;
  background: white;
  padding: 2px;
}
.qr-section-preview canvas {
  width: 56px !important;
  height: 56px !important;
}
.footer-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}
.print-count {
  font-size: 14px;
  color: #409EFF;
  font-weight: bold;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -191,7 +191,16 @@
    path: '/materielToMes',
    name: 'Dt_MaterielToMes',
    component: () => import('@/views/inbound/Dt_MaterielToMes.vue')
  },
  }, {
    path:'/allocateinboundOrder',
    name: 'allocateinboundOrder',
    component: () => import('@/views/inbound/allocateinboundOrder.vue')
  },{
    path:'allocateoutboundOrder',
    name: 'allocateoutboundOrder',
    component: () => import('@/views/outbound/allocateoutboundOrder.vue')
  },
  {
    path: '/allocateOrder',
    name: 'Dt_AllocateOrder',
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/inbound/allocateinboundOrder.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,397 @@
<template>
  <view-grid
    ref="grid"
    @openPalletDialog="handleOpenPalletDialog"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
    :editFormOptions="editFormOptions"
    :searchFormFields="searchFormFields"
    :searchFormOptions="searchFormOptions"
    :table="table"
    :extend="extend"
  >
  </view-grid>
      <!-- 2. ç»„盘弹窗:确保props和事件绑定正确 -->
    <PalletDialog
      v-model:visible="palletVisible"
      :docNo="currentPalletDocNo"
      @back-success="handlePalletBackSuccess"
    ></PalletDialog>
</template>
    <script>
import extend from "@/extension/inbound/allocateinboundOrder.js";
import ViewGrid from '@/components/basic/ViewGrid/ViewGrid.vue';
import { ref, defineComponent } from "vue";
import PalletDialog from "@/extension/inbound/extend/Pallet.vue";
export default defineComponent({
   components: {
    // å…³é”®ä¿®å¤2:组件注册名与模板标签名适配(kebab-case对应view-grid)
    viewGrid: ViewGrid,  // æ³¨å†Œä¸ºkebab-case,模板用<view-grid>
    PalletDialog      // æ³¨å†Œç»„盘弹窗
  },
  setup() {
    const table = ref({
      key: "id",
      footer: "Foots",
      cnName: "调拨单(智仓调外部仓库)",
      name: "inboundOrder",
      url: "/InboundOrder/",
      sortName: "id",
    });
    const editFormFields = ref({
      orderType: "",
      inboundOrderNo: "",
      upperOrderNo: "",
      remark: "",
    });
    const editFormOptions = ref([
      [
        {
          title: "单据类型",
          required: true,
          field: "orderType",
          type: "select",
          dataKey: "inOrderType",
          data: [],
        },
        {
          field: "inboundOrderNo",
          title: "单据编号",
          type: "string",
        },
        {
          title: "上游单据编号",
          field: "upperOrderNo",
          type: "string",
        },
        {
          title: "备注",
          field: "remark",
          type: "textarea",
        },
      ],
    ]);
    const searchFormFields = ref({
      inboundOrderNo: "",
      upperOrderNo: "",
      orderType: "115",
      orderStatus: "",
      createType: "",
      creater: "",
      createDate: "",
    });
    const searchFormOptions = ref([
      [
        { title: "单据编号", field: "inboundOrderNo", type: "like" },
        { title: "上游单据编号", field: "upperOrderNo", type: "like" },
        {
          title: "单据类型",
          field: "orderType",
          type: "select",
          dataKey: "inOrderType",
          data: [],
        },
        {
          title: "单据状态",
          field: "orderStatus",
          type: "select",
          dataKey: "inboundState",
          data: [],
        },
      ],
      [
        {
          title: "创建方式",
          field: "createType",
          type: "select",
          dataKey: "createType",
          data: [],
        },
        { title: "创建者", field: "creater", type: "like" },
        { title: "创建时间", field: "createDate", type: "datetime" },
      ],
    ]);
    const columns = ref([
      {
        field: "id",
        title: "Id",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "inboundOrderNo",
        title: "单据编号",
        type: "string",
        width: 120,
        align: "left",
        link: true,
      },
      {
        field: "upperOrderNo",
        title: "上游单据编号",
        type: "string",
        width: 150,
        align: "left",
      },
      {
        field: "orderType",
        title: "单据类型",
        type: "string",
        width: 150,
        align: "left",
        bind: { key: "inOrderType", data: [] },
      },
      {
        field: "businessType",
        title: "业务类型",
        type: "string",
        width: 150,
        align: "left",
        bind: { key: "businessType", data: [] },
      },
      {
        field: "orderStatus",
        title: "单据状态",
        type: "decimal",
        width: 90,
        align: "left",
        bind: { key: "inboundState", data: [] },
      },
      {
        field: "createType",
        title: "创建方式",
        type: "string",
        width: 120,
        align: "left",
        bind: { key: "createType", data: [] },
      },
      {
        field: "factoryArea",
        title: "厂区",
        type: "string",
        width: 120,
        align: "left"
      },
      {
        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: "调拨单明细",
      table: "InboundOrderDetail",
      columns: [
        {
          field: "id",
          title: "Id",
          type: "int",
          width: 90,
          hidden: true,
          readonly: true,
          require: true,
          align: "left",
        },
        {
          field: "orderId",
          title: "调拨单主键",
          type: "string",
          width: 90,
          align: "left",
          hidden: true,
        },
        {
          field: "materielCode",
          title: "物料编号",
          type: "select",
          width: 150,
          align: "left",
          edit: { type: "" },
          required: true,
        },
        {
          field: "materielCode",
          title: "物料名称",
          type: "string",
          width: 100,
          align: "left",
          bind: { key: "MaterielNames", data: [] },
        },
        {
          field: "batchNo",
          title: "批次号",
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "" },
          required: true,
        },
        {
          field: "supplyCode",
          title: "供应商编号",
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "" },
          required: true,
        },
        {
          field: "warehouseCode",
          title: "仓库号",
          type: "decimal",
          width: 90,
          align: "left",
          required: true
        },
        {
          field: "barcode",
          title: "条码",
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "" },
          required: true,
        },
        {
          field: "orderQuantity",
          title: "单据数量",
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "number" },
          required: true,
        },
        {
          field: "receiptQuantity",
          title: "组盘数量",
          type: "int",
          width: 120,
          align: "left",
        },
        {
          field: "overInQuantity",
          title: "上架数量",
          type: "string",
          width: 200,
          align: "left",
        },
        {
          field: "orderDetailStatus",
          title: "订单明细状态",
          type: "string",
          width: 180,
          align: "left",
          bind: { key: "orderDetailStatusEnum", 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",
        },
      ],
      sortName: "id",
      key: "id",
    });
     // 6. ç»„盘弹窗联动(所有变量必须返回)
    const palletVisible = ref(false);
    const currentPalletDocNo = ref("");
    const handleOpenPalletDialog = (docNo) => {
      console.log('主组件收到组盘事件,单据号:', docNo);
      currentPalletDocNo.value = docNo;
      palletVisible.value = true;
    };
    const handlePalletBackSuccess = () => {
      console.log('组盘回传成功,刷新表格');
      grid.value?.refresh();  // æ­¤æ—¶gridRef已挂载,可调用方法
    };
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
       // ç»„盘弹窗相关
      PalletDialog,    // å¼¹çª—组件(无需返回,注册即可,但变量需返回)
      palletVisible,
      currentPalletDocNo,
      handleOpenPalletDialog,
      handlePalletBackSuccess
    };
  },
});
</script>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue
@@ -78,7 +78,7 @@
    const searchFormFields = ref({
      inboundOrderNo: "",
      upperOrderNo: "",
      orderType: "",
      orderType: "0",
      orderStatus: "",
      createType: "",
      creater: "",
@@ -93,7 +93,7 @@
          field: "orderType",
          type: "select",
          dataKey: "inOrderType",
          data: [],
          data: [0],
        },
        {
          title: "单据状态",
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue
@@ -26,8 +26,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="primary" @click="openBatchReturnDialog">回库</el-button>
          <!-- <el-button type="danger" @click="handleDirectOutbound">直接出库</el-button>  -->
@@ -291,6 +291,7 @@
      </div>
    </div>
  </div>
  <print-view ref="childs" @parentcall="parentcall"></print-view>
</template>
<script>
@@ -298,12 +299,11 @@
import { ref, defineComponent } from "vue";
import { ElMessage } from 'element-plus' 
import { useRoute } from 'vue-router'
import printView from "@/extension/outbound/extend/printView.vue"
export default defineComponent({
  name: 'PickingConfirm',
  components: {
  },
  components: {printView},
  props: {
    orderNo: {
      type: String,
@@ -844,7 +844,12 @@
          this.$message.success('拣选确认成功');
          this.scanData.barcode = ''; // æ¸…空物料条码
          this.loadData();
          console.log(res.data.splitResults)
          if(res.data && res.data.splitResults.length>0){
            // è°ƒç”¨å­ç»„件打印方法
            this.$refs.childs.open(res.data.splitResults);
            //this.$refs.childs.printSplitLabel(res.data.splitResults);
          }
          // æˆåŠŸåŽç»§ç»­èšç„¦åˆ°ç‰©æ–™æ¡ç è¾“å…¥æ¡†ï¼Œå‡†å¤‡ä¸‹ä¸€ä¸ªæ‰«ç 
          this.$nextTick(() => {
            this.$refs.barcodeInput.focus();
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/allocateoutboundOrder.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,443 @@
<template>
  <view-grid
    ref="grid"
        @openOutboundDialog="handleOpenOutboundDialog"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
    :editFormOptions="editFormOptions"
    :searchFormFields="searchFormFields"
    :searchFormOptions="searchFormOptions"
    :table="table"
    :extend="extend"
  >
  </view-grid>
      <!-- å‡ºåº“操作弹窗 -->
    <OutboundDialog
      v-model:visible="outboundVisible"
      :selected-document="selectedOutboundDocument"
      @outbound-success="handleOutboundSuccess"
    ></OutboundDialog>
</template>
    <script>
import extend from "@/extension/outbound/allocateoutboundOrder.js";
import ViewGrid from '@/components/basic/ViewGrid/ViewGrid.vue';
import { ref, defineComponent } from "vue";
import { ElMessage } from "element-plus";
import OutboundDialog from "@/extension/outbound/extend/OutBound.vue";  // å¼•入出库弹窗组件
export default defineComponent({
   components: {
    // å…³é”®ä¿®å¤2:组件注册名与模板标签名适配(kebab-case对应view-grid)
    viewGrid: ViewGrid,  // æ³¨å†Œä¸ºkebab-case,模板用<view-grid>
    OutboundDialog  // æ³¨å†Œå‡ºåº“弹窗组件
  },
  setup() {
    const table = ref({
      key: "id",
      footer: "Foots",
      cnName: "调拨单(外部仓库调智仓)",
      name: "outboundOrder",
      url: "/OutboundOrder/",
      sortName: "id",
    });
    const editFormFields = ref({
      orderType: "",
      orderNo:"",
      upperOrderNo: "",
      orderStatus: "",
      remark: "",
      warehouseId:""
    });
    const editFormOptions = ref([
      [
        {
          title: "单据类型",
          required: true,
          field: "orderType",
          type: "select",
          dataKey: "outOrderType",
          data: [],
        },
        {
        field: "orderNo",
        title: "单据编号",
        type: "string",
        readonly:true
      },
        {
          title: "上游单据编号",
          field: "upperOrderNo",
          type: "string",
        },
        {
          title: "单据状态",
          field: "orderStatus",
          type: "select",
          dataKey: "inboundState",
          data: [],
          readonly: true,
        },
        {
          title: "仓库",
          field: "warehouseId",
          type: "select",
          dataKey: "warehouses",
          data: [],
          required: true,
        },
        {
          title: "备注",
          field: "remark",
          type: "textarea",
        },
      ],
    ]);
    const searchFormFields = ref({
      orderNo: "",
      upperOrderNo: "",
      orderType: "215",
      orderStatus: "",
      createType: "",
      creater: "",
      createDate: "",
    });
    const searchFormOptions = ref([
      [
        { title: "单据编号", field: "orderNo", type: "like" },
        { title: "上游单据编号", field: "upperOrderNo", type: "like" },
        {
          title: "单据类型",
          field: "orderType",
          type: "select",
          dataKey: "outOrderType",
          data: [],
        },
        {
          title: "单据状态",
          field: "orderStatus",
          type: "select",
          dataKey: "outboundStatusEnum",
          data: [],
        },
      ],
      [
        {
          title: "创建方式",
          field: "createType",
          type: "select",
          dataKey: "createType",
          data: [],
        },
        { title: "创建者", field: "creater", type: "like" },
        { title: "创建时间", field: "createDate", type: "datetime" },
      ],
    ]);
    const columns = ref([
      {
        field: "id",
        title: "Id",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "orderNo",
        title: "单据编号",
        type: "string",
        width: 160,
        align: "left",
        // link: true,
      },
      {
        field: "upperOrderNo",
        title: "上游单据编号",
        type: "string",
        width: 160,
        align: "left",
      },
      {
        field: "orderType",
        title: "单据类型",
        type: "string",
        width: 150,
        align: "left",
        bind: { key: "outOrderType", data: [] },
      },
      {
        field: "businessType",
        title: "业务类型",
        type: "string",
        width: 150,
        align: "left",
        bind: { key: "businessType", data: [] },
      },
      {
        field: "orderStatus",
        title: "单据状态",
        type: "decimal",
        width: 90,
        align: "left",
        bind: { key: "outboundStatusEnum", data: [] },
      },
      {
        field: "createType",
        title: "创建方式",
        type: "string",
        width: 120,
        align: "left",
        bind: { key: "createType", data: [] },
      },
      {
        field: "factoryArea",
        title: "厂区",
        type: "string",
        width: 120,
        align: "left"
      },
      {
        field: "departmentCode",
        title: "修改时间",
        type: "string",
        width: 120,
        align: "left",
        hidden:true
      },
      {
        field: "departmentName",
        title: "部门名称",
        type: "string",
        width: 160,
        align: "left",
      },
      {
        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: "调拨明细单",
      table: "OnboundOrderDetail",
      columns: [
        {
          field: "id",
          title: "Id",
          type: "int",
          width: 90,
          hidden: true,
          readonly: true,
          require: true,
          align: "left",
        },
        {
          field: "orderId",
          title: "调拨单主键",
          type: "string",
          width: 90,
          align: "left",
          hidden: true,
        },
        {
          field: "materielCode",
          title: "物料编号",
          type: "string",
          width: 150,
          align: "left",
          edit: { type: "string" },
          required: true,
        },
        {
          field: "materielName",
          title: "物料名称",
          type: "string",
          width: 150,
          align: "left",
          edit: { type: "string" },
        },
        {
          field: "batchNo",
          title: "批次号",
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "string" },
          required: true,
        },
        {
          field: "supplyCode",
          title: "供应商编号",
          type: "string",
          width: 90,
          align: "left",
          edit: { type: "string" },
          required: true,
        },
        {
          field: "orderQuantity",
          title: "单据数量",
          type: "string",
          width: 90,
          align: "left",
          edit: { type: "number" },
          required: true,
        },
        {
          field: "rowNo",
          title: "行号",
          type: "string",
          width: 90,
          align: "left",
          edit: { type: "number" },
          required: true,
        },
        {
          field: "lockQuantity",
          title: "锁定数量",
          type: "int",
          width: 120,
          align: "left",
        },
        {
          field: "overOutQuantity",
          title: "已出数量",
          type: "string",
          width: 200,
          align: "left",
        },
        {
          field: "orderDetailStatus",
          title: "订单明细状态",
          type: "string",
          width: 180,
          align: "left",
          bind: { key: "orderDetailStatusEnum", 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",
        },
      ],
      sortName: "id",
      key: "id",
    });
      // è°ƒæ‹¨å¼¹çª—相关
    const outboundVisible = ref(false);
    const selectedOutboundDocument = ref({});  // å­˜å‚¨é€‰ä¸­çš„调拨单数据
    // æ‰“开调拨弹窗(从扩展配置的按钮事件触发)
    const handleOpenOutboundDialog = (docData) => {
      selectedOutboundDocument.value = docData;  // ä¿å­˜é€‰ä¸­çš„单据数据
      outboundVisible.value = true;  // æ˜¾ç¤ºå¼¹çª—
    };
    // è°ƒæ‹¨æˆåŠŸåŽçš„å›žè°ƒ
    const handleOutboundSuccess = (docNo) => {
      ElMessage.success(`单据 ${docNo} è°ƒæ‹¨æˆåŠŸ`);
      gridRef.value?.refresh();  // åˆ·æ–°è¡¨æ ¼æ•°æ®
    };
    // åˆå§‹åŒ–扩展配置:为调拨按钮添加事件触发逻辑
    const initExtension = () => {
      // æ‰¾åˆ°"调拨"按钮并绑定打开弹窗的逻辑
      const outboundBtn = extend.buttons.view.find(btn => btn.name === '出库');
      if (outboundBtn) {
        const originalOnClick = outboundBtn.onClick;
        outboundBtn.onClick = function() {
          // å…ˆæ‰§è¡ŒåŽŸæœ‰æ ¡éªŒé€»è¾‘
          const selectedRows = this.$refs.table.getSelected();
          if (selectedRows.length === 1) {
            // æ ¡éªŒé€šè¿‡åŽï¼Œè§¦å‘主组件的出库弹窗事件
            this.$emit('openOutboundDialog', selectedRows[0]);
          } else {
            // åŽŸæœ‰é€»è¾‘å·²å¤„ç†æç¤ºï¼Œæ— éœ€é‡å¤
            originalOnClick.call(this);
          }
        };
      }
    };
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
       // å‡ºåº“相关
      outboundVisible,
      selectedOutboundDocument,
      handleOpenOutboundDialog,
      handleOutboundSuccess
    };
  },
});
</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_BasicService/InvokeMESService.cs
@@ -12,6 +12,7 @@
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_DTO.Allocate;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
@@ -41,6 +42,12 @@
            _inboundOrderRepository = inboundOrderRepository;
        }
        /// <summary>
        /// å…¥åº“反馈
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        /// <exception cref="HttpRequestException"></exception>
        public async Task<ResponseModel> FeedbackInbound(FeedbackInboundRequestModel model)
        {
            string json =JsonConvert.SerializeObject(model, new JsonSerializerSettings
@@ -63,7 +70,13 @@
            return JsonConvert.DeserializeObject<ResponseModel>(body);
        }
        /// <summary>
        /// å‡ºåº“反馈
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        /// <exception cref="HttpRequestException"></exception>
        public async Task<ResponseModel> FeedbackOutbound(FeedbackOutboundRequestModel model)
        {
            string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings
@@ -86,6 +99,29 @@
                throw new HttpRequestException(body);
            }
            return JsonConvert.DeserializeObject<ResponseModel>(body);
        }
        public async Task<ResponseModel> FeedbackAllocate(AllocateDto model)
        {
            string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings
            {
                ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
            });
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var _client = _httpClientFactory.CreateClient("MESUrl");
            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Add("Accept", "application/json");
            _logger.LogInformation("InvokeMESService  FeedbackAllocate :  " + json);
            var response = await _client.PostAsync("AldAllocationOperation/AllocationOperation", content);
            string body = await response.Content.ReadAsStringAsync();
            _logger.LogInformation("InvokeMESService  FeedbackAllocate  body:  " + body);
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(body);
            }
            return JsonConvert.DeserializeObject<ResponseModel>(body);
        }
@@ -116,10 +152,11 @@
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            _logger.LogInformation("InvokeMESService  FeedbackInbound :  " + json);
            _logger.LogInformation("InvokeMESService  NewMaterielToMes :  " + json);
            using var response = await client.PostAsync("AldBarcodeInformation/BarcodeInformation", content);
            var responseText = await response.Content.ReadAsStringAsync();
            _logger.LogInformation("InvokeMESService  NewMaterielToMes  body:  " + responseText);
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(responseText);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -140,7 +140,7 @@
                List<string> lockLocationCodes = locationCaches.Select(x => x.LocationCode).ToList();
                Dictionary<string, OrderByType> orderBy = new Dictionary<string, OrderByType>()
                Dictionary<string, SqlSugar.OrderByType> orderBy = new Dictionary<string, OrderByType>()
                {
                    { nameof(Dt_LocationInfo.RoadwayNo),OrderByType.Asc },
                    { nameof(Dt_LocationInfo.Layer),OrderByType.Asc },
@@ -153,7 +153,11 @@
                if (first != null)
                {
                    locationCaches.Add(new LocationCache { LocationCode = first?.LocationCode, DateTime = DateTime.Now });
                }
                    Db.Updateable<Dt_LocationInfo>().SetColumns(x => new Dt_LocationInfo
                    {
                        LocationStatus = (int)LocationStatusEnum.InStockLock,
                    }).Where(x => x.Id == first.Id).ExecuteCommand();
                }
                return first;
            }
@@ -182,8 +186,10 @@
                };
                var first = BaseDal.QueryFirst(x => x.LocationStatus == LocationStatusEnum.Free.ObjToInt() && x.EnableStatus != EnableStatusEnum.Disable.ObjToInt() && !lockLocationCodes.Contains(x.LocationCode), orderBy);//查询空货位信息并排除5分钟内分配的货位,根据层、列、深度、行排序
                locationCaches.Add(new LocationCache { LocationCode = first.LocationCode, DateTime = DateTime.Now });
                if (first != null)
                {
                    locationCaches.Add(new LocationCache { LocationCode = first.LocationCode, DateTime = DateTime.Now });
                }
                return first;
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/MaterielToMesService.cs
@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using Dm.filter;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
@@ -9,6 +11,7 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
@@ -19,10 +22,11 @@
    internal class MaterielToMesService : ServiceBase<Dt_MaterielToMes, IRepository<Dt_MaterielToMes>>, IMaterielToMesService
    {
        private readonly IInvokeMESService _invokeMESService;
        public MaterielToMesService(IRepository<Dt_MaterielToMes> BaseDal, IInvokeMESService invokeMESService) : base(BaseDal)
        private readonly ILogger<MaterielToMesService> _logger;
        public MaterielToMesService(IRepository<Dt_MaterielToMes> BaseDal, IInvokeMESService invokeMESService, ILogger<MaterielToMesService> logger) : base(BaseDal)
        {
            _invokeMESService = invokeMESService;
            _logger = logger;
        }
        // åˆ›å»ºä¸€ä¸ªä½¿ç”¨å°é©¼å³°å‘½åæ³•的序列化设置
        JsonSerializerSettings settings = new JsonSerializerSettings
@@ -31,29 +35,50 @@
        };
        public IRepository<Dt_MaterielToMes> Repository => BaseDal;
        public override WebResponseContent AddData(Dt_MaterielToMes saveModel)
        public async Task<WebResponseContent> AddMaterielToMes(Dt_MaterielToMes saveModel,int operationtype)
        {
            WebResponseContent content = base.AddData(saveModel);
            if (content.Status)
            try
            {
                base.AddData(saveModel);
                string request = JsonConvert.SerializeObject(saveModel, settings);
                MaterielToMesDTO dto = new MaterielToMesDTO
                {
                    batchNo = saveModel.BatchNo,
                    factoryArea = saveModel.factoryArea,
                    materialCode = saveModel.MaterielCode,
                    newmaterialCode = saveModel.NewMaterialBarCode,
                    oldmaterialCode = saveModel.OldMaterialBarCode,
                    operationType = 1,
                    qty = saveModel.Qty,
                    supplyCode = saveModel.supplyCode,
                    unit = saveModel.Unit,
                    warehouseCode = saveModel.warehouseCode,
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
                };
                var result = await _invokeMESService.NewMaterielToMes(dto);
                if (result != null && result.code == 200)
                {
                    await Db.Updateable<Dt_MaterielToMes>()
                      .SetColumns(it => it.ReturnToMESStatus ==1)
                      .Where(it => it.Id == saveModel.Id)
                      .ExecuteCommandAsync();
                }
                return WebResponseContent.Instance.OK();
            }
            else
            catch (Exception ex)
            {
                return content;
                _logger.LogError("MaterielToMesService  add Dt_MaterielToMes:  " + ex.Message);
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs
@@ -21,7 +21,7 @@
        å·²æ‹†åŒ… = 1,
        å·²æ’¤é”€ = 2,
        å·²æ‹£é€‰ = 3,
        å·²å›žåº“ = 4
        å·²å›žåº“ = 4,
    }
    public enum OutLockStockStatusEnum
    {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/StockStatusEmun.cs
@@ -58,7 +58,7 @@
        [Description("入库确认")]
        å…¥åº“确认 = 3,
        [Description("入库完成")]
        å…¥åº“完成 = 6,
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Allocate/AllocateDto.cs
@@ -25,7 +25,7 @@
        /// è¯·æ±‚æ—¶é—´
        /// </summary>
        [JsonProperty("reqTime")]
        public DateTime ReqTime { get; set; }
        public string ReqTime { get; set; }
        /// <summary>
        /// è®¢å•编号
@@ -39,6 +39,10 @@
        [JsonProperty("business_type")]
        public string BusinessType { get; set; }
        public string fromWarehouse { get; set; }
        public string toWarehouse { get; set; }
        /// <summary>
        /// æ˜¯å¦åˆ†æ‰¹
        /// </summary>
@@ -51,6 +55,9 @@
        [JsonProperty("factoryArea")]
        public string FactoryArea { get; set; }
        [JsonProperty("operator")]
        public string Operator { get; set; }
        /// <summary>
        /// æ“ä½œç±»åž‹
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs
@@ -52,7 +52,7 @@
    public class SummaryPickingDto
    {
       public string PalletCode { get; set; }
        public string PalletCode { get; set; }
        public string MaterielCode { get; set; }
        public int UnpickedCount { get; set; }
        public decimal UnpickedQuantity { get; set; }
@@ -180,7 +180,7 @@
        public string OrderNo { get; set; } = string.Empty; // å‡ºåº“单号
    }
    public class PickingConfirmReq
    {
@@ -189,7 +189,7 @@
        public string MaterielBarcode { get; set; } = string.Empty; // ç‰©æ–™æ¡ç 
    }
    public class SplitPackageReq
    {
@@ -198,7 +198,7 @@
        public decimal SplitQty { get; set; } // æ–°æ¡ç åˆ†é…æ•°é‡
    }
    public class StockReturnReq
    {
@@ -206,14 +206,14 @@
        public string TaskNum { get; set; } = string.Empty; // ä»»åŠ¡å·ï¼ˆå¯é€‰ï¼‰
    }
    public class OutStockLockDetailReq
    {
        public long OutStockLockId { get; set; } // å‡ºåº“详情ID
    }
    public class MaterielBarcodeValidateResp
    {
@@ -222,7 +222,7 @@
        public decimal PackageQty { get; set; } // æ•´åŒ…数量(分配数量)
        public string Message { get; set; } = string.Empty;
    }
    public class OutStockLockDetailResp
    {
@@ -230,7 +230,7 @@
        public decimal AssignQuantity { get; set; }
    }
    public class OutStockLockListResp
    {
@@ -244,7 +244,7 @@
        public bool IsSplitted { get; set; }
    }
    public class PickedRecordListResp
    {
@@ -278,9 +278,21 @@
    // æ‹†åŒ…结果类
    public class SplitResult
    {
        public string OriginalBarcode { get; set; }
        public string NewBarcode { get; set; }
        public decimal SplitQuantity { get; set; }
        public decimal RemainQuantity { get; set; }
        public string materialCode { get; set; }
        public string supplierCode { get; set; }
        public string quantityTotal { get; set; }
        public string batchNumber { get; set; }
        public string  batch { get; set; }
        public string factory { get; set; }
        public string date { get; set; }
       // public string OriginalBarcode { get; set; }
       // public string NewBarcode { get; set; }
        //public decimal SplitQuantity { get; set; }
        //public decimal RemainQuantity { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IBasicService/IInvokeMESService.cs
@@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_DTO.Allocate;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
@@ -16,6 +17,7 @@
        Task<ResponseModel> FeedbackOutbound(FeedbackOutboundRequestModel model);
        Task<ResponseModel> FeedbackAllocate(AllocateDto model);
        Task<string> GetToken(String username, string password);
        Task<ResponseModel> NewMaterielToMes(MaterielToMesDTO model);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IBasicService/IMaterielToMesService.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Model.Models;
@@ -13,5 +14,7 @@
    public interface IMaterielToMesService : IService<Dt_MaterielToMes>
    {
        IRepository<Dt_MaterielToMes> Repository { get; }
        Task<WebResponseContent> AddMaterielToMes(Dt_MaterielToMes saveModel, int operationtype);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs
@@ -27,6 +27,8 @@
        Task<LockInfoDetailDto> GetLockInfoDetail(int lockInfoId);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder,Dt_OutboundOrderDetail outboundOrderDetail,Dt_StockInfo outStock, decimal assignQuantity, string barcode = null);
        List<Dt_OutStockLockInfo> GetOutStockLockInfos(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, List<Dt_StockInfo> outStocks, int? taskNum = null);
        Task<List<Dt_OutStockLockInfo>> GetPalletLockInfos(string palletCode);
        Task<WebResponseContent> UpdateLockInfoBarcode(int lockInfoId, string newBarcode);
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundOrderDetailService.cs
@@ -21,7 +21,7 @@
        public (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) AssignStockOutbound(List<Dt_OutboundOrderDetail> outboundOrderDetails);
        WebResponseContent LockOutboundStockDataUpdate(List<Dt_StockInfo> stockInfos, List<Dt_OutboundOrderDetail> outboundOrderDetails, List<Dt_OutStockLockInfo> outStockLockInfos, List<Dt_LocationInfo> locationInfos, LocationStatusEnum locationStatus = LocationStatusEnum.Lock, List<Dt_Task>? tasks = null);
        (List<Dt_StockInfo>, Dt_OutboundOrderDetail, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) AssignStockOutbound(Dt_OutboundOrderDetail outboundOrderDetail, List<StockSelectViewDTO> stockSelectViews);
        //List<Dt_OutboundOrderDetail> GetOutboundStockDataById(int id);
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
@@ -15,17 +15,14 @@
    {
        IRepository<Dt_PickingRecord> Repository { get; }
        Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode);
        Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request);
        Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
        Task<object> GetPickingSummary(ConfirmPickingDto dto);
        Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason);
        Task<WebResponseContent> ValidateBarcode(string barcode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs
@@ -9,7 +9,7 @@
    {
        IRepository<Dt_StockInfo> Repository { get; }
        Dt_StockInfo? GetStockByPalletCode(string palletCode);
        List<Dt_StockInfo> GetStockInfosByPalletCodes(List<string> palletCodes);
        List<Dt_StockInfo> GetStockInfos(string materielCode, string lotNo, string supplyCode, List<string> locationCodes);
        List<Dt_StockInfo> GetUseableStocks(string materielCode, string batchNo,string supplyCode);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -45,6 +45,9 @@
        Task<WebResponseContent> TaskCompleted(string taskNum);
        Task<WebResponseContent> GenerateOutboundTasksAsync(int[] keys, string outStation);
        WebResponseContent GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs
@@ -37,10 +37,11 @@
        private readonly IRepository<Dt_StockInfoDetail> _stockDetailRepository;
        private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        private readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
        private readonly IRepository<Dt_LocationType> _locationTypeRepository;
        private readonly IRepository<Dt_StockInfo> _stockRepository;
        public IRepository<Dt_InboundOrder> Repository => BaseDal;
        public InboundOrderService(IRepository<Dt_InboundOrder> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_Task> taskRepository, IStockService stockService, IInboundOrderDetailService inboundOrderDetailService, IMaterialUnitService materialUnitService, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository,IRepository<Dt_StockInfo> stockRepository) : base(BaseDal)
        public InboundOrderService(IRepository<Dt_InboundOrder> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_Task> taskRepository, IStockService stockService, IInboundOrderDetailService inboundOrderDetailService, IMaterialUnitService materialUnitService, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IRepository<Dt_StockInfo> stockRepository, IRepository<Dt_LocationType> locationTypeRepository) : base(BaseDal)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -53,6 +54,7 @@
            _inboundOrderRepository = inboundOrderRepository;
            _warehouseAreaRepository = warehouseAreaRepository;
            _stockRepository = stockRepository;
            _locationTypeRepository = locationTypeRepository;
        }
        public async Task<WebResponseContent> ReceiveInboundOrder(List<Dt_InboundOrder> models, int operateType)
@@ -431,10 +433,10 @@
                (bool, string, object?) result2 = ModelValidate.ValidateModelData(materielGroupDTO);
                if (!result2.Item1) return content = WebResponseContent.Instance.Error(result2.Item2);
                bool code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>().Where(x => x.Id == materielGroupDTO.WarehouseCode).Any();
                bool code = _locationTypeRepository.Db.Queryable<Dt_LocationType>().Where(x => x.LocationType == materielGroupDTO.WarehouseCode).Any();
                if (!code)
                {
                    return content = WebResponseContent.Instance.Error($"仓库中没有该{materielGroupDTO.WarehouseCode}编号。");
                    return content = WebResponseContent.Instance.Error($"区域中没有该{materielGroupDTO.WarehouseCode}编号。");
                }
                if(_stockRepository.QueryFirst(x=>x.PalletCode == materielGroupDTO.PalletCode)!=null){
@@ -637,7 +639,7 @@
            {
                return WebResponseContent.Instance.Error("托盘号不能为空");
            }
           var stock= _stockRepository.Db.Queryable<Dt_StockInfo>().Includes(o=>o.Details).First(x => x.PalletCode == palletCode && x.StockStatus==3);
           var stock= _stockRepository.Db.Queryable<Dt_StockInfo>().Includes(o=>o.Details).First(x => x.PalletCode == palletCode );
            if (stock == null)
            {
                return WebResponseContent.Instance.Error($"未找到托盘号{palletCode}对应的库存记录");
@@ -652,7 +654,6 @@
            var barcodes = stock.Details.Select(d => d.Barcode).ToList();
                // åŒ¹é…åº“存条码对应的入库单明细
                var matchedInboundDetails = inboundOrders.Details
                    ?.Where(d => barcodes.Contains(d.Barcode))
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Basic/Dt_MaterielToMes.cs
@@ -87,7 +87,7 @@
        public string MaterielCode { get; set; } = null!;
         
        public int ReturnToMESStatus { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -128,6 +128,9 @@
        public int? ParentLockId { get; set; }
        [SugarColumn(IsNullable = false, Length = 50, ColumnDescription = "操作者")]
        public string Operator { get; set; }
        [Navigate(NavigateType.OneToOne, nameof(StockInfo))]//一对一 SchoolId是StudentA类里面的
        public Dt_StockInfo StockInfo { get; set; } //不能赋值只能是null
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs
@@ -57,20 +57,22 @@
            // ç¡®å®šæ¡ç 
            string targetBarcode;
            var  firstAvailableDetail=new Dt_StockInfoDetail();
            if (!string.IsNullOrEmpty(barcode))
            {
                // éªŒè¯æŒ‡å®šçš„æ¡ç æ˜¯å¦å­˜åœ¨
                var specifiedBarcodeDetail = stockDetails.FirstOrDefault(x => x.Barcode == barcode);
                if (specifiedBarcodeDetail == null)
                firstAvailableDetail = stockDetails.FirstOrDefault(x => x.Barcode == barcode);
                if (firstAvailableDetail == null)
                {
                    throw new Exception($"指定的条码[{barcode}]在库存中不存在");
                }
                targetBarcode = barcode;
            }
            else
            {
                // ä½¿ç”¨ç¬¬ä¸€ä¸ªå¯ç”¨æ¡ç 
                var firstAvailableDetail = stockDetails
                  firstAvailableDetail = stockDetails
                    .Where(x => x.StockQuantity > x.OutboundQuantity)
                    .OrderBy(x => x.CreateDate)
                    .FirstOrDefault();
@@ -83,13 +85,9 @@
            }
            return new Dt_OutStockLockInfo()
            {
                lineNo= outboundOrderDetail.lineNo,
            {
                PalletCode = outStock.PalletCode,
                AssignQuantity = assignQuantity,
                MaterielCode = outboundOrderDetail.MaterielCode,
                BatchNo = outboundOrderDetail.BatchNo ?? outStock.Details.FirstOrDefault()?.BatchNo,
                AssignQuantity = assignQuantity,
                LocationCode = outStock.LocationCode,
                MaterielName = outboundOrderDetail.MaterielName,
                OrderDetailId = outboundOrderDetail.Id,
@@ -99,18 +97,53 @@
                    .Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode)
                    .Sum(x => x.StockQuantity),
                Status = (int)OutLockStockStatusEnum.已分配,
                StockId = outStock.Id,
                Unit = outboundOrderDetail.Unit,
                FactoryArea = outboundOrder.FactoryArea,
                StockId = outStock.Id,
                OrderType=outboundOrder.OrderType,
                SupplyCode = outboundOrderDetail.SupplyCode,
                WarehouseCode = outboundOrderDetail.WarehouseCode,
                SupplyCode = firstAvailableDetail.SupplyCode,
                WarehouseCode = firstAvailableDetail.WarehouseCode,
                // æ–°å¢žå­—段
                CurrentBarcode = targetBarcode,
                OriginalLockQuantity = assignQuantity,
                IsSplitted = 0
                IsSplitted = 0,
                MaterielCode = outboundOrderDetail.MaterielCode,
                BatchNo = firstAvailableDetail.BatchNo,
                Unit = firstAvailableDetail.Unit,
                FactoryArea = firstAvailableDetail.FactoryArea,
                lineNo = outboundOrderDetail.lineNo,
            };
        }
        public List<Dt_OutStockLockInfo> GetOutStockLockInfos(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, List<Dt_StockInfo> outStocks, int? taskNum = null)
        {
            List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
            foreach (var item in outStocks)
            {
                Dt_OutStockLockInfo outStockLockInfo = new Dt_OutStockLockInfo()
                {
                    PalletCode = item.PalletCode,
                    AssignQuantity = item.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).Sum(x => x.OutboundQuantity),
                    MaterielCode = outboundOrderDetail.MaterielCode,
                    BatchNo = outboundOrderDetail.BatchNo,
                    LocationCode = item.LocationCode,
                    MaterielName = outboundOrderDetail.MaterielName,
                    OrderDetailId = outboundOrderDetail.Id,
                    OrderNo = outboundOrder.OrderNo,
                    OrderType = outboundOrder.OrderType,
                    OriginalQuantity = item.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).Sum(x => x.StockQuantity),
                    Status = taskNum == null ? OutLockStockStatusEnum.已分配.ObjToInt() : OutLockStockStatusEnum.出库中.ObjToInt(),
                    StockId = item.Id,
                    TaskNum = taskNum,
                };
                outStockLockInfos.Add(outStockLockInfo);
            }
            return outStockLockInfos;
        }
        /// <summary>
        /// æ ¹æ®è®¢å•明细ID获取出库锁定信息
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using SqlSugar;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
@@ -22,6 +23,7 @@
   
        private readonly IStockService _stockService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly IBasicService _basicService;
@@ -40,6 +42,7 @@
            _locationStatusChangeRecordService = locationStatusChangeRecordService;
            _outboundOrderService = outboundOrderService;
            _logger = logger;
        }
@@ -153,7 +156,7 @@
                    if (remainingAllocate <= 0) break;
                    // è®¡ç®—这个明细还需要分配的数量
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity-detail.MoveQty;
                    if (detailNeed <= 0) continue;
                    // åˆ†é…æ•°é‡
@@ -289,5 +292,72 @@
            return new PageGridData<Dt_OutboundOrderDetail> ();
        }
        public (List<Dt_StockInfo>, Dt_OutboundOrderDetail, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) AssignStockOutbound(Dt_OutboundOrderDetail outboundOrderDetail, List<StockSelectViewDTO> stockSelectViews)
        {
            (bool, string) checkResult = CheckSelectStockDeital(outboundOrderDetail, stockSelectViews);
            if (!checkResult.Item1) throw new Exception(checkResult.Item2);
            Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetail.OrderId);
            var originalNeedQuantity = outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity;
            var needQuantity = originalNeedQuantity;
            List<Dt_StockInfo> outStocks = _stockService.StockInfoService.GetStockInfosByPalletCodes(stockSelectViews.Select(x => x.PalletCode).ToList());
            var assignQuantity =0m;
            outStocks.ForEach(x =>
            {
                x.Details.ForEach(v =>
                {
                    assignQuantity += v.StockQuantity - v.OutboundQuantity;
                });
            });
            outboundOrderDetail.LockQuantity += assignQuantity;
            outStocks.ForEach(x =>
            {
                x.Details.ForEach(v =>
                {
                    v.OutboundQuantity = v.StockQuantity;
                });
            });
            needQuantity -= assignQuantity;
            if (outboundOrderDetail.OrderQuantity > outboundOrderDetail.LockQuantity)
            {
                List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo,"");
                stockInfos = stockInfos.Where(x => !stockSelectViews.Select(v => v.PalletCode).Contains(x.PalletCode)).ToList();
                var (autoAssignStocks, stockAllocations)   = _stockService.StockInfoService.GetOutboundStocks(stockInfos, outboundOrderDetail.MaterielCode, needQuantity, out decimal residueQuantity);
                outboundOrderDetail.LockQuantity += needQuantity - residueQuantity;
                outStocks.AddRange(autoAssignStocks);
                outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
                if (residueQuantity > 0)
                {
                    outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt();
                }
            }
            List<Dt_OutStockLockInfo> outStockLockInfos = _outStockLockInfoService.GetOutStockLockInfos(outboundOrder, outboundOrderDetail, outStocks);
            List<Dt_LocationInfo> locationInfos = _locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).ToList());
            return (outStocks, outboundOrderDetail, outStockLockInfos, locationInfos);
        }
        private (bool, string) CheckSelectStockDeital(Dt_OutboundOrderDetail outboundOrderDetail, List<StockSelectViewDTO> stockSelectViews)
        {
            if (outboundOrderDetail == null)
            {
                return (false, "未找到出库单明细信息");
            }
            if (outboundOrderDetail.OrderDetailStatus != OrderDetailStatusEnum.New.ObjToInt() && outboundOrderDetail.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt())
            {
                return (false, "该明细不可操作");
            }
            if (stockSelectViews.Sum(x => x.UseableQuantity) > outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity)
            {
                return (false, "选择数量超出单据数量");
            }
            return (true, "成功");
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,6 +1,7 @@
using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
@@ -84,1030 +85,6 @@
            _dailySequenceService = dailySequenceService;
        }
        #region æŸ¥è¯¢å‡ºåº“详情列表
        public async Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo)
        {
            var locks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(t => t.OrderNo == orderNo)
                .ToListAsync();
            return locks.Select(t => new OutStockLockListResp
            {
                Id = t.Id,
                // TaskNum = t.TaskNum,
                PalletCode = t.PalletCode,
                CurrentBarcode = t.CurrentBarcode,
                AssignQuantity = t.AssignQuantity,
                PickedQty = t.PickedQty,
                Status = t.Status,
                //  IsSplitted = t.IsSplitted
            }).ToList();
        }
        #endregion
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            try
            {
                if (string.IsNullOrEmpty(barcode))
                {
                    return WebResponseContent.Instance.Error("条码不能为空");
                }
                // æ ¹æ®æ¡ç æŸ¥è¯¢åº“存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Includes(x => x.StockInfo)
                    .Where(x => x.Barcode == barcode)
                    .FirstAsync();
                if (stockDetail == null)
                {
                    return WebResponseContent.Instance.Error("条码不存在");
                }
                var result = new
                {
                    Barcode = barcode,
                    MaterielCode = stockDetail.MaterielCode,
                    BatchNo = stockDetail.BatchNo,
                    AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
                    LocationCode = stockDetail.StockInfo?.LocationCode,
                    PalletCode = stockDetail.StockInfo?.PalletCode
                };
                return WebResponseContent.Instance.OK(null, result);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
            }
        }
        // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            var orderDetails = _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                      .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id) // å…³è”条件:父表 Id = å­è¡¨ OrderId
                      .Where((o, item) => item.OrderNo == orderNo) // è¿‡æ»¤çˆ¶è¡¨ OrderNo
                      .Select((o, item) => o) // åªè¿”回子表数据
                      .ToList();
            //var orderDetails = await _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
            //    .Where(x => x.OrderId == orderNo.ObjToInt())
            //    .ToListAsync();
            bool allCompleted = true;
            foreach (var detail in orderDetails)
            {
                if (detail.OverOutQuantity < detail.NeedOutQuantity)
                {
                    allCompleted = false;
                    break;
                }
            }
            if (allCompleted)
            {
                try
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == 2) // å·²å®Œæˆ
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    var outboundOrder = _stockInfoService.Db.Queryable<Dt_OutboundOrder>().First(x => x.OrderNo == orderNo);
                    if (outboundOrder != null && outboundOrder.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
                    {
                        if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt().ObjToInt())//调拨出库
                        {
                        }
                        else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt()) //重检出库
                        {
                        }
                        else
                        {
                            var feedmodel = new FeedbackOutboundRequestModel
                            {
                                reqCode = Guid.NewGuid().ToString(),
                                reqTime = DateTime.Now.ToString(),
                                business_type = outboundOrder.BusinessType,
                                factoryArea = outboundOrder.FactoryArea,
                                operationType = 1,
                                Operator = outboundOrder.Operator,
                                orderNo = outboundOrder.UpperOrderNo,
                                status = outboundOrder.OrderStatus,
                                details = new List<FeedbackOutboundDetailsModel>()
                            };
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo).ToList();
                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                               .Select(group => new FeedbackOutboundDetailsModel
                               {
                                   materialCode = group.Key.MaterielCode,
                                   lineNo = group.Key.lineNo,
                                   warehouseCode = group.Key.WarehouseCode,
                                   currentDeliveryQty = group.Sum(x => x.OrderQuantity),
                                   // warehouseCode= "1072",
                                   unit = group.Key.Unit,
                                   barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                                   {
                                       barcode = row.CurrentBarcode,
                                       supplyCode = row.SupplyCode,
                                       batchNo = row.BatchNo,
                                       unit = row.Unit,
                                       qty = row.AssignQuantity
                                   }).ToList()
                               }).ToList();
                            feedmodel.details = groupedData;
                            _invokeMESService.FeedbackOutbound(feedmodel);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(" OutboundPickingService  FeedbackOutbound : " + ex.Message);
                }
            }
        }
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
                               it.PalletCode == palletCode &&
                               it.CurrentBarcode == barcode)
                    .FirstAsync();
                if (lockInfo == null)
                {
                    lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
    .Where(it => it.CurrentBarcode == barcode &&
               it.Status == (int)OutLockStockStatusEnum.出库中)
    .FirstAsync();
                    if (lockInfo == null)
                        throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                }
                if (lockInfo.PalletCode != palletCode)
                    throw new Exception($"条码{barcode}不属于托盘{palletCode}");
                var outorderdetail = _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().First(x => x.Id == lockInfo.OrderDetailId);
                if (outorderdetail != null && lockInfo.AssignQuantity > outorderdetail.OrderQuantity)
                {
                    throw new Exception($"条码{barcode}的出库数量大于订单的数量");
                }
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                        .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal stockQuantity = stockDetail.StockQuantity;
                List<SplitResult> splitResults = new List<SplitResult>();
                if (actualQty < stockQuantity)
                {
                    // æƒ…况1: åˆ†é…æ•°é‡å°äºŽåº“存数量,需要自动拆包
                    // è®¡ç®—剩余库存数量
                    decimal remainingStockQty = stockQuantity - actualQty;
                    // æ›´æ–°åŽŸæ¡ç åº“å­˜ä¸ºå‰©ä½™æ•°é‡
                    stockDetail.StockQuantity = remainingStockQty;
                    stockDetail.OutboundQuantity = remainingStockQty;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // ç”Ÿæˆæ–°æ¡ç ç”¨äºŽè®°å½•拣选数量(但不创建库存记录)
                    var seq = await _dailySequenceService.GetNextSequenceAsync();
                    string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
                    // ä¸ºæ–°æ¡ç åˆ›å»ºå‡ºåº“锁定信息(用于记录拣选)
                    var newLockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = lockInfo.OrderNo,
                        OrderDetailId = lockInfo.OrderDetailId,
                        BatchNo = lockInfo.BatchNo,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        StockId = lockInfo.StockId,
                        OrderQuantity = actualQty,
                        OriginalQuantity = actualQty,
                        AssignQuantity = actualQty,
                        PickedQty = actualQty,
                        LocationCode = lockInfo.LocationCode,
                        PalletCode = lockInfo.PalletCode,
                        TaskNum = lockInfo.TaskNum,
                        Status = (int)OutLockStockStatusEnum.拣选完成,
                        Unit = lockInfo.Unit,
                        SupplyCode = lockInfo.SupplyCode,
                        OrderType = lockInfo.OrderType,
                        CurrentBarcode = newBarcode,
                        OriginalLockQuantity = actualQty,
                        IsSplitted = 1,
                        ParentLockId = lockInfo.Id
                    };
                    await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                    // è®°å½•拆包历史(用于追踪)
                    var splitHistory = new Dt_SplitPackageRecord
                    {
                        FactoryArea = lockInfo.FactoryArea,
                        TaskNum = lockInfo.TaskNum,
                        OutStockLockInfoId = lockInfo.Id,
                        StockId = stockDetail.StockId,
                        Operator = App.User.UserName,
                        IsReverted = false,
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQty = actualQty,
                        RemainQuantity = remainingStockQty,
                        MaterielCode = lockInfo.MaterielCode,
                        SplitTime = DateTime.Now,
                        OrderNo = lockInfo.OrderNo,
                        PalletCode = lockInfo.PalletCode,
                        Status = (int)SplitPackageStatusEnum.已拣选
                    };
                    await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
                    // æ›´æ–°åŽŸé”å®šä¿¡æ¯ä¸ºå‰©ä½™åº“å­˜æ•°é‡
                    lockInfo.AssignQuantity = remainingStockQty;
                    lockInfo.PickedQty = 0;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    splitResults.Add(new SplitResult
                    {
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQuantity = actualQty,
                        RemainQuantity = remainingStockQty
                    });
                    // æ›´æ–°æ‹£é€‰è®°å½•中的条码为新条码
                    barcode = newBarcode;
                    lockInfo = newLockInfo;
                }
                else if (actualQty == stockQuantity)
                {
                    // æƒ…况2: åˆ†é…æ•°é‡ç­‰äºŽåº“存数量,整包出库
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    lockInfo.PickedQty += actualQty;
                    lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                }
                else
                {
                    // æƒ…况3: åˆ†é…æ•°é‡å¤§äºŽåº“存数量,库存整包出库
                    // æ•´åŒ…出库当前库存
                    decimal stockOutQty = stockQuantity;
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // è®¡ç®—剩余分配数量
                    decimal remainingAssignQty = actualQty - stockQuantity;
                    // æ›´æ–°é”å®šä¿¡æ¯ï¼ˆåªå®Œæˆåº“存部分)
                    lockInfo.PickedQty += stockOutQty;
                    lockInfo.AssignQuantity = remainingAssignQty;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var _relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
        .Where(it => !it.IsReverted)
        .ToListAsync();
                    foreach (var record in _relatedSplitRecords)
                    {
                        record.Status = (int)SplitPackageStatusEnum.已拣选;
                        await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                    }
                }
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
                    .Where(it => it.Id == lockInfo.OrderDetailId)
                    .ExecuteCommandAsync();
                await CheckAndUpdateOrderStatus(orderNo);
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                // è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    FactoryArea = lockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = stockDetail.Id,
                    OrderNo = orderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = lockInfo.MaterielCode,
                    PickQuantity = actualQty,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = lockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                // å¦‚果有拆包结果,返回拆包信息
                if (splitResults.Any())
                {
                    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
                }
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
                var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
                var tasks = new List<Dt_Task>();
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = remainingLocks.Any()
                    ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
                    : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
                if (task == null)
                {
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                }
                // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
                var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.StockId == stockinfo.Id &&
                                (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                                 it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                                 it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                    .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
                    .ToListAsync();
                // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                // è®¡ç®—需要回库的拆包条码
                var splitBarcodesToReturn = new List<string>();
                foreach (var splitRecord in splitRecords)
                {
                    // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
                    }
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.NewBarcode);
                    }
                }
                // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
                {
                    // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
                    var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                        .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
                    if (allPicked)
                    {
                        return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
                    }
                }
                var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
                decimal totalReturnQty = 0;
                // æƒ…况1:处理未分拣的出库锁定记录
                if (remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
                    totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    if (totalReturnQty > 0)
                    {
                        // åˆ†é…æ–°è´§ä½
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        // æ›´æ–°å‡ºåº“锁定记录状态
                        var lockIds = palletLocks.Select(x => x.Id).ToList();
                        await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
                            .Where(it => lockIds.Contains(it.Id))
                            .ExecuteCommandAsync();
                        // å¤„理库存记录
                        foreach (var lockInfo in palletLocks)
                        {
                            decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                            // æ£€æŸ¥åº“存记录是否存在
                            var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                                .FirstAsync();
                            if (existingStock != null)
                            {
                                // åº“存记录存在,恢复锁定数量
                                existingStock.OutboundQuantity = 0;
                                await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
                            }
                            else
                            {
                                // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
                                var newStockDetail = new Dt_StockInfoDetail
                                {
                                    StockId = lockInfo.StockId,
                                    MaterielCode = lockInfo.MaterielCode,
                                    MaterielName = lockInfo.MaterielName,
                                    OrderNo = lockInfo.OrderNo,
                                    BatchNo = lockInfo.BatchNo,
                                    StockQuantity = returnQty,
                                    OutboundQuantity = 0,
                                    Barcode = lockInfo.CurrentBarcode,
                                    InboundOrderRowNo = "",
                                    Status = StockStatusEmun.入库完成.ObjToInt(),
                                    SupplyCode = lockInfo.SupplyCode,
                                    WarehouseCode = lockInfo.WarehouseCode,
                                    Unit = lockInfo.Unit
                                };
                                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                            }
                        }
                        // åˆ›å»ºå›žåº“任务
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况2:处理拆包剩余的库存货物
                if (splitBarcodesToReturn.Any())
                {
                    decimal splitReturnQty = 0;
                    foreach (var barcode in splitBarcodesToReturn)
                    {
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
                            .FirstAsync();
                        if (stockDetail != null && stockDetail.StockQuantity > 0)
                        {
                            splitReturnQty += stockDetail.StockQuantity;
                            // æ¢å¤åº“存状态为入库完成
                            stockDetail.OutboundQuantity = 0;
                            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        }
                    }
                    totalReturnQty += splitReturnQty;
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                    totalReturnQty += otherReturnQty;
                    // æ›´æ–°è¿™äº›åº“存货物的状态
                    foreach (var stockGood in palletStockGoods)
                    {
                        stockGood.OutboundQuantity = 0;
                        await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                    }
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                foreach (var record in allSplitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已回库;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
                if (tasks.Any())
                {
                    try
                    {
                        await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
                        var targetAddress = task.TargetAddress;
                        _taskRepository.DeleteData(task);
                        // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
                        try
                        {
                            var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                            {
                                slotCode = movestations[targetAddress],
                                containerCode = palletCode
                            });
                            if (result)
                            {
                                TaskModel esstask = new TaskModel()
                                {
                                    taskType = "putaway",
                                    taskGroupCode = "",
                                    groupPriority = 0,
                                    tasks = new List<TasksType>
                            {
                                new()
                                {
                                    taskCode = tasks.First().TaskNum.ToString(),
                                    taskPriority = 0,
                                    taskDescribe = new TaskDescribeType {
                                        containerCode = palletCode,
                                        containerType = "CT_KUBOT_STANDARD",
                                        fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                        toStationCode = "",
                                        toLocationCode = tasks.First().TargetAddress,
                                        deadline = 0, storageTag = ""
                                    }
                                }
                            }
                                };
                                var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
                                _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
                        }
                        _unitOfWorkManage.CommitTran();
                        return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
                    }
                }
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error("未创建任何回库任务");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        /// <summary>
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        private void CreateReturnTask(List<Dt_Task> tasks, Dt_Task originalTask, string palletCode, Dt_LocationInfo newLocation)
        {
            Dt_Task newTask = new()
            {
                CurrentAddress = stations[originalTask.TargetAddress],
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId,
            };
            tasks.Add(newTask);
        }
        /// <summary>
        /// æ£€æŸ¥æ‰˜ç›˜æ˜¯å¦éœ€è¦å›žåº“的辅助方法
        /// </summary>
        public async Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode)
        {
            // 1. æ£€æŸ¥æ˜¯å¦æœ‰æœªåˆ†æ‹£çš„出库记录
            var hasUnpickedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            if (hasUnpickedLocks)
                return true;
            // 2. æ£€æŸ¥å‡ºåº“是否已完成但托盘还有库存货物
            var outboundFinished = !await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
            var hasRemainingGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
                .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity)
                .AnyAsync();
            return outboundFinished && hasRemainingGoods;
        }
        // å–消拣选功能
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //查找拣选记录
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                //检查是否可以取消(状态必须是拣选完成)
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                decimal cancelQty = pickingRecord.PickQuantity;
                // æ£€æŸ¥æ‹†åŒ…链关系
                var splitChain = await GetSplitChain(barcode);
                if (splitChain.Any())
                {
                    // æƒ…况A:处理拆包链的取消(多次手动拆包)
                    await HandleSplitChainCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord, splitChain);
                }
                else
                {
                    // æƒ…况B:处理普通条码的取消
                    await HandleNormalBarcodeCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ï¼ˆä»Žå½“å‰æ¡ç è¿½æº¯åˆ°åŽŸå§‹æ¡ç ï¼‰
        /// </summary>
        // åœ¨ GetSplitChain æ–¹æ³•中添加更严格的验证
        private async Task<List<SplitChainItem>> GetSplitChain(string currentBarcode)
        {
            var chain = new List<SplitChainItem>();
            var visited = new HashSet<string>();
            string current = currentBarcode;
            int maxDepth = 10; // é˜²æ­¢æ— é™å¾ªçޝ
            while (!string.IsNullOrEmpty(current) && maxDepth > 0)
            {
                maxDepth--;
                if (visited.Contains(current))
                {
                    _logger.LogWarning($"检测到循环引用在拆包链中: {current}");
                    break;
                }
                visited.Add(current);
                var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.NewBarcode == current && !it.IsReverted)
                    .FirstAsync();
                if (splitRecord == null)
                    break;
                // éªŒè¯æ‹†åŒ…记录的完整性
                if (string.IsNullOrEmpty(splitRecord.OriginalBarcode))
                {
                    _logger.LogError($"拆包记录 {splitRecord.Id} ç¼ºå°‘原始条码");
                    break;
                }
                var item = new SplitChainItem
                {
                    SplitRecord = splitRecord,
                    OriginalBarcode = splitRecord.OriginalBarcode,
                    NewBarcode = splitRecord.NewBarcode,
                    SplitQuantity = splitRecord.SplitQty
                };
                chain.Add(item);
                current = splitRecord.OriginalBarcode;
            }
            if (maxDepth <= 0)
            {
                _logger.LogWarning($"拆包链追溯达到最大深度: {currentBarcode}");
            }
            chain.Reverse();
            return chain;
        }
        /// <summary>
        /// å¤„理拆包链的取消分拣
        /// </summary>
        private async Task HandleSplitChainCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, List<SplitChainItem> splitChain)
        {
            if (!splitChain.Any())
                return;
            //  æ‰¾åˆ°åŽŸå§‹æ¡ç ï¼ˆé“¾çš„ç¬¬ä¸€ä¸ªï¼‰
            var originalSplitItem = splitChain.First();
            var originalBarcode = originalSplitItem.OriginalBarcode;
            // æŸ¥æ‰¾åŽŸå§‹æ¡ç çš„é”å®šä¿¡æ¯å’Œåº“å­˜
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.CurrentBarcode == originalBarcode && it.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            var originalStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == originalBarcode && it.StockId == originalLockInfo.StockId)
                .FirstAsync();
            if (originalLockInfo == null || originalStockDetail == null)
                throw new Exception("未找到原始条码的锁定信息或库存信息");
            // æ¢å¤åŽŸå§‹æ¡ç åº“å­˜ï¼ˆå°†å–æ¶ˆçš„æ•°é‡åŠ å›žåŽ»ï¼‰
            originalStockDetail.StockQuantity += cancelQty;
            originalStockDetail.OutboundQuantity += cancelQty;
            await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync();
            // æ¢å¤åŽŸå§‹æ¡ç é”å®šä¿¡æ¯
            originalLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            //  åˆ é™¤æ‹†åŒ…链中所有新条码的锁定信息和库存记录
            var allNewBarcodes = splitChain.Select(x => x.NewBarcode).ToList();
            // åˆ é™¤é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(it => allNewBarcodes.Contains(it.CurrentBarcode))
                .ExecuteCommandAsync();
            // åˆ é™¤åº“存记录(只删除拆包产生的新条码库存,保留原始条码)
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(it => allNewBarcodes.Contains(it.Barcode) && it.Barcode != originalBarcode)
                .ExecuteCommandAsync();
            // æ›´æ–°æ‹†åŒ…链中所有拆包记录状态为已拆包
            foreach (var chainItem in splitChain)
            {
                chainItem.SplitRecord.Status = (int)SplitPackageStatusEnum.已拆包;
                await _splitPackageService.Db.Updateable(chainItem.SplitRecord).ExecuteCommandAsync();
            }
            //  æ¢å¤è®¢å•明细拣选数量(使用原始锁定信息的订单明细ID)
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == originalLockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            //   æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(it => it.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            ////  è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消拆包链分拣");
        }
        /// <summary>
        /// å¤„理普通条码的取消分拣
        /// </summary>
        private async Task HandleNormalBarcodeCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord)
        {
            // 1. æŸ¥æ‰¾åº“存信息
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == lockInfo.StockId)
                .FirstAsync();
            if (stockDetail == null)
                throw new Exception("未找到对应的库存信息");
            // 2. æ¢å¤åº“存数量
            if (stockDetail.StockQuantity == 0)
            {
                // æ•´åŒ…出库的情况
                stockDetail.StockQuantity = cancelQty;
                stockDetail.OutboundQuantity = cancelQty;
            }
            else
            {
                // éƒ¨åˆ†å‡ºåº“的情况
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 3. æ¢å¤é”å®šä¿¡æ¯çŠ¶æ€
            lockInfo.AssignQuantity += cancelQty;
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty == 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 4. å¤„理相关的拆包记录状态恢复
            var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OriginalBarcode == barcode &&
                           it.Status == (int)SplitPackageStatusEnum.已拣选)
                .ToListAsync();
            foreach (var record in relatedSplitRecords)
            {
                record.Status = (int)SplitPackageStatusEnum.已拆包;
                await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
            }
            // 5. æ¢å¤è®¢å•明细的拣选数量
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == lockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            // 6. æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            // 7. åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>().Where(it => it.Id == pickingRecord.Id).ExecuteCommandAsync();
            //// 8. è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消分拣");
        }
        /// <summary>
        /// æ£€æŸ¥å¹¶æ¢å¤è®¢å•状态
        /// </summary>
        private async Task CheckAndRevertOrderStatus(string orderNo)
        {
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order != null && order.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.OrderStatus == OutOrderStatusEnum.出库中.ObjToInt())
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// è®°å½•取消操作历史
        /// </summary>
        private async Task RecordCancelHistory(string orderNo, string palletCode, string barcode,
            decimal cancelQty, int pickingRecordId, string materielCode, string reason)
        {
            //var cancelHistory = new Dt_PickingCancelRecord
            //{
            //    OrderNo = orderNo,
            //    PalletCode = palletCode,
            //    Barcode = barcode,
            //    CancelQuantity = cancelQty,
            //    CancelTime = DateTime.Now,
            //    Operator = App.User.UserName,
            //    OriginalPickingRecordId = pickingRecordId,
            //    MaterielCode = materielCode,
            //    Reason = reason
            //};
            //await Db.Insertable(cancelHistory).ExecuteCommandAsync();
        }
        /// <summary>
        /// æ‹†åŒ…链项
        /// </summary>
        public class SplitChainItem
        {
            public Dt_SplitPackageRecord SplitRecord { get; set; }
            public string OriginalBarcode { get; set; }
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
@@ -1169,186 +146,1525 @@
            return summary;
        }
        #region æ ¸å¿ƒä¸šåŠ¡æµç¨‹
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// æ‹£é€‰
        /// </summary>
        public async Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId)
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="barcode"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            // é€šè¿‡å‡ºåº“单ID查询相关的拣选历史
            // æ³¨æ„ï¼šDt_PickingRecord ä¸­æ²¡æœ‰ç›´æŽ¥å­˜å‚¨OrderId,需要通过出库单明细关联
            var detailIds = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .Where(d => d.OrderId == orderId)
                .Select(d => d.Id)
                .ToListAsync();
            return await Db.Queryable<Dt_PickingRecord>()
                .Where(p => detailIds.Contains(p.OrderDetailId))
                .OrderByDescending(p => p.PickTime)
                .ToListAsync();
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€ä¿¡æ¯
        /// </summary>
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.PalletCode == palletCode)
                .ToListAsync();
            // èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .Includes(x => x.Details)
                .Where(x => x.PalletCode == palletCode)
                .FirstAsync();
            if (stockInfo == null)
                return WebResponseContent.Instance.Error("未找到托盘信息");
            // è®¡ç®—各种数量
            var totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
            var totalOutboundQuantity = stockInfo.Details.Sum(x => x.OutboundQuantity);
            var totalLockedQuantity = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.出库中)
                .Sum(x => x.AssignQuantity - x.PickedQty);
            var totalPickedQuantity = lockInfos.Sum(x => x.PickedQty);
            var result = new
            try
            {
                PalletCode = palletCode,
                LocationCode = stockInfo.LocationCode,
                StockStatus = stockInfo.StockStatus,
                TotalStockQuantity = totalStockQuantity,
                TotalOutboundQuantity = totalOutboundQuantity,
                TotalLockedQuantity = totalLockedQuantity,
                TotalPickedQuantity = totalPickedQuantity,
                AvailableQuantity = totalStockQuantity - totalOutboundQuantity,
                LockInfos = lockInfos.Select(x => new
                {
                    x.Id,
                    x.MaterielCode,
                    x.OrderDetailId,
                    x.AssignQuantity,
                    x.PickedQty,
                    x.Status,
                    x.CurrentBarcode,
                    x.IsSplitted
                }).ToList(),
                StockDetails = stockInfo.Details.Select(x => new
                {
                    x.Barcode,
                    x.MaterielCode,
                    StockQuantity = x.StockQuantity,
                    OutboundQuantity = x.OutboundQuantity,
                    AvailableQuantity = x.StockQuantity - x.OutboundQuantity
                }).ToList()
            };
                _unitOfWorkManage.BeginTran();
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
            return WebResponseContent.Instance.OK(null, result);
                var (lockInfo, orderDetail, stockDetail) = validationResult.Data;
                // è®¡ç®—实际拣选数量
                var quantityResult = await CalculateActualPickingQuantity(lockInfo, orderDetail, stockDetail);
                if (!quantityResult.IsValid)
                    return WebResponseContent.Instance.Error(quantityResult.ErrorMessage);
                var (actualQty, adjustedReason) = quantityResult.Data;
                var overPickingValidation = await ValidateOverPicking(orderDetail.Id, actualQty);
                if (!overPickingValidation.IsValid)
                {
                    return WebResponseContent.Instance.Error(overPickingValidation.ErrorMessage);
                }
                //  æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, orderNo, palletCode, barcode, actualQty);
                // æ›´æ–°ç›¸å…³æ•°æ®
                await UpdateOrderRelatedData(orderDetail.Id, pickingResult.ActualPickedQty, orderNo);
                // è®°å½•操作历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                _unitOfWorkManage.CommitTran();
                return CreatePickingResponse(pickingResult, adjustedReason);
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"ConfirmPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        /// <summary>
        /// ç›´æŽ¥å‡ºåº“ - æ•´ä¸ªæ‰˜ç›˜å‡ºåº“,清空库存
        /// å–消拣选
        /// </summary>
        public async Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request)
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="barcode"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                if (await IsPalletReturned(palletCode))
                {
                    return WebResponseContent.Instance.Error($"托盘{palletCode}已经回库,不能取消分拣");
                }
                _unitOfWorkManage.BeginTran();
                // 1. å‰ç½®éªŒè¯
                var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (pickingRecord, lockInfo, orderDetail) = validationResult.Data;
                // 2. æ‰§è¡Œå–消逻辑
                await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{pickingRecord.PickQuantity}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å›žåº“
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == request.PalletCode).FirstAsync();
                // 1. åŸºç¡€éªŒè¯
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                // 2. èŽ·å–åº“å­˜å’Œä»»åŠ¡ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>().FirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到托盘库存信息");
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                var task = await GetCurrentTask(orderNo, palletCode);
                if (task == null)
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode)
                    .ToListAsync();
                // 3. åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                if (!returnAnalysis.HasItemsToReturn)
                    return await HandleNoReturnItems(orderNo, palletCode,task);
                // 4. æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
                foreach (var lockInfo in lockInfos)
                {
                    if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中)
                    {
                        lockInfo.PickedQty = lockInfo.AssignQuantity;
                    }
                    lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == lockInfo.OrderDetailId)
                    .FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity += lockInfo.PickedQty;
                        orderDetail.LockQuantity -= lockInfo.PickedQty;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        orderDetail.LockQuantity = 0;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new
                {
                    OrderDetailId = x.Key,
                    TotalQuantity = x.Sum(o => o.PickedQty)
                }).ToList();
                foreach (var item in groupDetails)
                {
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.Id == item.OrderDetailId).FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity = item.TotalQuantity;
                        orderDetail.LockQuantity = 0;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                await CheckAndUpdateOrderStatus(request.OrderNo);
                var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
                               x.Status == (int)SplitPackageStatusEnum.已拆包)
                    .ToListAsync();
                foreach (var record in splitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已拣选;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == stockInfo.LocationCode)
                    .FirstAsync();
                if (location != null)
                {
                    location.LocationStatus = (int)LocationStatusEnum.Free;
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                foreach (var detail in stockInfo.Details)
                {
                    await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync();
                }
                await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync();
                // 5. åˆ›å»ºå›žåº“任务
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("直接出库成功");
                // 6. æ›´æ–°è®¢å•状态(不触发MES回传)
                await UpdateOrderStatusForReturn(orderNo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}");
                _logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        #endregion
        #region åˆ†æ‹£ç¡®è®¤ç§æœ‰æ–¹æ³•
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>> ValidatePickingRequest(string orderNo, string palletCode, string barcode)
        {
            // 1. åŸºç¡€å‚数验证
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("订单号、托盘码和条码不能为空");
            // 2. æŸ¥æ‰¾æœ‰æ•ˆçš„锁定信息
            var lockInfo = await FindValidLockInfo(orderNo, palletCode, barcode);
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到有效的锁定信息");
            // 3. æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单{orderNo}已完成,不能继续分拣");
            // 4. èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到订单明细");
            // 5. æ£€æŸ¥è®¢å•明细数量
            if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单明细需求数量已满足");
            // 6. èŽ·å–åº“å­˜æ˜Žç»†
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId &&
                   x.Status != StockStatusEmun.入库确认.ObjToInt())
                .FirstAsync();
            if (stockDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"无效的条码或物料编码");
            // 7. æ£€æŸ¥åº“存状态和数量
            if (stockDetail.StockQuantity <= 0)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}库存不足");
            if (stockDetail.Status != StockStatusEmun.出库锁定.ObjToInt())
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}状态不正确,无法分拣");
            // 8. æ£€æŸ¥æ˜¯å¦é‡å¤åˆ†æ‹£
            var existingPicking = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && x.PalletCode == palletCode && x.OutStockLockId == lockInfo.Id)
                .FirstAsync();
            if (existingPicking != null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}已经分拣过");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail));
        }
        private async Task<Dt_OutStockLockInfo> FindValidLockInfo(string orderNo, string palletCode, string barcode)
        {
            // ä¼˜å…ˆæŸ¥æ‰¾ç²¾ç¡®åŒ¹é…çš„记录
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.Status == (int)OutLockStockStatusEnum.出库中 &&
                           it.PalletCode == palletCode &&
                           it.CurrentBarcode == barcode &&
                           it.AssignQuantity > it.PickedQty).FirstAsync();
            if (lockInfo == null)
            {
                // æŸ¥æ‰¾åŒä¸€è®¢å•下的记录
                lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&  it.CurrentBarcode == barcode && it.Status == (int)OutLockStockStatusEnum.出库中 && it.AssignQuantity > it.PickedQty).FirstAsync();
                if (lockInfo == null)
                {
                    // æ£€æŸ¥æ˜¯å¦å·²ç»å®Œæˆåˆ†æ‹£
                    var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.CurrentBarcode == barcode &&
                                   (it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                                    it.PickedQty >= it.AssignQuantity)).FirstAsync();
                    if (completedLockInfo != null)
                        throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
                    else
                        return null;
                }
            }
            return lockInfo;
        }
        private async Task<ValidationResult<(decimal, string)>> CalculateActualPickingQuantity(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail)
        {
            decimal plannedQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
            decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
            decimal stockQuantity = stockDetail.StockQuantity;
            if (plannedQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"计划拣选数量必须大于0,当前: {plannedQty}");
            }
            if (remainingOrderQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"订单剩余需求数量必须大于0,当前: {remainingOrderQty}");
            }
            if (stockQuantity <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"库存数量必须大于0,当前: {stockQuantity}");
            }
            // ä¸‰é‡æ£€æŸ¥ï¼šå–最小值
            decimal actualQty = plannedQty;
            string adjustedReason = null;
            if (plannedQty > remainingOrderQty)
            {
                actualQty = remainingOrderQty;
                adjustedReason = $"订单数量限制:从{plannedQty}调整为{actualQty}";
            }
            if (actualQty > stockQuantity)
            {
                actualQty = stockQuantity;
                adjustedReason = adjustedReason != null
                    ? $"{adjustedReason},库存数量限制:进一步调整为{actualQty}"
                    : $"库存数量限制:从{plannedQty}调整为{actualQty}";
            }
            if (actualQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"无法分拣:计算后的实际数量为{actualQty}");
            }
            decimal projectedOverOut = orderDetail.OverOutQuantity + actualQty;
            if (projectedOverOut > orderDetail.NeedOutQuantity)
            {
                // å¦‚果会超拣,调整为刚好满足需求的数量
                actualQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
                adjustedReason = adjustedReason != null
                    ? $"{adjustedReason},防超拣限制:最终调整为{actualQty}"
                    : $"防超拣限制:从{plannedQty}调整为{actualQty}";
            }
            if (adjustedReason != null)
            {
                _logger.LogWarning($"分拣数量调整:{adjustedReason},订单{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity},库存{stockQuantity}");
            }
            return ValidationResult<(decimal, string)>.Success((actualQty, adjustedReason));
        }
        /// <summary>
        /// ä¸“门验证是否会发生超拣
        /// </summary>
        private async Task<ValidationResult<bool>> ValidateOverPicking(int orderDetailId, decimal pickingQty)
        {
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return ValidationResult<bool>.Error("未找到订单明细");
            decimal projectedOverOut = orderDetail.OverOutQuantity + pickingQty;
            if (projectedOverOut > orderDetail.NeedOutQuantity)
            {
                return ValidationResult<bool>.Error(
                    $"分拣后将导致超拣:当前已出库{orderDetail.OverOutQuantity},本次分拣{pickingQty},合计{projectedOverOut},超过需求{orderDetail.NeedOutQuantity}");
            }
            return ValidationResult<bool>.Success(true);
        }
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail,
            string orderNo, string palletCode, string barcode, decimal actualQty)
        {
            decimal stockQuantity = stockDetail.StockQuantity;
            var result = new PickingResult
            {
                FinalLockInfo = lockInfo,
                FinalBarcode = barcode,
                FinalStockId = stockDetail.Id,
                ActualPickedQty = actualQty
            };
            if (actualQty < stockQuantity)
            {
                await HandleSplitPacking(lockInfo, stockDetail, actualQty, stockQuantity, result);
            }
            else if (actualQty == stockQuantity)
            {
                await HandleFullPicking(lockInfo, stockDetail, actualQty, result);
            }
            else
            {
                await HandlePartialPicking(lockInfo, stockDetail, actualQty, stockQuantity, result);
            }
            return result;
        }
        private async Task HandleSplitPacking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, decimal stockQuantity, PickingResult result)
        {
            decimal remainingStockQty = stockQuantity - actualQty;
            // 1. æ›´æ–°åŽŸæ¡ç åº“å­˜
            stockDetail.StockQuantity = remainingStockQty;
            stockDetail.OutboundQuantity = remainingStockQty;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // 3. åˆ›å»ºæ–°é”å®šä¿¡æ¯
            var newLockInfo = await CreateSplitLockInfo(lockInfo, actualQty, newBarcode);
            // 4. è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, actualQty, remainingStockQty, newBarcode);
            // 5. æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity = remainingStockQty;
            lockInfo.PickedQty = 0;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 6. è®¾ç½®ç»“æžœ
            result.FinalLockInfo = newLockInfo;
            result.FinalBarcode = newBarcode;
            result.SplitResults.AddRange(CreateSplitResults(lockInfo, actualQty, remainingStockQty, newBarcode, stockDetail.Barcode));
        }
        private async Task HandleFullPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, PickingResult result)
        {
            // 1. æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualQty;
            lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        }
        private async Task HandlePartialPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, decimal stockQuantity, PickingResult result)
        {
            decimal stockOutQty = stockQuantity;
            decimal remainingAssignQty = actualQty - stockQuantity;
            // 1. æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += stockOutQty;
            lockInfo.AssignQuantity = remainingAssignQty;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 3. æ›´æ–°æ‹†åŒ…记录状态
            await UpdateSplitRecordsStatus(stockDetail.Barcode);
            result.ActualPickedQty = stockOutQty;
        }
        private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo)
        {
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity + pickedQty;
            decimal newPickedQty = currentOrderDetail.PickedQty + pickedQty;
            if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
            {
                _logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, å·²å‡ºåº“: {newOverOutQuantity}, éœ€æ±‚: {currentOrderDetail.NeedOutQuantity}, æœ¬æ¬¡åˆ†æ‹£: {pickedQty}");
                decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
                if (adjustedQty > 0)
                {
                    _logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}");
                    newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
                    newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
                }
                else
                {
                    throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity}),且无法自动调整");
                }
            }
            // æ›´æ–°è®¢å•明细
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
            // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
        }
        private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode)
        {
            var task = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (result.FinalLockInfo.Id <= 0)
            {
                throw new Exception($"锁定信息ID无效: {result.FinalLockInfo.Id},无法记录拣选历史");
            }
            var pickingHistory = new Dt_PickingRecord
            {
                FactoryArea = result.FinalLockInfo.FactoryArea,
                TaskNo = task?.TaskNum ?? 0,
                LocationCode = task?.SourceAddress ?? "",
                StockId = result.FinalStockId,
                OrderNo = orderNo,
                OrderDetailId = result.FinalLockInfo.OrderDetailId,
                PalletCode = palletCode,
                Barcode = result.FinalBarcode,
                MaterielCode = result.FinalLockInfo.MaterielCode,
                PickQuantity = result.ActualPickedQty,
                PickTime = DateTime.Now,
                Operator = App.User.UserName,
                OutStockLockId = result.FinalLockInfo.Id
            };
            await Db.Insertable(pickingHistory).ExecuteCommandAsync();
        }
        #endregion
        #region å–消分拣私有方法
        private async Task<ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>> ValidateCancelRequest(string orderNo, string palletCode, string barcode)
        {
            // åŸºç¡€å‚数验证
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单号、托盘码和条码不能为空");
            // æŸ¥æ‰¾æ‹£é€‰è®°å½•
            var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           it.Barcode == barcode)
                .OrderByDescending(it => it.PickTime)
                .FirstAsync();
            if (pickingRecord == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的拣选记录");
            if (pickingRecord.PickQuantity <= 0)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"拣选记录数量无效: {pickingRecord.PickQuantity}");
            }
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.Id == pickingRecord.OutStockLockId)
                .FirstAsync();
            if (lockInfo == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的出库锁定信息");
            if (lockInfo.PickedQty < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error(
                    $"取消数量({pickingRecord.PickQuantity})超过锁定信息的已拣选数量({lockInfo.PickedQty})");
            }
            // æ£€æŸ¥çŠ¶æ€æ˜¯å¦å…è®¸å–æ¶ˆ
            if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("当前状态不允许取消分拣");
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣");
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
            // æ£€æŸ¥è®¢å•明细的已拣选数量是否足够取消
            if (orderDetail.PickedQty < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已拣选数量({orderDetail.PickedQty})");
            }
            // æ£€æŸ¥è®¢å•明细的已出库数量是否足够取消
            if (orderDetail.OverOutQuantity < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已出库数量({orderDetail.OverOutQuantity})");
            }
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>().FirstAsync(it => it.Barcode == barcode && it.StockId == pickingRecord.StockId);
            if (stockDetail != null)
            {
                // æ£€æŸ¥åº“存状态 - å¦‚果状态是入库确认或入库完成,说明已经回库
                if (stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                    stockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
                {
                    return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"条码{barcode}已经回库,不能取消分拣");
                }
            }
            return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
        }
        /// <summary>
        /// æ£€æŸ¥æ¡ç æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
        private async Task<bool> IsBarcodeReturned(string barcode, int stockId)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == stockId)
                .FirstAsync();
            if (stockDetail == null)
                return false;
            // å¦‚果状态是入库确认或入库完成,说明已经回库
            return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        /// <summary>
        /// æ£€æŸ¥é”å®šä¿¡æ¯å¯¹åº”的条码是否已经回库
        /// </summary>
        private async Task<bool> IsLockInfoReturned(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                .FirstAsync();
            if (stockDetail == null)
                return false;
            return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord,
            Dt_OutboundOrderDetail orderDetail, string orderNo)
        {
            decimal cancelQty = pickingRecord.PickQuantity;
            var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        .Where(it => it.Barcode == pickingRecord.Barcode && it.StockId == pickingRecord.StockId)
        .FirstAsync();
            if (currentStockDetail != null &&
                (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                 currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt()))
            {
                throw new Exception($"条码{pickingRecord.Barcode}已经回库,无法取消分拣");
            }
            //   æ£€æŸ¥å–消后数量不会为负数
            decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = orderDetail.PickedQty - cancelQty;
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                throw new Exception($"取消分拣将导致数据异常:已出库{newOverOutQuantity},已拣选{newPickedQty}");
            }
            //  å¤„理不同类型的取消
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            else
            {
                await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            // æ›´æ–°è®¢å•明细
            await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(x => x.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            //  é‡æ–°æ£€æŸ¥è®¢å•状态
            await UpdateOrderStatusForReturn(orderNo);
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æŸ¥æ‰¾çˆ¶é”å®šä¿¡æ¯
            var parentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.ParentLockId.Value)
                .FirstAsync();
            if (parentLockInfo == null)
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            if (await IsLockInfoReturned(parentLockInfo))
            {
                throw new Exception($"父条码{parentLockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"拆包条码{lockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已撤销,
                    IsReverted = true,
                })
                .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
                .ExecuteCommandAsync();
            // åˆ é™¤æ‹†åŒ…产生的锁定信息
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.Id)
                .ExecuteCommandAsync();
        }
        private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"条码{lockInfo.CurrentBarcode}已经回库,无法取消分拣");
            }
            // æ¢å¤é”å®šä¿¡æ¯
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
            lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == pickingRecord.Barcode && x.StockId == pickingRecord.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        private async Task UpdateOrderDetailOnCancel(int orderDetailId, decimal cancelQty)
        {
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = currentOrderDetail.PickedQty - cancelQty;
            // æ£€æŸ¥å–消后数量不会为负数
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})或已拣选数量({newPickedQty})为负数");
            }
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
        }
        #endregion
        #region å›žåº“操作私有方法
        private async Task<Dt_StockInfo> GetStockInfo(string palletCode)
        {
            return await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
        }
        /// <summary>
        /// æ£€æŸ¥æ•´ä¸ªæ‰˜ç›˜æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
        private async Task<bool> IsPalletReturned(string palletCode)
        {
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .Where(x => x.PalletCode == palletCode)
                .FirstAsync();
            if (stockInfo == null)
                return false;
            // å¦‚果托盘状态是入库确认或入库完成,说明已经回库
            return stockInfo.StockStatus == StockStatusEmun.入库确认.ObjToInt() ||
                   stockInfo.StockStatus == StockStatusEmun.入库完成.ObjToInt();
        }
        private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
        {
            // å…ˆå°è¯•通过订单号和托盘号查找任务
            var task = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (task == null)
            {
                // å¦‚果找不到,再通过托盘号查找
                task = await _taskRepository.Db.Queryable<Dt_Task>()
                    .Where(x => x.PalletCode == palletCode)
                    .FirstAsync();
            }
            return task;
        }
        private async Task<ReturnAnalysisResult> AnalyzeReturnItems(string orderNo, string palletCode, int stockId)
        {
            var result = new ReturnAnalysisResult();
            // æƒ…况1:获取未分拣的出库锁定记录
            var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           it.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
            if (remainingLocks.Any())
            {
                result.HasRemainingLocks = true;
                result.RemainingLocks = remainingLocks;
                result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
            }
            // æƒ…况2:检查托盘上是否有其他库存货物
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockId &&
                            (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                             it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                             it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                .Where(it => it.StockQuantity > 0)
                .ToListAsync();
            if (palletStockGoods.Any())
            {
                result.HasPalletStockGoods = true;
                result.PalletStockGoods = palletStockGoods;
                result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity);
            }
            // æƒ…况3:检查拆包记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已回库)
                .ToListAsync();
            if (splitRecords.Any())
            {
                result.HasSplitRecords = true;
                result.SplitRecords = splitRecords;
                result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
            }
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            return result;
        }
        private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
        {
            decimal totalQty = 0;
            var processedBarcodes = new HashSet<string>();
            foreach (var splitRecord in splitRecords)
            {
                // æ£€æŸ¥åŽŸæ¡ç 
                if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
                {
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        totalQty += originalStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.OriginalBarcode);
                    }
                }
                // æ£€æŸ¥æ–°æ¡ç 
                if (!processedBarcodes.Contains(splitRecord.NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        totalQty += newStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.NewBarcode);
                    }
                }
            }
            return totalQty;
        }
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode,Dt_Task originalTask)
        {
            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
            if (allPicked)
            {
                // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
                await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
            }
            else
            {
                // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
                await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
            }
            //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
        }
        private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
            Dt_Task task, ReturnAnalysisResult analysis)
        {
            // æƒ…况1:处理未分拣的出库锁定记录
            if (analysis.HasRemainingLocks)
            {
                await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
                // å…³é”®ï¼šæ›´æ–°è®¢å•明细的已拣选数量
                await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
            // å¤„理托盘上其他库存货物
            if (analysis.HasPalletStockGoods)
            {
                await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
            }
            // å¤„理拆包记录
            if (analysis.HasSplitRecords)
            {
                await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
            }
            // æ›´æ–°åº“存主表状态
            await UpdateStockInfoStatus(stockInfo);
        }
        private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
        {
            var lockIds = remainingLocks.Select(x => x.Id).ToList();
            // æ›´æ–°å‡ºåº“锁定记录状态为回库中
            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                .SetColumns(it => new Dt_OutStockLockInfo
                {
                    Status = (int)OutLockStockStatusEnum.回库中,
                })
                .Where(it => lockIds.Contains(it.Id))
                .ExecuteCommandAsync();
            // å¤„理库存记录
            foreach (var lockInfo in remainingLocks)
            {
                decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                // æŸ¥æ‰¾å¯¹åº”的库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                    .FirstAsync();
                if (stockDetail != null)
                {
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库确认.ObjToInt();
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                else
                {
                    // åˆ›å»ºæ–°çš„库存记录
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = lockInfo.StockId,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        OrderNo = lockInfo.OrderNo,
                        BatchNo = lockInfo.BatchNo,
                        StockQuantity = returnQty,
                        OutboundQuantity = 0,
                        Barcode = lockInfo.CurrentBarcode,
                        InboundOrderRowNo = "",
                        Status = StockStatusEmun.入库确认.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit,
                    };
                    await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
            }
        }
        private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
        {
            // æŒ‰è®¢å•明细分组
            var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
                // èŽ·å–å½“å‰è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail != null)
                {
                    // è°ƒæ•´å·²æ‹£é€‰æ•°é‡å’Œå·²å‡ºåº“数量
                    decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty);
                    decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty);
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(it => new Dt_OutboundOrderDetail
                        {
                            PickedQty = newPickedQty,
                            OverOutQuantity = newOverOutQuantity,
                        })
                        .Where(it => it.Id == orderDetailId)
                        .ExecuteCommandAsync();
                }
            }
        }
        private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
        {
            _logger.LogInformation($"回库操作:发现{palletStockGoods.Count}个库存明细需要回库,等待AGV搬运");
            foreach (var stockGood in palletStockGoods)
            {
                _logger.LogInformation($"待回库货物 - æ¡ç : {stockGood.Barcode}, æ•°é‡: {stockGood.StockQuantity}, å½“前状态: {stockGood.Status}");
            // æ¢å¤åº“存状态
            stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库确认.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
            }
        }
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
        {
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已回库,
                })
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
                .ExecuteCommandAsync();
        }
        private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
        {
            _logger.LogInformation($"回库操作:托盘{stockInfo.PalletCode}等待AGV回库搬运");
            // æ›´æ–°åº“存主表状态
            stockInfo.StockStatus = StockStatusEmun.入库确认.ObjToInt();
            await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
        }
        /// <summary>
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="originalTask"></param>
        /// <param name="analysis"></param>
        /// <returns></returns>
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, ReturnAnalysisResult analysis)
        {
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                .FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
            // åˆ†é…æ–°è´§ä½
            var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
            Dt_Task returnTask = new()
            {
                CurrentAddress = stations[originalTask.TargetAddress],
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            var targetAddress = originalTask.TargetAddress;
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, targetAddress, returnTask);
        }
        /// <summary>
        /// ç»™ESS下任务
        /// </summary>
        /// <param name="palletCode"></param>
        /// <param name="targetAddress"></param>
        /// <param name="returnTask"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask)
        {
            try
            {
                // 1. å‘送流动信号
                var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                {
                    slotCode = movestations[targetAddress],
                    containerCode = palletCode
                });
                if (moveResult)
                {
                    // 2. åˆ›å»ºå›žåº“任务
                    var essTask = new TaskModel()
                    {
                        taskType = "putaway",
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>
                    {
                        new()
                        {
                            taskCode = returnTask.TaskNum.ToString(),
                            taskPriority = 0,
                            taskDescribe = new TaskDescribeType
                            {
                                containerCode = palletCode,
                                containerType = "CT_KUBOT_STANDARD",
                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                toStationCode = "",
                                toLocationCode = returnTask.TargetAddress,
                                deadline = 0,
                                storageTag = ""
                            }
                        }
                    }
                    };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                    _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}");
                throw new Exception($"ESS系统通信失败: {ex.Message}");
            }
        }
        #endregion
        #region è®¢å•状态管理
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            try
            {
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo)
                    .Select((o, item) => o)
                    .ToListAsync();
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                }
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                    .FirstAsync(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return;
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    // åªæœ‰æ­£å¸¸åˆ†æ‹£å®Œæˆæ—¶æ‰å‘MES反馈
                    if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    {
                        await HandleOrderCompletion(outboundOrder, orderNo);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
            try
            {
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo)
                    .Select((o, item) => o)
                    .ToListAsync();
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                }
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                    .FirstAsync(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return;
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    _logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, æ–°çŠ¶æ€: {newStatus}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // è°ƒæ‹¨å‡ºåº“和重检出库不需要反馈MES
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt() ||
                outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            {
                return;
            }
            try
            {
                var feedmodel = new FeedbackOutboundRequestModel
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = App.User.UserName,
                    orderNo = outboundOrder.UpperOrderNo,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
                };
                // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
                    .ToListAsync();
                var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                   .Select(group => new FeedbackOutboundDetailsModel
                   {
                       materialCode = group.Key.MaterielCode,
                       lineNo = group.Key.lineNo,
                       warehouseCode = group.Key.WarehouseCode,
                       qty = group.Sum(x => x.PickedQty),
                       currentDeliveryQty = group.Sum(x => x.PickedQty),
                       unit = group.Key.Unit,
                       barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                       {
                           barcode = row.CurrentBarcode,
                           supplyCode = row.SupplyCode,
                           batchNo = row.BatchNo,
                           unit = row.Unit,
                           qty = row.PickedQty
                       }).ToList()
                   }).ToList();
                feedmodel.details = groupedData;
                var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                if (result != null && result.code == 200)
                {
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderId == outboundOrder.Id)
                        .ExecuteCommandAsync();
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        #endregion
        #region è¾…助方法
        private async Task<string> GenerateNewBarcode()
        {
            var seq = await _dailySequenceService.GetNextSequenceAsync();
            return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
        }
        private async Task<Dt_OutStockLockInfo> CreateSplitLockInfo(Dt_OutStockLockInfo originalLock, decimal quantity, string newBarcode)
        {
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = originalLock.OrderNo,
                OrderDetailId = originalLock.OrderDetailId,
                BatchNo = originalLock.BatchNo,
                MaterielCode = originalLock.MaterielCode,
                MaterielName = originalLock.MaterielName,
                StockId = originalLock.StockId,
                OrderQuantity = quantity,
                OriginalQuantity = quantity,
                AssignQuantity = quantity,
                PickedQty = quantity,
                LocationCode = originalLock.LocationCode,
                PalletCode = originalLock.PalletCode,
                TaskNum = originalLock.TaskNum,
                Status = (int)OutLockStockStatusEnum.拣选完成,
                Unit = originalLock.Unit,
                SupplyCode = originalLock.SupplyCode,
                OrderType = originalLock.OrderType,
                CurrentBarcode = newBarcode,
                OriginalLockQuantity = quantity,
                IsSplitted = 1,
                ParentLockId = originalLock.Id,
                Operator=  App.User.UserName,
                FactoryArea=originalLock.FactoryArea,
                lineNo=originalLock.lineNo,
                WarehouseCode=originalLock.WarehouseCode,
            };
            var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
            newLockInfo.Id = newLockId;
            return newLockInfo;
        }
        private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQty, decimal remainQty, string newBarcode)
        {
            var splitHistory = new Dt_SplitPackageRecord
            {
                FactoryArea = lockInfo.FactoryArea,
                TaskNum = lockInfo.TaskNum,
                OutStockLockInfoId = lockInfo.Id,
                StockId = stockDetail.StockId,
                Operator = App.User.UserName,
                IsReverted = false,
                OriginalBarcode = stockDetail.Barcode,
                NewBarcode = newBarcode,
                SplitQty = splitQty,
                RemainQuantity = remainQty,
                MaterielCode = lockInfo.MaterielCode,
                SplitTime = DateTime.Now,
                OrderNo = lockInfo.OrderNo,
                PalletCode = lockInfo.PalletCode,
                Status = (int)SplitPackageStatusEnum.已拣选
            };
            await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
        }
        private List<SplitResult> CreateSplitResults(Dt_OutStockLockInfo lockInfo, decimal splitQty, decimal remainQty, string newBarcode, string originalBarcode)
        {
            return new List<SplitResult>
        {
            new SplitResult
            {
                materialCode = lockInfo.MaterielCode,
                supplierCode = lockInfo.SupplyCode,
                quantityTotal = splitQty.ToString("F2"),
                batchNumber = newBarcode,
                batch = lockInfo.BatchNo,
                factory = lockInfo.FactoryArea,
                date = DateTime.Now.ToString("yyyy-MM-dd"),
            },
            new SplitResult
            {
                materialCode = lockInfo.MaterielCode,
                supplierCode = lockInfo.SupplyCode,
                quantityTotal = remainQty.ToString("F2"),
                batchNumber = originalBarcode,
                batch = lockInfo.BatchNo,
                factory = lockInfo.FactoryArea,
                date = DateTime.Now.ToString("yyyy-MM-dd"),
            }
        };
        }
        private async Task UpdateSplitRecordsStatus(string barcode)
        {
            var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
                .Where(it => !it.IsReverted)
                .ToListAsync();
            foreach (var record in relatedSplitRecords)
            {
                record.Status = (int)SplitPackageStatusEnum.已拣选;
                await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
            }
        }
        private async Task<int> GenerateTaskNumber()
        {
            return await _dailySequenceService.GetNextSequenceAsync();
        }
        private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason)
        {
            //if (result.SplitResults.Any())
            //{
            //    var responseData = new { SplitResults = result.SplitResults, AdjustedReason = "" };
            //    if (!string.IsNullOrEmpty(adjustedReason))
            //    {
            //        responseData = new { SplitResults = result.SplitResults, AdjustedReason = adjustedReason };
            //    }
            //    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", responseData);
            //}
            //if (!string.IsNullOrEmpty(adjustedReason))
            //{
            //    return WebResponseContent.Instance.OK($"拣选确认成功({adjustedReason})");
            //}
            //return WebResponseContent.Instance.OK("拣选确认成功");
            if (result.SplitResults.Any())
            {
                return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = result.SplitResults });
            }
            return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
        }
        #endregion
    }
    #region æ”¯æŒç±»å®šä¹‰
    public class ValidationResult<T>
    {
        public bool IsValid { get; set; }
        public T Data { get; set; }
        public string ErrorMessage { get; set; }
        public static ValidationResult<T> Success(T data)
        {
            return new ValidationResult<T>
            {
                IsValid = true,
                Data = data
            };
        }
        public static ValidationResult<T> Error(string message)
        {
            return new ValidationResult<T>
            {
                IsValid = false,
                ErrorMessage = message
            };
        }
    }
    public class PickingResult
    {
        public Dt_OutStockLockInfo FinalLockInfo { get; set; }
        public string FinalBarcode { get; set; }
        public int FinalStockId { get; set; }
        public decimal ActualPickedQty { get; set; }
        public List<SplitResult> SplitResults { get; set; } = new List<SplitResult>();
    }
    public class ReturnAnalysisResult
    {
        public bool HasItemsToReturn { get; set; }
        public bool HasRemainingLocks { get; set; }
        public bool HasPalletStockGoods { get; set; }
        public bool HasSplitRecords { get; set; }
        public decimal RemainingLocksReturnQty { get; set; }
        public decimal PalletStockReturnQty { get; set; }
        public decimal SplitReturnQty { get; set; }
        public decimal TotalReturnQty { get; set; }
        public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
        public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
        public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
    }
    #endregion
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs
@@ -17,6 +17,7 @@
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
namespace WIDESEA_OutboundService
{
@@ -30,9 +31,9 @@
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        private readonly IDailySequenceService _dailySequenceService;
        private readonly IInvokeMESService _invokeMESService;
        private readonly IMaterielToMesService _materielToMesService;
        private readonly ILogger<SplitPackageService> _logger;
        public SplitPackageService(IRepository<Dt_SplitPackageRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, IDailySequenceService dailySequenceService, IInvokeMESService invokeMESService, ILogger<SplitPackageService> logger) : base(BaseDal)
        public SplitPackageService(IRepository<Dt_SplitPackageRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, IDailySequenceService dailySequenceService, IInvokeMESService invokeMESService, ILogger<SplitPackageService> logger, IMaterielToMesService materielToMesService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -41,172 +42,8 @@
            _dailySequenceService = dailySequenceService;
            _invokeMESService = invokeMESService;
            _logger = logger;
            _materielToMesService = materielToMesService;
        }
        /// <summary>
        /// æ‹†åŒ…拆箱操作
        /// </summary>
        //public async Task<WebResponseContent> SplitPackage(SplitPackageDto request)
        //{
        //    try
        //    {
        //        _unitOfWorkManage.BeginTran();
        //        // 1. éªŒè¯å‡ºåº“锁定信息
        //        var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
        //                  .Where(x => x.OrderNo == request.OrderNo &&
        //                   x.PalletCode == request.PalletCode &&
        //                   x.CurrentBarcode == request.OriginalBarcode &&
        //                   x.Status == 1)
        //            .FirstAsync();
        //        if (lockInfo == null)
        //            return WebResponseContent.Instance.Error("未找到有效的出库锁定信息");
        //        // 2. æ£€æŸ¥å‰©ä½™é”å®šæ•°é‡
        //        decimal remainingLockQuantity = lockInfo.OriginalQuantity - lockInfo.PickedQty;
        //        if (request.SplitQuantity > remainingLockQuantity)
        //            return WebResponseContent.Instance.Error($"拆包数量不能大于剩余锁定数量,剩余:{remainingLockQuantity}");
        //        var baseStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //       .Where(x => x.Barcode == request.OriginalBarcode && x.StockId == lockInfo.StockId)
        //       .FirstAsync();
        //        if (baseStockDetail == null)
        //            throw new Exception($"未找到条码{request.OriginalBarcode}对应的库存记录");
        //        // 4. è®¡ç®—拆分后的数量
        //        decimal remainingQty = baseStockDetail.StockQuantity - request.SplitQuantity;
        //        // æ›´æ–°åŸºç¡€æ¡ç çš„库存数量为剩余数量
        //        baseStockDetail.StockQuantity = remainingQty;
        //        baseStockDetail.OutboundQuantity = remainingQty;
        //        await _stockInfoDetailService.Db.Updateable(baseStockDetail).ExecuteCommandAsync();
        //        var seq = await _dailySequenceService.GetNextSequenceAsync();
        //        // 3. ç”Ÿæˆæ–°æ¡ç 
        //        string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
        //        // ä¸ºæ‹†åŒ…产生的新条码创建库存记录
        //        var newStockDetail = new Dt_StockInfoDetail
        //        {
        //            SupplyCode = baseStockDetail.SupplyCode,
        //            WarehouseCode = baseStockDetail.WarehouseCode,
        //            BarcodeQty = baseStockDetail.BarcodeQty,
        //            BarcodeUnit = baseStockDetail.BarcodeUnit,
        //            BusinessType = baseStockDetail.BusinessType,
        //            Unit = baseStockDetail.Unit,
        //            StockId = lockInfo.StockId,
        //            MaterielCode = baseStockDetail.MaterielCode,
        //            OrderNo = baseStockDetail.OrderNo,
        //            BatchNo = baseStockDetail.BatchNo,
        //            StockQuantity = request.SplitQuantity, // æ–°æ¡ç èŽ·å¾—æ‹†åˆ†æ•°é‡
        //            OutboundQuantity = request.SplitQuantity,
        //            Barcode = newBarcode,
        //            InboundOrderRowNo = baseStockDetail.InboundOrderRowNo,
        //        };
        //        await _outStockLockInfoService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
        //        // 4. åˆ›å»ºæ–°çš„出库锁定信息(新条码)
        //        var newLockInfo = new Dt_OutStockLockInfo
        //        {
        //            OrderNo = lockInfo.OrderNo,
        //            OrderDetailId = lockInfo.OrderDetailId,
        //            BatchNo = lockInfo.BatchNo,
        //            MaterielCode = lockInfo.MaterielCode,
        //            MaterielName = lockInfo.MaterielName,
        //            StockId = lockInfo.StockId,
        //            OrderQuantity = request.SplitQuantity,
        //            OriginalQuantity = request.SplitQuantity,
        //            AssignQuantity = request.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, // æ–°æ¡ç 
        //            OriginalLockQuantity = request.SplitQuantity,
        //            IsSplitted = 1,
        //            ParentLockId = lockInfo.Id // è®°å½•父级锁定ID
        //        };
        //        await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
        //        lockInfo.AssignQuantity = remainingQty;
        //        lockInfo.IsSplitted = 1; // æ ‡è®°ä¸ºå·²æ‹†åŒ…
        //        await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        //        var previousSplitRecord = await Db.Queryable<Dt_SplitPackageRecord>()
        //      .Where(x => x.OriginalBarcode == request.OriginalBarcode && !x.IsReverted)
        //      .OrderByDescending(x => x.SplitTime)
        //      .FirstAsync();
        //        // 6. è®°å½•拆包历史(用于追踪)
        //        var splitHistory = new Dt_SplitPackageRecord
        //        {
        //            FactoryArea = lockInfo.FactoryArea,
        //            TaskNum = lockInfo.TaskNum,
        //            OutStockLockInfoId = lockInfo.Id,
        //            StockId = baseStockDetail.StockId,
        //            Operator = App.User.UserName,
        //            IsReverted = false,
        //            OriginalBarcode = request.OriginalBarcode,
        //            NewBarcode = newBarcode,
        //            SplitQty = request.SplitQuantity,
        //            RemainQuantity = remainingQty, // è®°å½•拆分后的剩余数量
        //            MaterielCode = lockInfo.MaterielCode,
        //            SplitTime = DateTime.Now,
        //            OrderNo = request.OrderNo,
        //            PalletCode = request.PalletCode,
        //            Status = (int)SplitPackageStatusEnum.已拆包,
        //            PreviousSplitRecordId = previousSplitRecord?.Id??0 // è®°å½•前一次拆包ID,建立拆包链
        //        };
        //        await Db.Insertable(splitHistory).ExecuteCommandAsync();
        //        _unitOfWorkManage.CommitTran();
        //        try
        //        {
        //            MaterielToMesDTO dto = new MaterielToMesDTO
        //            {
        //                batchNo = baseStockDetail.BatchNo,
        //                factoryArea = baseStockDetail.FactoryArea,
        //                materialCode = baseStockDetail.MaterielCode,
        //                newmaterialCode = newBarcode,
        //                oldmaterialCode = request.OriginalBarcode,
        //                operationType = 1,
        //                qty = remainingQty,
        //                supplyCode = baseStockDetail.SupplyCode,
        //                unit = baseStockDetail.BarcodeUnit,
        //                warehouseCode = baseStockDetail.WarehouseCode,
        //                reqCode = Guid.NewGuid().ToString(),
        //                reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
        //            };
        //            _invokeMESService.NewMaterielToMes(dto);
        //        }
        //        catch(Exception ex)
        //        {
        //            _logger.LogError("SplitPackage å›žä¼ MES:  " + ex.Message);
        //        }
        //        return WebResponseContent.Instance.OK("拆包成功", new
        //        {
        //            NewBarcode = newBarcode,
        //            NewLockInfoId = newLockInfo.Id
        //        });
        //    }
        //    catch (Exception ex)
        //    {
        //        _unitOfWorkManage.RollbackTran();
        //        return WebResponseContent.Instance.Error($"拆包失败: {ex.Message}");
        //    }
        //}
        // èŽ·å–æ¡ç çš„æ‹†åŒ…åŽ†å²é“¾
        public async Task<WebResponseContent> SplitPackage(SplitPackageDto request)
        {
@@ -297,7 +134,11 @@
                    CurrentBarcode = newBarcode,
                    OriginalLockQuantity = request.SplitQuantity,
                    IsSplitted = 1,
                    ParentLockId = lockInfo.Id
                    ParentLockId = lockInfo.Id,
                     Operator = App.User.UserName,
                    FactoryArea = lockInfo.FactoryArea,
                    lineNo = lockInfo.lineNo,
                    WarehouseCode = lockInfo.WarehouseCode,
                };
                await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
@@ -330,6 +171,30 @@
                await Db.Insertable(splitHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                try
                {
                    var dt_MaterielToMes = new Dt_MaterielToMes
                    {
                        OldMaterialBarCode = request.OriginalBarcode,
                        NewMaterialBarCode = newBarcode,
                        Unit = baseStockDetail.BarcodeUnit,
                        factoryArea = baseStockDetail.FactoryArea,
                        Qty = remainingQty,
                        supplyCode = baseStockDetail.SupplyCode,
                        warehouseCode = baseStockDetail.WarehouseCode,
                        BatchNo = baseStockDetail.BatchNo,
                        MaterielCode = baseStockDetail.MaterielCode,
                    };
                    _materielToMesService.AddData(dt_MaterielToMes);
                }
                catch (Exception ex)
                {
                    _logger.LogError("SplitPackage å›žä¼ MES:  " + ex.Message);
                }
                return WebResponseContent.Instance.OK("拆包成功", new SplitPackageChainDto
                {
@@ -418,7 +283,7 @@
                {
                    record.IsReverted = true;
                    record.RevertTime = DateTime.Now;
                    record.Status = (int)SplitPackageStatusEnum.已撤销;
                }
                await Db.Updateable(splitRecords).ExecuteCommandAsync();
@@ -526,14 +391,14 @@
        // èŽ·å–å¯æ’¤é”€çš„æ‹†åŒ…è®°å½•åˆ—è¡¨
        public Dt_SplitPackageRecord GetRevertableSplitRecords(string originalBarcode)
        {
            var revertableRecords =   Db.Queryable<Dt_SplitPackageRecord>()
            var revertableRecords = Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OriginalBarcode == originalBarcode && !x.IsReverted)
                    .OrderBy(x => x.SplitTime)
                   .First();
            return   revertableRecords ;
            return revertableRecords;
        }
        // èŽ·å–æ‹†åŒ…ä¿¡æ¯
        public async Task<WebResponseContent> GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
        {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -225,6 +225,12 @@
            return Db.Queryable<Dt_StockInfo>().Where(x => x.StockStatus == StockStatusEmun.入库完成.ObjToInt() && SqlFunc.Subqueryable<Dt_LocationInfo>().Where(v => v.LocationCode == x.LocationCode && v.LocationType == locationType && v.LocationStatus == LocationStatusEnum.Pallet.ObjToInt() && (v.EnableStatus == EnableStatusEnum.OnlyOut.ObjToInt() || EnableStatusEnum.Normal.ObjToInt() == v.EnableStatus)).Any()).OrderBy(x => x.ModifyDate).First();
        }
        public List<Dt_StockInfo> GetStockInfosByPalletCodes(List<string> palletCodes)
        {
            return Db.Queryable<Dt_StockInfo>().Where(x => palletCodes.Contains(x.PalletCode)).Includes(x => x.Details).ToList();
        }
        /// <summary>
        /// 
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -38,9 +38,11 @@
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Allocate;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Task;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_IInboundService;
using WIDESEA_IOutboundService;
@@ -69,7 +71,7 @@
        private readonly IESSApiService _eSSApiService;
        private readonly IStockService _stockService;
        private readonly IRecordService _recordService;
        private readonly IAllocateService _allocateService;
        private readonly IInvokeMESService _invokeMESService;
        public IRepository<Dt_Task> Repository => BaseDal;
@@ -90,7 +92,7 @@
        public List<int> TaskOutboundTypes => typeof(TaskTypeEnum).GetEnumIndexList();
        public TaskService(IRepository<Dt_Task> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockRepository, ILocationInfoService locationInfoService, IInboundOrderService inboundOrderService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IESSApiService eSSApiService, ILogger<TaskService> logger, IStockService stockService, IRecordService recordService, IInboundOrderDetailService inboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IInvokeMESService invokeMESService, IOutStockLockInfoService outStockLockInfoService) : base(BaseDal)
        public TaskService(IRepository<Dt_Task> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockRepository, ILocationInfoService locationInfoService, IInboundOrderService inboundOrderService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IESSApiService eSSApiService, ILogger<TaskService> logger, IStockService stockService, IRecordService recordService, IInboundOrderDetailService inboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IInvokeMESService invokeMESService, IOutStockLockInfoService outStockLockInfoService, IAllocateService allocateService) : base(BaseDal)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -107,6 +109,7 @@
            _outboundOrderDetailService = outboundOrderDetailService;
            _invokeMESService = invokeMESService;
            _outStockLockInfoService = outStockLockInfoService;
            _allocateService = allocateService;
        }
@@ -250,7 +253,53 @@
                {
                    if (inboundOrder.OrderType == InOrderTypeEnum.Allocat.ObjToInt())//调拨入库
                    {
                        if (inboundOrder != null && inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt())
                        {
                            var allocate = _allocateService.Repository.QueryData(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                            var feedmodel = 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>()
                            };
                            var groupedData = inboundOrder.Details.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                               .Select(group => new AllocateDtoDetail
                               {
                                   MaterialCode = group.Key.MaterielCode,
                                   LineNo = group.Key.lineNo,
                                   WarehouseCode = group.Key.WarehouseCode,
                                   Qty = group.Sum(x => x.BarcodeQty),
                                   // warehouseCode= "1072",
                                   Unit = group.Key.BarcodeUnit,
                                   Barcodes = group.Select(row => new BarcodeInfo
                                   {
                                       Barcode = row.Barcode,
                                       Qty = row.BarcodeQty,
                                       BatchNo = row.BatchNo,
                                       SupplyCode = row.SupplyCode,
                                       Unit = row.Unit
                                   }).ToList()
                               }).ToList();
                            feedmodel.Details = groupedData;
                            var result = await _invokeMESService.FeedbackAllocate(feedmodel);
                            if (result != null && result.code == 200)
                            {
                                _inboundOrderService.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                                _inboundOrderDetailService.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
                                .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand();
                            }
                        }
                    }
                    else if (inboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()) //重检入库
                    {
@@ -405,7 +454,7 @@
            }
        }
        public WebResponseContent InPickTaskCompleted(Dt_Task task)
        public async Task<WebResponseContent> InPickTaskCompleted(Dt_Task task)
        {
            _logger.LogInformation($"TaskService  InPickTaskCompleted: {task.TaskNum}");
            //查库存
@@ -418,7 +467,12 @@
            {
                return WebResponseContent.Instance.Error($"未找到该托盘库存明细信息");
            }
            //查货位
            Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == task.TargetAddress);
            if (locationInfo == null)
            {
                return WebResponseContent.Instance.Error($"未找到对应的终点货位信息");
            }
            // èŽ·å–æ‰€æœ‰å›žåº“ä¸­çš„å‡ºåº“é”å®šè®°å½•
            var returnLocks = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == task.OrderNo && it.PalletCode == task.PalletCode && it.Status == (int)OutLockStockStatusEnum.回库中)
@@ -430,12 +484,8 @@
            }
            _outStockLockInfoService.Db.Updateable(returnLocks).ExecuteCommand();
            //查货位
            Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == task.TargetAddress);
            if (locationInfo == null)
            {
                return WebResponseContent.Instance.Error($"未找到对应的终点货位信息");
            }
            await DeleteZeroQuantityStockDetails(stockInfo.Id);
            stockInfo.LocationCode = task.TargetAddress;
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            stockInfo.Details.ForEach(x =>
@@ -444,6 +494,9 @@
            });
            _stockService.StockInfoService.Repository.UpdateData(stockInfo);
            _stockService.StockInfoDetailService.Repository.UpdateData(stockInfo.Details);
            await ProcessStockDetailsForReturn(task, stockInfo.Id);
            if (stockInfo.PalletType == PalletTypeEnum.Empty.ObjToInt())
            {
@@ -462,6 +515,60 @@
            return WebResponseContent.Instance.OK();
        }
        /// <summary>
        /// åˆ é™¤åº“存数为0的库存明细记录
        /// </summary>
        private async Task DeleteZeroQuantityStockDetails(int stockId)
        {
            try
            {
                // åˆ é™¤åº“存数量为0的记录
                var deleteCount = await _stockService.StockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId &&
                               x.StockQuantity == 0 &&
                               (x.Status==StockStatusEmun.出库完成.ObjToInt()|| x.Status==
                                          StockStatusEmun.入库完成.ObjToInt())) // åªåˆ é™¤å·²å®ŒæˆçŠ¶æ€çš„é›¶åº“å­˜
                    .ExecuteCommandAsync();
                if (deleteCount > 0)
                {
                    _logger.LogInformation($"删除{deleteCount}条零库存明细记录 - StockId: {stockId}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning($"删除零库存记录失败 - StockId: {stockId}, Error: {ex.Message}");
                // æ³¨æ„ï¼šåˆ é™¤å¤±è´¥ä¸åº”该影响主流程,记录日志后继续
            }
        }
        /// <summary>
        /// å¤„理回库相关的所有库存明细状态变更
        /// </summary>
        private async Task ProcessStockDetailsForReturn(Dt_Task returnTask, int stockId)
        {
            // èŽ·å–è¯¥æ‰˜ç›˜ä¸‹æ‰€æœ‰éœ€è¦å›žåº“çš„åº“å­˜æ˜Žç»†
            var stockDetails = await _stockService.StockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.StockId == stockId &&
                           x.StockQuantity > 0 &&
                          ( x.Status == StockStatusEmun.出库锁定.ObjToInt()|| x.Status==
                                      StockStatusEmun.入库确认.ObjToInt()))  // åŒ…括出库锁定和入库确认的
                .ToListAsync();
            foreach (var detail in stockDetails)
            {
                detail.Status = StockStatusEmun.入库完成.ObjToInt();
                detail.OutboundQuantity = 0;  // æ¸…空出库数量
                _logger.LogInformation($"更新库存明细状态 - æ¡ç : {detail.Barcode}, æ•°é‡: {detail.StockQuantity}");
            }
            if (stockDetails.Any())
            {
                await _stockService.StockInfoDetailService.Db.Updateable(stockDetails).ExecuteCommandAsync();
                _logger.LogInformation($"共更新{stockDetails.Count}个库存明细状态为入库完成");
            }
        }
        public async Task<WebResponseContent> OutEmptyTaskCompleted(Dt_Task task)
        {
            WebResponseContent content = new WebResponseContent();
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -206,7 +206,7 @@
                if (stockInfos != null && stockInfos.Count > 0 && outboundOrderDetails != null && outboundOrderDetails.Count > 0 && outStockLockInfos != null && outStockLockInfos.Count > 0 && locationInfos != null && locationInfos.Count > 0)
                {
                    stockInfos.ForEach(x =>
                    {
                    {
                        x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                    });
                    outboundOrderDetails.ForEach(x =>
@@ -218,6 +218,8 @@
                    {
                        _outboundOrderService.Repository.UpdateData(outboundOrder);
                    }
                    outboundOrder.Operator = App.User.UserName;
                    _outboundOrderService.Repository.UpdateData(outboundOrder);
                    WebResponseContent content = _outboundOrderDetailService.LockOutboundStockDataUpdate(stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos, tasks: tasks);
                    if (!content.Status)
@@ -335,6 +337,51 @@
            return tasks;
        }
        public List<Dt_Task> GetTasks(List<Dt_StockInfo> stockInfos, TaskTypeEnum taskType)
        {
            List<Dt_Task> tasks = new List<Dt_Task>();
            List<Dt_LocationInfo> locationInfos = _locationInfoService.Repository.QueryData(x => stockInfos.Select(x => x.LocationCode).Contains(x.LocationCode));
            for (int i = 0; i < stockInfos.Count; i++)
            {
                Dt_StockInfo stockInfo = stockInfos[i];
                if (stockInfo != null)
                {
                    Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == stockInfo.LocationCode);
                    if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
                    {
                        Dt_Task task = new()
                        {
                            CurrentAddress = stockInfo.LocationCode,
                            Grade = 0,
                            PalletCode = stockInfo.PalletCode,
                            NextAddress = "",
                            Roadway = locationInfo.RoadwayNo,
                            SourceAddress = stockInfo.LocationCode,
                            TargetAddress = "",
                            TaskStatus = TaskStatusEnum.New.ObjToInt(),
                            TaskType = taskType.ObjToInt(),
                            //TaskNum = BaseDal.GetTaskNum(nameof(SequenceEnum.SeqTaskNum)),
                            PalletType = stockInfo.PalletType,
                            WarehouseId = stockInfo.WarehouseId,
                        };
                        //if (taskType != TaskTypeEnum.OutEmpty)
                        //{
                        //    task.MaterielCode = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.MaterielCode;
                        //    task.Quantity = (float)stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity);
                        //    task.BatchNo = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.BatchNo;
                        //}
                        //if (stockInfo.StockLength > 0)
                        //{
                        //    task.TaskLength = stockInfo.StockLength;
                        //}
                        tasks.Add(task);
                    }
                }
            }
            return tasks;
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
@@ -385,5 +432,159 @@
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
        /// </summary>
        /// <param name="orderDetailId"></param>
        /// <param name="stockSelectViews"></param>
        /// <returns></returns>
        public WebResponseContent GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews)
        {
            try
            {
                (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(orderDetailId, stockSelectViews);
                WebResponseContent content = GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5);
                return content;
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        /// <summary>
        /// å‡ºåº“任务数据处理
        /// </summary>
        /// <param name="orderDetailId"></param>
        /// <param name="stockSelectViews"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) OutboundTaskDataHandle(int orderDetailId, List<StockSelectViewDTO> stockSelectViews)
        {
            List<Dt_Task> tasks = new List<Dt_Task>();
            Dt_OutboundOrderDetail outboundOrderDetail = _outboundOrderDetailService.Repository.QueryFirst(x => x.Id == orderDetailId);
            if (outboundOrderDetail == null)
            {
                throw new Exception("未找到出库单明细信息");
            }
            if (stockSelectViews.Sum(x => x.UseableQuantity) > outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity)
            {
                throw new Exception("选择数量超出单据数量");
            }
            List<Dt_StockInfo>? stockInfos = null;
            Dt_OutboundOrderDetail? orderDetail = null;
            List<Dt_OutStockLockInfo>? outStockLockInfos = null;
            List<Dt_LocationInfo>? locationInfos = null;
            if (outboundOrderDetail.OrderDetailStatus == OrderDetailStatusEnum.New.ObjToInt())
            {
                (List<Dt_StockInfo>, Dt_OutboundOrderDetail, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetail, stockSelectViews);
                if (result.Item1 != null && result.Item1.Count > 0)
                {
                    Dt_OutboundOrder outboundOrder = _outboundOrderService .Repository.QueryFirst(x => x.Id == outboundOrderDetail.OrderId);
                    TaskTypeEnum typeEnum = outboundOrder.OrderType switch
                    {
                        (int)OutOrderTypeEnum.Issue => TaskTypeEnum.Outbound,
                        (int)OutOrderTypeEnum.Allocate => TaskTypeEnum.OutAllocate,
                        (int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality,
                        _ => new TaskTypeEnum()
                    };
                    tasks = GetTasks(result.Item1, typeEnum);
                    result.Item2.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
                    result.Item3.ForEach(x =>
                    {
                        x.Status = OutLockStockStatusEnum.出库中.ObjToInt();
                    });
                    stockInfos = result.Item1;
                    orderDetail = result.Item2;
                    outStockLockInfos = result.Item3;
                    locationInfos = result.Item4;
                }
                else
                {
                    throw new Exception("无库存");
                }
            }
            else
            {
                List<Dt_OutStockLockInfo> stockLockInfos = _outStockLockInfoService.GetByOrderDetailId(outboundOrderDetail.OrderId, OutLockStockStatusEnum.已分配);
                if (stockLockInfos != null && stockLockInfos.Count > 0)
                {
                    List<Dt_StockInfo> stocks = _stockService.StockInfoService.GetStockInfosByPalletCodes(stockLockInfos.Select(x => x.PalletCode).Distinct().ToList());
                    tasks = GetTasks(stocks, TaskTypeEnum.Outbound);
                }
            }
            return (tasks, stockInfos, orderDetail == null ? null : new List<Dt_OutboundOrderDetail> { orderDetail }, outStockLockInfos, locationInfos);
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务后数据更新到数据库
        /// </summary>
        /// <param name="tasks"></param>
        /// <param name="stockInfos"></param>
        /// <param name="outboundOrderDetails"></param>
        /// <param name="outStockLockInfos"></param>
        /// <param name="locationInfos"></param>
        /// <returns></returns>
        public WebResponseContent GenerateOutboundTaskDataUpdate(List<Dt_Task> tasks, List<Dt_StockInfo>? stockInfos = null, List<Dt_OutboundOrderDetail>? outboundOrderDetails = null, List<Dt_OutStockLockInfo>? outStockLockInfos = null, List<Dt_LocationInfo>? locationInfos = null)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                BaseDal.AddData(tasks);
                if (stockInfos != null && stockInfos.Count > 0 && outboundOrderDetails != null && outboundOrderDetails.Count > 0 && outStockLockInfos != null && outStockLockInfos.Count > 0 && locationInfos != null && locationInfos.Count > 0)
                {
                    stockInfos.ForEach(x =>
                    {
                        x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                    });
                    outboundOrderDetails.ForEach(x =>
                    {
                        x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
                    });
                    Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
                    if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
                    {
                        _outboundOrderService.Repository.UpdateData(outboundOrder);
                    }
                    WebResponseContent content = _outboundOrderDetailService.LockOutboundStockDataUpdate(stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos, tasks: tasks);
                    if (!content.Status)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return content;
                    }
                }
                else if (outboundOrderDetails != null && outboundOrderDetails.Count > 0)
                {
                    outboundOrderDetails.ForEach(x =>
                    {
                        x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
                    });
                    Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
                    if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
                    {
                        _outboundOrderService.Repository.UpdateData(outboundOrder);
                    }
                    _outboundOrderDetailService.Repository.UpdateData(outboundOrderDetails);
                }
                _unitOfWorkManage.CommitTran();
                //PushTasksToWCS(tasks);
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WIDESEA_TaskInfoService.csproj
@@ -7,6 +7,7 @@
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\WIDESEA_IAllocateService\WIDESEA_IAllocateService.csproj" />
    <ProjectReference Include="..\WIDESEA_IBasicService\WIDESEA_IBasicService.csproj" />
    <ProjectReference Include="..\WIDESEA_IInboundService\WIDESEA_IInboundService.csproj" />
    <ProjectReference Include="..\WIDESEA_IOutboundService\WIDESEA_IOutboundService.csproj" />
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderController.cs
@@ -3,6 +3,7 @@
using System.Data.Common;
using System.Diagnostics.Eventing.Reader;
using System.Threading.Tasks;
using WIDESEA_Common.AllocateEnum;
using WIDESEA_Core;
using WIDESEA_Core.Attributes;
using WIDESEA_Core.BaseController;
@@ -36,15 +37,23 @@
            {
                OrderNo = model.OrderNo,
                UpperOrderNo = model.OrderNo,
                BusinessType=model.BusinessType,
                FactoryArea=model.FactoryArea,
                IsBatch=model.IsBatch,
                BusinessType = model.BusinessType,
                FactoryArea = model.FactoryArea,
                IsBatch = model.IsBatch,
                CreateType = model.OperationType,
                Details = new List<Dt_AllocateOrderDetail>()
                FromWarehouse=model.fromWarehouse,
                ToWarehouse=model.toWarehouse,
                Details = new List<Dt_AllocateOrderDetail>()
            };
            Enum.TryParse<BusinessTypeEnum>(allocateOrder.BusinessType, out var businessType);
            foreach (var detailDto in model.Details)
            {
                if (businessType == BusinessTypeEnum.智仓调外部仓库 && (detailDto.Barcodes == null || !detailDto.Barcodes.Any()))
                {
                    return WebResponseContent.Instance.Error($"条码不能为空");
                }
                if (detailDto.Barcodes != null && detailDto.Barcodes.Any())
                {
                    foreach (var barcodeDto in detailDto.Barcodes)
@@ -55,7 +64,7 @@
                            MaterielCode = detailDto.MaterialCode,
                            LineNo = detailDto.LineNo,
                            OrderQuantity = detailDto.Qty,
                            SupplyCode= barcodeDto.SupplyCode,
                            SupplyCode = barcodeDto.SupplyCode,
                            Unit = detailDto.Unit,
                            Barcode = barcodeDto.Barcode,
                            BatchNo = barcodeDto.BatchNo,
@@ -79,7 +88,7 @@
                }
                allocateOrder.Details.AddRange(allocateOrder.Details);
            }
            var content =await Service.ReceiveAllocateOrder(allocateOrder, model.OperationType);
            var content = await Service.ReceiveAllocateOrder(allocateOrder, model.OperationType);
            if (content.Status) return WebResponseContent.Instance.OK(200);
            else return WebResponseContent.Instance.Error(content.Message);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs
@@ -181,6 +181,12 @@
            return content;
        }
        [HttpPost, Route("UndoPalletGroup"), AllowAnonymous, MethodParamsValidate]
        public WebResponseContent UndoPalletGroup(string palletCode)
        {
            return Service.UndoPalletGroup(palletCode);
        }
        /// <summary>
        /// 
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
@@ -21,14 +21,7 @@
            _splitPackageService = splitPackageService;
            _outStockLockInfoService = outStockLockInfoService;
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€
        /// </summary>
        [HttpPost("GetPalletOutboundStatus")]
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            return await Service.GetPalletOutboundStatus(palletCode);
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
@@ -98,50 +91,13 @@
            return await Service.ReturnRemaining(dto.OrderNo, dto.PalletCode, "");
        }
        [HttpPost("direct-outbound")]
        public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest dto)
        {
           return await Service.DirectOutbound(dto);
        }
        ///// <summary>
        ///// æ‹£é€‰ç¡®è®¤
        ///// </summary>
        //[HttpPost("ConfirmPicking")]
        //public async Task<WebResponseContent> ConfirmPicking([FromBody] PickingConfirmRequest request)
        //[HttpPost("direct-outbound")]
        //public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest dto)
        //{
        //    return await Service.ConfirmPicking(request);
        //}
        /// <summary>
        /// éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        /// </summary>
        [HttpGet("ValidateBarcode")]
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            return await Service.ValidateBarcode(barcode);
        }
        //   return await Service.DirectOutbound(dto);
 
        ///// <summary>
        ///// ç›´æŽ¥å‡ºåº“
        ///// </summary>
        //[HttpPost("DirectOutbound")]
        //public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest request)
        //{
        //    return await Service.DirectOutbound(request);
        //}
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
        [HttpGet("GetPickingHistory")]
        public async Task<WebResponseContent> GetPickingHistory(int orderId)
        {
            var history = await Service.GetPickingHistory(orderId);
            return WebResponseContent.Instance.OK(null, history);
        }
        //}
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -47,5 +47,17 @@
            return await Service.GenerateOutboundTasksAsync(data.taskIds,data.outboundPlatform);
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
        /// </summary>
        /// <param name="orderDetailId"></param>
        /// <param name="stockSelectViews"></param>
        /// <returns></returns>
        [HttpPost, HttpGet, Route("GenerateOutboundTask"), AllowAnonymous]
        public WebResponseContent GenerateOutboundTask(int orderDetailId, [FromBody] List<StockSelectViewDTO> stockSelectViews)
        {
            return Service.GenerateOutboundTask(orderDetailId, stockSelectViews);
        }
    }
}