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;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.pms.controller.admin.iotattachment.vo.IotAttachmentSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotlockstock.vo.IotLockStockPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmaintenancebom.vo.IotMaintenanceBomPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmaintenancebom.vo.IotMaintenanceBomRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.*;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialSaveReqVO;
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;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotattachment.IotAttachmentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotlockstock.IotLockStockDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaintenancebom.IotMaintenanceBomDO;
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.dataobject.iotoutbound.IotOutboundDO;
import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotattachment.IotAttachmentMapper;
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;
import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbom.IotMainWorkOrderBomMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotoutbound.IotOutboundMapper;
import cn.iocoder.yudao.module.pms.dal.redis.BizNoRedisDAO;
import cn.iocoder.yudao.module.pms.enums.AttachmentCategoryEnum;
import cn.iocoder.yudao.module.pms.enums.AttachmentTypeEnum;
import cn.iocoder.yudao.module.pms.service.IotDeviceService;
import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
import cn.iocoder.yudao.module.pms.service.iotmaintenancebom.IotMaintenanceBomService;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
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.module.pms.enums.ErrorCodeConstant.IOT_MAIN_WORK_ORDER_EXECUTED;
import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant.IOT_MAIN_WORK_ORDER_NOT_EXISTS;

/**
 * 保养工单 Service 实现类
 *
 * @author ruiqi
 */
@Service
@Validated
public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {

    @Resource
    private IotMainWorkOrderMapper iotMainWorkOrderMapper;
    @Resource
    private BizNoRedisDAO bizNoRedisDAO;
    @Resource
    private IotMainWorkOrderBomMapper iotMainWorkOrderBomMapper;
    @Resource
    private IotMainWorkOrderBomMaterialMapper iotMainWorkOrderBomMaterialMapper;
    @Resource
    private IotDeviceService iotDeviceService;
    @Resource
    private IotLockStockMapper iotLockStockMapper;
    @Autowired
    private IotDeviceRunLogService iotDeviceRunLogService;
    @Autowired
    private IotMaintenanceBomService iotMaintenanceBomService;
    @Autowired
    private IotMaintenanceBomMapper iotMaintenanceBomMapper;
    @Autowired
    private IotDeviceMapper iotDeviceMapper;
    @Resource
    private DeptService deptService;
    @Resource
    private IotOutboundMapper iotOutboundMapper;
    @Resource
    private DeptMapper deptMapper;
    @Resource
    private IotAttachmentMapper iotAttachmentMapper;

    @Override
    public Long createIotMainWorkOrder(IotMainWorkOrderSaveReqVO createReqVO) {
        // 插入
        IotMainWorkOrderDO iotMainWorkOrder = BeanUtils.toBean(createReqVO, IotMainWorkOrderDO.class);
        iotMainWorkOrderMapper.insert(iotMainWorkOrder);
        // 返回
        return iotMainWorkOrder.getId();
    }

    @Override
    public void updateIotMainWorkOrder(IotMainWorkOrderSaveReqVO updateReqVO) {
        // 校验存在
        IotMainWorkOrderDO workOrder = validateIotMainWorkOrderExists(updateReqVO.getId());
        // IotMainWorkOrderDO updateObj = BeanUtils.toBean(updateReqVO, IotMainWorkOrderDO.class);
        if (StrUtil.isNotBlank(updateReqVO.getDelayReason())) {
            // 更新 保养工单的 延时原因
            workOrder.setDelayReason(updateReqVO.getDelayReason());
        }
        if (StrUtil.isNotBlank(updateReqVO.getBackFlag())) {
            // 设置工单 status = 1
            workOrder.setStatus(1);
        }
        iotMainWorkOrderMapper.updateById(workOrder);
    }

    @Override
    public void deleteIotMainWorkOrder(Long id) {
        // 校验存在
        validateIotMainWorkOrderExists(id);
        // 删除
        iotMainWorkOrderMapper.deleteById(id);
    }

    private IotMainWorkOrderDO validateIotMainWorkOrderExists(Long id) {
        IotMainWorkOrderDO workOrder = iotMainWorkOrderMapper.selectById(id);
        if (ObjUtil.isEmpty(workOrder)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        return workOrder;
    }

    @Override
    public IotMainWorkOrderDO getIotMainWorkOrder(Long id) {
        IotMainWorkOrderDO workOrder = iotMainWorkOrderMapper.selectById(id);
        if (ObjUtil.isEmpty(workOrder)) {
            throw exception(IOT_MAIN_WORK_ORDER_EXECUTED);
        }
        return workOrder;
    }

    @Override
    public PageResult<IotMainWorkOrderDO> getIotMainWorkOrderPage(IotMainWorkOrderPageReqVO pageReqVO) {
        if (ObjUtil.isNotEmpty(pageReqVO.getResult()) && 0 == pageReqVO.getResult()) {
            pageReqVO.setResult(null);
        }
        return iotMainWorkOrderMapper.selectPage(pageReqVO);
    }

    @Override
    public List<IotMainWorkOrderDO> workOrders(IotMainWorkOrderPageReqVO pageReqVO) {
        List<IotMainWorkOrderDO> orders= iotMainWorkOrderMapper.selectList(pageReqVO);
        return CollUtil.isNotEmpty(orders) ? orders : new ArrayList<>();
    }

    @Override
    public PageResult<IotMainWorkOrderRespVO> sortedMainWorkOrderPage(IotMainWorkOrderPageReqVO pageReqVO) {
        // 根据 设备编码 新旧油服号 搜索
        // 如果 pageReqVO.getName() 有值 先根据 name 值查询 新旧油服号都匹配的保养工单
        List<Long> eligibleDeviceIds = new ArrayList<>();
        List<Long> eligibleWorkOrderIds = new ArrayList<>();
        if (StrUtil.isNotBlank(pageReqVO.getName())) {
            IotDevicePageReqVO reqVO = new IotDevicePageReqVO();
            reqVO.setDeviceName(pageReqVO.getName());
            List<IotDeviceDO> eligibleDevices = iotDeviceMapper.selectListAlone(reqVO);
            if (CollUtil.isNotEmpty(eligibleDevices)) {
                eligibleDeviceIds = convertList(eligibleDevices, IotDeviceDO::getId);
                // 如果有相关的设备 查询对应的保养工单
                if (CollUtil.isNotEmpty(eligibleDeviceIds)) {
                    IotMainWorkOrderBomPageReqVO bomReqVO = new IotMainWorkOrderBomPageReqVO();
                    bomReqVO.setDeviceIds(eligibleDeviceIds);
                    List<IotMainWorkOrderBomDO> boms = iotMainWorkOrderBomMapper.selectList(bomReqVO);
                    if (CollUtil.isNotEmpty(boms)) {
                        Set<Long> workOrderIds = new HashSet<>();
                        boms.forEach(bom -> {
                            workOrderIds.add(bom.getWorkOrderId());
                        });
                        eligibleWorkOrderIds.addAll(workOrderIds);
                    }
                }
            }
            // 根据保养工单名称搜索
            IotMainWorkOrderPageReqVO orderReqVO = new IotMainWorkOrderPageReqVO();
            orderReqVO.setName(pageReqVO.getName());
            List<IotMainWorkOrderDO> likeNameWorkOrders = iotMainWorkOrderMapper.selectList(orderReqVO);
            if (CollUtil.isNotEmpty(likeNameWorkOrders)) {
                likeNameWorkOrders.forEach(order -> {
                    if (!eligibleWorkOrderIds.contains(order.getId())) {
                        eligibleWorkOrderIds.add(order.getId());
                    }
                });
            }
            if (CollUtil.isEmpty(eligibleWorkOrderIds)) {
                return PageResult.empty();
            }
        }
        // 查询 未执行 计划生成 的工单 计算距离保养使用
        IotMainWorkOrderPageReqVO reqVO = new IotMainWorkOrderPageReqVO();
        reqVO.setResult(1);
        reqVO.setType(1);
        List<Long> workOrderIds = new ArrayList<>();
        List<IotMainWorkOrderDO> workOrders = iotMainWorkOrderMapper.selectList(reqVO);
        if (CollUtil.isNotEmpty(workOrders)) {
            workOrders.forEach(order -> {
                workOrderIds.add(order.getId());
            });
        }
        List<IotMainWorkOrderBomDO> workOrderBomS = new ArrayList<>();
        IotMainWorkOrderBomPageReqVO bomReqVO = new IotMainWorkOrderBomPageReqVO();
        if (CollUtil.isNotEmpty(workOrderIds)) {
            bomReqVO.setWorkOrderIds(workOrderIds);
            if (CollUtil.isNotEmpty(eligibleDeviceIds)) {
                bomReqVO.setDeviceIds(eligibleDeviceIds);
            }
            // 查询 未执行的计划生成的工单 的保养项明细
            workOrderBomS= iotMainWorkOrderBomMapper.selectList(bomReqVO);
        }
        // 保养工单保养项明细中已经绑定的 多累计属性 名称
        Set<String> boundedMultiAttrNames = new HashSet<>();
        // 工单保养项中包含的设备id集合
        Set<Long> deviceIds = new HashSet<>();
        Set<Long> deviceCategoryIds = new HashSet<>();
        Map<Long, Long> deviceCategoryPair = new HashMap<>();
        if (CollUtil.isNotEmpty(workOrderBomS)) {
            // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
            workOrderBomS.forEach(bom -> {
                if (StrUtil.isNotBlank(bom.getType())) {
                    // 累计公里数属性
                    boundedMultiAttrNames.add(bom.getType());
                }
                if (StrUtil.isNotBlank(bom.getCode())) {
                    // 累计时长属性
                    boundedMultiAttrNames.add(bom.getCode());
                }
                if (ObjUtil.isNotEmpty(bom.getDeviceId())) {
                    deviceIds.add(bom.getDeviceId());
                }
            });
            // 组装bom关联的设备信息
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(workOrderBomS,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            }
        }

        // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
        List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
            multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
        }
        // key(设备id-累计时长属性名称)    value时长属性累计时长数值
        Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
        if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
            multipleAccumulatedData.forEach(data -> {
                String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
            });
        }
        // 查询 运行记录模板中 正常的累计时长 公里数集合
        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = new HashMap<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(deviceCategoryIds)) {
            deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds),
                    new ArrayList<>(deviceCategoryIds), deviceCategoryPair, null);
        }
        // 以保养工单为维度 统计每个工单相关的保养项的最近保养距离 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)) {
            for (IotMainWorkOrderBomDO bom : workOrderBomS) {
                BigDecimal runningTimeDistance = null;
                BigDecimal runningKiloDistance = null;
                BigDecimal naturalDateDistance = null;
                // 计算每个保养项 每个保养规则下的 距离保养时间
                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                    // 运行时间保养规则
                    BigDecimal totalRunTime = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                    BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                    runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
                }
                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                    // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                    BigDecimal totalMileage = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalMileage = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                    BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                    runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
                }
                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 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.longValue(); // 转为长整型天数
                        // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                        // 获取当前日期时间
                        LocalDateTime now = LocalDateTime.now();
                        // 计算日期差值（以天为单位）
                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                    }
                }
                // 找出距离0最近的数，作为工单的最近保养距离数据
                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
                // key距离单位(小时 公里 天)   value最小的距离值
                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.getWorkOrderId())) {
                    List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getWorkOrderId());
                    tempDistances.add(tempDistance);
                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
                } else {
                    List<Map<String, Object>> tempDistances = new ArrayList<>();
                    tempDistances.add(tempDistance);
                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
                }
            }
        }
        try {
            // 以 保养工单id 为维度 统计每个保养工单明细中 距离最近的保养数据
            resultMap = findClosestToZero(orderDistancePair);
            // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
            // 排序后输出一个 List<Long> 类型的集合，排序对应上面的排序规则 集合中的元素是 保养工单id
            List<Long> sortedWorkOrderIds = sortByNumericValue(resultMap);
            // 查询选择部门下面所有子部门
            Set<Long> ids = new HashSet<>();
            if (Objects.nonNull(pageReqVO.getDeptId())) {
                ids = deptService.getChildDeptIdListFromCache(pageReqVO.getDeptId());
                ids.add(pageReqVO.getDeptId());
            }
            // 查询所有设备列表 通过SQL形式 使用 FIELD 字段
            IPage<IotMainWorkOrderRespVO> page = iotMainWorkOrderMapper.sortedMainWorkOrders(
                    new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO, sortedWorkOrderIds, ids, eligibleWorkOrderIds);
            if (CollUtil.isNotEmpty(page.getRecords())) {
                Map<Long, String> finalResultMap = resultMap;
                page.getRecords().forEach(order -> {
                    if (finalResultMap.containsKey(order.getId())) {
                        order.setMainDistance(finalResultMap.get(order.getId()));
                    }
                });
            }
            return new PageResult<>(page.getRecords(), page.getTotal());
        } catch (Exception exception) {
            if (exception.getMessage().contains("Table does not exist")) {
                return PageResult.empty();
            }
            throw exception;
        }
    }

    @Override
    public PageResult<IotDeviceRespVO> deviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO) {
        // 左侧组织树 组织层级查询
        Set<Long> ids = new HashSet<>();
        if (ObjUtil.isEmpty(pageReqVO.getDeptId())) {
            // 如果没有传 deptId 查询当前登录人所属部门id
            Long loginUserDeptId = SecurityFrameworkUtils.getLoginUserDeptId();
            pageReqVO.setDeptId(loginUserDeptId);
        }
        Long deptId = pageReqVO.getDeptId();
        ids = deptService.getChildDeptIdListFromCache(deptId);
        ids.add(deptId);
        if (CollUtil.isEmpty(ids)) {
            return PageResult.empty();
        }
        // 查询条件 device_code 或 device_name
        Set<Long> queryDeviceIds = new HashSet<>();
        String deviceCode = pageReqVO.getDeviceCode();
        String deviceName = pageReqVO.getDeviceName();

        Set<Long> deviceCategoryIds = new HashSet<>();
        Map<Long, Long> deviceCategoryPair = new HashMap<>();

        // 需要根据查询条件中的设备查询对应的保养计划明细 运行记录累计值
        IotDevicePageReqVO reqVO = new IotDevicePageReqVO();
        if (StrUtil.isNotBlank(deviceCode)) {
            reqVO.setDeviceCode(deviceCode);
        }
        if (StrUtil.isNotBlank(deviceName)) {
            reqVO.setDeviceName(deviceName);
        }
        reqVO.setDeptIds(new ArrayList<>(ids));
        List<IotDeviceDO> qualifiedDevices = iotDeviceMapper.selectList(reqVO);
        if (CollUtil.isNotEmpty(qualifiedDevices)) {
            qualifiedDevices.forEach(device -> {
                queryDeviceIds.add(device.getId());
                // 设置设备类别
                deviceCategoryIds.add(device.getAssetClass());
                deviceCategoryPair.put(device.getId(), device.getAssetClass());
            });
        }

        // 20250624 只查询保养计划中的设备 不查询保养工单表
        // 所有保养计划 + 保养工单 明细中待保养的最近距离 里程/时间/自然日
        // 查询所有保养工单明细中所有设备的累计运行里程 累计运行时间 如果保养项有设置 里程/时间规则 可以计算保养距离
        Set<Long> deviceIds = new HashSet<>();  // 收集到 Set 中
        // 查询保养计划明细中不包含 deviceIds 的设备id集合
        Set<Long> mainBomDeviceIds = new HashSet<>();
        IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
        if (CollUtil.isNotEmpty(queryDeviceIds)) {
            mainBomReqVO.setDeviceIds(new ArrayList<>(queryDeviceIds));
        }
        List<IotMaintenanceBomDO> mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
        Set<String> boundedMultiAttrNames = new HashSet<>();

        if (CollUtil.isNotEmpty(mainBomS)) {
            mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
                    ? Collections.emptySet()
                    : mainBomS.stream()
                    .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
                    .filter(Objects::nonNull)      // 过滤非空值
                    .collect(Collectors.toSet());  // 收集到 Set 中
            // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
            mainBomS.forEach(bom -> {
                if (StrUtil.isNotBlank(bom.getType())) {
                    // 累计公里数属性
                    boundedMultiAttrNames.add(bom.getType());
                }
                if (StrUtil.isNotBlank(bom.getCode())) {
                    // 累计时长属性
                    boundedMultiAttrNames.add(bom.getCode());
                }
            });
            // 查询所有设备类别id
            // 组装bom关联的设备信息
            /*
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            } */
        }
        deviceIds.addAll(mainBomDeviceIds);
        if (CollUtil.isEmpty(deviceIds)){
            // 没有设备信息，返回空列表
            return PageResult.empty();
        }
        // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
        List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
            // multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
        }
        List<IotDeviceRunLogRespVO> deviceAttrRunLogs = new ArrayList<>();
        if (CollUtil.isNotEmpty(deviceIds)) {
            deviceAttrRunLogs = iotDeviceRunLogService.deviceAttrRunLogsSearch(new ArrayList<>(deviceIds));
            if (CollUtil.isNotEmpty(deviceAttrRunLogs) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
                deviceAttrRunLogs.forEach(runLog -> {
                    if (boundedMultiAttrNames.contains(runLog.getPointName())) {
                        multipleAccumulatedData.add(runLog);
                    }
                });
            }
        }

        // key(设备id-累计时长属性名称)    value时长属性累计时长数值
        Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
        if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
            multipleAccumulatedData.forEach(data -> {
                String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
            });
        }

        // 查询 运行记录模板中 正常的累计时长 公里数集合
        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = new HashMap<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(deviceCategoryIds)) {
            deviceRunLogMap =
                    iotDeviceRunLogService.getDeviceRunLogMapSearch(new ArrayList<>(deviceIds), new ArrayList<>(deviceCategoryIds), deviceCategoryPair, deviceAttrRunLogs);
        }
        // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
        Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
        // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
        Map<Long, String> resultMap = new HashMap<>();
        // 设置每个设备的 累计运转时长 累计运转公里数 保养查询列表使用
        Map<Long, BigDecimal> deviceRunningTimePair = new HashMap<>();
        Map<Long, BigDecimal> deviceRunningKiloPair = new HashMap<>();
        // 多属性累计运行时长
        Map<Long, Map<String, BigDecimal>> multiAttrRunningTimePair = new HashMap<>();
        // 多属性累计运行里程
        Map<Long, Map<String, BigDecimal>> multiAttrRunningKiloPair = new HashMap<>();
        // 记录保养项的 提前量 key设备id-保养项id-距离保养值   value提前量
        Map<String, BigDecimal> bomLeadPair = new HashMap<>();
        if (CollUtil.isNotEmpty(mainBomS)) {
            // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
            for (IotMaintenanceBomDO bom : mainBomS) {
                BigDecimal runningTimeDistance = null;
                BigDecimal runningKiloDistance = null;
                BigDecimal naturalDateDistance = null;
                // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                    // 运行时间保养规则
                    BigDecimal totalRunTime = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                        // 单一累计运行时长集合
                        deviceRunningTimePair.put(bom.getDeviceId(), totalRunTime);
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                            // 设置多属性累计运行时长集合
                            if (StrUtil.isNotBlank(bom.getCode())) {
                                if (multiAttrRunningTimePair.containsKey(bom.getDeviceId())) {
                                    Map<String, BigDecimal> tempRunningTimePair = multiAttrRunningTimePair.get(bom.getDeviceId());
                                    tempRunningTimePair.put(bom.getCode(), totalRunTime);
                                    multiAttrRunningTimePair.put(bom.getDeviceId(), tempRunningTimePair);
                                } else {
                                    Map<String, BigDecimal> tempRunningTimePair = new HashMap<>();
                                    tempRunningTimePair.put(bom.getCode(), totalRunTime);
                                    multiAttrRunningTimePair.put(bom.getDeviceId(), tempRunningTimePair);
                                }
                            }
                        }
                    }
                    BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                    BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                    BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
                    runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningTimeDistance);
                    bomLeadPair.put(uniqueKey, timePeriodLead);
                }
                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                    // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                    BigDecimal totalMileage = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                        // 单一累计运行里程集合
                        deviceRunningKiloPair.put(bom.getDeviceId(), totalMileage);
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalMileage = tempTotalRunDataPair.get(uniqueKey);
                            // 设置多属性累计运行里程集合
                            if (StrUtil.isNotBlank(bom.getType())) {
                                if (multiAttrRunningKiloPair.containsKey(bom.getDeviceId())) {
                                    Map<String, BigDecimal> tempRunningKiloPair = multiAttrRunningKiloPair.get(bom.getDeviceId());
                                    tempRunningKiloPair.put(bom.getType(), totalMileage);
                                    multiAttrRunningKiloPair.put(bom.getDeviceId(), tempRunningKiloPair);
                                } else {
                                    Map<String, BigDecimal> tempRunningKiloPair = new HashMap<>();
                                    tempRunningKiloPair.put(bom.getCode(), totalMileage);
                                    multiAttrRunningKiloPair.put(bom.getDeviceId(), tempRunningKiloPair);
                                }
                            }
                        }
                    }
                    BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                    BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                    BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
                    runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningKiloDistance);
                    bomLeadPair.put(uniqueKey, kiloCycleLead);
                }
                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 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.longValue(); // 转为长整型天数
                        // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                        // 获取当前日期时间
                        LocalDateTime now = LocalDateTime.now();
                        // 计算日期差值（以天为单位）
                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), naturalDateDistance);
                        bomLeadPair.put(uniqueKey, natualDateLead);
                    }
                }
                // 找出距离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);
        // 根据保养项规则应该生成保养工单的设备id集合
        Set<Long> shouldWorkOrderFlags = new HashSet<>();
        // 找出设备保养项距离保养最小值对应的 提前量
        if (CollUtil.isNotEmpty(resultMap)) {
            resultMap.forEach((k,v) -> {
                // k设备id    v距离保养最小值(带单位)
                // 去掉v中带的单位 310 D   -61.00 H
                String[] distanceStrs = v.split(" ");
                BigDecimal tempDistance = new BigDecimal(distanceStrs[0]);
                if (tempDistance.compareTo(BigDecimal.ZERO) < 0) {
                    shouldWorkOrderFlags.add(k);
                }
                if (CollUtil.isNotEmpty(bomLeadPair)) {
                    bomLeadPair.forEach((key,value) -> {
                        // key设备id-保养项id-距离保养值   value提前量
                        String[] uniqueKeyStrs = key.split("-");
                        if (uniqueKeyStrs.length == 3) {
                            if (String.valueOf(k).equals(uniqueKeyStrs[0]) && distanceStrs[0].equals(uniqueKeyStrs[2])
                                    && tempDistance.compareTo(BigDecimal.ZERO)>=0 && tempDistance.compareTo(value)<=0 )  {
                                shouldWorkOrderFlags.add(k);
                            }
                        }
                    });
                }
            });
        }
        // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
        // 排序后输出一个 List<Long> 类型的集合，排序对应上面的排序规则
        List<Long> sortedDeviceIds = sortByNumericValue(resultMap);
        try {
            List<IotDeviceRespVO> alarmDevices = iotDeviceMapper.deviceAlarmDistancesSearch(sortedDeviceIds, mainBomDeviceIds);

            // 处理当前分页数据 拼接上已经排序的 筛选出的设备保养项 最小保养距离
            List<Long> alarmDeviceIds = new ArrayList<>();
            Map<Long, IotDeviceRespVO> alarmDevicePair = new HashMap<>();
            if (CollUtil.isNotEmpty(alarmDevices)) {
                for (IotDeviceRespVO device : alarmDevices) {
                    if (resultMap.containsKey(device.getId())) {
                        device.setMainDistance(resultMap.get(device.getId()));
                        alarmDeviceIds.add(device.getId());
                    }
                    alarmDevicePair.put(device.getId(), device);
                }
            }
            // 查询所有设备列表 通过SQL形式 使用 FIELD 字段
            IPage<IotDeviceRespVO> page = iotDeviceMapper.allDeviceDistances(
                    new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO, alarmDeviceIds, ids);
            if (CollUtil.isNotEmpty(page.getRecords())) {
                page.getRecords().forEach(device -> {
                    if (alarmDevicePair.containsKey(device.getId())) {
                        device.setMainDistance(alarmDevicePair.get(device.getId()).getMainDistance());
                        device.setPlanId(alarmDevicePair.get(device.getId()).getPlanId());
                        device.setWorkOrderId(alarmDevicePair.get(device.getId()).getWorkOrderId());
                    }
                    if (shouldWorkOrderFlags.contains(device.getId())) {
                        device.setShouldWorkOrder(true);
                    }
                    // 设置每个设备 不同保养项 的 累计运转时间 累计运转公里数
                    if (deviceRunningTimePair.containsKey(device.getId())) {
                        device.setTotalRunTime(deviceRunningTimePair.get(device.getId()));
                    }
                    if (deviceRunningKiloPair.containsKey(device.getId())) {
                        device.setTotalMileage(deviceRunningKiloPair.get(device.getId()));
                    }
                    // 设置多属性累计值集合
                    if (multiAttrRunningTimePair.containsKey(device.getId())) {
                        device.setMultiAttrsTotalRuntime(multiAttrRunningTimePair.get(device.getId()));
                    }
                    if (multiAttrRunningKiloPair.containsKey(device.getId())) {
                        device.setMultiAttrsTotalMileage(multiAttrRunningKiloPair.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;
        }
    }

    @Override
    public List<IotDeviceRespVO> deviceMainTimeoutList(IotMainWorkOrderPageReqVO pageReqVO) {
        // 所有保养计划 + 保养工单 明细中待保养的最近距离 里程/时间/自然日
        List<IotMainWorkOrderBomDO> workOrderBomS = new ArrayList<>();
        Set<Long> deviceIds = CollUtil.isEmpty(workOrderBomS)
                ? new HashSet<>()
                : workOrderBomS.stream()
                .map(IotMainWorkOrderBomDO::getDeviceId) // 获取 deviceId
                .filter(Objects::nonNull)      // 过滤非空值
                .collect(Collectors.toSet());  // 收集到 Set 中
        Set<Long> orderDeviceIds = new HashSet<>();
        orderDeviceIds.addAll(deviceIds);
        // 查询保养计划明细中不包含 deviceIds 的设备id集合
        List<IotMaintenanceBomDO> mainBomS = new ArrayList<>();
        Set<Long> mainBomDeviceIds = new HashSet<>();
        IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
        if (CollUtil.isNotEmpty(deviceIds)) {
            mainBomReqVO.setDeviceIds(new ArrayList<>(deviceIds));
            mainBomS = iotMaintenanceBomMapper.selectAlarmList(mainBomReqVO);
        } else {
            mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
        }
        Set<String> boundedMultiAttrNames = new HashSet<>();
        Set<Long> deviceCategoryIds = new HashSet<>();
        Map<Long, Long> deviceCategoryPair = new HashMap<>();
        if (CollUtil.isNotEmpty(mainBomS)) {
            mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
                    ? Collections.emptySet()
                    : mainBomS.stream()
                    .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
                    .filter(Objects::nonNull)      // 过滤非空值
                    .collect(Collectors.toSet());  // 收集到 Set 中
            // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
            mainBomS.forEach(bom -> {
                if (StrUtil.isNotBlank(bom.getType())) {
                    // 累计公里数属性
                    boundedMultiAttrNames.add(bom.getType());
                }
                if (StrUtil.isNotBlank(bom.getCode())) {
                    // 累计时长属性
                    boundedMultiAttrNames.add(bom.getCode());
                }
            });
            // 查询所有设备类别id
            // 组装bom关联的设备信息
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            }
        }
        deviceIds.addAll(mainBomDeviceIds);
        if (CollUtil.isEmpty(deviceIds)){
            // 没有设备信息，返回空列表
            return new ArrayList<>();
        }
        // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
        List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
            multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
        }
        // key(设备id-累计时长属性名称)    value时长属性累计时长数值
        Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
        if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
            multipleAccumulatedData.forEach(data -> {
                String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
            });
        }

        // 查询 运行记录模板中 正常的累计时长 公里数集合
        // Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap =
                iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds), new ArrayList<>(deviceCategoryIds), deviceCategoryPair, null);
        // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
        Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
        // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
        Map<Long, String> resultMap = new HashMap<>();
        // 记录保养项的 提前量 key设备id-保养项id-距离保养值   value提前量
        Map<String, BigDecimal> bomLeadPair = new HashMap<>();
        if (CollUtil.isNotEmpty(mainBomS)) {
            // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
            for (IotMaintenanceBomDO bom : mainBomS) {
                BigDecimal runningTimeDistance = null;
                BigDecimal runningKiloDistance = null;
                BigDecimal naturalDateDistance = null;
                // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                    // 运行时间保养规则
                    BigDecimal totalRunTime = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                    BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                    BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
                    runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningTimeDistance);
                    bomLeadPair.put(uniqueKey, timePeriodLead);
                }
                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                    // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                    BigDecimal totalMileage = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalMileage = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                    BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                    BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
                    runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningKiloDistance);
                    bomLeadPair.put(uniqueKey, kiloCycleLead);
                }
                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 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.longValue(); // 转为长整型天数
                        // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                        // 获取当前日期时间
                        LocalDateTime now = LocalDateTime.now();
                        // 计算日期差值（以天为单位）
                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), naturalDateDistance);
                        bomLeadPair.put(uniqueKey, natualDateLead);
                    }
                }
                // 找出距离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);
        // 根据保养项规则应该生成保养工单的设备id集合
        Set<Long> shouldWorkOrderFlags = new HashSet<>();
        // 找出设备保养项距离保养最小值对应的 提前量
        if (CollUtil.isNotEmpty(resultMap)) {
            resultMap.forEach((k,v) -> {
                // k设备id    v距离保养最小值(带单位)
                // 去掉v中带的单位 310 D   -61.00 H
                String[] distanceStrs = v.split(" ");
                BigDecimal tempDistance = new BigDecimal(distanceStrs[0]);
                if (tempDistance.compareTo(BigDecimal.ZERO) < 0) {
                    shouldWorkOrderFlags.add(k);
                }
                if (CollUtil.isNotEmpty(bomLeadPair)) {
                    bomLeadPair.forEach((key,value) -> {
                        // key设备id-保养项id-距离保养值   value提前量
                        String[] uniqueKeyStrs = key.split("-");
                        if (uniqueKeyStrs.length == 3) {
                            if (String.valueOf(k).equals(uniqueKeyStrs[0]) && distanceStrs[0].equals(uniqueKeyStrs[2])
                                    && tempDistance.compareTo(BigDecimal.ZERO)>=0 && tempDistance.compareTo(value)<=0 )  {
                                shouldWorkOrderFlags.add(k);
                            }
                        }
                    });
                }
            });
        }
        // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
        // 排序后输出一个 List<Long> 类型的集合，排序对应上面的排序规则
        List<Long> sortedDeviceIds = sortByNumericTimeoutValue(resultMap);
        try {
            // 左侧组织树 组织层级查询
            Set<Long> ids = new HashSet<>();
            if (Objects.nonNull(pageReqVO.getDeptId())) {
                ids = deptService.getChildDeptIdListFromCache(pageReqVO.getDeptId());
                ids.add(pageReqVO.getDeptId());
            }
            // 不查询保养订单表 暂时传一个不会出现的设备id
            orderDeviceIds.add(Long.MIN_VALUE);
            List<IotDeviceRespVO> alarmDevices = iotDeviceMapper.deviceAlarmDistances(sortedDeviceIds, orderDeviceIds, sortedDeviceIds);
            // 处理当前分页数据 拼接上已经排序的 筛选出的设备保养项 最小保养距离
            List<Long> alarmDeviceIds = new ArrayList<>();
            Map<Long, IotDeviceRespVO> alarmDevicePair = new HashMap<>();
            if (CollUtil.isNotEmpty(alarmDevices)) {
                for (IotDeviceRespVO device : alarmDevices) {
                    if (resultMap.containsKey(device.getId())) {
                        device.setMainDistance(resultMap.get(device.getId()));
                        alarmDeviceIds.add(device.getId());
                    }
                    alarmDevicePair.put(device.getId(), device);
                }
            }
            // 查询所有设备列表 通过SQL形式 使用 FIELD 字段
            List<IotDeviceRespVO> page = iotDeviceMapper.timeoutDeviceMaintenances(pageReqVO, alarmDeviceIds, ids);
            if (CollUtil.isNotEmpty(page)) {
                page.forEach(device -> {
                    if (alarmDevicePair.containsKey(device.getId())) {
                        device.setMainDistance(alarmDevicePair.get(device.getId()).getMainDistance());
                        device.setPlanId(alarmDevicePair.get(device.getId()).getPlanId());
                        device.setWorkOrderId(alarmDevicePair.get(device.getId()).getWorkOrderId());
                    }
                    if (shouldWorkOrderFlags.contains(device.getId())) {
                        device.setShouldWorkOrder(true);
                    }
                });
            }
            return page;
        } catch (Exception exception) {
            if (exception.getMessage().contains("Table does not exist")) {
                return new ArrayList<>();
            }
            throw exception;
        }
    }


    @Override
    public List<IotDeviceMainTimeoutVO> timeoutDeviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO) {
        // key公司级部门名称     value公司下所有保养超时的设备数量
        Map<String, Integer> companyNameTimeoutDevices = new HashMap<>();
        List<IotDeviceMainTimeoutVO> companyMainTimeoutDevices = new ArrayList<>();
        // 所有保养计划 明细中待保养的最近距离 里程/时间/自然日
        DataPermissionUtils.executeIgnore(() -> {
            List<IotMainWorkOrderBomDO> workOrderBomS = new ArrayList<>();
            Set<Long> deviceIds = CollUtil.isEmpty(workOrderBomS)
                    ? new HashSet<>()
                    : workOrderBomS.stream()
                    .map(IotMainWorkOrderBomDO::getDeviceId) // 获取 deviceId
                    .filter(Objects::nonNull)      // 过滤非空值
                    .collect(Collectors.toSet());  // 收集到 Set 中
            Set<Long> orderDeviceIds = new HashSet<>();
            orderDeviceIds.addAll(deviceIds);
            // 查询保养计划明细中不包含 deviceIds 的设备id集合
            List<IotMaintenanceBomDO> mainBomS = new ArrayList<>();
            Set<Long> mainBomDeviceIds = new HashSet<>();
            IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
            if (CollUtil.isNotEmpty(deviceIds)) {
                mainBomReqVO.setDeviceIds(new ArrayList<>(deviceIds));
                mainBomS = iotMaintenanceBomMapper.selectAlarmList(mainBomReqVO);
            } else {
                mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
            }
            Set<String> boundedMultiAttrNames = new HashSet<>();
            Set<Long> deviceCategoryIds = new HashSet<>();
            Map<Long, Long> deviceCategoryPair = new HashMap<>();
            if (CollUtil.isNotEmpty(mainBomS)) {
                mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
                        ? Collections.emptySet()
                        : mainBomS.stream()
                        .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
                        .filter(Objects::nonNull)      // 过滤非空值
                        .collect(Collectors.toSet());  // 收集到 Set 中
                // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
                mainBomS.forEach(bom -> {
                    if (StrUtil.isNotBlank(bom.getType())) {
                        // 累计公里数属性
                        boundedMultiAttrNames.add(bom.getType());
                    }
                    if (StrUtil.isNotBlank(bom.getCode())) {
                        // 累计时长属性
                        boundedMultiAttrNames.add(bom.getCode());
                    }
                });
                // 查询所有设备类别id
                // 组装bom关联的设备信息
                Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
                        bom -> Stream.of(bom.getDeviceId())));
                if (CollUtil.isNotEmpty(deviceMap)) {
                    deviceMap.forEach((k,v) -> {
                        deviceCategoryIds.add(v.getAssetClass());
                        deviceCategoryPair.put(k, v.getAssetClass());
                    });
                }
            }
            deviceIds.addAll(mainBomDeviceIds);
            if (CollUtil.isEmpty(deviceIds)){
                // 没有设备信息，返回空列表
                return companyMainTimeoutDevices;
            }
            // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
            List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
            if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
                multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
            }
            // key(设备id-累计时长属性名称)    value时长属性累计时长数值
            Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
            if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
                multipleAccumulatedData.forEach(data -> {
                    String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                    tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
                });
            }

            // 查询 运行记录模板中 正常的累计时长 公里数集合
            // Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap =
                    iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds), new ArrayList<>(deviceCategoryIds), deviceCategoryPair, null);
            // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
            Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
            // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
            Map<Long, String> resultMap = new HashMap<>();
            // 记录保养项的 提前量 key设备id-保养项id-距离保养值   value提前量
            Map<String, BigDecimal> bomLeadPair = new HashMap<>();
            if (CollUtil.isNotEmpty(mainBomS)) {
                // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
                for (IotMaintenanceBomDO bom : mainBomS) {
                    BigDecimal runningTimeDistance = null;
                    BigDecimal runningKiloDistance = null;
                    BigDecimal naturalDateDistance = null;
                    // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
                    if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                        // 运行时间保养规则
                        BigDecimal totalRunTime = BigDecimal.ZERO;
                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                            totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                        } else {
                            // 运行记录模板中包含多个累计时长 公里数类型的属性
                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                            if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                                totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                            }
                        }
                        BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                        BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                        BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
                        runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningTimeDistance);
                        bomLeadPair.put(uniqueKey, timePeriodLead);
                    }
                    if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                        // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                        BigDecimal totalMileage = BigDecimal.ZERO;
                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                            totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                        } else {
                            // 运行记录模板中包含多个累计时长 公里数类型的属性
                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                            if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                                totalMileage = tempTotalRunDataPair.get(uniqueKey);
                            }
                        }
                        BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                        BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                        BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
                        runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningKiloDistance);
                        bomLeadPair.put(uniqueKey, kiloCycleLead);
                    }
                    if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 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.longValue(); // 转为长整型天数
                            // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                            LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                            // 获取当前日期时间
                            LocalDateTime now = LocalDateTime.now();
                            // 计算日期差值（以天为单位）
                            naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), naturalDateDistance);
                            bomLeadPair.put(uniqueKey, natualDateLead);
                        }
                    }
                    // 找出距离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);
            // 根据保养项规则应该生成保养工单的设备id集合
            Set<Long> shouldWorkOrderFlags = new HashSet<>();
            // 找出设备保养项距离保养最小值对应的 提前量
            if (CollUtil.isNotEmpty(resultMap)) {
                resultMap.forEach((k,v) -> {
                    // k设备id    v距离保养最小值(带单位)
                    // 去掉v中带的单位 310 D   -61.00 H
                    String[] distanceStrs = v.split(" ");
                    BigDecimal tempDistance = new BigDecimal(distanceStrs[0]);
                    if (tempDistance.compareTo(BigDecimal.ZERO) < 0) {
                        shouldWorkOrderFlags.add(k);
                    }
                    if (CollUtil.isNotEmpty(bomLeadPair)) {
                        bomLeadPair.forEach((key,value) -> {
                            // key设备id-保养项id-距离保养值   value提前量
                            String[] uniqueKeyStrs = key.split("-");
                            if (uniqueKeyStrs.length == 3) {
                                if (String.valueOf(k).equals(uniqueKeyStrs[0]) && distanceStrs[0].equals(uniqueKeyStrs[2])
                                        && tempDistance.compareTo(BigDecimal.ZERO)>=0 && tempDistance.compareTo(value)<=0 )  {
                                    shouldWorkOrderFlags.add(k);
                                }
                            }
                        });
                    }
                });
            }
            // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
            // 排序后输出一个 List<Long> 类型的集合，排序对应上面的排序规则
            // 筛选出所有 保养距离 小于0 的设备
            List<Long> sortedDeviceIds = sortByNumericTimeoutValue(resultMap);
            try {
                // 查询所有公司级别的组织部门
                Set<Long> parentIds = new HashSet<>();
                parentIds.add(DeptDO.PARENT_ID_ROOT);
                // 公司级部门
                List<DeptDO> theFirstLevelDepts = deptMapper.selectListByParentId(parentIds);
                AtomicReference<List<DeptDO>> theSecondLevelDepts = new AtomicReference<>(new ArrayList<>());
                if (CollUtil.isNotEmpty(theFirstLevelDepts)) {
                    theFirstLevelDepts.forEach(dept -> {
                        parentIds.clear();
                        parentIds.add(dept.getId());
                        theSecondLevelDepts.set(deptMapper.selectListByParentId(parentIds));
                    });
                }
                Map<Long, String> firstLevelDeptNamePair = new HashMap<>();
                // key子部门id     value公司级部门id
                Map<Long, Long> deptCompanyPair = new HashMap<>();
                if (CollUtil.isNotEmpty(theSecondLevelDepts.get())) {
                    AtomicReference<Set<Long>> ids = new AtomicReference<>(new HashSet<>());
                    theSecondLevelDepts.get().forEach(dept -> {
                        // 对部门名称进行 处理 去掉多语言部分
                        String deptName = dept.getName();
                        deptName = StrUtil.subBefore(deptName, "~~", false);
                        firstLevelDeptNamePair.put(dept.getId(), deptName);
                        // 查询每个公司下所有子部门 将子部门与公司建立对应关系
                        ids.set(deptService.getChildDeptIdListFromCache(dept.getId()));
                        ids.get().add(dept.getId());
                        if (CollUtil.isNotEmpty(ids.get())) {
                            ids.get().forEach(deptId -> {
                                deptCompanyPair.put(deptId, dept.getId());
                            });
                        }
                        ids.get().clear();;
                    });
                    // 查询 排序后的保养距离超时的 sortedDeviceIds 集合中 每个设备 对应的部门
                    IotDevicePageReqVO deviceReqVO = new IotDevicePageReqVO();
                    deviceReqVO.setDeviceIds(sortedDeviceIds);
                    List<IotDeviceDO> devices = iotDeviceMapper.selectListAlone(deviceReqVO);
                    // key公司级部门id     value公司下所有保养超时的设备数量
                    Map<Long, Integer> companyTimeoutDevices = new HashMap<>();

                    if (CollUtil.isNotEmpty(devices)) {
                        devices.forEach(device -> {
                            if (deptCompanyPair.containsKey(device.getDeptId())) {
                                Long tempCompanyDeptId = deptCompanyPair.get(device.getDeptId());
                                if (companyTimeoutDevices.containsKey(tempCompanyDeptId)) {
                                    Integer tempCount = companyTimeoutDevices.get(tempCompanyDeptId);
                                    companyTimeoutDevices.put(tempCompanyDeptId, ++tempCount);
                                } else {
                                    companyTimeoutDevices.put(tempCompanyDeptId, 1);
                                }
                            }
                        });
                        if (CollUtil.isNotEmpty(companyTimeoutDevices)) {
                            companyTimeoutDevices.forEach((k,v) -> {
                                // key公司级部门id     value公司下所有保养超时的设备数量
                                if (firstLevelDeptNamePair.containsKey(k)) {
                                    String companyName = firstLevelDeptNamePair.get(k);
                                    companyNameTimeoutDevices.put(companyName, v);
                                    IotDeviceMainTimeoutVO timeoutDevice = new IotDeviceMainTimeoutVO();
                                    timeoutDevice.setDeptName(companyName);
                                    timeoutDevice.setDeptId(k);
                                    timeoutDevice.setCount(v);
                                    companyMainTimeoutDevices.add(timeoutDevice);
                                }
                            });
                        }
                    }
                }
                return companyMainTimeoutDevices;
            } catch (Exception exception) {
                return new HashMap<>();
            }
        });
        return companyMainTimeoutDevices;
    }

    @Override
    public PageResult<IotDeviceRespVO> maintenanceSearch(IotMainWorkOrderPageReqVO pageReqVO) {
        // 所有保养计划 明细中待保养的最近距离 里程/时间/自然日
        Set<Long> deviceIds = new HashSet<>();
        Set<Long> deviceCategoryIds = new HashSet<>();
        Map<Long, Long> deviceCategoryPair = new HashMap<>();
        Set<Long> orderDeviceIds = new HashSet<>();
        // 查询保养计划明细中不包含 deviceIds 的设备id集合
        List<IotMaintenanceBomDO> mainBomS = new ArrayList<>();
        Set<Long> mainBomDeviceIds = new HashSet<>();

        IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
        mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
        Set<String> boundedMultiAttrNames = new HashSet<>();
        if (CollUtil.isNotEmpty(mainBomS)) {
            mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
                    ? Collections.emptySet()
                    : mainBomS.stream()
                    .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
                    .filter(Objects::nonNull)      // 过滤非空值
                    .collect(Collectors.toSet());  // 收集到 Set 中
            // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
            mainBomS.forEach(bom -> {
                if (StrUtil.isNotBlank(bom.getType())) {
                    // 累计公里数属性
                    boundedMultiAttrNames.add(bom.getType());
                }
                if (StrUtil.isNotBlank(bom.getCode())) {
                    // 累计时长属性
                    boundedMultiAttrNames.add(bom.getCode());
                }
            });
            // 组装bom关联的设备信息
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            }
        }
        deviceIds.addAll(mainBomDeviceIds);
        if (CollUtil.isEmpty(deviceIds)){
            // 没有设备信息，返回空列表
            return PageResult.empty();
        }
        // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
        List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
            multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
        }
        // key(设备id-累计时长属性名称)    value时长属性累计时长数值
        Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
        if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
            multipleAccumulatedData.forEach(data -> {
                String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
            });
        }

        // 查询 运行记录模板中 正常的累计时长 公里数集合
        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds),
                new ArrayList<>(deviceCategoryIds), deviceCategoryPair, null);
        // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
        Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
        // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
        Map<Long, String> resultMap = new HashMap<>();
        // 设备保养明细 key设备id-保养项分组名称  value设备保养项分组下最近的保养距离
        Map<String, String> groupResultMap = new HashMap<>();
        // 以设备为维度统计每个设备相关的保养项(分组)的最近保养距离 key设备id-保养项分组名称    value设备下每个保养项的的最小保养距离集合
        Map<String, List<Map<String, Object>>> minBomGroupDistancePair = new HashMap<>();
        // 以“设备id-分组bom名称-bomNodeId”为维度统计每个设备相关的保养项的最近保养距离关联的对象 key设备id-分组bom名称-bomNodeId
        // value设备下每个保养项的的最小保养距离关联的保养项对象
        Map<String, IotMaintenanceBomDO> minBomDistancePair = new HashMap<>();
        // 以“设备id-分组bom名称-bomNodeId”为维度统计每个设备相关的保养项的最近保养距离关联的对象 key设备id-分组bom名称-bomNodeId
        // value设备下每个保养项的的最小保养距离 数值
        Map<String, String> bomDistancePair = new HashMap<>();
        // key设备id   value设备对应的各个bom分组及分组下的最短保养距离
        Map<Long, List<IotMaintenanceBomDO>> deviceBomGroupPair = new HashMap<>();
        // key设备id   value设备对应的各个bom分组及分组下的最短保养距离
        Map<Long, List<IotMaintenanceBomRespVO>> resultDeviceBomGroupPair = new HashMap<>();
        // key设备id-bomNodeId   value保养项的累计运行时长/里程
        Map<String, IotMaintenanceBomRespVO> maintenanceBomRespPair = new HashMap<>();
        if (CollUtil.isNotEmpty(mainBomS)) {
            // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
            mainBomS.forEach(bom -> {
                // 拷贝一份保养明细对象 设置 累计运行时长/里程 使用
                IotMaintenanceBomRespVO bomResp = new IotMaintenanceBomRespVO();
                BeanUtils.copyProperties(bom, bomResp);
                BigDecimal runningTimeDistance = null;
                BigDecimal runningKiloDistance = null;
                BigDecimal naturalDateDistance = null;
                // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                    // 运行时间保养规则
                    BigDecimal totalRunTime = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                    BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                    BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
                    runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
                    bomResp.setTotalRunTime(totalRunTime);
                }
                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                    // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                    BigDecimal totalMileage = BigDecimal.ZERO;
                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                    } else {
                        // 运行记录模板中包含多个累计时长 公里数类型的属性
                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                            totalMileage = tempTotalRunDataPair.get(uniqueKey);
                        }
                    }
                    BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                    BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                    BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
                    runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
                    bomResp.setTotalMileage(totalMileage);
                }
                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 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.longValue(); // 转为长整型天数
                        // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                        // 获取当前日期时间
                        LocalDateTime now = LocalDateTime.now();
                        // 计算日期差值（以天为单位）
                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                    }
                }
                // 按照保养项的父BOM节点分组统计 最短保养距离
                // 先按照 保养项目 层级分组 电驱空压机->油气分离器滤芯
                String bomNodeName = bom.getName();
                Long bomNodeId = bom.getBomNodeId();
                String uniqueKey = StrUtil.EMPTY;
                String uniqueBomKey = StrUtil.EMPTY;
                String uniqueBomNodeIdKey = StrUtil.EMPTY;
                if (StrUtil.isNotBlank(bomNodeName)) {
                    String[] bomNodeNameAttr = bomNodeName.split("->");
                    if (ObjUtil.isNotEmpty(bomNodeNameAttr) && ObjUtil.isNotEmpty(bomNodeId) && bomNodeNameAttr.length > 1) {
                        // 当前保养项是 多级 BOM 可以分组统计 每个分组下的 最短保养距离   key"设备id-电驱空压机"
                        uniqueKey = StrUtil.join("-", bom.getDeviceId(), bomNodeNameAttr[0]);
                        uniqueBomKey = StrUtil.join("-", bom.getDeviceId(), bomNodeNameAttr[0], bomNodeId);
                        uniqueBomNodeIdKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                    }
                }
                // 设置每个保养项的 累计运行时长/里程
                maintenanceBomRespPair.put(uniqueBomNodeIdKey, bomResp);

                IotMaintenanceBomDO tempBom = new IotMaintenanceBomDO();
                tempBom.setDeviceId(bom.getDeviceId());
                tempBom.setBomNodeId(bom.getBomNodeId());
                tempBom.setName(bom.getName());
                tempBom.setRunningTimeRule(bom.getRunningTimeRule());
                tempBom.setMileageRule(bom.getMileageRule());
                tempBom.setNaturalDateRule(bom.getNaturalDateRule());
                // 找出距离0最近的数，作为工单的最近保养距离数据
                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
                String tempMinDistance = StrUtil.EMPTY;
                Map<String, Object> tempDistance = new HashMap<>();
                if (closet == runningTimeDistance) {
                    tempDistance.put("H", closet);
                    tempMinDistance = StrUtil.join(" ", closet, "H");
                    // 运行时长
                    tempBom.setLastRunningTime(bom.getLastRunningTime());
                    tempBom.setNextRunningTime(bom.getNextRunningTime());
                    tempBom.setTimePeriodLead(bom.getTimePeriodLead());
                } else if (closet == runningKiloDistance) {
                    tempDistance.put("KM", closet);
                    tempMinDistance = StrUtil.join(" ", closet, "KM");
                    // 运行里程
                    tempBom.setLastRunningKilometers(bom.getLastRunningKilometers());
                    tempBom.setNextRunningKilometers(bom.getNextRunningKilometers());
                    tempBom.setKiloCycleLead(bom.getKiloCycleLead());
                } else if (closet == naturalDateDistance) {
                    tempDistance.put("D", closet);
                    tempMinDistance = StrUtil.join(" ", closet, "D");
                    // 自然日期
                    tempBom.setLastNaturalDate(bom.getLastNaturalDate());
                    tempBom.setNextNaturalDate(bom.getNextNaturalDate());
                    tempBom.setNaturalDatePeriodLead(bom.getNaturalDatePeriodLead());
                }
                // 保存每个保养项对象 之后与 每个保养项分组的 最短保养距离匹配
                minBomDistancePair.put(uniqueBomKey, tempBom);
                // 保存每个保养项的最短距离 之后与 每个保养项分组的 最短保养距离匹配
                bomDistancePair.put(uniqueBomKey, tempMinDistance);
                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);
                }
                // 按照保养项的父BOM节点分组统计 最短保养距离
                // 先按照 保养项目 层级分组 电驱空压机->油气分离器滤芯
                if (StrUtil.isNotBlank(uniqueKey)) {
                    if (minBomGroupDistancePair.containsKey(uniqueKey)) {
                        List<Map<String, Object>> tempDistances = minBomGroupDistancePair.get(uniqueKey);
                        tempDistances.add(tempDistance);
                        minBomGroupDistancePair.put(uniqueKey, tempDistances);
                    } else {
                        List<Map<String, Object>> tempDistances = new ArrayList<>();
                        tempDistances.add(tempDistance);
                        minBomGroupDistancePair.put(uniqueKey, tempDistances);
                    }
                }
            });
        }
        // 以“设备id-保养项分组” 为维度 统计每个设备保养项分组下 距离最近的保养数据
        groupResultMap = findGroupClosestToZero(minBomGroupDistancePair);
        List<Map.Entry<String, String>> keys = groupResultMap.entrySet().stream().filter(e -> e.getKey().contains("530-")).collect(Collectors.toList());
        // minBomDistancePair  bomDistancePair 循环这2个对象 组装每个分组需要显示的最短保养距离 BOM对象
        if (CollUtil.isNotEmpty(bomDistancePair)) {
            Map<String, String> finalGroupResultMap = groupResultMap;
            bomDistancePair.forEach((k, v) -> {
                // k设备id-分组bom名称-bomNodeId   v设备下每个保养项的的最小保养距离 数值
                // 分割出 k 的前部分 “设备id-分组bom名称”
                String[] firstKeyAttr = splitAtSecondHyphen(k);
                if (ObjUtil.isNotEmpty(firstKeyAttr) && firstKeyAttr.length > 1) {
                    String firstKeyStr = firstKeyAttr[0];
                    Long deviceId = 0l;
                    if (StrUtil.isNotBlank(firstKeyStr)) {
                        String[] deviceIdAttr = firstKeyStr.split("-");
                        if (ObjUtil.isNotEmpty(deviceIdAttr) && deviceIdAttr.length > 1) {
                            deviceId = Long.valueOf(deviceIdAttr[0]);
                        }
                    }
                    if (finalGroupResultMap.containsKey(firstKeyAttr[0])) {
                        // "-2249.00 H"
                        String groupMinDistance = finalGroupResultMap.get(firstKeyAttr[0]);
                        // 去掉 距离单位 获得 数值
                        if (StrUtil.isNotBlank(groupMinDistance)) {
                            if (StrUtil.equals(groupMinDistance, v)) {
                                // 根据 k 找到 minBomDistancePair 中对应的 最小距离 保养对象
                                if (minBomDistancePair.containsKey(k)) {
                                    IotMaintenanceBomDO tempBom = minBomDistancePair.get(k);
                                    if (deviceBomGroupPair.containsKey(deviceId)) {
                                        List<IotMaintenanceBomDO> tempBomS = deviceBomGroupPair.get(deviceId);
                                        tempBomS.add(tempBom);
                                        deviceBomGroupPair.put(deviceId, tempBomS);
                                    } else {
                                        List<IotMaintenanceBomDO> tempBomS = new ArrayList<>();
                                        tempBomS.add(tempBom);
                                        deviceBomGroupPair.put(deviceId, tempBomS);
                                    }
                                }
                            }
                        }
                    }
                }
            });
        }

        // 重新整理 deviceBomGroupPair 集合 每个 设备的保养项分组只保留1个对象 因为存在 相同保养距离的保养项
        if (CollUtil.isNotEmpty(deviceBomGroupPair)) {
            deviceBomGroupPair.forEach((k,v) -> {
                // k设备id    v分组bom中距离保养最近的对象
                Set<String> bomNames = new HashSet<>();
                List<IotMaintenanceBomRespVO> copyBomS = new ArrayList<>();
                v.forEach(bom -> {
                    String leveledBomName = bom.getName();
                    String[] bomNameAttr = leveledBomName.split("->");
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                    if (ObjUtil.isNotEmpty(bomNameAttr) && bomNameAttr.length > 1) {
                        if (!bomNames.contains(bomNameAttr[0])) {
                            IotMaintenanceBomRespVO bomResp = new IotMaintenanceBomRespVO();
                            BeanUtils.copyProperties(bom, bomResp);
                            if (maintenanceBomRespPair.containsKey(uniqueKey)) {
                                IotMaintenanceBomRespVO tempBomResp = maintenanceBomRespPair.get(uniqueKey);
                                bomResp.setTotalRunTime(tempBomResp.getTotalRunTime());
                                bomResp.setTotalMileage(tempBomResp.getTotalMileage());
                            }
                            copyBomS.add(bomResp);
                        }
                        bomNames.add(bomNameAttr[0]);
                    }
                });
                resultDeviceBomGroupPair.put(k, copyBomS);
            });
        }

        // 以设备id 为维度 统计每个保养计划明细中 距离最近的保养数据
        resultMap = findClosestToZero(orderDistancePair);
        // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
        // 排序后输出一个 List<Long> 类型的集合，排序对应上面的排序规则
        List<Long> sortedDeviceIds = sortByNumericValue(resultMap);
        try {
            // 左侧组织树 组织层级查询
            Set<Long> ids = new HashSet<>();
            if (Objects.nonNull(pageReqVO.getDeptId())) {
                ids = deptService.getChildDeptIdListFromCache(pageReqVO.getDeptId());
                ids.add(pageReqVO.getDeptId());
            }
            // 不查询保养工单表 暂时传一个不会出现的设备id
            orderDeviceIds.add(Long.MIN_VALUE);
            List<IotDeviceRespVO> alarmDevices = iotDeviceMapper.deviceAlarmDistances(sortedDeviceIds, orderDeviceIds, mainBomDeviceIds);
            // 处理当前分页数据 拼接上已经排序的 筛选出的设备保养项 最小保养距离
            List<Long> alarmDeviceIds = new ArrayList<>();
            Map<Long, IotDeviceRespVO> alarmDevicePair = new HashMap<>();
            if (CollUtil.isNotEmpty(alarmDevices)) {
                for (IotDeviceRespVO device : alarmDevices) {
                    if (resultMap.containsKey(device.getId())) {
                        device.setMainDistance(resultMap.get(device.getId()));
                        alarmDeviceIds.add(device.getId());
                    }
                    alarmDevicePair.put(device.getId(), device);
                }
            }
            // 查询所有设备列表 通过SQL形式 使用 FIELD 字段
            IPage<IotDeviceRespVO> page = iotDeviceMapper.allDeviceDistances(
                    new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO, alarmDeviceIds, ids);
            if (CollUtil.isNotEmpty(page.getRecords())) {
                page.getRecords().forEach(device -> {
                    if (alarmDevicePair.containsKey(device.getId())) {
                        device.setMainDistance(alarmDevicePair.get(device.getId()).getMainDistance());
                        device.setPlanId(alarmDevicePair.get(device.getId()).getPlanId());
                        device.setWorkOrderId(alarmDevicePair.get(device.getId()).getWorkOrderId());
                    }
                    // 查询 每个设备对应的 保养项 分组 集合 已经设置了 最短距离
                    if (resultDeviceBomGroupPair.containsKey(device.getId())) {
                        List<IotMaintenanceBomRespVO> tempBomS = resultDeviceBomGroupPair.get(device.getId());
                        device.setGroupBomDistances(tempBomS);
                    }
                });
            }
            return new PageResult<>(page.getRecords(), page.getTotal());
        } catch (Exception exception) {
            if (exception.getMessage().contains("Table does not exist")) {
                return PageResult.empty();
            }
            throw exception;
        }
    }

    private String[] splitAtSecondHyphen(String str) {
        int firstIndex = str.indexOf('-');
        if (firstIndex == -1) {
            // 没有找到任何 '-'，返回整个字符串和空字符串
            return new String[]{str, ""};
        }

        // 从第一个 '-' 后开始查找第二个 '-'
        int secondIndex = str.indexOf('-', firstIndex + 1);
        if (secondIndex == -1) {
            // 没有找到第二个 '-'，返回整个字符串和空字符串
            return new String[]{str, ""};
        }

        // 分割字符串
        String part1 = str.substring(0, secondIndex);
        String part2 = str.substring(secondIndex + 1);
        return new String[]{part1, part2};
    }

    /**
     * 排序 Map<Long, String> 中的元素 删除 类似 D、 H、 KM 后进行升序排列
     * @param map
     * @return
     */
    private List<Long> sortByNumericValue(Map<Long, String> map) {
        return map.entrySet().stream()
                .map(entry -> {
                    String[] parts = entry.getValue().split("\\s+");
                    if (parts.length < 2) {
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
                    }

                    try {
                        BigDecimal value = new BigDecimal(parts[0]);
                        // 单位转换：H→天，KM/D保持不变
                        if ("H".equals(parts[1])) {
                            value = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
                        }
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), value.doubleValue());
                    } catch (NumberFormatException e) {
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
                    }
                })
                .sorted(Comparator
                        .<AbstractMap.SimpleEntry<Long, Double>>comparingDouble(AbstractMap.SimpleEntry::getValue) // 按转换值升序
                        .thenComparingLong(AbstractMap.SimpleEntry::getKey) // 值相同时按键升序
                )
                .map(AbstractMap.SimpleEntry::getKey)
                .collect(Collectors.toList());
    }

    /**
     * 筛选出 保养距离 超时的所有设备
     * 排序 Map<Long, String> 中的元素 删除 类似 D、 H、 KM 后进行升序排列
     * @param map
     * @return
     */
    private List<Long> sortByNumericTimeoutValue(Map<Long, String> map) {
        return map.entrySet().stream()
                .map(entry -> {
                    String[] parts = entry.getValue().split("\\s+");
                    if (parts.length < 2) {
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
                    }

                    try {
                        BigDecimal value = new BigDecimal(parts[0]);
                        // 单位转换：H→天，KM/D保持不变
                        if ("H".equals(parts[1])) {
                            value = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
                        }
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), value.doubleValue());
                    } catch (NumberFormatException e) {
                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
                    }
                })
                // 2. 过滤出转换后数值小于0的数据
                .filter(entry -> entry.getValue() < 0)
                .sorted(Comparator
                        .<AbstractMap.SimpleEntry<Long, Double>>comparingDouble(AbstractMap.SimpleEntry::getValue) // 按转换值升序
                        .thenComparingLong(AbstractMap.SimpleEntry::getKey) // 值相同时按键升序
                )
                .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 workOrderId = entry.getKey();
            List<Map<String, Object>> mapList = entry.getValue();
            // 存储所有有效值（原始值、单位、转换值）
            List<Triple<BigDecimal, String, BigDecimal>> allValues = new ArrayList<>();
            // 收集所有值并进行单位转换
            for (Map<String, Object> map : mapList) {
                for (Map.Entry<String, Object> valueEntry : map.entrySet()) {
                    if (valueEntry.getValue() instanceof BigDecimal) {
                        BigDecimal value = (BigDecimal) valueEntry.getValue();
                        String unit = valueEntry.getKey();
                        BigDecimal convertedValue;
                        // 小时转换为天
                        if ("H".equals(unit)) {
                            convertedValue = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
                        } else {
                            convertedValue = value;
                        }
                        allValues.add(new Triple<>(value, unit, convertedValue));
                    }
                }
            }
            // 如果没有有效值，跳过
            if (allValues.isEmpty()) {
                continue;
            }
            // 找到转换后值最小的值
            Triple<BigDecimal, String, BigDecimal> minTriple = allValues.get(0);
            for (Triple<BigDecimal, String, BigDecimal> triple : allValues) {
                if (triple.getThird().compareTo(minTriple.getThird()) < 0) {
                    minTriple = triple;
                }
            }
            // 格式化为字符串: "原始值 单位"
            result.put(workOrderId, minTriple.getFirst() + " " + minTriple.getSecond());
        }
        return result;
    }

    /**
     * 找出每个设备对应的bom保养项(层级分组)集合中 距离保养最短的 数据
     *
     * @param minBomGroupDistancePair key设备id-保养项一级分组名称
     */
    private Map<String, String> findGroupClosestToZero(Map<String, List<Map<String, Object>>> minBomGroupDistancePair) {
        Map<String, String> result = new HashMap<>();
        for (Map.Entry<String, List<Map<String, Object>>> entry : minBomGroupDistancePair.entrySet()) {
            String uniqueKey = entry.getKey();  // 设备id-保养项节点第一层级名称
            List<Map<String, Object>> mapList = entry.getValue();
            // 存储所有有效值（原始值、单位、转换值）
            List<Triple<BigDecimal, String, BigDecimal>> allValues = new ArrayList<>();
            // 收集所有值并进行单位转换
            for (Map<String, Object> map : mapList) {
                for (Map.Entry<String, Object> valueEntry : map.entrySet()) {
                    if (valueEntry.getValue() instanceof BigDecimal) {
                        BigDecimal value = (BigDecimal) valueEntry.getValue();
                        String unit = valueEntry.getKey();
                        BigDecimal convertedValue;
                        // 小时转换为天
                        if ("H".equals(unit)) {
                            convertedValue = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
                        } else {
                            convertedValue = value;
                        }
                        allValues.add(new Triple<>(value, unit, convertedValue));
                    }
                }
            }
            // 如果没有有效值，跳过
            if (allValues.isEmpty()) {
                continue;
            }
            // 找到转换后值最小的值
            Triple<BigDecimal, String, BigDecimal> minTriple = allValues.get(0);
            for (Triple<BigDecimal, String, BigDecimal> triple : allValues) {
                if (triple.getThird().compareTo(minTriple.getThird()) < 0) {
                    minTriple = triple;
                }
            }
            // 格式化为字符串: "原始值 单位"
            result.put(uniqueKey, minTriple.getFirst() + " " + minTriple.getSecond());
        }
        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) {
        List<Triple<BigDecimal, String, BigDecimal>> values = new ArrayList<>();
        if (runningTimeDistance != null) {
            // 转换为天数用于比较，但保留原始值
            BigDecimal converted = runningTimeDistance.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
            values.add(new Triple<>(runningTimeDistance, "H", converted));
        }
        if (runningKiloDistance != null) {
            // 公里值不转换
            values.add(new Triple<>(runningKiloDistance, "KM", runningKiloDistance));
        }
        if (naturalDateDistance != null) {
            // 自然日值不转换
            values.add(new Triple<>(naturalDateDistance, "D", naturalDateDistance));
        }
        // 如果没有有效值，返回null
        if (values.isEmpty()) {
            return null;
        }
        // 找到转换后值最小的原始值
        Triple<BigDecimal, String, BigDecimal> minTriple = values.get(0);
        for (Triple<BigDecimal, String, BigDecimal> triple : values) {
            if (triple.getThird().compareTo(minTriple.getThird()) < 0) {
                minTriple = triple;
            }
        }
        return minTriple.getFirst(); // 返回原始值
    }

    @Override
    public Long theMaxId() {
        List<IotMainWorkOrderDO> workOrderDOS = iotMainWorkOrderMapper.theMaxOne();
        return CollUtil.isNotEmpty(workOrderDOS) ? workOrderDOS.get(0).getId() : 0l;
    }

    @Override
    public Long theMaxWorkOrderId() {
        return iotMainWorkOrderMapper.theMaxWorkOrderId();
    }

    @Override
    public String createWorkOrderNumber() {
        return bizNoRedisDAO.generate(BizNoRedisDAO.MAIN_WORK_ORDER_NO_PREFIX);
    }

    @Override
    public void batchAddOrders(List<IotMainWorkOrderDO> workOrders) {
        iotMainWorkOrderMapper.insertBatch(workOrders);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void fillWorkOrder(IotMainWorkOrderSaveVO saveVO) {
        if (ObjectUtil.isEmpty(saveVO)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        IotMainWorkOrderSaveReqVO mainWorkOrder = saveVO.getMainWorkOrder();
        if (ObjectUtil.isEmpty(saveVO.getMainWorkOrder())) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        List<IotMainWorkOrderBomSaveReqVO> workOrderBOMs = saveVO.getMainWorkOrderBom();
        if (ObjectUtil.isEmpty(workOrderBOMs)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        // 不是必填项：推迟保养；委外保养
        List<IotMainWorkOrderBomMaterialSaveReqVO> workOrderMaterials = saveVO.getMainWorkOrderMaterials();

        // 保养工单 主表 还有 保养费用 、人工费用 、其他费用
        IotMainWorkOrderDO updateObj = BeanUtils.toBean(mainWorkOrder, IotMainWorkOrderDO.class);

        // 保养工单明细
        List<IotMainWorkOrderBomDO> workOrderBomDOS = new ArrayList<>();

        // 保养工单 bom 明细关联的物料消耗
        List<IotMainWorkOrderBomMaterialDO> workOrderBomMaterialDOS = new ArrayList<>();
        Set<Long> factoryIds = new HashSet<>();
        Set<Long> costCenterIds = new HashSet<>();
        Set<Long> storageLocationIds = new HashSet<>();
        Set<String> materialCodes = new HashSet<>();
        Map<String, BigDecimal> lockStockPair = new HashMap<>();
        // key工厂id-成本中心id-物料编码-bomId        value保养项物料对象
        Map<String, IotMainWorkOrderBomMaterialSaveReqVO> bomMaterialPair = new HashMap<>();
        // 标识保养项是否已经添加了物料
        Set<Long> bomNodeIds = new HashSet<>();
        AtomicBoolean mainCompleted = new AtomicBoolean(true);
        // 兼容APP未优化完成的情况 所有保养项都添加了物料 设置工单 为 已执行
        AtomicBoolean appCompleted = new AtomicBoolean(true);
        // 已经完成保养的保养项关联的 设备id 集合
        Set<Long> deviceIds = new HashSet<>();
        Set<Long> deviceCategoryIds = new HashSet<>();
        Map<Long, Long> deviceCategoryPair = new HashMap<>();
        // 需要更新 运行时长周期 的设备id 集合
        Set<Long> mctDeviceIds = new HashSet<>();
        // 需要更新 运行时长周期 的保养项id 集合
        Set<Long> mctBomNodeIds = new HashSet<>();
        // 需要消耗物料的保养项id集合
        Set<Long> materialBomNodeIds = new HashSet<>();
        // 保养完成的保养项id集合
        Set<Long> completedBomNodeIds = new HashSet<>();
        // 每个保养项关联的物料集合
        Map<Long, List<IotMainWorkOrderBomMaterialSaveReqVO>> bomMaterialsPair = new HashMap<>();
        // key设备id-bomNodeId    value保养时累计运行里程
        Map<String, BigDecimal> mainMileagePair = new HashMap<>();
        // key设备id-bomNodeId    value保养时累计运行时长
        Map<String, BigDecimal> mainRuntimePair = new HashMap<>();
        // todo 先查询当前工单已经保养完成的保养项 避免重复更新 保养项的 上次保养时长...
        // 统计保养项信息 是否保养完成 是否需要消耗物料
        workOrderBOMs.forEach(bom -> {
            if ("0".equals(bom.getRule())) {
                // 需要消耗物料
                materialBomNodeIds.add(bom.getBomNodeId());
            }
            if (1 == bom.getStatus()) {
                // 保养完成
                completedBomNodeIds.add(bom.getBomNodeId());
            }
            // 组装每个保养项的 保养时累计运行里程 时长
            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
            mainMileagePair.put(uniqueKey, ObjUtil.isEmpty(bom.getMainMileage()) ? BigDecimal.ZERO : bom.getMainMileage());
            mainRuntimePair.put(uniqueKey, ObjUtil.isEmpty(bom.getMainRuntime()) ? BigDecimal.ZERO : bom.getMainRuntime());
        });
        // 只有保养完成的保养项关联的物料才会被统计出库(如果物料来源是 本地库存)
        workOrderMaterials.forEach(material -> {
            // 兼容保养工单中部分保养项保养完成 部分保养项延期保养的情况 这里只新增延期保养 或 正常保养的物料
            if (materialBomNodeIds.contains(material.getBomNodeId())) {
                IotMainWorkOrderBomMaterialDO tempMaterial = BeanUtil.copyProperties(material, IotMainWorkOrderBomMaterialDO.class, "id");
                tempMaterial.setWorkOrderId(mainWorkOrder.getId());
                workOrderBomMaterialDOS.add(tempMaterial);
            }

            // 兼容 APP 未更新的情况 暂定 所有保养项都添加了物料 设置 保养工单 为已执行
            if (bomMaterialsPair.containsKey(material.getBomNodeId())) {
                List<IotMainWorkOrderBomMaterialSaveReqVO> tempBomMaterials = bomMaterialsPair.get(material.getBomNodeId());
                tempBomMaterials.add(material);
                bomMaterialsPair.put(material.getBomNodeId(), tempBomMaterials);
            } else {
                List<IotMainWorkOrderBomMaterialSaveReqVO> tempBomMaterials = new ArrayList<>();
                tempBomMaterials.add(material);
                bomMaterialsPair.put(material.getBomNodeId(), tempBomMaterials);
            }

            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()) {
                // 兼容保养工单中部分保养项保养完成 部分保养项 延期保养的情况 这里只更新延期保养 或正常 保养的物料
                // 如果工单明细中任何一个保养项设置了 任何一种推迟保养规则 工单结果 不能设置为 已执行
                tempBom.setStatus(0);
                mainCompleted.set(false);
            }
            if ((ObjUtil.isNotEmpty(bom.getStatus()) && 1 == bom.getStatus()) /* || bomMaterialsPair.containsKey(bom.getBomNodeId()) */) {
                // 当前保养项已经保养完成 查询当前设备的 累计运行公里数 累计运行时间 当前时间日期 赋值到 关联保养计划 明细 的 对应保养规则数据上
                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关联的设备信息
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(workOrderBOMs,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            }
        }
        iotMainWorkOrderBomMapper.updateBatch(workOrderBomDOS);
        // 只有工单明细中所有保养项的物料都选择完成，才会将工单结果设置为 ‘已执行’
        if (mainCompleted.get()) {
            // 兼容APP未优化完成的情况 所有保养项都添加了物料 设置 保养工单 为 已执行
            updateObj.setResult(2);
        }
        iotMainWorkOrderMapper.updateById(updateObj);

        // 更新工单的附件记录 rq_iot_attachment
        List<IotAttachmentSaveReqVO> attachmentReqVO = mainWorkOrder.getAttachments();
        List<IotAttachmentDO> tobeAddedAttachments = new ArrayList<>();
        if (CollUtil.isNotEmpty(attachmentReqVO)) {
            attachmentReqVO.forEach(att -> {
                IotAttachmentDO attachment = new IotAttachmentDO();
                attachment.setBizId(updateObj.getId());
                attachment.setCategory(AttachmentCategoryEnum.MAINTENANCE.getCode());
                attachment.setType(AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode());
                attachment.setFilePath(att.getFilePath());
                attachment.setFileSize(att.getFileSize());
                attachment.setFilename(att.getFilename());
                attachment.setFileType(att.getFileType());
                attachment.setRemark(att.getRemark());
                tobeAddedAttachments.add(attachment);
            });
        }
        // 先删除附件再新增附件
        int count = iotAttachmentMapper.deleteByMap(ImmutableMap.of(
                "category", AttachmentCategoryEnum.MAINTENANCE.getCode(),
                "biz_id", updateObj.getId(),
                "type", AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode()
        ));
        if (CollUtil.isNotEmpty(tobeAddedAttachments)) {
            iotAttachmentMapper.insertBatch(tobeAddedAttachments);
        }

        // 如果有保养项已经保养完成，根据保养项规则 实时更新 关联 的 保养计划 明细 保养项的 ‘上次保养运行时间、上次保养自然日期、上次保养公里数’
        // 查询明细中所有设备的累计运行里程、累计运行时间
        // 全部按照多个累计属性 来统计
        if (CollUtil.isNotEmpty(completedBomNodeIds) && CollUtil.isNotEmpty(deviceIds)) {
            // 运行记录模板中 多种 累计时长 公里数 属性名称集合
            Set<String> boundedMultiAttrNames = new HashSet<>();
            workOrderBomDOS.forEach(bom -> {
                // 查询保养计划明细已经绑定的 多种 累计时长 公里数 属性名称
                if (StrUtil.isNotBlank(bom.getCode())) {
                    boundedMultiAttrNames.add(bom.getCode());
                }
                if (StrUtil.isNotBlank(bom.getType())) {
                    boundedMultiAttrNames.add(bom.getType());
                }
            });
            List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
            if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
                multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
            }
            // key(设备id-累计时长属性名称)    value时长属性累计时长数值
            Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
            if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
                multipleAccumulatedData.forEach(data -> {
                    String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                    tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
                });
            }

            // 单一累计属性集合
            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds),
                    new ArrayList<>(deviceCategoryIds), deviceCategoryPair, null);
            // 查询保养工单关联的保养计划 明细
            IotMaintenanceBomPageReqVO reqVO = new IotMaintenanceBomPageReqVO();
            reqVO.setPlanId(updateObj.getPlanId());
            reqVO.setDeviceIds(new ArrayList<>(deviceIds));
            reqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));
            List<IotMaintenanceBomDO> maintenanceBomS = iotMaintenanceBomService.getIotMainPlanBomList(reqVO);
            // 根据保养工单 保养项 保养数据 更新对应的 保养计划 保养项 数据
            // 如果没有填写 保养时的累计运行时长 累计运行公里数 则使用 运行记录的累计值 如果都没有值 则不反写
            if (CollUtil.isNotEmpty(maintenanceBomS)) {
                List<IotMaintenanceBomDO> tobeUpdatedMainBomS = new ArrayList<>();
                maintenanceBomS.forEach(bom -> {
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                    if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule() && completedBomNodeIds.contains(bom.getBomNodeId())) {
                        BigDecimal totalMileage = BigDecimal.ZERO;
                        // 设置了 运行里程 保养规则
                        if (mainMileagePair.containsKey(uniqueKey)) {
                            BigDecimal tempMainMileage = mainMileagePair.get(uniqueKey);
                            if (ObjUtil.isNotEmpty(tempMainMileage) && tempMainMileage.compareTo(BigDecimal.ZERO)>0) {
                                bom.setLastRunningKilometers(tempMainMileage);
                            } else {
                                // 没有填写 保养时的 累计运行时长 累计运行公里数 使用运行记录中的累计运行数据反写
                                if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                                    IotDeviceRunLogRespVO deviceRunLog = deviceRunLogMap.get(bom.getDeviceId());
                                    totalMileage = deviceRunLog.getTotalMileage();
                                } else {
                                    // 保养项绑定的 累计公里数类型的属性值
                                    String uniqueKeyType = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                                    if (tempTotalRunDataPair.containsKey(uniqueKeyType)) {
                                        totalMileage = tempTotalRunDataPair.get(uniqueKeyType);
                                    }
                                }
                                if (ObjUtil.isNotEmpty(totalMileage) && totalMileage.compareTo(BigDecimal.ZERO)>0) {
                                    bom.setLastRunningKilometers(totalMileage);
                                }
                            }
                        }
                    }
                    if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule() && completedBomNodeIds.contains(bom.getBomNodeId())) {
                        BigDecimal totalRunTime = BigDecimal.ZERO;
                        // 设置了 运行时间 保养规则
                        if (mainRuntimePair.containsKey(uniqueKey)) {
                            BigDecimal tempRuntime = mainRuntimePair.get(uniqueKey);
                            if (ObjUtil.isNotEmpty(tempRuntime) && tempRuntime.compareTo(BigDecimal.ZERO)>0) {
                                bom.setLastRunningTime(tempRuntime);
                            } else {
                                if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                                    IotDeviceRunLogRespVO deviceRunLog = deviceRunLogMap.get(bom.getDeviceId());
                                    totalRunTime = deviceRunLog.getTotalRunTime();
                                } else {
                                    // 保养项绑定的 累计时长 类型的属性值
                                    String uniqueKeyCode = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                                    if (tempTotalRunDataPair.containsKey(uniqueKeyCode)) {
                                        totalRunTime = tempTotalRunDataPair.get(uniqueKeyCode);
                                    }
                                }
                                if (ObjUtil.isNotEmpty(totalRunTime) && totalRunTime.compareTo(BigDecimal.ZERO)>0) {
                                    bom.setLastRunningTime(totalRunTime);
                                }
                            }
                        }
                    }
                    if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0 == bom.getNaturalDateRule() && completedBomNodeIds.contains(bom.getBomNodeId())) {
                        // 设置了 自然日期 保养规则
                        bom.setLastNaturalDate(LocalDateTime.now());
                    }
                    tobeUpdatedMainBomS.add(bom);
                });
                if (CollUtil.isNotEmpty(tobeUpdatedMainBomS)) {
                    iotMaintenanceBomMapper.updateBatch(tobeUpdatedMainBomS);
                }
            }
        }
        // 更新工单关联的保养计划 保养项 的 运行时间周期(H)
        if (CollUtil.isNotEmpty(mctDeviceIds) && CollUtil.isNotEmpty(mctBomNodeIds)) {
            IotMaintenanceBomPageReqVO mctReqVO = new IotMaintenanceBomPageReqVO();
            mctReqVO.setPlanId(updateObj.getPlanId());
            mctReqVO.setDeviceIds(new ArrayList<>(mctDeviceIds));
            mctReqVO.setBomNodeIds(new ArrayList<>(mctBomNodeIds));
            List<IotMaintenanceBomDO> mctMaintenanceBomS = iotMaintenanceBomService.getIotMainPlanBomList(mctReqVO);
            if (CollUtil.isNotEmpty(mctMaintenanceBomS)) {
                List<IotMaintenanceBomDO> mctMainBomS = new ArrayList<>();
                mctMaintenanceBomS.forEach(bom -> {
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                    if (deviceBomPair.containsKey(uniqueKey)) {
                        IotMainWorkOrderBomSaveReqVO workOrderBom = deviceBomPair.get(uniqueKey);
                        bom.setNextRunningTime(workOrderBom.getNextRunningTime());
                        mctMainBomS.add(bom);
                    }
                });
                if (CollUtil.isNotEmpty(mctMainBomS)) {
                    iotMaintenanceBomMapper.updateBatch(mctMainBomS);
                }
            }
        }

        // 查询关联的保养工单
        IotMainWorkOrderPageReqVO orderReqVO = new IotMainWorkOrderPageReqVO();
        orderReqVO.setOrderGroupId(updateObj.getOrderGroupId());
        List<IotMainWorkOrderDO> associateOrders = iotMainWorkOrderMapper.selectList(orderReqVO);
        List<IotMainWorkOrderDO> filteredOrders = associateOrders.stream()
                .filter(order -> !order.getId().equals(updateObj.getId()))
                .collect(Collectors.toList());

        // 如果当前工单已经关闭，需要同时将当前工单的关联工单 一并关闭
        if (2 == updateObj.getResult()) {
            if (CollUtil.isNotEmpty(filteredOrders)) {
                // 将关联工单设置为 已执行 直接删除
                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 直接删除
                IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
                reqVO.setWorkOrderIds(workOrderIds);
                List<IotMainWorkOrderBomDO> associateOrderBomS = iotMainWorkOrderBomMapper.selectList(reqVO);
                associateOrderBomS.forEach(order -> { order.setStatus(1); order.setDeleted(true); });
                iotMainWorkOrderBomMapper.updateBatch(associateOrderBomS);
            }
        }
        // todo 如果关联工单中的一个工单进行了保养(有2个保养项) 其中一个保养项已经保养完成 另外一个保养项 延期保养
        // todo 此时应该将当前保养工单的保养数据（保养起止时间 保养费用  其他费用 备注...保养项信息...）同步到 所有关联的 保养工单
        if (1 == updateObj.getResult()) {
            if (CollUtil.isNotEmpty(filteredOrders)) {
                filteredOrders.forEach(order -> {
                    order.setActualStartTime(updateObj.getActualStartTime());
                    order.setActualEndTime(updateObj.getActualEndTime());
                    order.setCost(updateObj.getCost());
                    order.setOtherCost(updateObj.getOtherCost());
                    order.setLaborCost(updateObj.getLaborCost());
                    order.setRemark(updateObj.getRemark());
                });
                // 更新 关联保养工单的相关数据
                iotMainWorkOrderMapper.updateBatch(filteredOrders);
            }
        }
        // 只扣减本地库存 不处理SAP库存
        // 保养基于单设备 出库时记录 单设备所属的部门id(即保养工单的部门id)
        if (CollUtil.isNotEmpty(factoryIds) && CollUtil.isNotEmpty(costCenterIds) && CollUtil.isNotEmpty(materialCodes)) {
            IotLockStockPageReqVO reqVO = new IotLockStockPageReqVO();
            reqVO.setFactoryIds(factoryIds);
            reqVO.setCostCenterIds(costCenterIds);
            reqVO.setMaterialCodes(materialCodes);
            processLockStock(reqVO, lockStockPair, updateObj, bomMaterialPair);
        }
    }

    /**
     * 批量处理 库存扣减
     * @param reqVO
     * @param lockStockPair
     */
    private void processLockStock(IotLockStockPageReqVO reqVO, Map<String, BigDecimal> lockStockPair,
                                  IotMainWorkOrderDO workOrder, Map<String, IotMainWorkOrderBomMaterialSaveReqVO> bomMaterialPair) {
        List<IotLockStockDO> lockStocks = iotLockStockMapper.selectList(reqVO);
        List<IotLockStockDO> tobeUpdatedLockStocks = new ArrayList<>();
        List<IotOutboundDO> outbounds = new ArrayList<>();
        if (CollUtil.isNotEmpty(lockStocks)) {
            lockStocks.forEach(lockStock -> {
                String tempKey = lockStock.getFactoryId() + String.valueOf(lockStock.getCostCenterId())
                        + lockStock.getMaterialCode();
                BigDecimal outQuantity = lockStockPair.get(tempKey);
                if (lockStockPair.containsKey(tempKey)) {
                    lockStock.setQuantity(lockStock.getQuantity().subtract(outQuantity));
                    tobeUpdatedLockStocks.add(lockStock);
                }
            });
        }
        // 记录出库 最好是保存 保养项id 方便后续统计 数据分析
        if (CollUtil.isNotEmpty(bomMaterialPair)) {
            bomMaterialPair.forEach((k,v) -> {
                // k工厂id-成本中心id-物料编码-bomId
                // 每个物料都对应一条出库记录
                IotOutboundDO outbound = new IotOutboundDO();
                outbound.setBizId(workOrder.getId());
                outbound.setDeptId(workOrder.getDeptId());
                outbound.setFactoryId(v.getFactoryId());
                outbound.setCostCenterId(v.getCostCenterId());
                outbound.setBomNodeId(v.getBomNodeId());
                outbound.setMaterialCode(v.getMaterialCode());
                outbound.setMaterialName(v.getMaterialName());
                outbound.setQuantity(v.getQuantity());
                outbound.setUnitPrice(v.getUnitPrice());
                outbound.setUnit(v.getUnit());
                outbound.setType(2);
                outbound.setDeliveryTime(LocalDateTime.now());
                outbound.setReason("保养");
                outbound.setCreator(workOrder.getCreator());
                outbound.setUpdater(workOrder.getUpdater());
                outbounds.add(outbound);
            });
        }
        if (CollUtil.isNotEmpty(tobeUpdatedLockStocks)) {
            // 扣减本地库存
            iotLockStockMapper.updateBatch(tobeUpdatedLockStocks);
        }
        // 先删除当前 保养工单 下的物料出库记录
        iotOutboundMapper.updateToDelete(workOrder.getId(), LocalDateTime.now(), "保养");
        // 添加出库记录
        if (CollUtil.isNotEmpty(outbounds)) {
            iotOutboundMapper.insertBatch(outbounds);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addWorkOrder(IotMainWorkOrderSaveVO saveVO) {
        if (ObjectUtil.isEmpty(saveVO)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        IotMainWorkOrderSaveReqVO mainWorkOrder = saveVO.getMainWorkOrder();
        if (ObjectUtil.isEmpty(saveVO.getMainWorkOrder())) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        List<IotMainWorkOrderBomSaveReqVO> workOrderBOMs = saveVO.getMainWorkOrderBom();
        if (ObjectUtil.isEmpty(workOrderBOMs)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        List<IotMainWorkOrderBomMaterialSaveReqVO> workOrderMaterials = saveVO.getMainWorkOrderMaterials();
        if (ObjectUtil.isEmpty(workOrderMaterials)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        // 保养工单 主表 还有 保养费用 、人工费用 、其他费用
        IotMainWorkOrderDO order = BeanUtils.toBean(mainWorkOrder, IotMainWorkOrderDO.class);
        // 生成保养工单号
        order.setOrderNumber(createWorkOrderNumber());
        order.setType(2);
        order.setResult(2);
        // 如果前端没有获取到部门信息 deptId 则使用设备所属部门 作为工单的deptId
        if (ObjUtil.isEmpty(order.getDeptId())) {
            List<Long> deviceIds = new ArrayList<>();
            // 查询 mainWorkOrderBom 集合内部各设备所在部门deptId
            workOrderBOMs.forEach(bom -> {
                deviceIds.add(bom.getDeviceId());
            });
            IotDevicePageReqVO reqVO = new IotDevicePageReqVO();
            reqVO.setDeviceIds(deviceIds);
            List<IotDeviceDO> devices = iotDeviceMapper.selectListAlone(reqVO);
            if (CollUtil.isNotEmpty(devices)) {
                devices.forEach(device -> {
                    order.setDeptId(device.getDeptId());
                });
            }
        }
        iotMainWorkOrderMapper.insert(order);

        // 添加附件 先删除再新增
        // 更新工单的附件记录 rq_iot_attachment
        List<IotAttachmentSaveReqVO> attachmentReqVO = mainWorkOrder.getAttachments();
        List<IotAttachmentDO> tobeAddedAttachments = new ArrayList<>();
        if (CollUtil.isNotEmpty(attachmentReqVO)) {
            attachmentReqVO.forEach(att -> {
                IotAttachmentDO attachment = new IotAttachmentDO();
                attachment.setBizId(order.getId());
                attachment.setCategory(AttachmentCategoryEnum.MAINTENANCE.getCode());
                attachment.setType(AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode());
                attachment.setFilePath(att.getFilePath());
                attachment.setFileSize(att.getFileSize());
                attachment.setFilename(att.getFilename());
                attachment.setFileType(att.getFileType());
                attachment.setRemark(att.getRemark());
                tobeAddedAttachments.add(attachment);
            });
        }
        // 先删除附件再新增附件
        int count = iotAttachmentMapper.deleteByMap(ImmutableMap.of(
                "category", AttachmentCategoryEnum.MAINTENANCE.getCode(),
                "biz_id", order.getId(),
                "type", AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode()
        ));
        if (CollUtil.isNotEmpty(tobeAddedAttachments)) {
            iotAttachmentMapper.insertBatch(tobeAddedAttachments);
        }

        // 保养工单明细
        Set<Long> bomNodeIds = new HashSet<>();
        Set<Long> deviceIds = new HashSet<>();
        Set<String> bomNodeNames = new HashSet<>();
        // key设备id-保养项id   value保养工单明细对象(包含累计公里/时长)
        Map<String, IotMainWorkOrderBomSaveReqVO> bomAccumulatedValuePair = new HashMap<>();
        // key设备id-保养项名称   value保养工单明细对象(包含累计公里/时长)
        Map<String, IotMainWorkOrderBomSaveReqVO> bomNameAccumulatedValuePair = new HashMap<>();
        List<IotMainWorkOrderBomDO> workOrderBomDOS = new ArrayList<>();
        workOrderBOMs.forEach(bom -> {
            bomAccumulatedValuePair.put(StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId()), bom);
            bomNameAccumulatedValuePair.put(StrUtil.join("-", bom.getDeviceId(), bom.getName()), bom);
            IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
            tempBom.setWorkOrderId(order.getId());
            tempBom.setLastRunningTime(bom.getTotalRunTime());
            tempBom.setLastRunningKilometers(bom.getTotalMileage());
            tempBom.setLastNaturalDate(LocalDateTimeUtil.now());
            tempBom.setDeviceCategoryId(bom.getAssetClass());
            tempBom.setStatus(1);
            workOrderBomDOS.add(tempBom);
            bomNodeIds.add(bom.getBomNodeId());
            deviceIds.add(bom.getDeviceId());
            bomNodeNames.add(bom.getName());
        });
        iotMainWorkOrderBomMapper.insertBatch(workOrderBomDOS);
        // 查找各保养项对应的保养计划中的保养项 根据保养计划中的保养规则 更新 上次保养里程 or 上次保养时长 or 上次保养时间
        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(bomNodeIds) && CollUtil.isNotEmpty(bomNodeNames)
                && CollUtil.isNotEmpty(bomAccumulatedValuePair)) {
            IotMaintenanceBomPageReqVO reqVO = new IotMaintenanceBomPageReqVO();
            reqVO.setDeviceIds(new ArrayList<>(deviceIds));
            reqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));   // 保养项id 可能变化 重新生成设备BOM
            List<IotMaintenanceBomDO> planBomS = iotMaintenanceBomService.getIotMainPlanBomList(reqVO);
            if (CollUtil.isNotEmpty(planBomS)) {
                planBomS.forEach(bom -> {
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId());
                    if (bomAccumulatedValuePair.containsKey(uniqueKey)) {
                        IotMainWorkOrderBomSaveReqVO saveReqVO = bomAccumulatedValuePair.get(uniqueKey);
                        if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0==bom.getRunningTimeRule()) {
                            bom.setLastRunningTime(saveReqVO.getTotalRunTime());
                        }
                        if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0==bom.getMileageRule()) {
                            bom.setLastRunningKilometers(saveReqVO.getTotalMileage());
                        }
                        if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0==bom.getNaturalDateRule()) {
                            bom.setLastNaturalDate(LocalDateTime.now());
                        }
                    }
                });
                // 批量更新保养计划明细
                iotMaintenanceBomMapper.updateBatch(planBomS);
            }
            // 根据保养项名称更新上次保养 公里数 时长
            IotMaintenanceBomPageReqVO bomReqVO = new IotMaintenanceBomPageReqVO();
            bomReqVO.setDeviceIds(new ArrayList<>(deviceIds));
            bomReqVO.setBomNodeNames(new ArrayList<>(bomNodeNames));   // 保养项id 可能变化 重新生成设备BOM
            List<IotMaintenanceBomDO> planBomNames = iotMaintenanceBomService.getIotMainPlanBomList(bomReqVO);
            if (CollUtil.isNotEmpty(planBomNames)) {
                planBomNames.forEach(bom -> {
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getName());
                    if (bomNameAccumulatedValuePair.containsKey(uniqueKey)) {
                        IotMainWorkOrderBomSaveReqVO saveReqVO = bomNameAccumulatedValuePair.get(uniqueKey);
                        if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0==bom.getRunningTimeRule()) {
                            bom.setLastRunningTime(saveReqVO.getTotalRunTime());
                        }
                        if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0==bom.getMileageRule()) {
                            bom.setLastRunningKilometers(saveReqVO.getTotalMileage());
                        }
                        if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0==bom.getNaturalDateRule()) {
                            bom.setLastNaturalDate(LocalDateTime.now());
                        }
                    }
                });
                // 批量更新保养计划明细
                iotMaintenanceBomMapper.updateBatch(planBomNames);
            }
        }

        // 保养工单 bom 明细关联的物料消耗
        List<IotMainWorkOrderBomMaterialDO> workOrderBomMaterialDOS = new ArrayList<>();
        Set<Long> factoryIds = new HashSet<>();
        Set<Long> costCenterIds = new HashSet<>();
        Set<Long> storageLocationIds = new HashSet<>();
        Set<String> materialCodes = new HashSet<>();
        // key工厂id-成本中心id-物料编码-bomId      value物料数量
        Map<String, BigDecimal> lockStockPair = new HashMap<>();
        // key工厂id-成本中心id-物料编码-bomId        value保养项物料对象
        Map<String, IotMainWorkOrderBomMaterialSaveReqVO> bomMaterialPair = new HashMap<>();
        workOrderMaterials.forEach(material -> {
            IotMainWorkOrderBomMaterialDO tempMaterial = BeanUtil.copyProperties(material, IotMainWorkOrderBomMaterialDO.class, "id");
            tempMaterial.setWorkOrderId(order.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() + material.getBomNodeId() ;
                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);
            }
            iotMainWorkOrderBomMaterialMapper.insert(tempMaterial);
        });
        // 暂时不用 insertBatch 看看是否能避免 java.sql.BatchUpdateException: Duplicate entry 异常
        // iotMainWorkOrderBomMaterialMapper.insertBatch(workOrderBomMaterialDOS);
        // 只扣减本地库存 不处理SAP库存 同时 记录出库
        if (CollUtil.isNotEmpty(factoryIds) && CollUtil.isNotEmpty(costCenterIds) && CollUtil.isNotEmpty(materialCodes)) {
            IotLockStockPageReqVO reqVO = new IotLockStockPageReqVO();
            reqVO.setFactoryIds(factoryIds);
            reqVO.setCostCenterIds(costCenterIds);
            reqVO.setMaterialCodes(materialCodes);
            processLockStock(reqVO, lockStockPair, order, bomMaterialPair);
        }
    }

    @Override
    public List<Map<String, Integer>> allWorkOrderCountByResult() {
        return iotMainWorkOrderMapper.allWorkOrderCountByResult();
    }

    @Override
    public PageResult<IotMainWorkOrderRespVO> deviceOrderPage(IotMainWorkOrderPageReqVO pageReqVO) {
        if (ObjUtil.isEmpty(pageReqVO.getDeviceId())) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        try {
            // 根据设备id查询与此设备关联的保养工单列表
            IPage<IotMainWorkOrderRespVO> page = iotMainWorkOrderMapper.deviceOrderPage(
                    new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO.getDeviceId());
            return new PageResult<>(page.getRecords(), page.getTotal());
        } catch (Exception exception) {
            if (exception.getMessage().contains("Table does not exist")) {
                return PageResult.empty();
            }
            throw exception;
        }
    }

    @Override
    public IotMainWorkOrderRespVO getDeviceMainWorkOrder(Long workOrderId, Long deviceId) {
        if (ObjUtil.isEmpty(workOrderId) || ObjUtil.isEmpty(deviceId)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        // 查询指定 保养工单 下 根据 设备筛选的保养明细
        IotMainWorkOrderDO workOrder = iotMainWorkOrderMapper.selectById(workOrderId);
        IotMainWorkOrderRespVO workOrderResp = BeanUtils.toBean(workOrder, IotMainWorkOrderRespVO.class);
        IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
        reqVO.setWorkOrderId(workOrderId);
        reqVO.setDeviceId(deviceId);
        List<IotMainWorkOrderBomDO> deviceWorkOrderBomS = iotMainWorkOrderBomMapper.selectList(reqVO);
        Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(deviceWorkOrderBomS,
                bom -> Stream.of(bom.getDeviceId())));
        List<IotMainWorkOrderBomRespVO> deviceOrderBomS = BeanUtils.toBean(deviceWorkOrderBomS, IotMainWorkOrderBomRespVO.class, bomVO -> {
            // 设置设备相关信息
            MapUtils.findAndThen(deviceMap, bomVO.getDeviceId(),
                    device -> bomVO.setDeviceName(device.getDeviceName()));
            MapUtils.findAndThen(deviceMap, bomVO.getDeviceId(),
                    device -> bomVO.setDeviceCode(device.getDeviceCode()));
        });
        workOrderResp.setWorkOrderBomS(deviceOrderBomS);
        return workOrderResp;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncWorkOrderData(List<Long> deviceIds, Long deptId, List<Long> personIds, Map<Long, Long> oldDeptPair) {
        if (CollUtil.isEmpty(deviceIds) || CollUtil.isEmpty(personIds)) {
            return;
        }
        // 查询设备集合修改前的部门
        IotDevicePageReqVO deviceReqVO = new IotDevicePageReqVO();
        deviceReqVO.setDeviceIds(deviceIds);
        List<IotDeviceDO> devices = iotDeviceMapper.selectList(deviceReqVO);
        Set<Long> deviceDeptIds = devices.stream()
                .map(IotDeviceDO::getDeptId)    // 提取deptId属性
                .collect(Collectors.toSet());   // 收集为Set以保证唯一性

        // 查询当前设备对应的 责任人 前端页面已经统一设置了相同的责任人(有可能多个 多个责任人时随机取1个)
        Long personId = personIds.get(0);
        // 查询当前设备集合的所有历史保养工单 如果历史工单明细中包含2个设备A,B，但是只调拨了1个设备A，此时 只复制设备A明细生成一个 执行完成的保养工单
        IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
        reqVO.setDeviceIds(deviceIds);
        reqVO.setStatus(1);
        // 查询已经保养完成的工单
        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.historyWorkOrderBomS(new ArrayList<>(deviceDeptIds), deviceIds);
        // 以 工单id+设备id+设备负责人 为维度组成 工单BOM集合
        Map<Long, List<IotMainWorkOrderBomDO>> workOrderBomPair = new HashMap<>();
        Map<Long, IotMainWorkOrderDO> workOrderPair = new HashMap<>();
        Set<Long> workOrderIds = new HashSet<>();
        Set<Long> bomNodeIds = new HashSet<>();
        // 需要新增的保养工单集合 (历史工单)
        List<IotMainWorkOrderDO> tobeAddedOrders = new ArrayList<>();
        // 需要新增的保养工单明细集合 (历史工单)
        List<IotMainWorkOrderBomDO> tobeAddedOrderBomS = new ArrayList<>();
        // 需要新增的保养工单物料明细集合 (历史工单)
        List<IotMainWorkOrderBomMaterialDO> tobeAddedOrderBomMaterialS = new ArrayList<>();
        if (CollUtil.isNotEmpty(workOrderBomS)) {
            workOrderBomS.forEach(bom -> {
                if (workOrderBomPair.containsKey(bom.getWorkOrderId())) {
                    List<IotMainWorkOrderBomDO> tempBomS = workOrderBomPair.get(bom.getWorkOrderId());
                    tempBomS.add(bom);
                    workOrderBomPair.put(bom.getWorkOrderId(), tempBomS);
                } else {
                    List<IotMainWorkOrderBomDO> tempBomS = new ArrayList<>();
                    tempBomS.add(bom);
                    workOrderBomPair.put(bom.getWorkOrderId(), tempBomS);
                }
                workOrderIds.add(bom.getWorkOrderId());
                bomNodeIds.add(bom.getBomNodeId());
            });
            // 查询 指定保养工单集合内 bomNodeId集合内 的挂载的物料集合
            Map<String, List<IotMainWorkOrderBomMaterialDO>> workOrderMaterialPair = new HashMap<>();
            IotMainWorkOrderBomMaterialPageReqVO materialReqVO = new IotMainWorkOrderBomMaterialPageReqVO();
            materialReqVO.setWorkOrderIds(new ArrayList<>(workOrderIds));
            materialReqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));
            List<IotMainWorkOrderBomMaterialDO> historyBomMaterials = iotMainWorkOrderBomMaterialMapper.selectList(materialReqVO);
            if (CollUtil.isNotEmpty(historyBomMaterials)) {
                historyBomMaterials.forEach(material -> {
                    String uniqueKey = StrUtil.join("-", material.getWorkOrderId(), material.getBomNodeId());
                    if (workOrderMaterialPair.containsKey(uniqueKey)) {
                        List<IotMainWorkOrderBomMaterialDO> tempMaterials = workOrderMaterialPair.get(uniqueKey);
                        tempMaterials.add(material);
                        workOrderMaterialPair.put(uniqueKey, tempMaterials);
                    } else {
                        List<IotMainWorkOrderBomMaterialDO> tempMaterials = new ArrayList<>();
                        tempMaterials.add(material);
                        workOrderMaterialPair.put(uniqueKey, tempMaterials);
                    }
                });
            }
            // 查询所有保养工单明细对应的保养工单主表集合
            IotMainWorkOrderPageReqVO orderReqVO = new IotMainWorkOrderPageReqVO();
            orderReqVO.setWorkOrderIds(new ArrayList<>(workOrderIds));
            orderReqVO.setResult(2);     // 查询已经执行完成的工单
            List<IotMainWorkOrderDO> workOrders = iotMainWorkOrderMapper.selectList(orderReqVO);
            if (CollUtil.isNotEmpty(workOrders)) {
                workOrders.forEach(order -> {
                    workOrderPair.put(order.getId(), order);
                });
            }
            // 根据历史工单 拷贝生成 新的历史工单
            // 查询最新的保养工单id
            AtomicReference<Long> theMaxId = new AtomicReference<>(theMaxId());
            workOrderBomPair.forEach((k, v) -> {
                // k保养工单id  v工单明细集合
                IotMainWorkOrderDO workOrder = new IotMainWorkOrderDO();
                theMaxId.getAndSet(theMaxId.get() + 1);
                workOrder.setId(theMaxId.get());
                workOrder.setDeptId(deptId);
                if (workOrderPair.containsKey(k)) {
                    workOrder.setName(workOrderPair.get(k).getName() + " 历史");
                    workOrder.setPlanId(workOrderPair.get(k).getPlanId());
                    workOrder.setType(workOrderPair.get(k).getType());
                    workOrder.setCost(workOrderPair.get(k).getCost());
                    workOrder.setOtherCost(workOrderPair.get(k).getOtherCost());
                    workOrder.setLaborCost(workOrderPair.get(k).getLaborCost());
                    workOrder.setActualStartTime(workOrderPair.get(k).getActualStartTime());
                    workOrder.setActualEndTime(workOrderPair.get(k).getActualEndTime());
                    workOrder.setRemark(workOrderPair.get(k).getRemark());
                }
                workOrder.setOrderNumber(createWorkOrderNumber());
                workOrder.setResult(2);
                workOrder.setResponsiblePerson(String.valueOf(personId));
                workOrder.setCreator(String.valueOf(personId));
                tobeAddedOrders.add(workOrder);
                // 2. 处理关联的BOM明细
                final long workOrderId = workOrder.getId(); // 获取当前工单ID
                v.forEach(bom -> {
                    IotMainWorkOrderBomDO tempBom = new IotMainWorkOrderBomDO();
                    BeanUtils.copyProperties(bom,tempBom);
                    tempBom.setId(null);
                    tempBom.setWorkOrderId(workOrderId);
                    tobeAddedOrderBomS.add(tempBom);
                    // 3. 处理BOM明细关联的物料
                    String uniqueKey = StrUtil.join("-", k, bom.getBomNodeId());
                    if (workOrderMaterialPair.containsKey(uniqueKey)) {
                        List<IotMainWorkOrderBomMaterialDO> bomMaterials = workOrderMaterialPair.get(uniqueKey);
                        bomMaterials.forEach(material -> {
                            IotMainWorkOrderBomMaterialDO tempMaterial = new IotMainWorkOrderBomMaterialDO();
                            BeanUtils.copyProperties(material, tempMaterial);
                            tempMaterial.setId(null);
                            tempMaterial.setWorkOrderId(workOrderId);
                            tobeAddedOrderBomMaterialS.add(tempMaterial);
                        });
                    }
                });
            });
            // 插入 保养工单 保养工单明细 保养工单明细关联的物料
            if (CollUtil.isNotEmpty(tobeAddedOrders) && CollUtil.isNotEmpty(tobeAddedOrderBomS)) {
                iotMainWorkOrderMapper.insertBatch(tobeAddedOrders);
                iotMainWorkOrderBomMapper.insertBatch(tobeAddedOrderBomS);
            }
            // 插入 保养工单明细关联的物料
            if (CollUtil.isNotEmpty(tobeAddedOrderBomMaterialS)) {
                iotMainWorkOrderBomMaterialMapper.insertBatch(tobeAddedOrderBomMaterialS);
            }
        }
    }

    @Override
    public void syncRunningWorkOrder(List<Long> deviceIds, Long deptId, List<Long> personIds, Map<Long, Long> oldDeptPair) {
        if (CollUtil.isEmpty(deviceIds) || CollUtil.isEmpty(personIds)) {
            return;
        }
        // 查询设备集合修改前的部门
        Set<Long> deviceDeptIds = oldDeptPair != null ? new HashSet<>(oldDeptPair.values()) : new HashSet<>();

        // 查询当前设备对应的 责任人 前端页面已经统一设置了相同的责任人(有可能多个 多个责任人时随机取1个)
        Long personId = personIds.get(0);
        // 现在保养工单都是 单设备
        // 查询 执行中 的保养工单
        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.runningWorkOrderBomS(new ArrayList<>(deviceDeptIds), deviceIds);
        // 查询所有部门的名称信息
        deviceDeptIds.add(deptId);
        Map<Long, DeptDO> deptPair = deptService.getDeptMap(deviceDeptIds);

        // 以 工单id+设备id+设备负责人 为维度组成 工单BOM集合
        Map<Long, List<IotMainWorkOrderBomDO>> workOrderBomPair = new HashMap<>();
        Map<Long, IotMainWorkOrderDO> workOrderPair = new HashMap<>();
        Set<Long> workOrderIds = new HashSet<>();
        Set<Long> bomNodeIds = new HashSet<>();
        // 需要新增的保养工单集合 (历史工单)
        List<IotMainWorkOrderDO> tobeAddedOrders = new ArrayList<>();
        // 需要 更新 的保养工单集合 (部门 责任人 创建人 备注)
        List<IotMainWorkOrderDO> tobeUpdatedOrders = new ArrayList<>();
        // 需要新增的保养工单明细集合 (历史工单)
        List<IotMainWorkOrderBomDO> tobeAddedOrderBomS = new ArrayList<>();
        // 需要新增的保养工单物料明细集合 (历史工单)
        List<IotMainWorkOrderBomMaterialDO> tobeAddedOrderBomMaterialS = new ArrayList<>();
        if (CollUtil.isNotEmpty(workOrderBomS)) {
            workOrderBomS.forEach(bom -> {
                if (workOrderBomPair.containsKey(bom.getWorkOrderId())) {
                    List<IotMainWorkOrderBomDO> tempBomS = workOrderBomPair.get(bom.getWorkOrderId());
                    tempBomS.add(bom);
                    workOrderBomPair.put(bom.getWorkOrderId(), tempBomS);
                } else {
                    List<IotMainWorkOrderBomDO> tempBomS = new ArrayList<>();
                    tempBomS.add(bom);
                    workOrderBomPair.put(bom.getWorkOrderId(), tempBomS);
                }
                workOrderIds.add(bom.getWorkOrderId());
                bomNodeIds.add(bom.getBomNodeId());
            });
            // 查询所有保养工单明细对应的保养工单主表集合
            IotMainWorkOrderPageReqVO orderReqVO = new IotMainWorkOrderPageReqVO();
            orderReqVO.setWorkOrderIds(new ArrayList<>(workOrderIds));
            orderReqVO.setResult(1);
            // 查询 执行中 的保养工单
            List<IotMainWorkOrderDO> workOrders = iotMainWorkOrderMapper.selectList(orderReqVO);
            if (CollUtil.isNotEmpty(workOrders)) {
                workOrders.forEach(order -> {
                    workOrderPair.put(order.getId(), order);
                });
            }
            // 修改执行中保养工单的 部门 责任人 创建人 并添加备注信息
            workOrderBomPair.forEach((k, v) -> {
                // k保养工单id  v工单明细集合 单设备
                if (workOrderPair.containsKey(k)) {
                    // 获得当前设备对应的 历史部门id
                    Long bomDeviceId = v.get(0).getDeviceId();
                    Long oldDeptId = oldDeptPair.get(bomDeviceId);
                    // 获取当前时间的 yyyy-MM-dd HH:mm:ss 格式
                    String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND));
                    IotMainWorkOrderDO tempWorkOrder = workOrderPair.get(k);
                    tempWorkOrder.setDeptId(deptId);
                    tempWorkOrder.setResponsiblePerson(String.valueOf(personId));
                    tempWorkOrder.setCreator(String.valueOf(personId));
                    String historyRemark = StrUtil.isNotBlank(tempWorkOrder.getRemark()) ? tempWorkOrder.getRemark() : StrUtil.EMPTY;
                    tempWorkOrder.setRemark(historyRemark + "\n" + currentDate +" 设备从 " + deptPair.get(oldDeptId).getName()
                            + " 调拨到部门 " + deptPair.get(deptId).getName());
                    tobeUpdatedOrders.add(tempWorkOrder);
                }
            });
            // 更新 执行中的 保养工单
            if (CollUtil.isNotEmpty(tobeUpdatedOrders)) {
                iotMainWorkOrderMapper.updateBatch(tobeUpdatedOrders);
            }
        }
    }

    @Override
    public List<IotMainWorkOrderBomDO> historyWorkOrderBoms(Set<Long> deviceIds, Set<Long> bomNodeIds) {
        IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
        reqVO.setDeviceIds(new ArrayList<>(deviceIds));
        reqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));
        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.latestWorkOrderBomS(reqVO);
        return CollUtil.isNotEmpty(workOrderBomS) ? workOrderBomS : new ArrayList<>();
    }

    @Override
    public void historyWorkOrderOutbound() {
        // 查询历史已经执行完成的保养工单 将保养工单关联的物料 添加出库记录
        // 查询已经执行完成的保养工单
        // 查询保养工单明细 status=1 记录 即查询出所有已经保养完成的保养项 但是保养工单不一定执行完成
        IotMainWorkOrderBomPageReqVO bomReqVO = new IotMainWorkOrderBomPageReqVO();
        bomReqVO.setStatus(1);
        List<IotMainWorkOrderBomDO> workOrderBoms = iotMainWorkOrderBomMapper.selectList(bomReqVO);
        if (CollUtil.isNotEmpty(workOrderBoms)) {
            List<IotOutboundDO> outbounds = new ArrayList<>();
            Set<Long> workOrderIds = new HashSet<>();
            Set<Long> bomNodeIds = new HashSet<>();
            workOrderBoms.forEach(bom -> {
                workOrderIds.add(bom.getWorkOrderId());
                bomNodeIds.add(bom.getBomNodeId());
            });
            if (CollUtil.isNotEmpty(workOrderIds) && CollUtil.isNotEmpty(bomNodeIds)) {
                // 查询保养工单id
                IotMainWorkOrderPageReqVO orderReqVO = new IotMainWorkOrderPageReqVO();
                orderReqVO.setWorkOrderIds(new ArrayList<>(workOrderIds));
                List<IotMainWorkOrderDO> finishedOrders = iotMainWorkOrderMapper.selectList(orderReqVO);
                Map<Long, Long> orderDeptPair = new HashMap<>();
                if (CollUtil.isNotEmpty(finishedOrders)) {
                    finishedOrders.forEach(order -> {
                        orderDeptPair.put(order.getId(), order.getDeptId());
                    });
                }
                // 查询保养工单明细关联的所有物料
                IotMainWorkOrderBomMaterialPageReqVO reqVO = new IotMainWorkOrderBomMaterialPageReqVO();
                reqVO.setWorkOrderIds(new ArrayList<>(workOrderIds));
                reqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));
                List<IotMainWorkOrderBomMaterialDO> bomMaterials = iotMainWorkOrderBomMaterialMapper.selectList(reqVO);
                if (CollUtil.isNotEmpty(bomMaterials)) {
                    bomMaterials.forEach(material -> {
                        if ("本地库存".equals(material.getMaterialSource())) {
                            IotOutboundDO outbound = new IotOutboundDO();
                            if (orderDeptPair.containsKey(material.getWorkOrderId())) {
                                outbound.setDeptId(orderDeptPair.get(material.getWorkOrderId()));
                            }
                            outbound.setBomNodeId(material.getBomNodeId());
                            outbound.setFactoryId(material.getFactoryId());
                            outbound.setCostCenterId(material.getCostCenterId());
                            outbound.setMaterialCode(material.getMaterialCode());
                            outbound.setMaterialName(material.getMaterialName());
                            outbound.setQuantity(material.getQuantity());
                            outbound.setUnitPrice(material.getUnitPrice());
                            outbound.setUnit(material.getUnit());
                            outbound.setType(2);
                            outbound.setDeliveryTime(material.getCreateTime());
                            outbound.setReason("保养");
                            outbound.setCreator(material.getCreator());
                            outbound.setCreateTime(material.getCreateTime());
                            outbound.setUpdater(material.getUpdater());
                            outbound.setUpdateTime(material.getUpdateTime());
                            outbounds.add(outbound);
                        }
                    });
                }
            }
            if (CollUtil.isNotEmpty(outbounds)) {
                iotOutboundMapper.insertBatch(outbounds);
            }
        }
    }

    @Override
    public void modifyWorkOrder(IotMainWorkOrderSaveVO saveVO) {
        if (ObjectUtil.isEmpty(saveVO)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        IotMainWorkOrderSaveReqVO mainWorkOrder = saveVO.getMainWorkOrder();
        if (ObjectUtil.isEmpty(saveVO.getMainWorkOrder())) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        List<IotMainWorkOrderBomSaveReqVO> workOrderBOMs = saveVO.getMainWorkOrderBom();
        if (ObjectUtil.isEmpty(workOrderBOMs)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        // 保养项物料列表
        List<IotMainWorkOrderBomMaterialSaveReqVO> workOrderMaterials = saveVO.getMainWorkOrderMaterials();

        // 保养工单 主表 保养费用 备注
        IotMainWorkOrderDO updateObj = BeanUtils.toBean(mainWorkOrder, IotMainWorkOrderDO.class);

        // 保养工单 保养项列表
        List<IotMainWorkOrderBomDO> workOrderBomDOS = new ArrayList<>();

        // 保养工单 保养项bom 关联的物料列表
        List<IotMainWorkOrderBomMaterialDO> workOrderBomMaterialDOS = new ArrayList<>();
        Set<Long> factoryIds = new HashSet<>();
        Set<Long> costCenterIds = new HashSet<>();
        Set<Long> storageLocationIds = new HashSet<>();
        Set<String> materialCodes = new HashSet<>();
        Map<String, BigDecimal> lockStockPair = new HashMap<>();
        // key工厂id-成本中心id-物料编码-bomId        value保养项物料对象
        Map<String, IotMainWorkOrderBomMaterialSaveReqVO> bomMaterialPair = new HashMap<>();

        // 需要消耗物料的保养项id集合
        Set<Long> materialBomNodeIds = new HashSet<>();
        // 统计保养项信息 是否需要消耗物料
        workOrderBOMs.forEach(bom -> {
            if ("0".equals(bom.getRule())) {
                // 需要消耗物料
                materialBomNodeIds.add(bom.getBomNodeId());
            }
            // 每个保养项都已经保养完成
        });
        // 保养完成的保养项关联的物料 统计出库(物料来源是 本地库存)
        workOrderMaterials.forEach(material -> {
            // 兼容保养工单中部分保养项保养完成 部分保养项延期保养的情况 这里只新增延期保养 或 正常保养的物料
            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 (ObjUtil.isNotEmpty(updateObj.getId())) {
            iotMainWorkOrderBomMaterialMapper.updateToDelete(updateObj.getId(), LocalDateTime.now());
        }
        // 添加 工单消耗的物料记录
        if (CollUtil.isNotEmpty(workOrderBomMaterialDOS)) {
            iotMainWorkOrderBomMaterialMapper.insertBatch(workOrderBomMaterialDOS);
        }

        workOrderBOMs.forEach(bom -> {
            IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
            // 有可能修改 保养项 字段 是否消耗物料
            workOrderBomDOS.add(tempBom);
        });
        if (CollUtil.isNotEmpty(workOrderBomDOS)) {
            iotMainWorkOrderBomMapper.updateBatch(workOrderBomDOS);
        }
        // 保养工单 已经是 已执行的状态
        // 设置 status = 0 修改完成
        updateObj.setStatus(0);
        iotMainWorkOrderMapper.updateById(updateObj);

        // 只扣减本地库存 不处理SAP库存
        // 保养基于单设备 出库时记录 单设备所属的部门id(即保养工单的部门id)
        if (CollUtil.isNotEmpty(factoryIds) && CollUtil.isNotEmpty(costCenterIds) && CollUtil.isNotEmpty(materialCodes)) {
            IotLockStockPageReqVO reqVO = new IotLockStockPageReqVO();
            reqVO.setFactoryIds(factoryIds);
            reqVO.setCostCenterIds(costCenterIds);
            reqVO.setMaterialCodes(materialCodes);
            processLockStock(reqVO, lockStockPair, updateObj, bomMaterialPair);
        }
    }

    @Override
    public void hiWorkOrderAttachment(IotMainWorkOrderSaveReqVO orderReqVO) {
        if (ObjectUtil.isEmpty(orderReqVO)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        Long mainWorkOrderId = orderReqVO.getId();
        if (ObjectUtil.isEmpty(mainWorkOrderId)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }
        // 查询保养工单是否存在
        IotMainWorkOrderDO workOrder = iotMainWorkOrderMapper.selectById(mainWorkOrderId);
        if (ObjectUtil.isEmpty(workOrder)) {
            throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
        }

        // 更新工单的附件记录 rq_iot_attachment
        List<IotAttachmentSaveReqVO> attachmentReqVO = orderReqVO.getAttachments();
        List<IotAttachmentDO> tobeAddedAttachments = new ArrayList<>();
        if (CollUtil.isNotEmpty(attachmentReqVO)) {
            attachmentReqVO.forEach(att -> {
                IotAttachmentDO attachment = new IotAttachmentDO();
                attachment.setBizId(mainWorkOrderId);
                attachment.setCategory(AttachmentCategoryEnum.MAINTENANCE.getCode());
                attachment.setType(AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode());
                attachment.setFilePath(att.getFilePath());
                attachment.setFileSize(att.getFileSize());
                attachment.setFilename(att.getFilename());
                attachment.setFileType(att.getFileType());
                attachment.setRemark(att.getRemark());
                tobeAddedAttachments.add(attachment);
            });
        }
        // 先删除附件再新增附件
        int count = iotAttachmentMapper.deleteByMap(ImmutableMap.of(
                "category", AttachmentCategoryEnum.MAINTENANCE.getCode(),
                "biz_id", mainWorkOrderId,
                "type", AttachmentTypeEnum.MAINTENANCE_STANDARD.getCode()
        ));
        if (CollUtil.isNotEmpty(tobeAddedAttachments)) {
            iotAttachmentMapper.insertBatch(tobeAddedAttachments);
        }
    }

    private static class Triple<A, B, C> {
        private final A first;
        private final B second;
        private final C third;

        public Triple(A first, B second, C third) {
            this.first = first;
            this.second = second;
            this.third = third;
        }

        public A getFirst() {
            return first;
        }

        public B getSecond() {
            return second;
        }

        public C getThird() {
            return third;
        }
    }
}