cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
@@ -17,14 +17,17 @@
import com.ruoyi.common.utils.WxCpUtils;
import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
import com.ruoyi.device.mapper.DeviceMaintenancePlanDetailsMapper;
import com.ruoyi.device.mapper.DeviceMaintenancePlanMapper;
import com.ruoyi.device.mapper.DeviceMapper;
import com.ruoyi.device.pojo.Device;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.pojo.DeviceMaintenancePlan;
import com.ruoyi.device.pojo.DeviceMaintenancePlanDetails;
import com.ruoyi.device.service.DeviceMaintenancePlanDetailsService;
import com.ruoyi.device.service.DeviceMaintenancePlanService;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
import com.ruoyi.inspect.util.UserUtils;
import com.ruoyi.system.mapper.UserMapper;
@@ -42,9 +45,12 @@
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -71,6 +77,8 @@
    private UserMapper userMapper;
    @Resource
    private DeviceMapper deviceMapper;
    @Resource
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
    /**
     * 分页查询设备保养计划
@@ -328,20 +336,38 @@
    }
    /**
     * 设备档案变更后,把已到点的设备同步到对应年份的设备保养计划明细
     * 保养计划年份列表:删除没有任何明细的空年份计划后,返回有明细的年份(倒序)
     */
    @Override
    public List<String> getMaintenancePlanYears() {
        // 历史数据自愈:先让已审核的明细按审核完成年份归位,再清理空年份计划
        reclassifyReviewedDetails();
        List<DeviceMaintenancePlan> plans = baseMapper.selectList(null);
        for (DeviceMaintenancePlan plan : plans) {
            Long detailCount = deviceMaintenancePlanDetailsMapper.selectCount(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                    .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId()));
            if (detailCount == null || detailCount == 0) {
                removeById(plan.getMaintenancePlanId());
            }
        }
        return baseMapper.selectPlanYears();
    }
    /**
     * 设备档案变更后,立即把设备登记到下次保养日期所在年份的设备保养计划明细
     */
    @Override
    public void syncDeviceMaintenanceDetail(Device device) {
        LocalDate remind = calcMaintenanceRemindDate(device);
        if (remind == null || remind.isAfter(LocalDate.now())) {
        LocalDate due = calcMaintenanceDueDate(device);
        if (due == null) {
            return;
        }
        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(remind.getYear()));
        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(due.getYear()));
        fillMaintenanceDetailIfAbsent(plan, device);
    }
    /**
     * 保养记录保存后,滚动设备档案的最近/下次保养日期
     * 审核通过后,按审核通过当天滚动设备档案的最近/下次保养日期
     */
    @Override
    public void updateDeviceMaintenanceDate(Integer deviceId, LocalDate maintenanceDate) {
@@ -365,6 +391,75 @@
            }
        }
        deviceMapper.updateById(device);
        // 本轮保养完成后立即登记下一轮,历史明细保持不动
        syncDeviceMaintenanceDetail(device);
    }
    /**
     * 审核通过后,把计划明细归到审核完成年份的计划下;提前完成的明细不再留在原计划年份
     */
    @Override
    public void reclassifyMaintenanceDetailByReviewDate(Integer maintenancePlanDetailId, LocalDate reviewDate) {
        if (maintenancePlanDetailId == null || reviewDate == null) {
            return;
        }
        DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
        if (detail == null || detail.getMaintenancePlanId() == null) {
            return;
        }
        DeviceMaintenancePlan sourcePlan = baseMapper.selectById(detail.getMaintenancePlanId());
        if (sourcePlan == null) {
            return;
        }
        String targetYear = String.valueOf(reviewDate.getYear());
        if (StringUtils.isNotBlank(sourcePlan.getPlanYear()) && targetYear.equals(sourcePlan.getPlanYear().trim())) {
            return;
        }
        moveMaintenanceDetailToYear(detail, reviewDate);
    }
    /**
     * 把计划明细挪到审核完成年份的计划下,计划日期按实际完成日期记录
     */
    private void moveMaintenanceDetailToYear(DeviceMaintenancePlanDetails detail, LocalDate reviewDate) {
        DeviceMaintenancePlan targetPlan = getOrCreatePlanEntity(String.valueOf(reviewDate.getYear()));
        DeviceMaintenancePlanDetails update = new DeviceMaintenancePlanDetails();
        update.setMaintenancePlanDetailId(detail.getMaintenancePlanDetailId());
        update.setMaintenancePlanId(targetPlan.getMaintenancePlanId());
        // 计划日期按实际完成日期记录,列表按完成年份排序,不再按原计划日期标红
        update.setMaintenanceDate(reviewDate);
        deviceMaintenancePlanDetailsMapper.updateById(update);
    }
    /**
     * 历史数据自愈:已审核通过但计划年份与审核完成年份不一致的明细,按审核完成年份归位
     */
    private void reclassifyReviewedDetails() {
        List<DeviceMaintenance> records = deviceMaintenanceMapper.selectList(Wrappers.<DeviceMaintenance>lambdaQuery()
                .eq(DeviceMaintenance::getReviewStatus, 1)
                .isNotNull(DeviceMaintenance::getMaintenancePlanDetailId)
                .isNotNull(DeviceMaintenance::getReviewTime));
        if (CollectionUtils.isEmpty(records)) {
            return;
        }
        List<Integer> detailIds = records.stream().map(DeviceMaintenance::getMaintenancePlanDetailId)
                .distinct().collect(Collectors.toList());
        Map<Integer, DeviceMaintenancePlanDetails> detailMap = deviceMaintenancePlanDetailsMapper.selectBatchIds(detailIds)
                .stream().collect(Collectors.toMap(DeviceMaintenancePlanDetails::getMaintenancePlanDetailId, item -> item, (a, b) -> a));
        Map<Integer, String> planYears = baseMapper.selectList(null).stream()
                .collect(Collectors.toMap(DeviceMaintenancePlan::getMaintenancePlanId,
                        item -> item.getPlanYear() == null ? "" : item.getPlanYear().trim(), (a, b) -> a));
        for (DeviceMaintenance record : records) {
            DeviceMaintenancePlanDetails detail = detailMap.get(record.getMaintenancePlanDetailId());
            if (detail == null || detail.getMaintenancePlanId() == null) {
                continue;
            }
            String reviewYear = String.valueOf(record.getReviewTime().getYear());
            if (reviewYear.equals(planYears.get(detail.getMaintenancePlanId()))) {
                continue;
            }
            moveMaintenanceDetailToYear(detail, record.getReviewTime().toLocalDate());
        }
    }
    /**
@@ -401,7 +496,7 @@
    }
    /**
     * 扫描所有设备,把已到点(下次保养日期前1个月起,含已过期)的设备补进本年度计划
     * 扫描所有设备,把应保养日期落在该计划年份的设备补进计划(当前年及以往年度包含逾期未保养的设备)
     */
    private void ensureMaintenanceDetails(DeviceMaintenancePlan plan) {
        List<Device> devices = deviceMapper.selectList(null);
@@ -411,9 +506,9 @@
    }
    /**
     * 设备提醒起点:下次保养日期前1个月(没填下次日期时用最近日期+保养周期兜底)
     * 设备本轮应保养日期(没填下次日期时用最近日期+保养周期兜底)
     */
    private LocalDate calcMaintenanceRemindDate(Device device) {
    private LocalDate calcMaintenanceDueDate(Device device) {
        LocalDateTime base = device.getNextMaintenanceDate();
        if (base == null && device.getLastMaintenanceDate() != null && StringUtils.isNotBlank(device.getMaintenanceCycle())) {
            try {
@@ -421,24 +516,42 @@
            } catch (NumberFormatException ignored) {
            }
        }
        return base == null ? null : base.toLocalDate().minusMonths(1);
        return base == null ? null : base.toLocalDate();
    }
    /**
     * 已到点且计划中尚无该设备本轮明细时自动补一条
     * 计划中尚无该设备本轮明细时自动补一条;保养时间取应保养日期
     */
    private void fillMaintenanceDetailIfAbsent(DeviceMaintenancePlan plan, Device device) {
        LocalDate remind = calcMaintenanceRemindDate(device);
        if (remind == null || remind.isAfter(LocalDate.now())) {
        LocalDate due = calcMaintenanceDueDate(device);
        if (due == null) {
            return;
        }
        if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
        if (StringUtils.isBlank(plan.getPlanYear())) {
            return;
        }
        int planYearValue;
        try {
            planYearValue = Integer.parseInt(plan.getPlanYear().trim());
        } catch (NumberFormatException e) {
            return;
        }
        int dueYear = due.getYear();
        // 未来年份的计划只登记应保养日期就在该年份的设备,避免把往年的旧日期补进未来的计划
        if (planYearValue > LocalDate.now().getYear()) {
            if (dueYear != planYearValue) {
                return;
            }
        } else if (dueYear > planYearValue) {
            return;
        }
        // 同一设备同一应保养月份只保留一条,避免手改过行内日期后刷新重复生成
        LocalDate monthStart = due.withDayOfMonth(1);
        Long exist = deviceMaintenancePlanDetailsMapper.selectCount(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId())
                .eq(DeviceMaintenancePlanDetails::getDeviceId, device.getId())
                .eq(DeviceMaintenancePlanDetails::getMaintenanceDate, remind));
                .ge(DeviceMaintenancePlanDetails::getMaintenanceDate, monthStart)
                .lt(DeviceMaintenancePlanDetails::getMaintenanceDate, monthStart.plusMonths(1)));
        if (exist != null && exist > 0) {
            return;
        }
@@ -446,8 +559,117 @@
        detail.setMaintenancePlanId(plan.getMaintenancePlanId());
        detail.setDeviceId(device.getId());
        detail.setMaintenanceIntervals(device.getMaintenanceCycle());
        detail.setMaintenanceDate(remind);
        detail.setMaintenanceDate(due);
        deviceMaintenancePlanDetailsMapper.insert(detail);
    }
    /**
     * 为保养记录找一条可关联的计划明细;没有可用明细时在当前年度计划新增本轮明细
     */
    @Override
    public DeviceMaintenancePlanDetails resolveMaintenancePlanDetail(Integer deviceId, Integer maintenancePlanDetailId, LocalDate maintenanceDate) {
        if (maintenancePlanDetailId != null) {
            DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
            if (detail == null) {
                throw new ErrorException("保养计划明细不存在");
            }
            return detail;
        }
        if (deviceId == null) {
            throw new ErrorException("缺少设备id");
        }
        Device device = deviceMapper.selectById(deviceId);
        if (device == null) {
            throw new ErrorException("保养设备不存在");
        }
        LocalDate planDate = maintenanceDate == null ? LocalDate.now() : maintenanceDate;
        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(planDate.getYear()));
        List<DeviceMaintenancePlanDetails> details = deviceMaintenancePlanDetailsMapper.selectList(
                Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                        .eq(DeviceMaintenancePlanDetails::getDeviceId, deviceId)
                        .orderByAsc(DeviceMaintenancePlanDetails::getMaintenanceDate));
        DeviceMaintenancePlanDetails nearest = null;
        long nearestDistance = Long.MAX_VALUE;
        for (DeviceMaintenancePlanDetails detail : details) {
            // 已有保养记录的明细代表已完成的一轮,后续记录永远不复用、不覆盖
            Long recordCount = deviceMaintenanceMapper.selectCount(Wrappers.<DeviceMaintenance>lambdaQuery()
                    .eq(DeviceMaintenance::getMaintenancePlanDetailId, detail.getMaintenancePlanDetailId()));
            if (recordCount != null && recordCount > 0) {
                continue;
            }
            if (detail.getMaintenancePlanId() != null && detail.getMaintenancePlanId().equals(plan.getMaintenancePlanId())) {
                return detail;
            }
            long distance = monthDistance(detail.getMaintenanceDate(), planDate);
            if (distance < nearestDistance) {
                nearest = detail;
                nearestDistance = distance;
            }
        }
        if (nearest != null) {
            return nearest;
        }
        DeviceMaintenancePlanDetails detail = new DeviceMaintenancePlanDetails();
        detail.setMaintenancePlanId(plan.getMaintenancePlanId());
        detail.setDeviceId(device.getId());
        detail.setMaintenanceIntervals(device.getMaintenanceCycle());
        detail.setMaintenanceDate(planDate);
        deviceMaintenancePlanDetailsMapper.insert(detail);
        return detail;
    }
    /**
     * 修改设备保养计划详情(已审核的明细不允许修改)
     */
    @Override
    public boolean updateMaintenancePlanDetail(DeviceMaintenancePlanDetails detail) {
        if (detail == null || detail.getMaintenancePlanDetailId() == null) {
            throw new ErrorException("缺少设备保养计划明细id");
        }
        DeviceMaintenance record = deviceMaintenanceMapper.selectOne(Wrappers.<DeviceMaintenance>lambdaQuery()
                .eq(DeviceMaintenance::getMaintenancePlanDetailId, detail.getMaintenancePlanDetailId())
                .last("limit 1"));
        if (record != null && record.getReviewStatus() != null) {
            throw new ErrorException("该计划已审核,不允许修改");
        }
        return deviceMaintenancePlanDetailsMapper.updateById(detail) > 0;
    }
    /**
     * 删除设备保养计划详情;已出审核结果的记录随历史留档,未审核的记录随明细一起删除
     */
    @Override
    public boolean removeMaintenancePlanDetail(Integer maintenancePlanDetailId) {
        if (maintenancePlanDetailId == null) {
            throw new ErrorException("缺少设备保养计划明细id");
        }
        DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
        if (detail == null) {
            return false;
        }
        List<DeviceMaintenance> records = deviceMaintenanceMapper.selectList(Wrappers.<DeviceMaintenance>lambdaQuery()
                .eq(DeviceMaintenance::getMaintenancePlanDetailId, maintenancePlanDetailId));
        // 审核不通过的记录允许连同明细一起删除;审核通过的记录为历史留档,不允许删除
        boolean hasPassedRecord = records.stream().anyMatch(item -> item.getReviewStatus() != null
                && item.getReviewStatus() == 1);
        if (hasPassedRecord) {
            throw new ErrorException("该计划已存在审核通过的保养记录,为保留历史记录不允许删除");
        }
        if (!records.isEmpty()) {
            List<Integer> recordIds = records.stream().map(DeviceMaintenance::getId).collect(Collectors.toList());
            deviceMaintenanceMapper.deleteBatchIds(recordIds);
        }
        return deviceMaintenancePlanDetailsMapper.deleteById(maintenancePlanDetailId) > 0;
    }
    /**
     * 两个保养日期之间相差的月数
     */
    private long monthDistance(LocalDate from, LocalDate target) {
        if (from == null || target == null) {
            return Long.MAX_VALUE;
        }
        return Math.abs(ChronoUnit.MONTHS.between(YearMonth.from(from), YearMonth.from(target)));
    }
}