ソースを参照

Merge branch 'materials' into test

zhangcl 1 週間 前
コミット
952fa41f34

+ 8 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorderbommaterial/IotMainWorkOrderBomMaterialController.java

@@ -89,7 +89,14 @@ public class IotMainWorkOrderBomMaterialController {
     @PreAuthorize("@ss.hasPermission('pms:iot-main-work-order-bom-material:query')")
     public CommonResult<List<IotMainWorkOrderBomMaterialRespVO>> getIotMainWorkOrderBomMaterialList(@Valid IotMainWorkOrderBomMaterialPageReqVO pageReqVO) {
         List<IotMainWorkOrderBomMaterialDO> list = iotMainWorkOrderBomMaterialService.getIotMainWorkOrderBomMaterialList(pageReqVO);
-        return success(BeanUtils.toBean(list, IotMainWorkOrderBomMaterialRespVO.class));
+        // 查询已经关联的物料的 工厂 成本中心 库存地点 信息
+        Map<Long, SapOrgRespDTO> factoryMap = sapOrgApi.getSapOrgMap(
+                convertList(list, IotMainWorkOrderBomMaterialDO::getFactoryId));
+        Map<Long, SapOrgRespDTO> costCenterMap = sapOrgApi.getSapOrgMap(
+                convertList(list, IotMainWorkOrderBomMaterialDO::getCostCenterId));
+        Map<Long, SapOrgRespDTO> storageLocationMap = sapOrgApi.getSapOrgMap(
+                convertList(list, IotMainWorkOrderBomMaterialDO::getStorageLocationId));
+        return success(IotWorkOrderBomMaterialConvert.INSTANCE.convertList1(list, factoryMap, costCenterMap, storageLocationMap));
     }
 
     @GetMapping("/workOrderMaterials")

+ 23 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/convert/iotmainworkorderbommaterial/IotWorkOrderBomMaterialConvert.java

@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialDO;
 import cn.iocoder.yudao.module.system.api.saporg.dto.SapOrgRespDTO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -23,6 +24,13 @@ public interface IotWorkOrderBomMaterialConvert {
                 costCenterMap.get(stock.getCostCenterId()), stockLocationMap.get(stock.getStorageLocationId())));
     }
 
+    default List<IotMainWorkOrderBomMaterialRespVO> convertList1(List<IotMainWorkOrderBomMaterialDO> list,
+                                                                Map<Long, SapOrgRespDTO> factoryMap,
+                                                                Map<Long, SapOrgRespDTO> costCenterMap, Map<Long, SapOrgRespDTO> stockLocationMap) {
+        return CollectionUtils.convertList(list, stock -> convert2(stock, factoryMap.get(stock.getFactoryId()),
+                costCenterMap.get(stock.getCostCenterId()), stockLocationMap.get(stock.getStorageLocationId())));
+    }
+
     default IotMainWorkOrderBomMaterialRespVO convert1(IotMainWorkOrderBomMaterialRespVO stock,
                                                        SapOrgRespDTO factory, SapOrgRespDTO costCenter, SapOrgRespDTO projectDepartment) {
         IotMainWorkOrderBomMaterialRespVO stockVO = BeanUtils.toBean(stock, IotMainWorkOrderBomMaterialRespVO.class);
@@ -38,4 +46,19 @@ public interface IotWorkOrderBomMaterialConvert {
         return stockVO;
     }
 
+    default IotMainWorkOrderBomMaterialRespVO convert2(IotMainWorkOrderBomMaterialDO stock,
+                                                       SapOrgRespDTO factory, SapOrgRespDTO costCenter, SapOrgRespDTO projectDepartment) {
+        IotMainWorkOrderBomMaterialRespVO stockVO = BeanUtils.toBean(stock, IotMainWorkOrderBomMaterialRespVO.class);
+        if (ObjectUtil.isNotEmpty(factory)) {
+            stockVO.setFactory(factory.getFactoryName());
+        }
+        if (ObjectUtil.isNotEmpty(costCenter)) {
+            stockVO.setCostCenter(costCenter.getCostCenterName());
+        }
+        if (ObjectUtil.isNotEmpty(projectDepartment)) {
+            stockVO.setProjectDepartment(projectDepartment.getStorageLocationName());
+        }
+        return stockVO;
+    }
+
 }

+ 5 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmainworkorderbommaterial/IotMainWorkOrderBomMaterialMapper.java

@@ -10,7 +10,9 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbommaterial.Io
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Set;
 
@@ -93,4 +95,7 @@ public interface IotMainWorkOrderBomMaterialMapper extends BaseMapperX<IotMainWo
                                                                      @Param("stockLocationIds") Set<Long> stockLocationIds,
                                                                      @Param("materialName") String materialName,
                                                                      @Param("materialCode") String materialCode);
+
+    @Update("UPDATE rq_iot_main_work_order_bom_material SET deleted = 1, update_time = #{deletedTime} WHERE work_order_id = #{workOrderId}")
+    void updateToDelete(@Param("workOrderId") Long workOrderId, @Param("deletedTime") LocalDateTime deletedTime);
 }

+ 8 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotdevicematerial/IotDeviceMaterialServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pms.service.iotdevicematerial;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
@@ -335,7 +336,13 @@ public class IotDeviceMaterialServiceImpl implements IotDeviceMaterialService {
                 }
 
                 // 设置保养项关联的物料列表
-                List<IotDeviceMaterialRespVO> deviceMaterials = BeanUtils.toBean(deviceBomMaterials, IotDeviceMaterialRespVO.class);
+                List<IotDeviceMaterialRespVO> deviceMaterials = deviceBomMaterials.stream()
+                        .map(source -> {
+                            IotDeviceMaterialRespVO target = new IotDeviceMaterialRespVO();
+                            BeanUtil.copyProperties(source, target, "id");
+                            return target;
+                        })
+                        .collect(Collectors.toList());
                 // 绑定保养项关联的物料 优先级 本地库存 > sap库存 > sap主数据
                 deviceMaterials.forEach(material -> {
                     if (lockStockPair.containsKey(material.getCode())) {

+ 71 - 44
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pms.service.iotmainworkorder;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.ObjUtil;
@@ -1298,72 +1299,98 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         Set<Long> mctDeviceIds = new HashSet<>();
         // 需要更新 运行时长周期 的保养项id 集合
         Set<Long> mctBomNodeIds = new HashSet<>();
+        // 需要消耗物料的保养项id集合
+        Set<Long> materialBomNodeIds = new HashSet<>();
+        // 保养完成的保养项id集合
+        Set<Long> completedBomNodeIds = new HashSet<>();
+        // 统计保养项信息 是否保养完成 是否需要消耗物料
+        workOrderBOMs.forEach(bom -> {
+            if ("0".equals(bom.getRule())) {
+                // 需要消耗物料
+                materialBomNodeIds.add(bom.getBomNodeId());
+            }
+            if (1 == bom.getStatus()) {
+                // 保养完成
+                completedBomNodeIds.add(bom.getBomNodeId());
+            }
+        });
+        // 只有保养完成的保养项关联的物料才会被统计出库(如果物料来源是 本地库存)
         workOrderMaterials.forEach(material -> {
-            if (ObjUtil.isEmpty(material.getId())) {
-                // 兼容保养工单中部分保养项保养完成 部分保养项延期保养的情况 这里只新增延期保养 或 正常保养的物料
-                IotMainWorkOrderBomMaterialDO tempMaterial = BeanUtils.toBean(material, IotMainWorkOrderBomMaterialDO.class);
+            // 兼容保养工单中部分保养项保养完成 部分保养项延期保养的情况 这里只新增延期保养 或 正常保养的物料
+            if (materialBomNodeIds.contains(material.getBomNodeId())) {
+                IotMainWorkOrderBomMaterialDO tempMaterial = BeanUtil.copyProperties(material, IotMainWorkOrderBomMaterialDO.class, "id");
                 tempMaterial.setWorkOrderId(mainWorkOrder.getId());
                 workOrderBomMaterialDOS.add(tempMaterial);
+            }
 
-                if ("本地库存".equals(material.getMaterialSource())) {
-                    if (ObjUtil.isNotEmpty(material.getFactoryId())) {
-                        factoryIds.add(material.getFactoryId());
-                    }
-                    if (ObjUtil.isNotEmpty(material.getCostCenterId())) {
-                        costCenterIds.add(material.getCostCenterId());
-                    }
-                    if (ObjUtil.isNotEmpty(material.getStorageLocationId())) {
-                        storageLocationIds.add(material.getStorageLocationId());
-                    }
-                    if (ObjUtil.isNotEmpty(material.getMaterialCode())) {
-                        materialCodes.add(material.getMaterialCode());
-                    }
-                    String uniqueKey = StrUtil.join("-", material.getFactoryId(),
-                            material.getCostCenterId(), material.getMaterialCode(), material.getBomNodeId());
-                    String tempKey = material.getFactoryId() + String.valueOf(material.getCostCenterId())
-                            + material.getMaterialCode();
-                    // 扣减库存使用 合并不同保养项下的相同物料
-                    if (lockStockPair.containsKey(tempKey)) {
-                        BigDecimal tempQuantity = lockStockPair.get(tempKey);
-                        BigDecimal totalQuantity = tempQuantity.add(material.getQuantity());
-                        lockStockPair.put(tempKey, totalQuantity);
-                    } else {
-                        lockStockPair.put(tempKey, material.getQuantity());
-                    }
-                    // 记录出库使用 记录保养项id
-                    bomMaterialPair.put(uniqueKey, material);
+            if (completedBomNodeIds.contains(material.getBomNodeId()) && "本地库存".equals(material.getMaterialSource())) {
+                if (ObjUtil.isNotEmpty(material.getFactoryId())) {
+                    factoryIds.add(material.getFactoryId());
+                }
+                if (ObjUtil.isNotEmpty(material.getCostCenterId())) {
+                    costCenterIds.add(material.getCostCenterId());
+                }
+                if (ObjUtil.isNotEmpty(material.getStorageLocationId())) {
+                    storageLocationIds.add(material.getStorageLocationId());
+                }
+                if (ObjUtil.isNotEmpty(material.getMaterialCode())) {
+                    materialCodes.add(material.getMaterialCode());
+                }
+                String uniqueKey = StrUtil.join("-", material.getFactoryId(),
+                        material.getCostCenterId(), material.getMaterialCode(), material.getBomNodeId());
+                String tempKey = material.getFactoryId() + String.valueOf(material.getCostCenterId())
+                        + material.getMaterialCode();
+                // 扣减库存使用 合并不同保养项下的相同物料
+                if (lockStockPair.containsKey(tempKey)) {
+                    BigDecimal tempQuantity = lockStockPair.get(tempKey);
+                    BigDecimal totalQuantity = tempQuantity.add(material.getQuantity());
+                    lockStockPair.put(tempKey, totalQuantity);
+                } else {
+                    lockStockPair.put(tempKey, material.getQuantity());
                 }
+                // 记录出库使用 记录保养项id
+                bomMaterialPair.put(uniqueKey, material);
             }
         });
+        // 删除当前工单已有的保养项物料 再新增
+        if (ObjUtil.isNotEmpty(updateObj.getId())) {
+            iotMainWorkOrderBomMaterialMapper.updateToDelete(updateObj.getId(), LocalDateTime.now());
+        }
         // 添加 工单消耗的物料记录
         iotMainWorkOrderBomMaterialMapper.insertBatch(workOrderBomMaterialDOS);
         // key设备id-保养项BOM节点id   value保养工单明细对象   用于更新关联的保养计划保养项
         Map<String, IotMainWorkOrderBomSaveReqVO> deviceBomPair = new HashMap<>();
         workOrderBOMs.forEach(bom -> {
+            IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
             if (ObjUtil.isNotEmpty(bom.getStatus()) && 0 == bom.getStatus()) {
                 // 兼容保养工单中部分保养项保养完成 部分保养项 延期保养的情况 这里只更新延期保养 或正常 保养的物料
-                IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
                 // 如果工单明细中任何一个保养项设置了 任何一种推迟保养规则 工单结果 不能设置为 已执行
-                if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)>0
-                        || bom.getDelayDuration().compareTo(BigDecimal.ZERO)>0 || bom.getDelayNaturalDate().compareTo(BigDecimal.ZERO)>0) {
-                    tempBom.setStatus(0);
-                    mainCompleted.set(false);
-                }
-                if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)==0
+                /* if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)>0
+                        || bom.getDelayDuration().compareTo(BigDecimal.ZERO)>0 || bom.getDelayNaturalDate().compareTo(BigDecimal.ZERO)>0) { */
+                tempBom.setStatus(0);
+                mainCompleted.set(false);
+                // }
+                /* if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)==0
                         && bom.getDelayDuration().compareTo(BigDecimal.ZERO)==0 && bom.getDelayNaturalDate().compareTo(BigDecimal.ZERO)==0) {
                     // 如果保养项没有设置延时保养 则查询当前设备的 累计运行公里数 累计运行时间 当前时间日期 赋值到 关联保养计划 明细 的 对应保养规则数据上
                     deviceIds.add(bom.getDeviceId());
                     // 保养项下如果已经添加了物料 说明该保养项已经保养完成
                     bomNodeIds.add(bom.getBomNodeId());
                     tempBom.setStatus(1);
-                }
-                workOrderBomDOS.add(tempBom);
+                } */
+            } else if (ObjUtil.isNotEmpty(bom.getStatus()) && 1 == bom.getStatus()) {
+                // 当前保养项已经保养完成 查询当前设备的 累计运行公里数 累计运行时间 当前时间日期 赋值到 关联保养计划 明细 的 对应保养规则数据上
+                deviceIds.add(bom.getDeviceId());
+                // 保养项下如果已经添加了物料 说明该保养项已经保养完成
+                bomNodeIds.add(bom.getBomNodeId());
+                tempBom.setStatus(1);
                 // 设置需要更新 关联保养计划 保养项 运行时间周期(H) 的设备id 保养项id 集合
                 mctDeviceIds.add(bom.getDeviceId());
                 mctBomNodeIds.add(bom.getBomNodeId());
                 String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                 deviceBomPair.put(uniqueKey, bom);
             }
+            workOrderBomDOS.add(tempBom);
         });
         if (CollUtil.isNotEmpty(workOrderBOMs)) {
             // 组装bom关联的设备信息
@@ -1463,16 +1490,16 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         if (2 == updateObj.getResult()) {
             if (CollUtil.isNotEmpty(filteredOrders)) {
                 // 将关联工单设置为 已执行 直接删除
-                filteredOrders.forEach(order -> {order.setResult(2);  order.setDeleted(true);});
+                filteredOrders.forEach(order -> { order.setResult(2);  order.setDeleted(true); });
                 iotMainWorkOrderMapper.updateBatch(filteredOrders);
                 List<Long> workOrderIds = filteredOrders.stream()
                         .map(IotMainWorkOrderDO::getId)
                         .collect(Collectors.toList());
-                // 将关联工单的明细 status 设置为 1
+                // 将关联工单的明细 status 设置为 1 直接删除
                 IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
                 reqVO.setWorkOrderIds(workOrderIds);
                 List<IotMainWorkOrderBomDO> associateOrderBomS = iotMainWorkOrderBomMapper.selectList(reqVO);
-                associateOrderBomS.forEach(order -> order.setStatus(1));
+                associateOrderBomS.forEach(order -> { order.setStatus(1); order.setDeleted(true); });
                 iotMainWorkOrderBomMapper.updateBatch(associateOrderBomS);
             }
         }
@@ -1488,10 +1515,10 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
                     order.setLaborCost(updateObj.getLaborCost());
                     order.setRemark(updateObj.getRemark());
                 });
+                // 更新 关联保养工单的相关数据
+                iotMainWorkOrderMapper.updateBatch(filteredOrders);
             }
         }
-        // 如果在填报工单时 填写了 保养项 中的 ‘运行时间周期(H)’ 保存时需要同步到 保养工单 关联的 保养计划的 对应的保养项的 ‘运行时间周期(H)’
-        // 包含 延时保养 的情况
         // 只扣减本地库存 不处理SAP库存
         // 保养基于单设备 出库时记录 单设备所属的部门id(即保养工单的部门id)
         if (CollUtil.isNotEmpty(factoryIds) && CollUtil.isNotEmpty(costCenterIds) && CollUtil.isNotEmpty(materialCodes)) {

+ 233 - 6
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotWorkOrderMaterialMapper.xml

@@ -2,9 +2,9 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialMapper">
 
+    <!--
     <select id="selectMaterialsBySapOrg"
             resultType="cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialRespVO">
-        -- 本地库存中与bom节点关联的物料
         SELECT
             lo.factory_id AS factoryId,
             lo.cost_center_id AS costCenterId,
@@ -46,7 +46,6 @@
 
         UNION ALL
 
-        -- sap库存物料
         SELECT
         sap.factory_id AS factoryId,
         0 AS costCenterId,
@@ -87,7 +86,6 @@
         </if>
 
         UNION ALL
-        -- sap主数据
         SELECT
         0 AS factoryId,
         0 AS costCenterId,
@@ -116,7 +114,6 @@
         </if>
 
         UNION ALL
-        -- 本地库存物料
         SELECT
         lo.factory_id AS factoryId,
         lo.cost_center_id AS costCenterId,
@@ -157,7 +154,6 @@
         </if>
 
         UNION ALL
-        -- sap库存物料
         SELECT
         sap.factory_id AS factoryId,
         0 AS costCenterId,
@@ -198,7 +194,6 @@
         </if>
 
         UNION ALL
-        -- sap主数据
         SELECT
         0 AS factoryId,
         0 AS costCenterId,
@@ -226,5 +221,237 @@
             AND m.`code` LIKE concat(concat("%",#{materialCode}),"%")
         </if>
 
+    </select> -->
+
+    <select id="selectMaterialsBySapOrg"
+            resultType="cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialRespVO">
+        -- 1. 本地库存中与bom节点关联的物料(IN materialCodes)
+        SELECT
+        lo.factory_id AS factoryId,
+        lo.cost_center_id AS costCenterId,
+        lo.storage_location_id AS storageLocationId,
+        lo.material_code AS materialCode,
+        lo.material_name AS materialName,
+        lo.unit_price AS unitPrice,
+        lo.quantity AS totalInventoryQuantity,
+        lo.unit AS unit,
+        '本地库存' AS materialSource,
+        1 AS sortOrder -- 添加排序字段
+        FROM
+        rq_iot_lock_stock lo
+        WHERE
+        1=1
+        <if test="materialCodes != null and materialCodes.size > 0">
+            AND lo.material_code IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="factoryIds != null and factoryIds.size > 0">
+            AND lo.factory_id IN
+            <foreach collection="factoryIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="costCenterIds != null and costCenterIds.size > 0">
+            AND lo.cost_center_id IN
+            <foreach collection="costCenterIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="materialName!=null and materialName!=''">
+            AND lo.material_name LIKE concat(concat("%",#{materialName}),"%")
+        </if>
+        <if test="materialCode!=null and materialCode!=''">
+            AND lo.material_code LIKE concat(concat("%",#{materialCode}),"%")
+        </if>
+
+        UNION ALL
+
+        -- 2. sap库存物料(IN materialCodes)
+        SELECT
+        sap.factory_id AS factoryId,
+        0 AS costCenterId,
+        sap.storage_location_id AS storageLocationId,
+        sap.material_code AS materialCode,
+        sap.material_name AS materialName,
+        sap.unit_price AS unitPrice,
+        sap.quantity AS totalInventoryQuantity,
+        sap.unit AS unit,
+        'sap库存' AS materialSource,
+        2 AS sortOrder -- 添加排序字段
+        FROM
+        rq_iot_sap_stock sap
+        WHERE
+        1=1
+        <if test="materialCodes != null and materialCodes.size > 0">
+            AND sap.material_code IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="factoryIds != null and factoryIds.size > 0">
+            AND sap.factory_id IN
+            <foreach collection="factoryIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="stockLocationIds != null and stockLocationIds.size > 0">
+            AND sap.storage_location_id IN
+            <foreach collection="stockLocationIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="materialName!=null and materialName!=''">
+            AND sap.material_name LIKE concat(concat("%",#{materialName}),"%")
+        </if>
+        <if test="materialCode!=null and materialCode!=''">
+            AND sap.material_code LIKE concat(concat("%",#{materialCode}),"%")
+        </if>
+
+        UNION ALL
+        -- 3. sap主数据(IN materialCodes)
+        SELECT
+        0 AS factoryId,
+        0 AS costCenterId,
+        0 AS storageLocationId,
+        m.`code` AS materialCode,
+        m.`name` AS materialName,
+        m.unit_price AS unitPrice,
+        0 AS totalInventoryQuantity,
+        m.unit AS unit,
+        'sap主数据' AS materialSource,
+        3 AS sortOrder -- 添加排序字段
+        FROM
+        rq_iot_material m
+        WHERE
+        1=1
+        <if test="materialCodes != null and materialCodes.size > 0">
+            AND m.`code` IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="materialName!=null and materialName!=''">
+            AND m.`name` LIKE concat(concat("%",#{materialName}),"%")
+        </if>
+        <if test="materialCode!=null and materialCode!=''">
+            AND m.`code` LIKE concat(concat("%",#{materialCode}),"%")
+        </if>
+
+        <if test="materialCodes != null and materialCodes.size > 0">
+            UNION ALL
+            -- 4. 本地库存物料(NOT IN materialCodes)
+            SELECT
+            lo.factory_id AS factoryId,
+            lo.cost_center_id AS costCenterId,
+            lo.storage_location_id AS storageLocationId,
+            lo.material_code AS materialCode,
+            lo.material_name AS materialName,
+            lo.unit_price AS unitPrice,
+            lo.quantity AS totalInventoryQuantity,
+            lo.unit AS unit,
+            '本地库存' AS materialSource,
+            4 AS sortOrder -- 添加排序字段
+            FROM
+            rq_iot_lock_stock lo
+            WHERE
+            1=1
+            AND lo.material_code NOT IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+            <if test="factoryIds != null and factoryIds.size > 0">
+                AND lo.factory_id IN
+                <foreach collection="factoryIds" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+            <if test="costCenterIds != null and costCenterIds.size > 0">
+                AND lo.cost_center_id IN
+                <foreach collection="costCenterIds" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+            <if test="materialName!=null and materialName!=''">
+                AND lo.material_name LIKE concat(concat("%",#{materialName}),"%")
+            </if>
+            <if test="materialCode!=null and materialCode!=''">
+                AND lo.material_code LIKE concat(concat("%",#{materialCode}),"%")
+            </if>
+
+            UNION ALL
+            -- 5. sap库存物料(NOT IN materialCodes)
+            SELECT
+            sap.factory_id AS factoryId,
+            0 AS costCenterId,
+            sap.storage_location_id AS storageLocationId,
+            sap.material_code AS materialCode,
+            sap.material_name AS materialName,
+            sap.unit_price AS unitPrice,
+            sap.quantity AS totalInventoryQuantity,
+            sap.unit AS unit,
+            'sap库存' AS materialSource,
+            5 AS sortOrder -- 添加排序字段
+            FROM
+            rq_iot_sap_stock sap
+            WHERE
+            1=1
+            AND sap.material_code NOT IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+            <if test="factoryIds != null and factoryIds.size > 0">
+                AND sap.factory_id IN
+                <foreach collection="factoryIds" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+            <if test="stockLocationIds != null and stockLocationIds.size > 0">
+                AND sap.storage_location_id IN
+                <foreach collection="stockLocationIds" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+            <if test="materialName!=null and materialName!=''">
+                AND sap.material_name LIKE concat(concat("%",#{materialName}),"%")
+            </if>
+            <if test="materialCode!=null and materialCode!=''">
+                AND sap.material_code LIKE concat(concat("%",#{materialCode}),"%")
+            </if>
+
+            UNION ALL
+            -- 6. sap主数据(NOT IN materialCodes)
+            SELECT
+            0 AS factoryId,
+            0 AS costCenterId,
+            0 AS storageLocationId,
+            m.`code` AS materialCode,
+            m.`name` AS materialName,
+            m.unit_price AS unitPrice,
+            0 AS totalInventoryQuantity,
+            m.unit AS unit,
+            'sap主数据' AS materialSource,
+            6 AS sortOrder -- 添加排序字段
+            FROM
+            rq_iot_material m
+            WHERE
+            1=1
+            AND m.`code` NOT IN
+            <foreach collection="materialCodes" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+            <if test="materialName!=null and materialName!=''">
+                AND m.`name` LIKE concat(concat("%",#{materialName}),"%")
+            </if>
+            <if test="materialCode!=null and materialCode!=''">
+                AND m.`code` LIKE concat(concat("%",#{materialCode}),"%")
+            </if>
+        </if>
+
+        ORDER BY sortOrder, materialCode -- 按排序字段和物料编码排序
     </select>
+
+
+
 </mapper>