Merge remote-tracking branch 'origin/dev_pro_河南鹤壁_帮太优选' into dev_pro_河南鹤壁_帮太优选
已添加2个文件
已修改1个文件
823 ■■■■■ 文件已修改
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/device/service/impl/MaintenanceTaskJobTest.java 448 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJobTest.java 339 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -26,31 +26,41 @@
        left join device_ledger dl on dm.device_ledger_id = dl.id
        left join sys_user su on dm.create_user = su.user_id
        <where>
            1 = 1
            <if test="deviceMaintenanceDto.deviceName != null">
                and dl.device_name like concat('%',#{deviceMaintenanceDto.deviceName},'%')
            <if test="deviceMaintenanceDto.deviceName != null and deviceMaintenanceDto.deviceName != ''">
                and dl.device_name like concat('%', #{deviceMaintenanceDto.deviceName}, '%')
            </if>
            <if test="deviceMaintenanceDto.deviceModel != null">
                and dl.device_model like concat('%',#{deviceMaintenanceDto.deviceModel},'%')
            <if test="deviceMaintenanceDto.deviceModel != null and deviceMaintenanceDto.deviceModel != ''">
                and dl.device_model like concat('%', #{deviceMaintenanceDto.deviceModel}, '%')
            </if>
            <if test="deviceMaintenanceDto.status != null">
                and dm.status = #{deviceMaintenanceDto.status}
            </if>
            <if test="deviceMaintenanceDto.maintenanceActuallyName != null">
                and dm.maintenance_actually_name like concat('%',#{deviceMaintenanceDto.maintenanceActuallyName},'%')
            <if test="deviceMaintenanceDto.maintenanceActuallyName != null and deviceMaintenanceDto.maintenanceActuallyName != ''">
                and dm.maintenance_actually_name like concat('%', #{deviceMaintenanceDto.maintenanceActuallyName}, '%')
            </if>
            <if test="deviceMaintenanceDto.maintenancePlanTime != null">
                and dm.maintenance_plan_time like concat('%',#{deviceMaintenanceDto.maintenancePlanTime},'%')
                and dm.maintenance_plan_time = #{deviceMaintenanceDto.maintenancePlanTime}
            </if>
            <if test="deviceMaintenanceDto.maintenanceActuallyTime != null">
                and dm.maintenance_actually_time like concat('%',#{deviceMaintenanceDto.maintenanceActuallyTime},'%')
            </if>
            <if test="deviceMaintenanceDto.maintenanceActuallyTime != null">
                and dm.maintenance_actually_time >= str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d')
                and dm.maintenance_actually_time &lt; date_add(str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d'), interval 1 day)
                and dm.maintenance_actually_time &gt;= str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime},
                '%Y-%m-%d')
                and dm.maintenance_actually_time &lt;
                date_add(str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d'), interval 1 day)
            </if>
        </where>
        order by
        <!--    å¾…保养(0)优先排在上面,已完结(1)在下面 -->
        dm.status asc,
        case
        <!-- å½“状态是 0(待保养)时,按计划时间升序,即时间最远的单子在最上面 -->
        when dm.status = 0 then dm.maintenance_plan_time
        end asc,
        case
        <!-- å½“状态是 1(已完结)时,按实际保养时间降序,最近刚保养完的单子在已完结里排最前 -->
        when dm.status = 1 then dm.maintenance_actually_time
        end desc
    </select>
    <select id="detailById" resultType="com.ruoyi.device.vo.DeviceMaintenanceVo">
        select dm.id,
               dm.device_ledger_id,
src/test/java/com/ruoyi/device/service/impl/MaintenanceTaskJobTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,448 @@
package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.device.mapper.MaintenanceTaskMapper;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.pojo.MaintenanceTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.LocalDate;
import java.time.DayOfWeek;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * è®¾å¤‡ä¿å…»ä»»åŠ¡æµ‹è¯•ç±»
 *
 * æŸ¥è¯¢æ•°æ®åº“中的保养任务,根据登记日期、频次、开始日期与时间来生成保养记录
 * ä»Žç™»è®°æ—¥æœŸåˆ°ä»Šå¤©ï¼ŒæŒ‰é¢‘次生成所有符合的记录
 */
@SpringBootTest
public class MaintenanceTaskJobTest {
    @Autowired
    private MaintenanceTaskMapper maintenanceTaskMapper;
    @Autowired
    private DeviceMaintenanceServiceImpl deviceMaintenanceService;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * æµ‹è¯•:按日期分散生成保养记录
     * ä»Žç™»è®°æ—¥æœŸåˆ°ä»Šå¤©ï¼ŒæŒ‰æ—¥æœŸé¡ºåºï¼Œæ¯å¤©çš„æ‰€æœ‰è®¾å¤‡ä¿å…»è®°å½•一起生成
     */
    @Test
    void testGenerateOneByOne() {
        // æŸ¥è¯¢æ‰€æœ‰å¯ç”¨çš„保养任务
        List<MaintenanceTask> tasks = maintenanceTaskMapper.selectList(
                new LambdaQueryWrapper<MaintenanceTask>()
                        .eq(MaintenanceTask::getIsActive, 1)
                        .eq(MaintenanceTask::getDeleted, 0)
        );
        if (tasks.isEmpty()) {
            System.out.println("=== æ²¡æœ‰æ‰¾åˆ°ä¿å…»ä»»åŠ¡ ===");
            return;
        }
        // æ‰¾å‡ºæœ€æ—©å’Œæœ€æ™šçš„登记日期
        LocalDate earliestDate = tasks.stream()
                .map(MaintenanceTask::getRegistrationDate)
                .filter(Objects::nonNull)
                .min(LocalDate::compareTo)
                .orElse(LocalDate.now());
        LocalDate latestDate = LocalDate.now();
        System.out.println("=== ç™»è®°æ—¥æœŸèŒƒå›´: " + earliestDate + " è‡³ " + latestDate + " ===");
        System.out.println("=== å…±æ‰¾åˆ° " + tasks.size() + " ä¸ªä¿å…»ä»»åŠ¡ ===\n");
        // æŒ‰æ—¥æœŸé¡ºåºç”Ÿæˆï¼Œæ¯å¤©ç”Ÿæˆä¸€æ¬¡
        LocalDate currentDate = earliestDate;
        int totalRecords = 0;
        while (!currentDate.isAfter(latestDate)) {
            final LocalDate date = currentDate;
            final List<DeviceMaintenance> recordsToSave = new ArrayList<>();
            // æ‰¾å‡ºå½“天需要保养的所有任务
            for (MaintenanceTask task : tasks) {
                LocalDate startDate = task.getRegistrationDate() != null ? task.getRegistrationDate() : LocalDate.now();
                if (date.isBefore(startDate)) {
                    continue; // è·³è¿‡ç™»è®°æ—¥æœŸä¹‹å‰çš„æ—¥æœŸ
                }
                // æ£€æŸ¥è¯¥ä»»åŠ¡çš„é¢‘æ¬¡æ˜¯å¦åŒ¹é…å½“å¤©
                if (isExecutionDate(task.getFrequencyType(), task.getFrequencyDetail(), startDate, date)) {
                    LocalTime time = getExecutionTime(task.getFrequencyDetail());
                    LocalDateTime executionDateTime = LocalDateTime.of(date, time);
                    DeviceMaintenance record = createMaintenanceRecord(task, executionDateTime);
                    recordsToSave.add(record);
                }
            }
            // å¦‚果当天有记录,批量保存
            if (!recordsToSave.isEmpty()) {
                for (DeviceMaintenance record : recordsToSave) {
                    try {
                        deviceMaintenanceService.save(record);
                        System.out.println("  â†’ è®¡åˆ’日期: " + date + " | è®¾å¤‡: " + record.getDeviceName()
                                + " | è®°å½•ID: " + record.getId());
                    } catch (Exception e) {
                        System.out.println("  âœ— è®¡åˆ’日期: " + date + " | è®¾å¤‡: " + record.getDeviceName()
                                + " | å¤±è´¥: " + e.getMessage());
                    }
                }
                totalRecords += recordsToSave.size();
                System.out.println("  === æ—¥æœŸ " + date + " å…±ç”Ÿæˆ " + recordsToSave.size() + " æ¡è®°å½• ===\n");
            }
            currentDate = currentDate.plusDays(1);
        }
        System.out.println("=== æ‰§è¡Œå®Œæˆ: å…±ç”Ÿæˆ " + totalRecords + " æ¡è®°å½• ===");
    }
    /**
     * æ£€æŸ¥æŒ‡å®šæ—¥æœŸæ˜¯å¦ç¬¦åˆä»»åŠ¡çš„æ‰§è¡Œé¢‘æ¬¡
     */
    private boolean isExecutionDate(String frequencyType, String frequencyDetail, LocalDate startDate, LocalDate checkDate) {
        if (checkDate.isBefore(startDate)) {
            return false;
        }
        switch (frequencyType) {
            case "DAILY":
                return true; // æ¯å¤©éƒ½éœ€è¦æ‰§è¡Œ
            case "WEEKLY":
                return isWeeklyMatch(frequencyDetail, checkDate);
            case "MONTHLY":
                return isMonthlyMatch(frequencyDetail, checkDate);
            case "QUARTERLY":
                return isQuarterlyMatch(frequencyDetail, checkDate);
            default:
                return false;
        }
    }
    private boolean isWeeklyMatch(String detail, LocalDate date) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];
        DayOfWeek targetDay = parseDayOfWeek(dayOfWeekStr);
        return date.getDayOfWeek() == targetDay;
    }
    private boolean isMonthlyMatch(String detail, LocalDate date) {
        String[] parts = detail.split(",");
        int targetDay = Integer.parseInt(parts[0]);
        return date.getDayOfMonth() == targetDay;
    }
    private boolean isQuarterlyMatch(String detail, LocalDate date) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // å­£åº¦ä¸­çš„第几个月
        int targetDay = Integer.parseInt(parts[1]);
        int currentMonth = date.getMonthValue();
        int monthInQuarter = (currentMonth - 1) % 3 + 1;
        return monthInQuarter == quarterMonth && date.getDayOfMonth() == targetDay;
    }
    private LocalTime getExecutionTime(String frequencyDetail) {
        String[] parts = frequencyDetail.split(",");
        if (parts.length >= 2 && (parts.length == 2 || parts.length == 3)) {
            return LocalTime.parse(parts[parts.length - 1]);
        }
        return LocalTime.of(9, 0); // é»˜è®¤æ—¶é—´
    }
    /**
     * æµ‹è¯•:根据所有保养任务生成设备保养记录
     * æŒ‰é¡ºåºå¤„理每个任务,从登记日期到今天,按频次依次生成所有符合的记录
     */
    @Test
    void testGenerateMaintenanceRecordForAllTasks() {
        // æŸ¥è¯¢æ‰€æœ‰å¯ç”¨çš„保养任务,按ID排序
        List<MaintenanceTask> tasks = maintenanceTaskMapper.selectList(
                new LambdaQueryWrapper<MaintenanceTask>()
                        .eq(MaintenanceTask::getIsActive, 1)
                        .eq(MaintenanceTask::getDeleted, 0)
                        .orderByAsc(MaintenanceTask::getId)
        );
        System.out.println("=== å…±æ‰¾åˆ° " + tasks.size() + " ä¸ªä¿å…»ä»»åŠ¡ ===\n");
        int totalRecords = 0;
        for (MaintenanceTask task : tasks) {
            try {
                // æŒ‰é¡ºåºç”Ÿæˆè¯¥ä»»åŠ¡æ‰€æœ‰ç¬¦åˆé¢‘æ¬¡çš„è®°å½•
                int recordCount = generateRecordsForTaskSequentially(task);
                totalRecords += recordCount;
                System.out.println("✓ ä»»åŠ¡ID: " + task.getId() + " | " + task.getTaskName()
                        + " | ç™»è®°æ—¥æœŸ: " + task.getRegistrationDate()
                        + " | ç”Ÿæˆè®°å½•æ•°: " + recordCount);
            } catch (Exception e) {
                System.out.println("✗ ä»»åŠ¡ID: " + task.getId() + " | " + task.getTaskName() + " | å¤±è´¥: " + e.getMessage());
            }
        }
        System.out.println("\n=== æ‰§è¡Œå®Œæˆ: å…±ç”Ÿæˆ " + totalRecords + " æ¡è®°å½• ===");
    }
    /**
     * ä¸ºå•个任务按顺序生成所有符合频次的记录
     */
    private int generateRecordsForTaskSequentially(MaintenanceTask task) {
        LocalDate startDate = task.getRegistrationDate();
        if (startDate == null) {
            startDate = LocalDate.now();
        }
        LocalDate endDate = LocalDate.now();
        // æ ¹æ®é¢‘次获取所有需要执行的日期
        List<LocalDateTime> executionDates = getExecutionDates(task.getFrequencyType(), task.getFrequencyDetail(), startDate, endDate);
        if (executionDates.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (LocalDateTime executionDate : executionDates) {
            try {
                // æŒ‰é¡ºåºç”Ÿæˆä¿å…»è®°å½•
                DeviceMaintenance record = createMaintenanceRecord(task, executionDate);
                deviceMaintenanceService.save(record);
                count++;
            } catch (Exception e) {
                System.out.println("  âœ— æ—¥æœŸ: " + executionDate + " | å¤±è´¥: " + e.getMessage());
            }
        }
        // æ›´æ–°ä»»åŠ¡çš„ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
        if (count > 0) {
            // first_execution = ç¬¬ä¸€æ¡è®°å½•的日期 = last_execution_time
            LocalDateTime firstExecution = executionDates.get(0);
            // next_execution = æœ€åŽä¸€æ¡è®°å½•的下一次执行日期
            LocalDateTime lastExecution = executionDates.get(executionDates.size() - 1);
            LocalDateTime nextExecution = calculateNextExecutionTime(task.getFrequencyType(), task.getFrequencyDetail(), lastExecution);
            updateTaskExecutionTime(task.getId(), firstExecution, nextExecution);
        }
        return count;
    }
    /**
     * èŽ·å–æŒ‡å®šæ—¥æœŸèŒƒå›´å†…æ‰€æœ‰ç¬¦åˆé¢‘æ¬¡çš„æ‰§è¡Œæ—¶é—´
     */
    private List<LocalDateTime> getExecutionDates(String frequencyType, String frequencyDetail, LocalDate startDate, LocalDate endDate) {
        List<LocalDateTime> dates = new ArrayList<>();
        switch (frequencyType) {
            case "DAILY":
                dates.addAll(getDailyDates(startDate, endDate, frequencyDetail));
                break;
            case "WEEKLY":
                dates.addAll(getWeeklyDates(startDate, endDate, frequencyDetail));
                break;
            case "MONTHLY":
                dates.addAll(getMonthlyDates(startDate, endDate, frequencyDetail));
                break;
            case "QUARTERLY":
                dates.addAll(getQuarterlyDates(startDate, endDate, frequencyDetail));
                break;
        }
        return dates;
    }
    private List<LocalDateTime> getDailyDates(LocalDate startDate, LocalDate endDate, String timeStr) {
        List<LocalDateTime> dates = new ArrayList<>();
        LocalTime time = LocalTime.parse(timeStr);
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusDays(1);
        }
        return dates;
    }
    private List<LocalDateTime> getMonthlyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new ArrayList<>();
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        // ä»Žç™»è®°æ—¥æœŸæ‰€åœ¨æœˆæ‰¾ç¬¬ä¸€ä¸ªç¬¦åˆæ¡ä»¶çš„æ—¥æœŸ
        int actualDay = Math.min(dayOfMonth, startDate.lengthOfMonth());
        LocalDate current = startDate.withDayOfMonth(actualDay);
        // å¦‚果这个日期在登记日期之前,往后推一个月
        if (current.isBefore(startDate)) {
            current = current.plusMonths(1);
            actualDay = Math.min(dayOfMonth, current.lengthOfMonth());
            current = current.withDayOfMonth(actualDay);
        }
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusMonths(1);
            actualDay = Math.min(dayOfMonth, current.lengthOfMonth());
            current = current.withDayOfMonth(actualDay);
        }
        return dates;
    }
    private List<LocalDateTime> getWeeklyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new ArrayList<>();
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        java.time.DayOfWeek targetDay = parseDayOfWeek(dayOfWeekStr);
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            if (current.getDayOfWeek() == targetDay) {
                dates.add(LocalDateTime.of(current, time));
            }
            current = current.plusDays(1);
        }
        return dates;
    }
    private List<LocalDateTime> getQuarterlyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new ArrayList<>();
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        int currentMonth = startDate.getMonthValue();
        int targetMonth = ((currentMonth - 1) / 3) * 3 + quarterMonth;
        int yearAdjust = 0;
        if (targetMonth > 12) {
            targetMonth -= 12;
            yearAdjust = 1;
        }
        int actualDay = Math.min(dayOfMonth, YearMonth.of(startDate.getYear() + yearAdjust, targetMonth).lengthOfMonth());
        LocalDate current = startDate.withYear(startDate.getYear() + yearAdjust)
                .withMonth(targetMonth)
                .withDayOfMonth(actualDay);
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusMonths(3);
            actualDay = Math.min(dayOfMonth, current.lengthOfMonth());
            current = current.withDayOfMonth(actualDay);
        }
        return dates;
    }
    private java.time.DayOfWeek parseDayOfWeek(String dayOfWeekStr) {
        switch (dayOfWeekStr.toUpperCase()) {
            case "MON": return java.time.DayOfWeek.MONDAY;
            case "TUE": return java.time.DayOfWeek.TUESDAY;
            case "WED": return java.time.DayOfWeek.WEDNESDAY;
            case "THU": return java.time.DayOfWeek.THURSDAY;
            case "FRI": return java.time.DayOfWeek.FRIDAY;
            case "SAT": return java.time.DayOfWeek.SATURDAY;
            case "SUN": return java.time.DayOfWeek.SUNDAY;
            default: throw new IllegalArgumentException("无效的星期几: " + dayOfWeekStr);
        }
    }
    /**
     * åˆ›å»ºä¿å…»è®°å½•
     */
    private DeviceMaintenance createMaintenanceRecord(MaintenanceTask task, LocalDateTime executionDate) {
        DeviceMaintenance record = new DeviceMaintenance();
        record.setDeviceName(task.getTaskName());
        record.setMaintenanceTaskId(task.getId());
        record.setDeviceLedgerId(task.getTaskId());
        record.setMaintenancePlanTime(executionDate);
        record.setMaintenanceActuallyName(task.getMaintenancePerson());
        record.setFrequencyType(task.getFrequencyType());
        record.setFrequencyDetail(task.getFrequencyDetail());
        record.setTenantId(task.getTenantId());
        record.setStatus(0); // å¾…保养
        record.setDeviceModel(task.getDeviceModel());
        record.setMachineryCategory(task.getMachineryCategory());
        record.setCreateUser(Integer.parseInt(task.getRegistrantId().toString()));
        record.setUpdateTime(executionDate);
        record.setCreateTime(executionDate);
        record.setUpdateUser(Integer.parseInt(task.getRegistrantId().toString()));
        return record;
    }
    /**
     * æ›´æ–°ä»»åŠ¡çš„æ‰§è¡Œæ—¶é—´
     */
    private void updateTaskExecutionTime(Long taskId, LocalDateTime lastExecutionTime, LocalDateTime nextExecutionTime) {
        String sql = "UPDATE maintenance_task SET last_execution_time = ?, next_execution_time = ? WHERE id = ?";
        jdbcTemplate.update(sql, lastExecutionTime, nextExecutionTime, taskId);
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) {
        return switch (frequencyType) {
            case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
            case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
            case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
            case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
            default -> throw new IllegalArgumentException("不支持的频次类型: " + frequencyType);
        };
    }
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr);
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        return current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
    }
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        return current.plusWeeks(1);
    }
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            targetYearMonth = YearMonth.from(current).plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            targetYearMonth = YearMonth.from(current).plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(targetYearMonth.getYear(), targetYearMonth.getMonthValue(), adjustedDay, time.getHour(), time.getMinute());
    }
}
src/test/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJobTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,339 @@
package com.ruoyi.inspectiontask.service.impl;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * è®¾å¤‡å·¡æ£€å®šæ—¶ä»»åŠ¡æµ‹è¯•ç±»
 *
 * æŸ¥è¯¢æ•°æ®åº“中的巡检任务,根据登记日期、频次、开始日期与时间来生成巡检记录
 * å¹¶æ›´æ–° timing_task è¡¨çš„æœ€åŽæ‰§è¡Œæ—¶é—´å’Œä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
 */
@SpringBootTest
public class TimingTaskJobTest {
    @Autowired
    private InspectionTaskMapper inspectionTaskMapper;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * æµ‹è¯•:根据所有巡检任务生成巡检记录
     * ä»Žç™»è®°æ—¥æœŸåˆ°ä»Šå¤©ï¼ŒæŒ‰é¢‘次每周生成一条记录
     */
    @Test
    void testGenerateInspectionRecordForAllTasks() {
        // æŸ¥è¯¢æ‰€æœ‰å¯ç”¨çš„巡检任务
        String sql = "SELECT * FROM timing_task WHERE is_enabled = 1 AND deleted = 0";
        List<TimingTask> tasks = jdbcTemplate.query(sql,
                (rs, rowNum) -> {
                    TimingTask task = new TimingTask();
                    task.setId(rs.getLong("id"));
                    task.setTaskName(rs.getString("task_name"));
                    task.setInspectionProject(rs.getString("inspection_project"));
                    task.setTaskId(rs.getInt("task_id"));
                    task.setInspectorIds(rs.getString("inspector_ids"));
                    task.setInspectionLocation(rs.getString("inspection_location"));
                    task.setFrequencyType(rs.getString("frequency_type"));
                    task.setFrequencyDetail(rs.getString("frequency_detail"));
                    task.setRemarks(rs.getString("remarks"));
                    task.setRegistrantId(rs.getLong("registrant_id"));
                    task.setRegistrant(rs.getString("registrant"));
                    task.setTenantId(rs.getLong("tenant_id"));
                    // èŽ·å–ç™»è®°æ—¥æœŸ
                    java.sql.Date regDate = rs.getDate("registration_date");
                    if (regDate != null) {
                        task.setRegistrationDate(regDate.toLocalDate());
                    }
                    return task;
                });
        System.out.println("=== å…±æ‰¾åˆ° " + tasks.size() + " ä¸ªå·¡æ£€ä»»åŠ¡ ===\n");
        int totalRecords = 0;
        for (TimingTask task : tasks) {
            try {
                // ç”Ÿæˆæ‰€æœ‰ç¬¦åˆé¢‘次的记录
                int recordCount = generateRecordsForTask(task);
                totalRecords += recordCount;
                System.out.println("✓ ä»»åŠ¡ID: " + task.getId() + " | " + task.getTaskName()
                        + " | ç™»è®°æ—¥æœŸ: " + task.getRegistrationDate()
                        + " | ç”Ÿæˆè®°å½•æ•°: " + recordCount);
            } catch (Exception e) {
                System.out.println("✗ ä»»åŠ¡ID: " + task.getId() + " | " + task.getTaskName() + " | å¤±è´¥: " + e.getMessage());
            }
        }
        System.out.println("\n=== æ‰§è¡Œå®Œæˆ: å…±ç”Ÿæˆ " + totalRecords + " æ¡è®°å½• ===");
    }
    /**
     * ä¸ºå•个任务生成所有符合频次的记录
     */
    private int generateRecordsForTask(TimingTask task) {
        LocalDate startDate = task.getRegistrationDate();
        if (startDate == null) {
            startDate = LocalDate.now();
        }
        LocalDate endDate = LocalDate.now();
        // æ ¹æ®é¢‘次获取所有需要执行的日期
        List<LocalDateTime> executionDates = getExecutionDates(task.getFrequencyType(), task.getFrequencyDetail(), startDate, endDate);
        int count = 0;
        for (LocalDateTime executionDate : executionDates) {
            try {
                // ç”Ÿæˆå·¡æ£€è®°å½•
                InspectionTask record = createInspectionRecord(task, executionDate);
                inspectionTaskMapper.insert(record);
                // æ›´æ–°ä»»åŠ¡çš„ä¸Šæ¬¡æ‰§è¡Œæ—¶é—´
                updateTaskLastExecutionTime(task.getId(), executionDate);
                count++;
            } catch (Exception e) {
                System.out.println("  âœ— æ—¥æœŸ: " + executionDate + " | å¤±è´¥: " + e.getMessage());
            }
        }
        // æ›´æ–°ä»»åŠ¡çš„ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
        if (count > 0) {
            LocalDateTime lastExecution = executionDates.get(executionDates.size() - 1);
            LocalDateTime nextExecution = calculateNextExecutionTime(task.getFrequencyType(), task.getFrequencyDetail(), lastExecution);
            updateTaskNextExecutionTime(task.getId(), nextExecution);
        }
        return count;
    }
    /**
     * èŽ·å–æŒ‡å®šæ—¥æœŸèŒƒå›´å†…æ‰€æœ‰ç¬¦åˆé¢‘æ¬¡çš„æ‰§è¡Œæ—¶é—´
     */
    private List<LocalDateTime> getExecutionDates(String frequencyType, String frequencyDetail, LocalDate startDate, LocalDate endDate) {
        List<LocalDateTime> dates = new java.util.ArrayList<>();
        switch (frequencyType) {
            case "DAILY":
                dates.addAll(getDailyDates(startDate, endDate, frequencyDetail));
                break;
            case "WEEKLY":
                dates.addAll(getWeeklyDates(startDate, endDate, frequencyDetail));
                break;
            case "MONTHLY":
                dates.addAll(getMonthlyDates(startDate, endDate, frequencyDetail));
                break;
            case "QUARTERLY":
                dates.addAll(getQuarterlyDates(startDate, endDate, frequencyDetail));
                break;
        }
        return dates;
    }
    private List<LocalDateTime> getDailyDates(LocalDate startDate, LocalDate endDate, String timeStr) {
        List<LocalDateTime> dates = new java.util.ArrayList<>();
        LocalTime time = LocalTime.parse(timeStr);
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusDays(1);
        }
        return dates;
    }
    private List<LocalDateTime> getWeeklyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new java.util.ArrayList<>();
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            if (targetDays.contains(current.getDayOfWeek())) {
                dates.add(LocalDateTime.of(current, time));
            }
            current = current.plusDays(1);
        }
        return dates;
    }
    private List<LocalDateTime> getMonthlyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new java.util.ArrayList<>();
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        LocalDate current = startDate.plusMonths(0).withDayOfMonth(Math.min(dayOfMonth, startDate.lengthOfMonth()));
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusMonths(1).withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).lengthOfMonth()));
        }
        return dates;
    }
    private List<LocalDateTime> getQuarterlyDates(LocalDate startDate, LocalDate endDate, String detail) {
        List<LocalDateTime> dates = new java.util.ArrayList<>();
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        int currentMonth = startDate.getMonthValue();
        int targetMonth = ((currentMonth - 1) / 3) * 3 + quarterMonth;
        int yearAdjust = 0;
        if (targetMonth > 12) {
            targetMonth -= 12;
            yearAdjust = 1;
        }
        LocalDate current = startDate.withYear(startDate.getYear() + yearAdjust)
                .withMonth(targetMonth)
                .withDayOfMonth(Math.min(dayOfMonth, YearMonth.of(startDate.getYear() + yearAdjust, targetMonth).lengthOfMonth()));
        while (!current.isAfter(endDate)) {
            dates.add(LocalDateTime.of(current, time));
            current = current.plusMonths(3).withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(3).lengthOfMonth()));
        }
        return dates;
    }
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
        for (String dayStr : dayStrs) {
            switch (dayStr.toUpperCase()) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
            }
        }
        return days;
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) {
        return switch (frequencyType) {
            case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
            case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
            case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
            case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
            default -> throw new IllegalArgumentException("不支持的频次类型: " + frequencyType);
        };
    }
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr);
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        return current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
    }
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            targetYearMonth = YearMonth.from(current).plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            targetYearMonth = YearMonth.from(current).plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(targetYearMonth.getYear(), targetYearMonth.getMonthValue(), adjustedDay, time.getHour(), time.getMinute());
    }
    /**
     * åˆ›å»ºå·¡æ£€è®°å½•
     */
    private InspectionTask createInspectionRecord(TimingTask timingTask, LocalDateTime executionDate) {
        InspectionTask inspectionTask = new InspectionTask();
        inspectionTask.setTaskName(timingTask.getTaskName());
        inspectionTask.setInspectionProject(timingTask.getInspectionProject());
        inspectionTask.setTaskId(timingTask.getTaskId());
        inspectionTask.setInspectorId(timingTask.getInspectorIds());
        inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
        inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId() + ";" + timingTask.getRemarks());
        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
        inspectionTask.setTenantId(timingTask.getTenantId());
        // è®¾ç½®ç™»è®°æ—¥æœŸä¸ºæ‰§è¡Œæ—¥æœŸ
        inspectionTask.setCreateTime(executionDate);
        inspectionTask.setUpdateTime(executionDate);
        return inspectionTask;
    }
    /**
     * æ›´æ–°ä»»åŠ¡çš„ä¸Šæ¬¡æ‰§è¡Œæ—¶é—´
     */
    private void updateTaskLastExecutionTime(Long taskId, LocalDateTime lastExecutionTime) {
        String updateSql = "UPDATE timing_task SET last_execution_time = ? WHERE id = ?";
        jdbcTemplate.update(updateSql, lastExecutionTime, taskId);
    }
    /**
     * æ›´æ–°ä»»åŠ¡çš„ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
     */
    private void updateTaskNextExecutionTime(Long taskId, LocalDateTime nextExecutionTime) {
        String updateSql = "UPDATE timing_task SET next_execution_time = ? WHERE id = ?";
        jdbcTemplate.update(updateSql, nextExecutionTime, taskId);
    }
}