Sfoglia il codice sorgente

pms 设备bom精简列表 设备保养距离列表

zhangcl 2 mesi fa
parent
commit
18600447e7

+ 3 - 3
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotdevicebom/IotDeviceBomController.java

@@ -88,9 +88,9 @@ public class IotDeviceBomController {
 
     @GetMapping(value = {"/list-all-simple", "/simple-list"})
     @Operation(summary = "获取 设备BOM树 精简信息列表", description = "只包含被开启的BOM树节点,主要用于前端的下拉选项")
-    public CommonResult<List<IotDeviceBomSimpleRespVO>> getSimpleDeviceBomList() {
-        List<IotDeviceBomDO> list = iotDeviceBomService.getDeviceBomList(
-                new IotDeviceBomListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
+    public CommonResult<List<IotDeviceBomSimpleRespVO>> getSimpleDeviceBomList(IotDeviceBomListReqVO reqVO) {
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        List<IotDeviceBomDO> list = iotDeviceBomService.getDeviceBomList(reqVO);
         return success(BeanUtils.toBean(list, IotDeviceBomSimpleRespVO.class));
     }
 

+ 34 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/IotMainWorkOrderController.java

@@ -15,11 +15,14 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainW
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.service.iotmainworkorder.IotMainWorkOrderService;
 import cn.iocoder.yudao.module.pms.service.iotmainworkorderbom.IotMainWorkOrderBomService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,7 +42,9 @@ import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 
 @Tag(name = "管理后台 - 保养工单")
 @RestController
@@ -53,6 +58,8 @@ public class IotMainWorkOrderController {
     private AdminUserApi adminUserApi;
     @Resource
     private IotMainWorkOrderBomService iotMainWorkOrderBomService;
+    @Resource
+    private DeptService deptService;
 
     @PostMapping("/create")
     @Operation(summary = "创建保养工单")
@@ -121,6 +128,14 @@ public class IotMainWorkOrderController {
         return success(new PageResult<>(buildMainWorkOrderList(pageResult.getList()), pageResult.getTotal()));
     }
 
+    @GetMapping("/deviceMainDistances")
+    @Operation(summary = "以设备为维度统计所有保养明细中最近的保养数据 里程/时间/自然日期")
+    @PreAuthorize("@ss.hasPermission('pms:iot-main-work-order:query')")
+    public CommonResult<PageResult<IotDeviceRespVO>> deviceMainDistances(@Valid IotMainWorkOrderPageReqVO pageReqVO) {
+        PageResult<IotDeviceRespVO> pageResult = iotMainWorkOrderService.deviceMainDistances(pageReqVO);
+        return success(new PageResult<>(buildDeviceDistanceList(pageResult.getList()), pageResult.getTotal()));
+    }
+
     @GetMapping("/deviceOrderPage")
     @Operation(summary = "根据设备id获得保养工单分页")
     @PreAuthorize("@ss.hasPermission('pms:iot-main-work-order:query')")
@@ -138,6 +153,25 @@ public class IotMainWorkOrderController {
         return success(CollectionUtil.isEmpty(workOrderCount) ? CollectionUtil.newArrayList() : workOrderCount);
     }
 
+    /**
+     * 组装 设备-bom 关联关系对象
+     * @param devices
+     * @return
+     */
+    private List<IotDeviceRespVO> buildDeviceDistanceList(List<IotDeviceRespVO> devices) {
+        if (CollUtil.isEmpty(devices)) {
+            return Collections.emptyList();
+        }
+        // 设备部门信息
+        Map<Long, DeptDO> deptMap = deptService.getDeptMap(
+                convertList(devices, IotDeviceRespVO::getDeptId));
+        // 2. 拼接数据
+        return BeanUtils.toBean(devices, IotDeviceRespVO.class, (deviceVO) -> {
+            // 2.1 拼接部门信息
+            findAndThen(deptMap, deviceVO.getDeptId(), dept -> deviceVO.setDeptName(dept.getName()));
+        });
+    }
+
     private List<IotMainWorkOrderRespVO> buildMainWorkOrderList(List<IotMainWorkOrderDO> orders) {
         if (CollUtil.isEmpty(orders)) {
             return Collections.emptyList();

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/vo/IotDeviceRespVO.java

@@ -174,4 +174,7 @@ public class IotDeviceRespVO {
     private String responsibleNames;
     @Schema(description = "排序字段")
     private Integer sortColumn;
+
+    @Schema(description = "距离保养时间 单位可能是 H D KM")
+    private String mainDistance;
 }

+ 3 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/IotDeviceMapper.java

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDevicePageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
@@ -169,4 +168,7 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
 
     @Update("UPDATE rq_iot_device SET bom_sync_status = 1 WHERE id = #{id}")
     void updateBomSyncStatus(@Param("id") Long id);
+
+
+    IPage<IotDeviceRespVO> deviceDistances(IPage<IotDeviceRespVO> page, @Param("sortedDeviceIds") Collection<Long> sortedDeviceIds);
 }

+ 9 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderService.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainW
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
+import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 
 import javax.validation.Valid;
@@ -56,6 +57,14 @@ public interface IotMainWorkOrderService {
      */
     PageResult<IotMainWorkOrderDO> getIotMainWorkOrderPage(IotMainWorkOrderPageReqVO pageReqVO);
 
+    /**
+     * 以设备为维度统计所有保养明细中最近的保养数据 里程/时间/自然日期
+     *
+     * @param pageReqVO 分页查询
+     * @return 保养工单分页
+     */
+    PageResult<IotDeviceRespVO> deviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO);
+
     /**
      * 查询工单表中最大的id
      *

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

@@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaintenancebom.IotMaintenan
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWorkOrderBomDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotlockstock.IotLockStockMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmaintenancebom.IotMaintenanceBomMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorder.IotMainWorkOrderMapper;
@@ -43,8 +44,10 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -78,6 +81,8 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
     private IotMaintenanceBomService iotMaintenanceBomService;
     @Autowired
     private IotMaintenanceBomMapper iotMaintenanceBomMapper;
+    @Autowired
+    private IotDeviceMapper iotDeviceMapper;
 
     @Override
     public Long createIotMainWorkOrder(IotMainWorkOrderSaveReqVO createReqVO) {
@@ -124,6 +129,277 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         return iotMainWorkOrderMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public PageResult<IotDeviceRespVO> deviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO) {
+        // 所有保养计划 + 保养工单 明细中待保养的最近距离 里程/时间/自然日
+        // 查询所有保养工单明细中所有设备的累计运行里程 累计运行时间 如果保养项有设置 里程/时间规则 可以计算保养距离
+        IotMainWorkOrderBomPageReqVO bomReqVO = new IotMainWorkOrderBomPageReqVO();
+        bomReqVO.setDeviceIds(null);
+        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.selectList(bomReqVO);
+        Set<Long> deviceIds = CollUtil.isEmpty(workOrderBomS)
+                ? Collections.emptySet()
+                : workOrderBomS.stream()
+                .map(IotMainWorkOrderBomDO::getDeviceId) // 获取 deviceId
+                .filter(Objects::nonNull)      // 过滤非空值
+                .collect(Collectors.toSet());  // 收集到 Set 中
+        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+        // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
+        Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
+        // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
+        Map<Long, String> resultMap = new HashMap<>();
+        // 计算出每个保养工单明细的不同保养规则的保养距离最小值
+        if (CollUtil.isNotEmpty(workOrderBomS)) {
+            workOrderBomS.forEach(bom -> {
+                BigDecimal runningTimeDistance = null;
+                BigDecimal runningKiloDistance = null;
+                BigDecimal naturalDateDistance = null;
+                // 计算每个保养项 每个保养规则下的 距离保养时间
+                if (0 == bom.getRunningTimeRule()) {
+                    // 运行时间保养规则
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        BigDecimal totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
+                        BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
+                        BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
+                        BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
+                        runningTimeDistance = (lastRunningTime.add(runningTimePeriod).subtract(timePeriodLead)).subtract(totalRunTime);
+                    }
+                }
+                if (0 == bom.getMileageRule()) {
+                    // 运行里程保养规则
+                    // 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        BigDecimal totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
+                        BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
+                        BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
+                        BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
+                        runningKiloDistance = (lastRunningKilo.add(runningKiloPeriod).subtract(kiloCycleLead)).subtract(totalMileage);
+                    }
+                }
+                if (0 == bom.getNaturalDateRule()) {
+                    // 自然日期保养规则
+                    LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
+                    BigDecimal naturalDatePeriod = bom.getNextNaturalDate();        // 自然日周期
+                    BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
+                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
+                        // 计算有效天数 = 自然日周期 - 提前量
+                        long days = naturalDatePeriod.subtract(natualDateLead).longValue(); // 转为长整型天数
+                        // 计算目标日期:上次保养日期 + 有效天数(注意:LocalDateTime加天数会自动处理日期进位)
+                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
+                        // 获取当前日期时间
+                        LocalDateTime now = LocalDateTime.now();
+                        // 计算日期差值(以天为单位)
+                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(targetDate, now));
+                    }
+                }
+                // 找出距离0最近的数,作为工单的最近保养距离数据
+                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
+                Map<String, Object> tempDistance = new HashMap<>();
+                if (closet == runningTimeDistance) {
+                    tempDistance.put("H", closet);
+                } else if (closet == runningKiloDistance) {
+                    tempDistance.put("KM", closet);
+                } else if (closet == naturalDateDistance) {
+                    tempDistance.put("D", closet);
+                }
+                if (orderDistancePair.containsKey(bom.getDeviceId())) {
+                    List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getDeviceId());
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                } else {
+                    List<Map<String, Object>> tempDistances = new ArrayList<>();
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                }
+            });
+            // 以设备id 为维度 统计每个保养工单明细中 距离最近的保养数据
+            resultMap = findClosestToZero(orderDistancePair);
+            // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
+            // 排序后输出一个 List<Long> 类型的集合,排序对应上面的排序规则
+            List<Long> sortedDeviceIds = sortByNumericValue(resultMap);
+            try {
+                IPage<IotDeviceRespVO> page = iotDeviceMapper.deviceDistances(
+                        new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), sortedDeviceIds);
+                // 处理当前分页数据 拼接上已经排序的 筛选出的设备保养项 最小保养距离
+                if (CollUtil.isNotEmpty(page.getRecords())) {
+                    for (IotDeviceRespVO device : page.getRecords()) {
+                        if (resultMap.containsKey(device.getId())) {
+                            device.setMainDistance(resultMap.get(device.getId()));
+                        }
+                    }
+                }
+                return new PageResult<>(page.getRecords(), page.getTotal());
+            } catch (Exception exception) {
+                if (exception.getMessage().contains("Table does not exist")) {
+                    return PageResult.empty();
+                }
+                throw exception;
+            }
+        }
+        return PageResult.empty();
+    }
+
+    /**
+     * 排序 Map<Long, String> 中的元素 删除 类似 D、 H、 KM 后进行升序排列
+     * @param map
+     * @return
+     */
+    private List<Long> sortByNumericValue(Map<Long, String> map) {
+        return map.entrySet().stream()
+                // 转换数值并保留原始entry
+                .map(entry -> {
+                    try {
+                        // 移除所有字母后缀并转换为double
+                        String cleanValue = entry.getValue()
+                                .replaceAll("[A-Za-z]+$", "");
+                        double numericValue = Double.parseDouble(cleanValue);
+                        return new AbstractMap.SimpleEntry<>(entry.getKey(), numericValue);
+                    } catch (NumberFormatException e) {
+                        // 数值转换失败处理(返回最大值确保排在最后)
+                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
+                    }
+                })
+                // 双级排序:先按数值升序,数值相同再按key升序
+                // 显式指定泛型类型
+                .sorted(Comparator
+                        .<AbstractMap.SimpleEntry<Long, Double>>comparingDouble(AbstractMap.SimpleEntry::getValue)
+                        .thenComparingLong(AbstractMap.SimpleEntry::getKey)
+                )
+                // 提取排序后的key
+                .map(AbstractMap.SimpleEntry::getKey)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 找出每个设备对应的bom保养项集合中 距离保养最短的 数据
+     * @param orderDistancePair
+     */
+    private Map<Long, String> findClosestToZero(Map<Long, List<Map<String, Object>>> orderDistancePair) {
+        Map<Long, String> result = new HashMap<>();
+        for (Map.Entry<Long, List<Map<String, Object>>> entry : orderDistancePair.entrySet()) {
+            Long key = entry.getKey();
+            List<Map<String, Object>> mapList = entry.getValue();
+
+            // 初始化最小距离和对应的Map
+            BigDecimal minAbsDistance = null;
+            Map<String, Object> closestMap = null;
+
+            for (Map<String, Object> currentMap : mapList) {
+                // 计算当前Map所有值的最小绝对值
+                BigDecimal currentMinAbs = calculateMinAbsolute(currentMap);
+
+                // 跳过无效Map(无有效值)
+                if (currentMinAbs == null) continue;
+
+                // 比较并更新最小值
+                if (minAbsDistance == null || currentMinAbs.compareTo(minAbsDistance) < 0) {
+                    minAbsDistance = currentMinAbs;
+                    closestMap = currentMap;
+                }
+            }
+            // 将有效结果放入最终Map
+            if (closestMap != null) {
+                result.put(key, convertMapToString(closestMap));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 将Map转换为String格式:value1 key1; value2 key2
+     */
+    private String convertMapToString(Map<String, Object> map) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            if (sb.length() > 0) {
+                sb.append(";");  // 添加分隔符
+            }
+            String key = entry.getKey();
+            Object value = entry.getValue();
+
+            // 处理null值
+            String valueStr = (value != null) ? value.toString() : "null";
+
+            // 转义特殊字符(如有需要)
+            sb.append(valueStr).append(" ").append(key);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 计算单个Map中所有BigDecimal值的最小绝对值
+     * @param map
+     * @return
+     */
+    private BigDecimal calculateMinAbsolute(Map<String, Object> map) {
+        BigDecimal minAbs = null;
+
+        for (Object value : map.values()) {
+            if (!(value instanceof BigDecimal)) continue;
+
+            BigDecimal decimalValue = (BigDecimal) value;
+            BigDecimal absValue = decimalValue.abs();
+
+            if (minAbs == null || absValue.compareTo(minAbs) < 0) {
+                minAbs = absValue;
+            }
+        }
+        return minAbs; // 可能为null(无BigDecimal值)
+    }
+
+    /**
+     * 筛选出3个保养规则数据的最小值
+     * @param runningTimeDistance
+     * @param runningKiloDistance
+     * @param naturalDateDistance
+     * @return
+     */
+    private BigDecimal chooseNearestDistance(BigDecimal runningTimeDistance, BigDecimal runningKiloDistance, BigDecimal naturalDateDistance) {
+        // 存储当前找到的最小绝对值的变量
+        BigDecimal closest = null;
+        // 存储当前最小绝对值(BigDecimal 类型)
+        BigDecimal minAbs = null;
+        // 比较第一个变量
+        if (runningTimeDistance != null) {
+            closest = runningTimeDistance;
+            minAbs = runningTimeDistance.abs();
+        }
+        // 比较第二个变量
+        if (runningKiloDistance != null) {
+            if (minAbs == null) {
+                // 如果之前没有有效值,直接使用 b
+                closest = runningKiloDistance;
+                minAbs = runningKiloDistance.abs();
+            } else {
+                // 比较绝对值大小
+                BigDecimal absB = runningKiloDistance.abs();
+                int cmp = absB.compareTo(minAbs);
+                if (cmp < 0 || (cmp == 0 && runningKiloDistance.compareTo(closest) > 0)) {
+                    // 如果 b 的绝对值更小,或绝对值相等但 b 是正数(更大值表示正数)
+                    closest = runningKiloDistance;
+                    minAbs = absB;
+                }
+            }
+        }
+        // 比较第三个变量
+        if (naturalDateDistance != null) {
+            if (minAbs == null) {
+                // 如果之前没有有效值,直接使用 c
+                closest = naturalDateDistance;
+                minAbs = naturalDateDistance.abs();
+            } else {
+                // 比较绝对值大小
+                BigDecimal absC = naturalDateDistance.abs();
+                int cmp = absC.compareTo(minAbs);
+                if (cmp < 0 || (cmp == 0 && naturalDateDistance.compareTo(closest) > 0)) {
+                    // 如果 c 的绝对值更小,或绝对值相等但 c 是正数
+                    closest = naturalDateDistance;
+                    minAbs = absC;
+                }
+            }
+        }
+        return closest; // 可能返回 null(当所有输入均为 null 时)
+    }
+
     @Override
     public Long theMaxId() {
         List<IotMainWorkOrderDO> workOrderDOS = iotMainWorkOrderMapper.theMaxOne();

+ 30 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotDeviceMapper.xml

@@ -47,6 +47,36 @@
         </if>
     </select>
 
+    <select id="deviceDistances"
+            resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">
+        SELECT
+        rid.id,
+        rid.dept_id	deptId,
+        rid.device_code deviceCode,
+        rid.device_name deviceName,
+        rid.device_status deviceStatus,
+        rid.asset_property assetProperty
+        FROM rq_iot_main_work_order_bom mwoo
+        LEFT JOIN rq_iot_device rid ON rid.id = mwoo.device_id
+        WHERE mwoo.deleted = 0
+        AND mwoo.`status` = 0
+        AND rid.device_code IS NOT NULL
+        <if test="sortedDeviceIds != null and sortedDeviceIds.size &gt; 0">
+            AND rid.id IN
+            <foreach collection="sortedDeviceIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        GROUP BY mwoo.device_id
+        ORDER BY FIELD(rid.id,
+        <if test="sortedDeviceIds != null and sortedDeviceIds.size &gt; 0">
+            <foreach collection="sortedDeviceIds" index="index" item="key" separator=",">
+                #{key}
+            </foreach>
+        </if>
+        )
+    </select>
+
     <select id="personRelationDevices"
             resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">
         SELECT