|
@@ -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();
|