吉安感知网项目-后端
linwei
2026-01-19 3664ff0935aeab26d23926331333a1f55caea1c1
Merge remote-tracking branch 'origin/master'
25 files modified
1 files deleted
3 files added
1379 ■■■■■ changed files
drone-common/src/main/java/org/sxkj/common/utils/DataUtils.java 25 ●●●●● patch | view | raw | blame | history
drone-common/src/main/java/org/sxkj/common/utils/DateUtils.java 134 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/FwApplication.java 2 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/mapper/FwDeviceMaintainRecordMapper.java 6 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/mapper/FwDeviceMaintainRecordMapper.xml 6 ●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/scheduler/MaintainPlanScheduler.java 156 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/IFwDeviceMaintainPlanService.java 6 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/IFwDeviceMaintainRecordService.java 7 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/impl/FwDeviceMaintainPlanServiceImpl.java 16 ●●●●● patch | view | raw | blame | history
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/impl/FwDeviceMaintainRecordServiceImpl.java 137 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandAuditAttachmentController.java 7 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandAuditController.java 7 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandController.java 117 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/dto/GdSupplyDemandAuditActionDTO.java 46 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/entity/GdSupplyDemandAuditEntity.java 2 ●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/entity/GdSupplyDemandEntity.java 4 ●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/excel/GdSupplyDemandAuditExcel.java 2 ●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditAttachmentMapper.java 16 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditAttachmentMapper.xml 29 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditMapper.java 16 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditMapper.xml 28 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandMapper.java 38 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandMapper.xml 208 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandAuditAttachmentService.java 16 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandAuditService.java 16 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandService.java 45 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandAuditAttachmentServiceImpl.java 38 ●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandAuditServiceImpl.java 31 ●●●●● patch | view | raw | blame | history
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandServiceImpl.java 218 ●●●●● patch | view | raw | blame | history
drone-common/src/main/java/org/sxkj/common/utils/DataUtils.java
File was deleted
drone-common/src/main/java/org/sxkj/common/utils/DateUtils.java
New file
@@ -0,0 +1,134 @@
package org.sxkj.common.utils;
import org.sxkj.common.enums.DateEnum;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class DateUtils {
    /**
     * 判断两个时间是否跨日、跨月或跨年
     * @param start 开始时间
     * @param end 结束时间
     * @return 返回跨日、跨月或跨年的枚举值
     */
    public static DateEnum compareDate(LocalDateTime start, LocalDateTime end) {
        if (start.getYear() != end.getYear()) {
            return DateEnum.YEARS;
        } else if (start.getMonthValue() != end.getMonthValue()) {
            return DateEnum.MONTHS;
        } else if (start.getDayOfYear() != end.getDayOfYear()) {
            return DateEnum.DAYS;
        } else {
            return DateEnum.DAYS;
        }
    }
    /**
     * 计算年度周期边界
     * @param maintainTime 维护时间
     * @param ruleMonth 规则月份
     * @param ruleDay 规则日期
     * @return 周期边界 [start, end]
     */
    public static LocalDateTime[] calculateAnnualCycle(Date maintainTime, int ruleMonth, int ruleDay) {
        LocalDateTime maintainDateTime = maintainTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        LocalDateTime currentYearStart = LocalDateTime.of(
                maintainDateTime.getYear(), ruleMonth, ruleDay, 0, 0, 0);
        LocalDateTime currentYearEnd = LocalDateTime.of(
                maintainDateTime.getYear() + 1, ruleMonth, ruleDay, 0, 0, 0);
        // 如果当前年份的规则日期已经过去,则使用下一年的周期
        if (maintainDateTime.isBefore(currentYearStart)) {
            currentYearStart = currentYearStart.minusYears(1);
            currentYearEnd = currentYearEnd.minusYears(1);
        }
        return new LocalDateTime[]{currentYearStart, currentYearEnd};
    }
    /**
     * 计算月度周期边界
     * @param maintainTime 维护时间
     * @param ruleDay 规则日期
     * @return 周期边界 [start, end]
     */
    public static LocalDateTime[] calculateMonthlyCycle(Date maintainTime, int ruleDay) {
        LocalDateTime maintainDateTime = maintainTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        LocalDateTime currentMonthStart = LocalDateTime.of(
                maintainDateTime.getYear(), maintainDateTime.getMonthValue(), ruleDay, 0, 0, 0);
        LocalDateTime currentMonthEnd = currentMonthStart.plusMonths(1);
        // 如果当前月份的规则日期已经过去,则使用下个月的周期
        if (maintainDateTime.isBefore(currentMonthStart)) {
            currentMonthStart = currentMonthStart.minusMonths(1);
            currentMonthEnd = currentMonthEnd.minusMonths(1);
        }
        return new LocalDateTime[]{currentMonthStart, currentMonthEnd};
    }
    /**
     * 计算周度周期边界
     * @param maintainTime 维护时间
     * @param ruleDayOfWeek 规则星期几(1-7)
     * @return 周期边界 [start, end]
     */
    public static LocalDateTime[] calculateWeeklyCycle(Date maintainTime, int ruleDayOfWeek) {
        LocalDateTime maintainDateTime = maintainTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        DayOfWeek currentDayOfWeek = maintainDateTime.getDayOfWeek();
        int daysDiff = currentDayOfWeek.getValue() - ruleDayOfWeek;
        if (daysDiff < 0) daysDiff += 7; // 确保差值为正
        LocalDateTime weekStart = maintainDateTime.minusDays(daysDiff);
        LocalDateTime weekEnd = weekStart.plusWeeks(1);
        return new LocalDateTime[]{weekStart, weekEnd};
    }
    /**
     * 判断维护时间是否在周期内
     * @param maintainTime 维护时间
     * @param cycleStart 周期开始时间
     * @param cycleEnd 周期结束时间
     * @return 是否在周期内
     */
    public static boolean isMaintainTimeInCycle(Date maintainTime, LocalDateTime cycleStart, LocalDateTime cycleEnd) {
        LocalDateTime maintainDateTime = maintainTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        return !maintainDateTime.isBefore(cycleStart) && maintainDateTime.isBefore(cycleEnd);
    }
    /**
     * 获取星期几的数字表示
     * @param weekday 星期几字符串
     * @return 数字表示(1-7)
     */
    public static int getDayOfWeekNumber(String weekday) {
        switch (weekday) {
            case "星期日": return 1;
            case "星期一": return 2;
            case "星期二": return 3;
            case "星期三": return 4;
            case "星期四": return 5;
            case "星期五": return 6;
            case "星期六": return 7;
            default: return 1; // 默认为星期日
        }
    }
}
drone-service/drone-fw/src/main/java/org/sxkj/fw/FwApplication.java
@@ -21,6 +21,7 @@
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * 系统模块启动器
@@ -29,6 +30,7 @@
@BladeCloudApplication
@EnableFeignClients({"org.sxkj","org.springblade","com.dji.sample"})
@MapperScan({"org.springblade.**.mapper.**","org.sxkj.**.mapper.**"})
@EnableScheduling
public class FwApplication {
    public static void main(String[] args) {
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/mapper/FwDeviceMaintainRecordMapper.java
@@ -35,6 +35,12 @@
public interface FwDeviceMaintainRecordMapper extends BaseMapper<FwDeviceMaintainRecordEntity> {
    /**
     * 查询最新一条维修记录
     * @param planId 设备维护计划ID
     */
    FwDeviceMaintainRecordVO selectLastOne(@Param("planId") Long planId);
    /**
     * 自定义分页
     *
     * @param page
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/mapper/FwDeviceMaintainRecordMapper.xml
@@ -21,8 +21,12 @@
        <result column="is_deleted" property="isDeleted"/>
    </resultMap>
    <select id="selectLastOne" resultType="org.sxkj.fw.device.vo.FwDeviceMaintainRecordVO">
        select * from ja_fw_device_maintain_record
        where is_deleted = 0 and plan_id = #{planId}
    </select>
    <select id="selectFwDeviceMaintainRecordPage" resultMap="fwDeviceMaintainRecordResultMap">
    <select id="selectFwDeviceMaintainRecordPage" resultType="org.sxkj.fw.device.vo.FwDeviceMaintainRecordVO">
        select * from ja_fw_device_maintain_record
        <where>
            is_deleted = 0
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/scheduler/MaintainPlanScheduler.java
New file
@@ -0,0 +1,156 @@
package org.sxkj.fw.device.scheduler;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.sxkj.common.utils.DateUtils;
import org.sxkj.fw.device.entity.FwDeviceMaintainPlanEntity;
import org.sxkj.fw.device.service.IFwDeviceMaintainPlanService;
import org.sxkj.fw.device.service.IFwDeviceMaintainRecordService;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * @Description 设备维护计划状态更新定时器
 * @Author AIX
 * @Date 2026/1/19 11:34
 * @Version 1.0
 */
@Component
@Slf4j
@AllArgsConstructor
public class MaintainPlanScheduler {
    private final IFwDeviceMaintainPlanService fwDeviceMaintainPlanService;
    @Scheduled(cron = "0 0 0 * * ?") // 每天凌晨0点执行
    // 每10秒执行一次
//    @Scheduled(cron = "*/10 * * * * ?") // 每10秒执行一次,用于本地调试
    public void refreshMaintainStatus() {
        log.info("开始执行设备维护计划状态刷新任务");
        try {
            // 获取所有有效的维护计划
            List<FwDeviceMaintainPlanEntity> plans = fwDeviceMaintainPlanService.list();
            Date currentTime = new Date();
            List<Long> expiredPlanIds = new ArrayList<>();
            // 先收集需要更新的计划ID
            for (FwDeviceMaintainPlanEntity plan : plans) {
                boolean isExpired = checkPlanExpired(plan, currentTime);
                if (isExpired && plan.getMaintainStatus() != null && plan.getMaintainStatus() == 1) {
                    expiredPlanIds.add(plan.getId());
                }
            }
            // 批量更新数据库
            if (!expiredPlanIds.isEmpty()) {
                fwDeviceMaintainPlanService.batchUpdateExpiredPlans(expiredPlanIds);
                log.info("批量更新 {} 个过期计划状态为未维护", expiredPlanIds.size());
            }
            log.info("设备维护计划状态刷新任务完成,共处理 {} 条计划,更新 {} 条过期计划", plans.size(), expiredPlanIds.size());
        } catch (Exception e) {
            log.error("设备维护计划状态刷新任务执行失败", e);
        }
    }
    /**
     * 检查维护计划是否已过期
     */
    private boolean checkPlanExpired(FwDeviceMaintainPlanEntity plan, Date currentTime) {
        String planCycleType = plan.getPlanCycleType();
        // 如果从未维护过,且计划周期已过,则认为已过期
        if (plan.getLastMaintainTime() == null) {
            return true;
        }
        LocalDateTime currentDateTime = currentTime.toInstant()
                .atZone(ZoneId.of("Asia/Shanghai"))
                .toLocalDateTime();
        switch (planCycleType) {
            case "1":
                return checkAnnualPlanExpired(plan, currentDateTime);
            case "2":
                return checkMonthlyPlanExpired(plan, currentDateTime);
            case "3":
                return checkWeeklyPlanExpired(plan, currentDateTime);
            default:
                return false;
        }
    }
    /**
     * 检查年度计划是否过期
     */
    private boolean checkAnnualPlanExpired(FwDeviceMaintainPlanEntity plan, LocalDateTime currentDateTime) {
        List<String> planCycleValue = plan.getPlanCycleValue();
        Date lastMaintainTime = plan.getLastMaintainTime();
        for (String ruleValue : planCycleValue) {
            String[] parts = ruleValue.split("月");
            int ruleMonth = Integer.parseInt(parts[0]);
            int ruleDay = Integer.parseInt(parts[1].replace("号", ""));
            LocalDateTime[] cycleBounds = DateUtils.calculateAnnualCycle(lastMaintainTime, ruleMonth, ruleDay);
            LocalDateTime nextCycleStart = cycleBounds[1]; // 下一周期开始时间
            if (currentDateTime.isAfter(nextCycleStart)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 检查月度计划是否过期
     */
    private boolean checkMonthlyPlanExpired(FwDeviceMaintainPlanEntity plan, LocalDateTime currentDateTime) {
        List<String> planCycleValue = plan.getPlanCycleValue();
        Date lastMaintainTime = plan.getLastMaintainTime();
        for (String ruleValue : planCycleValue) {
            int ruleDay = Integer.parseInt(ruleValue.replace("号", ""));
            LocalDateTime[] cycleBounds = DateUtils.calculateMonthlyCycle(lastMaintainTime, ruleDay);
            LocalDateTime nextCycleStart = cycleBounds[1]; // 下一周期开始时间
            if (currentDateTime.isAfter(nextCycleStart)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 检查周度计划是否过期
     */
    private boolean checkWeeklyPlanExpired(FwDeviceMaintainPlanEntity plan, LocalDateTime currentDateTime) {
        List<String> planCycleValue = plan.getPlanCycleValue();
        Date lastMaintainTime = plan.getLastMaintainTime();
        for (String ruleValue : planCycleValue) {
            int ruleDayOfWeek = DateUtils.getDayOfWeekNumber(ruleValue);
            LocalDateTime[] cycleBounds = DateUtils.calculateWeeklyCycle(lastMaintainTime, ruleDayOfWeek);
            LocalDateTime nextCycleStart = cycleBounds[1]; // 下一周期开始时间
            if (currentDateTime.isAfter(nextCycleStart)) {
                return true;
            }
        }
        return false;
    }
}
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/IFwDeviceMaintainPlanService.java
@@ -58,4 +58,10 @@
     */
    List<FwDeviceMaintainPlanExcel> exportFwDeviceMaintainPlan(Wrapper<FwDeviceMaintainPlanEntity> queryWrapper);
    /**
     * 批量更新已过期的维护计划
     * @param expiredPlanIds 已过期的维护计划ID列表
     */
    void batchUpdateExpiredPlans(List<Long> expiredPlanIds);
}
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/IFwDeviceMaintainRecordService.java
@@ -32,6 +32,13 @@
 * @since 2026-01-08
 */
public interface IFwDeviceMaintainRecordService extends BaseService<FwDeviceMaintainRecordEntity> {
    /**
     * 查询最新一条维修记录
     * @param planId 设备维护计划ID
     */
    FwDeviceMaintainRecordVO selectLastOne(Long planId);
    /**
     * 自定义分页
     *
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/impl/FwDeviceMaintainPlanServiceImpl.java
@@ -16,6 +16,8 @@
 */
package org.sxkj.fw.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.sxkj.fw.device.dto.FwDeviceMaintainPlanDTO;
import org.sxkj.fw.device.entity.FwDeviceMaintainPlanEntity;
import org.sxkj.fw.device.vo.FwDeviceMaintainPlanVO;
@@ -58,4 +60,18 @@
        return fwDeviceMaintainPlanList;
    }
    @Override
    public void batchUpdateExpiredPlans(List<Long> planIds) {
        if (planIds == null || planIds.isEmpty()) {
            return;
        }
        // 构建更新条件
        LambdaUpdateWrapper<FwDeviceMaintainPlanEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(FwDeviceMaintainPlanEntity::getId, planIds)
                .set(FwDeviceMaintainPlanEntity::getMaintainStatus, 0); // 未维护状态
        // 执行批量更新
        update(updateWrapper);
    }
}
drone-service/drone-fw/src/main/java/org/sxkj/fw/device/service/impl/FwDeviceMaintainRecordServiceImpl.java
@@ -16,16 +16,26 @@
 */
package org.sxkj.fw.device.service.impl;
import org.sxkj.fw.device.dto.FwDeviceMaintainRecordDTO;
import org.sxkj.fw.device.entity.FwDeviceMaintainRecordEntity;
import org.sxkj.fw.device.vo.FwDeviceMaintainRecordVO;
import org.sxkj.fw.device.excel.FwDeviceMaintainRecordExcel;
import org.sxkj.fw.device.mapper.FwDeviceMaintainRecordMapper;
import org.sxkj.fw.device.service.IFwDeviceMaintainRecordService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springframework.stereotype.Service;
import org.sxkj.common.utils.DateUtils;
import org.sxkj.fw.device.dto.FwDeviceMaintainRecordDTO;
import org.sxkj.fw.device.entity.FwDeviceMaintainPlanEntity;
import org.sxkj.fw.device.entity.FwDeviceMaintainRecordEntity;
import org.sxkj.fw.device.excel.FwDeviceMaintainRecordExcel;
import org.sxkj.fw.device.mapper.FwDeviceMaintainRecordMapper;
import org.sxkj.fw.device.service.IFwDeviceMaintainPlanService;
import org.sxkj.fw.device.service.IFwDeviceMaintainRecordService;
import org.sxkj.fw.device.vo.FwDeviceMaintainRecordVO;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
/**
@@ -35,21 +45,128 @@
 * @since 2026-01-08
 */
@Service
@AllArgsConstructor
public class FwDeviceMaintainRecordServiceImpl extends BaseServiceImpl<FwDeviceMaintainRecordMapper, FwDeviceMaintainRecordEntity> implements IFwDeviceMaintainRecordService {
    private final IFwDeviceMaintainPlanService fwDeviceMaintainPlanService;
    @Override
    public FwDeviceMaintainRecordVO selectLastOne(Long planId) {
        return baseMapper.selectLastOne(planId);
    }
    @Override
    public IPage<FwDeviceMaintainRecordVO> selectFwDeviceMaintainRecordPage(IPage<FwDeviceMaintainRecordVO> page, FwDeviceMaintainRecordDTO fwDeviceMaintainRecord) {
        return page.setRecords(baseMapper.selectFwDeviceMaintainRecordPage(page, fwDeviceMaintainRecord));
    }
    @Override
    public boolean saveOrUpdate(FwDeviceMaintainRecordEntity entity) {
        // 维护时间
        Date maintainTime = entity.getMaintainTime();
        // 查询计划的规则
        FwDeviceMaintainPlanEntity fwDeviceMaintainPlan = fwDeviceMaintainPlanService.getById(entity.getPlanId());
        if (null == fwDeviceMaintainPlan) {
            return false;
        }
        // 验证维护时间是否符合计划周期
        boolean isInCycle = validateMaintainTimeInCycle(maintainTime, fwDeviceMaintainPlan);
        // 在当前周期内修改计划维修状态
        if (isInCycle) {
            updatePlanMaintainStatus(fwDeviceMaintainPlan, maintainTime);
        }
        return super.saveOrUpdate(entity);
    }
    @Override
    public List<FwDeviceMaintainRecordExcel> exportFwDeviceMaintainRecord(Wrapper<FwDeviceMaintainRecordEntity> queryWrapper) {
        List<FwDeviceMaintainRecordExcel> fwDeviceMaintainRecordList = baseMapper.exportFwDeviceMaintainRecord(queryWrapper);
        //fwDeviceMaintainRecordList.forEach(fwDeviceMaintainRecord -> {
        //    fwDeviceMaintainRecord.setTypeName(DictCache.getValue(DictEnum.YES_NO, FwDeviceMaintainRecord.getType()));
        //});
        return fwDeviceMaintainRecordList;
    }
    // region 维修计划状态更新相关
    /**
     * 验证维护时间是否在计划周期内
     */
    private boolean validateMaintainTimeInCycle(Date maintainTime, FwDeviceMaintainPlanEntity plan) {
        String planCycleType = plan.getPlanCycleType();
        List<String> planCycleValue = plan.getPlanCycleValue();
        for (String ruleValue : planCycleValue) {
            boolean isValid = false;
            switch (planCycleType) {
                case "1": // 年度规则
                    isValid = validateAnnualCycle(maintainTime, ruleValue);
                    break;
                case "2": // 月度规则
                    isValid = validateMonthlyCycle(maintainTime, ruleValue);
                    break;
                case "3": // 周度规则
                    isValid = validateWeeklyCycle(maintainTime, ruleValue);
                    break;
            }
            if (isValid) {
                return true;
            }
        }
        return false;
    }
    /**
     * 验证年度周期
     */
    private boolean validateAnnualCycle(Date maintainTime, String ruleValue) {
        String[] parts = ruleValue.split("月");
        int ruleMonth = Integer.parseInt(parts[0]);
        int ruleDay = Integer.parseInt(parts[1].replace("号", ""));
        LocalDateTime[] cycleBounds = DateUtils.calculateAnnualCycle(new Date(), ruleMonth, ruleDay);
        return DateUtils.isMaintainTimeInCycle(maintainTime, cycleBounds[0], cycleBounds[1]);
    }
    /**
     * 验证月度周期
     */
    private boolean validateMonthlyCycle(Date maintainTime, String ruleValue) {
        int ruleDay = Integer.parseInt(ruleValue.replace("号", ""));
        LocalDateTime[] cycleBounds = DateUtils.calculateMonthlyCycle(new Date(), ruleDay);
        return DateUtils.isMaintainTimeInCycle(maintainTime, cycleBounds[0], cycleBounds[1]);
    }
    /**
     * 验证周度周期
     */
    private boolean validateWeeklyCycle(Date maintainTime, String ruleValue) {
        int ruleDayOfWeek = DateUtils.getDayOfWeekNumber(ruleValue);
        LocalDateTime[] cycleBounds = DateUtils.calculateWeeklyCycle(new Date(), ruleDayOfWeek);
//        String dateStart = cycleBounds[0].atZone(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//        String dateEnd = cycleBounds[1].atZone(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//        String dateMaintain = maintainTime.toInstant().atZone(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//
//        log.debug("维护时间: " + dateMaintain + ", 周期开始: " + dateStart + ", 周期结束: " + dateEnd);
        return DateUtils.isMaintainTimeInCycle(maintainTime, cycleBounds[0], cycleBounds[1]);
    }
    /**
     * 更新计划维护状态
     */
    private void updatePlanMaintainStatus(FwDeviceMaintainPlanEntity plan, Date maintainTime) {
        FwDeviceMaintainPlanEntity updateEntity = new FwDeviceMaintainPlanEntity();
        updateEntity.setId(plan.getId());
        updateEntity.setMaintainStatus(1); // 维护状态
        updateEntity.setLastMaintainTime(maintainTime); // 最后一次维护时间
        fwDeviceMaintainPlanService.updateById(updateEntity);
    }
    // endregion 维修计划状态更新相关
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandAuditAttachmentController.java
@@ -64,10 +64,9 @@
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入gdSupplyDemandAuditAttachment")
    public R<GdSupplyDemandAuditAttachmentVO> detail(GdSupplyDemandAuditAttachmentEntity gdSupplyDemandAuditAttachment) {
        GdSupplyDemandAuditAttachmentEntity detail = gdSupplyDemandAuditAttachmentService.getOne(Condition.getQueryWrapper(gdSupplyDemandAuditAttachment));
        return R.data(GdSupplyDemandAuditAttachmentWrapper.build().entityVO(detail));
    @ApiOperation(value = "详情", notes = "传入demandId")
    public R<List<GdSupplyDemandAuditAttachmentVO>> detail(@ApiParam(value = "供需需求ID", required = true) @RequestParam Long demandId) {
        return R.data(gdSupplyDemandAuditAttachmentService.listSupplyDemandAuditAttachments(demandId));
    }
    /**
     * 供需需求审核附件表 分页
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandAuditController.java
@@ -64,10 +64,9 @@
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入gdSupplyDemandAudit")
    public R<GdSupplyDemandAuditVO> detail(GdSupplyDemandAuditEntity gdSupplyDemandAudit) {
        GdSupplyDemandAuditEntity detail = gdSupplyDemandAuditService.getOne(Condition.getQueryWrapper(gdSupplyDemandAudit));
        return R.data(GdSupplyDemandAuditWrapper.build().entityVO(detail));
    @ApiOperation(value = "详情", notes = "传入demandId")
    public R<List<GdSupplyDemandAuditVO>> detail(@ApiParam(value = "供需需求ID", required = true) @RequestParam Long demandId) {
        return R.data(gdSupplyDemandAuditService.listSupplyDemandAudit(demandId));
    }
    /**
     * 供需需求审核记录表 分页
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/controller/GdSupplyDemandController.java
@@ -16,35 +16,32 @@
 */
package org.sxkj.gd.orderdata.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import javax.validation.Valid;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.sxkj.gd.orderdata.dto.GdSupplyDemandAuditActionDTO;
import org.sxkj.gd.orderdata.dto.GdSupplyDemandDTO;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandEntity;
import org.sxkj.gd.orderdata.param.GdSupplyDemandPageParam;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
import org.sxkj.gd.orderdata.excel.GdSupplyDemandExcel;
import org.sxkj.gd.orderdata.wrapper.GdSupplyDemandWrapper;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandService;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.tool.constant.BladeConstant;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
import org.sxkj.gd.orderdata.wrapper.GdSupplyDemandWrapper;
import javax.validation.Valid;
/**
 * 供需需求信息表 控制器
@@ -64,33 +61,33 @@
     * 供需需求信息表 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入gdSupplyDemand")
    public R<GdSupplyDemandVO> detail(GdSupplyDemandEntity gdSupplyDemand) {
        GdSupplyDemandEntity detail = gdSupplyDemandService.getOne(Condition.getQueryWrapper(gdSupplyDemand));
        return R.data(GdSupplyDemandWrapper.build().entityVO(detail));
    }
    /**
     * 供需需求信息表 分页
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入gdSupplyDemand")
    public R<IPage<GdSupplyDemandVO>> list(@ApiIgnore @RequestParam Map<String, Object> gdSupplyDemand, Query query) {
        IPage<GdSupplyDemandEntity> pages = gdSupplyDemandService.page(Condition.getPage(query), Condition.getQueryWrapper(gdSupplyDemand, GdSupplyDemandEntity.class));
        return R.data(GdSupplyDemandWrapper.build().pageVO(pages));
    }
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入gdSupplyDemand")
    public R<GdSupplyDemandVO> detail(GdSupplyDemandDTO gdSupplyDemand) {
        GdSupplyDemandEntity detail = gdSupplyDemandService.detailSupplyDemand(gdSupplyDemand);
        return R.data(GdSupplyDemandWrapper.build().entityVO(detail));
    }
//    /**
//     * 供需需求信息表 分页
//     */
//    @GetMapping("/list")
//    @ApiOperationSupport(order = 2)
//    @ApiOperation(value = "分页", notes = "传入gdSupplyDemand")
//    public R<IPage<GdSupplyDemandVO>> list(GdSupplyDemandDTO gdSupplyDemand, Query query) {
//        IPage<GdSupplyDemandEntity> pages = gdSupplyDemandService.selectGdSupplyDemandList(Condition.getPage(query), gdSupplyDemand);
//        return R.data(GdSupplyDemandWrapper.build().pageVO(pages));
//    }
    /**
     * 供需需求信息表 自定义分页
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入gdSupplyDemand")
    public R<IPage<GdSupplyDemandVO>> page(GdSupplyDemandPageParam gdSupplyDemand, Query query) {
        IPage<GdSupplyDemandVO> pages = gdSupplyDemandService.selectGdSupplyDemandPage(Condition.getPage(query), gdSupplyDemand);
        return R.data(pages);
    }
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入gdSupplyDemand")
    public R<IPage<GdSupplyDemandVO>> page(GdSupplyDemandPageParam gdSupplyDemand, Query query) {
        IPage<GdSupplyDemandVO> pages = gdSupplyDemandService.selectGdSupplyDemandPage(Condition.getPage(query), gdSupplyDemand);
        return R.data(pages);
    }
//    /**
//     * 供需需求信息表 新增
@@ -116,21 +113,41 @@
     * 供需需求信息表 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入gdSupplyDemand")
    public R submit(@Valid @RequestBody GdSupplyDemandEntity gdSupplyDemand) {
        return R.status(gdSupplyDemandService.saveOrUpdate(gdSupplyDemand));
    }
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入gdSupplyDemand")
    public R submit(@Valid @RequestBody GdSupplyDemandDTO gdSupplyDemand) {
        return R.status(gdSupplyDemandService.submitSupplyDemand(gdSupplyDemand));
    }
    /**
     * 供需需求信息表 审核通过
     */
    @PostMapping("/audit-pass")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "审核通过", notes = "传入审核参数")
    public R auditPass(@Valid @RequestBody GdSupplyDemandAuditActionDTO auditAction) {
        return R.status(gdSupplyDemandService.approveSupplyDemand(auditAction.getDemandId(), auditAction.getAuditOpinion(), auditAction.getAttachIds()));
    }
    /**
     * 供需需求信息表 拒绝申请
     */
    @PostMapping("/audit-reject")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "拒绝申请", notes = "传入审核参数")
    public R auditReject(@Valid @RequestBody GdSupplyDemandAuditActionDTO auditAction) {
        return R.status(gdSupplyDemandService.rejectSupplyDemand(auditAction.getDemandId(), auditAction.getAuditOpinion()));
    }
    /**
     * 供需需求信息表 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(gdSupplyDemandService.deleteLogic(Func.toLongList(ids)));
    }
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(gdSupplyDemandService.deleteLogic(Func.toLongList(ids)));
    }
//    /**
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/dto/GdSupplyDemandAuditActionDTO.java
New file
@@ -0,0 +1,46 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.sxkj.gd.orderdata.dto;
import lombok.Data;
import java.util.List;
/**
 * 供需需求审核操作 请求对象
 *
 * @author lw
 * @since 2026-01-16
 */
@Data
public class GdSupplyDemandAuditActionDTO {
    /**
     * 供需需求ID
     */
    private Long demandId;
    /**
     * 审核意见/驳回原因
     */
    private String auditOpinion;
    /**
     * 附件ID列表
     */
    private List<Long> attachIds;
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/entity/GdSupplyDemandAuditEntity.java
@@ -41,7 +41,7 @@
     * 关联供需需求表ID
     */
    @ApiModelProperty(value = "关联供需需求表ID")
    private Integer demandId;
    private Long demandId;
    /**
     * 审核结果(0待审核 1通过 2驳回)
     */
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/entity/GdSupplyDemandEntity.java
@@ -63,9 +63,9 @@
    @ApiModelProperty(value = "需求邮箱")
    private String contactEmail;
    /**
     * 需求状态(草稿/申请中/审核通过/拒绝申请)
     * 需求状态:0-草稿、1-申请中、2-审核通过、3-拒绝申请
     */
    @ApiModelProperty(value = "需求状态(草稿/申请中/审核通过/拒绝申请)")
    @ApiModelProperty(value = "需求状态:0-草稿、1-申请中、2-审核通过、3-拒绝申请")
    private String demandStatus;
    /**
     * 更新周期(每年/每月/每周/不更新)
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/excel/GdSupplyDemandAuditExcel.java
@@ -46,7 +46,7 @@
     */
    @ColumnWidth(20)
    @ExcelProperty("关联供需需求表ID")
    private Integer demandId;
    private Long demandId;
    /**
     * 审核结果(0待审核 1通过 2驳回)
     */
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditAttachmentMapper.java
@@ -51,4 +51,20 @@
     */
    List<GdSupplyDemandAuditAttachmentExcel> exportGdSupplyDemandAuditAttachment(@Param("ew") Wrapper<GdSupplyDemandAuditAttachmentEntity> queryWrapper);
    /**
     * 批量新增附件
     *
     * @param attachments
     * @return
     */
    int insertSupplyDemandAuditAttachments(@Param("attachments") List<GdSupplyDemandAuditAttachmentEntity> attachments);
    /**
     * 按需求ID查询附件
     *
     * @param demandId
     * @return
     */
    List<GdSupplyDemandAuditAttachmentEntity> selectSupplyDemandAuditAttachmentList(@Param("demandId") Long demandId);
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditAttachmentMapper.xml
@@ -22,6 +22,35 @@
        select * from ja_gd_supply_demand_audit_attachment where is_deleted = 0
    </select>
    <insert id="insertSupplyDemandAuditAttachments">
        insert into ja_gd_supply_demand_audit_attachment (
            demand_id,
            attach_id,
            area_code,
            create_user,
            create_dept
        ) values
        <foreach collection="attachments" item="item" separator=",">
            (
                #{item.demandId},
                #{item.attachId},
                #{item.areaCode},
                #{item.createUser},
                #{item.createDept}
            )
        </foreach>
    </insert>
    <select id="selectSupplyDemandAuditAttachmentList" resultMap="gdSupplyDemandAuditAttachmentResultMap">
        select * from ja_gd_supply_demand_audit_attachment
        <where>
            is_deleted = 0
            <if test="demandId != null">
                AND demand_id = #{demandId}
            </if>
        </where>
    </select>
    <select id="exportGdSupplyDemandAuditAttachment" resultType="org.sxkj.gd.orderdata.excel.GdSupplyDemandAuditAttachmentExcel">
        SELECT * FROM ja_gd_supply_demand_audit_attachment ${ew.customSqlSegment}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditMapper.java
@@ -51,4 +51,20 @@
     */
    List<GdSupplyDemandAuditExcel> exportGdSupplyDemandAudit(@Param("ew") Wrapper<GdSupplyDemandAuditEntity> queryWrapper);
    /**
     * 新增审核记录
     *
     * @param gdSupplyDemandAudit
     * @return
     */
    int insertSupplyDemandAudit(GdSupplyDemandAuditEntity gdSupplyDemandAudit);
    /**
     * 按需求ID查询审核记录
     *
     * @param demandId
     * @return
     */
    List<GdSupplyDemandAuditEntity> selectSupplyDemandAuditList(@Param("demandId") Long demandId);
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandAuditMapper.xml
@@ -23,6 +23,34 @@
        select * from ja_gd_supply_demand_audit where is_deleted = 0
    </select>
    <insert id="insertSupplyDemandAudit" useGeneratedKeys="true" keyProperty="id">
        insert into ja_gd_supply_demand_audit (
            demand_id,
            audit_status,
            audit_opinion,
            area_code,
            create_user,
            create_dept
        ) values (
            #{demandId},
            #{auditStatus},
            #{auditOpinion},
            #{areaCode},
            #{createUser},
            #{createDept}
        )
    </insert>
    <select id="selectSupplyDemandAuditList" resultMap="gdSupplyDemandAuditResultMap">
        select * from ja_gd_supply_demand_audit
        <where>
            is_deleted = 0
            <if test="demandId != null">
                AND demand_id = #{demandId}
            </if>
        </where>
    </select>
    <select id="exportGdSupplyDemandAudit" resultType="org.sxkj.gd.orderdata.excel.GdSupplyDemandAuditExcel">
        SELECT * FROM ja_gd_supply_demand_audit ${ew.customSqlSegment}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandMapper.java
@@ -16,6 +16,7 @@
 */
package org.sxkj.gd.orderdata.mapper;
import org.sxkj.gd.orderdata.dto.GdSupplyDemandDTO;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandEntity;
import org.sxkj.gd.orderdata.param.GdSupplyDemandPageParam;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
@@ -39,9 +40,44 @@
     *
     * @param page
     * @param gdSupplyDemand
     * @param deptIdList
     * @return
     */
    List<GdSupplyDemandVO> selectGdSupplyDemandPage(IPage page, @Param("param") GdSupplyDemandPageParam gdSupplyDemand);
    List<GdSupplyDemandVO> selectGdSupplyDemandPage(IPage page, @Param("param") GdSupplyDemandPageParam gdSupplyDemand, @Param("deptIdList") List<Long> deptIdList);
    /**
     * 列表分页
     *
     * @param page
     * @param gdSupplyDemand
     * @return
     */
    List<GdSupplyDemandEntity> selectGdSupplyDemandList(IPage page, @Param("param") GdSupplyDemandDTO gdSupplyDemand);
    /**
     * 详情查询
     *
     * @param gdSupplyDemand
     * @param deptIdList
     * @return
     */
    GdSupplyDemandEntity selectGdSupplyDemandDetail(@Param("param") GdSupplyDemandDTO gdSupplyDemand, @Param("deptIdList") List<Long> deptIdList);
    /**
     * 新增数据
     *
     * @param gdSupplyDemand
     * @return
     */
    int insertGdSupplyDemand(GdSupplyDemandEntity gdSupplyDemand);
    /**
     * 更新数据
     *
     * @param gdSupplyDemand
     * @return
     */
    int updateGdSupplyDemand(GdSupplyDemandEntity gdSupplyDemand);
    /**
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/mapper/GdSupplyDemandMapper.xml
@@ -29,6 +29,103 @@
        <result column="is_deleted" property="isDeleted"/>
    </resultMap>
    <sql id="gdSupplyDemandCondition">
        <if test="param.id != null">
            AND id = #{param.id}
        </if>
        <if test="param.demandName != null and param.demandName != ''">
            AND demand_name = #{param.demandName}
        </if>
        <if test="param.demandType != null and param.demandType != ''">
            AND demand_type = #{param.demandType}
        </if>
        <if test="param.contactPerson != null and param.contactPerson != ''">
            AND contact_person = #{param.contactPerson}
        </if>
        <if test="param.contactPhone != null and param.contactPhone != ''">
            AND contact_phone = #{param.contactPhone}
        </if>
        <if test="param.contactEmail != null and param.contactEmail != ''">
            AND contact_email = #{param.contactEmail}
        </if>
        <if test="param.demandStatus != null and param.demandStatus != ''">
            AND demand_status = #{param.demandStatus}
        </if>
        <if test="param.updateCycle != null and param.updateCycle != ''">
            AND update_cycle = #{param.updateCycle}
        </if>
        <if test="param.applyBasis != null and param.applyBasis != ''">
            AND apply_basis = #{param.applyBasis}
        </if>
        <if test="param.shareType != null and param.shareType != ''">
            AND share_type = #{param.shareType}
        </if>
        <if test="param.applicationScene != null and param.applicationScene != ''">
            AND application_scene = #{param.applicationScene}
        </if>
        <if test="param.responsibleDeptId != null and param.responsibleDeptId != ''">
            AND responsible_dept_id = #{param.responsibleDeptId}
        </if>
        <if test="param.applyTargetDeptId != null and param.applyTargetDeptId != ''">
            AND apply_target_dept_id = #{param.applyTargetDeptId}
        </if>
        <if test="param.dataSource != null and param.dataSource != ''">
            AND data_source = #{param.dataSource}
        </if>
        <if test="param.demandInfo != null and param.demandInfo != ''">
            AND demand_info = #{param.demandInfo}
        </if>
        <if test="param.areaCode != null and param.areaCode != ''">
            AND area_code = #{param.areaCode}
        </if>
        <if test="param.createUser != null">
            AND create_user = #{param.createUser}
        </if>
        <if test="param.createDept != null">
            AND create_dept = #{param.createDept}
        </if>
        <if test="param.createTime != null">
            AND create_time = #{param.createTime}
        </if>
        <if test="param.updateUser != null">
            AND update_user = #{param.updateUser}
        </if>
        <if test="param.updateTime != null">
            AND update_time = #{param.updateTime}
        </if>
        <if test="param.status != null and param.status != ''">
            AND status = #{param.status}
        </if>
    </sql>
    <select id="selectGdSupplyDemandList" resultMap="gdSupplyDemandResultMap">
        select * from ja_gd_supply_demand
        <where>
            is_deleted = 0
            <include refid="gdSupplyDemandCondition"/>
        </where>
    </select>
    <select id="selectGdSupplyDemandDetail" resultMap="gdSupplyDemandResultMap">
        select * from ja_gd_supply_demand
        <where>
            is_deleted = 0
            <include refid="gdSupplyDemandCondition"/>
            <if test="deptIdList != null and deptIdList.size > 0">
                AND (
                    create_dept IN
                    <foreach collection="deptIdList" item="deptId" open="(" separator="," close=")">
                        #{deptId}
                    </foreach>
                    OR apply_target_dept_id IN
                    <foreach collection="deptIdList" item="deptId" open="(" separator="," close=")">
                        #{deptId}
                    </foreach>
                )
            </if>
        </where>
        limit 1
    </select>
    <select id="selectGdSupplyDemandPage" resultMap="gdSupplyDemandResultMap">
        select * from ja_gd_supply_demand
@@ -40,9 +137,120 @@
            <if test="param.demandStatus != null and param.demandStatus != ''">
                AND demand_status = #{param.demandStatus}
            </if>
            <if test="deptIdList != null and deptIdList.size > 0">
                AND (
                    create_dept IN
                    <foreach collection="deptIdList" item="deptId" open="(" separator="," close=")">
                        #{deptId}
                    </foreach>
                    OR apply_target_dept_id IN
                    <foreach collection="deptIdList" item="deptId" open="(" separator="," close=")">
                        #{deptId}
                    </foreach>
                )
            </if>
        </where>
    </select>
    <insert id="insertGdSupplyDemand" useGeneratedKeys="true" keyProperty="id">
        insert into ja_gd_supply_demand (
            demand_name,
            demand_type,
            contact_person,
            contact_phone,
            contact_email,
            demand_status,
            update_cycle,
            apply_basis,
            share_type,
            application_scene,
            responsible_dept_id,
            apply_target_dept_id,
            data_source,
            demand_info,
            area_code,
            create_user,
            create_dept,
            update_user
        ) values (
            #{demandName},
            #{demandType},
            #{contactPerson},
            #{contactPhone},
            #{contactEmail},
            #{demandStatus},
            #{updateCycle},
            #{applyBasis},
            #{shareType},
            #{applicationScene},
            #{responsibleDeptId},
            #{applyTargetDeptId},
            #{dataSource},
            #{demandInfo},
            #{areaCode},
            #{createUser},
            #{createDept},
            #{updateUser}
        )
    </insert>
    <update id="updateGdSupplyDemand">
        update ja_gd_supply_demand
        <set>
            <if test="demandName != null">
                demand_name = #{demandName},
            </if>
            <if test="demandType != null">
                demand_type = #{demandType},
            </if>
            <if test="contactPerson != null">
                contact_person = #{contactPerson},
            </if>
            <if test="contactPhone != null">
                contact_phone = #{contactPhone},
            </if>
            <if test="contactEmail != null">
                contact_email = #{contactEmail},
            </if>
            <if test="demandStatus != null">
                demand_status = #{demandStatus},
            </if>
            <if test="updateCycle != null">
                update_cycle = #{updateCycle},
            </if>
            <if test="applyBasis != null">
                apply_basis = #{applyBasis},
            </if>
            <if test="shareType != null">
                share_type = #{shareType},
            </if>
            <if test="applicationScene != null">
                application_scene = #{applicationScene},
            </if>
            <if test="responsibleDeptId != null">
                responsible_dept_id = #{responsibleDeptId},
            </if>
            <if test="applyTargetDeptId != null">
                apply_target_dept_id = #{applyTargetDeptId},
            </if>
            <if test="dataSource != null">
                data_source = #{dataSource},
            </if>
            <if test="demandInfo != null">
                demand_info = #{demandInfo},
            </if>
            <if test="areaCode != null">
                area_code = #{areaCode},
            </if>
            <if test="updateUser != null">
                update_user = #{updateUser},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
        </set>
        where id = #{id}
    </update>
    <select id="exportGdSupplyDemand" resultType="org.sxkj.gd.orderdata.excel.GdSupplyDemandExcel">
        SELECT * FROM ja_gd_supply_demand ${ew.customSqlSegment}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandAuditAttachmentService.java
@@ -49,4 +49,20 @@
     */
    List<GdSupplyDemandAuditAttachmentExcel> exportGdSupplyDemandAuditAttachment(Wrapper<GdSupplyDemandAuditAttachmentEntity> queryWrapper);
    /**
     * 批量新增附件
     *
     * @param attachments
     * @return
     */
    boolean saveSupplyDemandAuditAttachments(List<GdSupplyDemandAuditAttachmentEntity> attachments);
    /**
     * 按需求ID查询附件
     *
     * @param demandId
     * @return
     */
    List<GdSupplyDemandAuditAttachmentVO> listSupplyDemandAuditAttachments(Long demandId);
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandAuditService.java
@@ -49,4 +49,20 @@
     */
    List<GdSupplyDemandAuditExcel> exportGdSupplyDemandAudit(Wrapper<GdSupplyDemandAuditEntity> queryWrapper);
    /**
     * 新增审核记录
     *
     * @param gdSupplyDemandAudit
     * @return
     */
    boolean saveSupplyDemandAudit(GdSupplyDemandAuditEntity gdSupplyDemandAudit);
    /**
     * 按需求ID查询审核记录
     *
     * @param demandId
     * @return
     */
    List<GdSupplyDemandAuditVO> listSupplyDemandAudit(Long demandId);
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/IGdSupplyDemandService.java
@@ -17,6 +17,7 @@
package org.sxkj.gd.orderdata.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import org.sxkj.gd.orderdata.dto.GdSupplyDemandDTO;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandEntity;
import org.sxkj.gd.orderdata.param.GdSupplyDemandPageParam;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
@@ -41,6 +42,50 @@
     */
    IPage<GdSupplyDemandVO> selectGdSupplyDemandPage(IPage<GdSupplyDemandVO> page, GdSupplyDemandPageParam gdSupplyDemand);
    /**
     * 列表分页
     *
     * @param page
     * @param gdSupplyDemand
     * @return
     */
    IPage<GdSupplyDemandEntity> selectGdSupplyDemandList(IPage<GdSupplyDemandEntity> page, GdSupplyDemandDTO gdSupplyDemand);
    /**
     * 详情查询
     *
     * @param gdSupplyDemand
     * @return
     */
    GdSupplyDemandEntity detailSupplyDemand(GdSupplyDemandDTO gdSupplyDemand);
    /**
     * 保存或发起需求
     *
     * @param gdSupplyDemand
     * @return
     */
    boolean submitSupplyDemand(GdSupplyDemandEntity gdSupplyDemand);
    /**
     * 审核通过
     *
     * @param demandId
     * @param auditOpinion
     * @param attachIds
     * @return
     */
    boolean approveSupplyDemand(Long demandId, String auditOpinion, List<Long> attachIds);
    /**
     * 拒绝申请
     *
     * @param demandId
     * @param auditOpinion
     * @return
     */
    boolean rejectSupplyDemand(Long demandId, String auditOpinion);
    /**
     * 导出数据
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandAuditAttachmentServiceImpl.java
@@ -16,16 +16,21 @@
 */
package org.sxkj.gd.orderdata.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.Func;
import org.springframework.stereotype.Service;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandAuditAttachmentEntity;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandAuditAttachmentVO;
import org.sxkj.gd.orderdata.excel.GdSupplyDemandAuditAttachmentExcel;
import org.sxkj.gd.orderdata.mapper.GdSupplyDemandAuditAttachmentMapper;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandAuditAttachmentService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandAuditAttachmentVO;
import org.sxkj.gd.orderdata.wrapper.GdSupplyDemandAuditAttachmentWrapper;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 供需需求审核附件表 服务实现类
@@ -51,4 +56,27 @@
        return gdSupplyDemandAuditAttachmentList;
    }
    @Override
    public boolean saveSupplyDemandAuditAttachments(List<GdSupplyDemandAuditAttachmentEntity> attachments) {
        if (attachments == null || attachments.isEmpty()) {
            return true;
        }
        return baseMapper.insertSupplyDemandAuditAttachments(attachments) > 0;
    }
    @Override
    public List<GdSupplyDemandAuditAttachmentVO> listSupplyDemandAuditAttachments(Long demandId) {
        if (demandId == null) {
            throw new ServiceException("需求编号不能为空");
        }
        List<GdSupplyDemandAuditAttachmentEntity> entities = baseMapper.selectSupplyDemandAuditAttachmentList(demandId);
        if (Func.isEmpty(entities)) {
            return java.util.Collections.emptyList();
        }
        GdSupplyDemandAuditAttachmentWrapper wrapper = GdSupplyDemandAuditAttachmentWrapper.build();
        return entities.stream()
            .map(wrapper::entityVO)
            .collect(Collectors.toList());
    }
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandAuditServiceImpl.java
@@ -16,16 +16,21 @@
 */
package org.sxkj.gd.orderdata.service.impl;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.Func;
import org.springframework.stereotype.Service;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandAuditEntity;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandAuditVO;
import org.sxkj.gd.orderdata.excel.GdSupplyDemandAuditExcel;
import org.sxkj.gd.orderdata.mapper.GdSupplyDemandAuditMapper;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandAuditService;
import org.springframework.stereotype.Service;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandAuditVO;
import org.sxkj.gd.orderdata.wrapper.GdSupplyDemandAuditWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseServiceImpl;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 供需需求审核记录表 服务实现类
@@ -51,4 +56,24 @@
        return gdSupplyDemandAuditList;
    }
    @Override
    public boolean saveSupplyDemandAudit(GdSupplyDemandAuditEntity gdSupplyDemandAudit) {
        return baseMapper.insertSupplyDemandAudit(gdSupplyDemandAudit) > 0;
    }
    @Override
    public List<GdSupplyDemandAuditVO> listSupplyDemandAudit(Long demandId) {
        if (demandId == null) {
            throw new ServiceException("需求编号不能为空");
        }
        List<GdSupplyDemandAuditEntity> entities = baseMapper.selectSupplyDemandAuditList(demandId);
        if (Func.isEmpty(entities)) {
            return java.util.Collections.emptyList();
        }
        GdSupplyDemandAuditWrapper wrapper = GdSupplyDemandAuditWrapper.build();
        return entities.stream()
            .map(wrapper::entityVO)
            .collect(Collectors.toList());
    }
}
drone-service/drone-gd/src/main/java/org/sxkj/gd/orderdata/service/impl/GdSupplyDemandServiceImpl.java
@@ -16,16 +16,30 @@
 */
package org.sxkj.gd.orderdata.service.impl;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandEntity;
import org.sxkj.gd.orderdata.param.GdSupplyDemandPageParam;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
import org.sxkj.gd.orderdata.excel.GdSupplyDemandExcel;
import org.sxkj.gd.orderdata.mapper.GdSupplyDemandMapper;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.sxkj.gd.orderdata.dto.GdSupplyDemandDTO;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandAuditAttachmentEntity;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandAuditEntity;
import org.sxkj.gd.orderdata.entity.GdSupplyDemandEntity;
import org.sxkj.gd.orderdata.excel.GdSupplyDemandExcel;
import org.sxkj.gd.orderdata.mapper.GdSupplyDemandMapper;
import org.sxkj.gd.orderdata.param.GdSupplyDemandPageParam;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandAuditAttachmentService;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandAuditService;
import org.sxkj.gd.orderdata.service.IGdSupplyDemandService;
import org.sxkj.gd.orderdata.vo.GdSupplyDemandVO;
import org.sxkj.system.cache.SysCache;
import java.util.ArrayList;
import java.util.List;
/**
@@ -35,13 +49,201 @@
 * @since 2026-01-16
 */
@Service
@AllArgsConstructor
public class GdSupplyDemandServiceImpl extends BaseServiceImpl<GdSupplyDemandMapper, GdSupplyDemandEntity> implements IGdSupplyDemandService {
    /**
     * 需求状态常量:0-草稿、1-申请中、2-审核通过、3-拒绝申请
     */
    private static final String STATUS_DRAFT = "0";
    private static final String STATUS_APPLY = "1";
    private static final String STATUS_APPROVED = "2";
    private static final String STATUS_REJECTED = "3";
    /**
     * 审核状态常量:0-待审核、1-审核通过、2-审核拒绝
     */
    private static final String AUDIT_PENDING = "0";
    private static final String AUDIT_PASS = "1";
    private static final String AUDIT_REJECT = "2";
    private final IGdSupplyDemandAuditService gdSupplyDemandAuditService;
    private final IGdSupplyDemandAuditAttachmentService gdSupplyDemandAuditAttachmentService;
    @Override
    public IPage<GdSupplyDemandVO> selectGdSupplyDemandPage(IPage<GdSupplyDemandVO> page, GdSupplyDemandPageParam gdSupplyDemand) {
        return page.setRecords(baseMapper.selectGdSupplyDemandPage(page, gdSupplyDemand));
        return page.setRecords(baseMapper.selectGdSupplyDemandPage(page, gdSupplyDemand, buildDeptIdList()));
    }
    @Override
    public IPage<GdSupplyDemandEntity> selectGdSupplyDemandList(IPage<GdSupplyDemandEntity> page, GdSupplyDemandDTO gdSupplyDemand) {
        return page.setRecords(baseMapper.selectGdSupplyDemandList(page, gdSupplyDemand));
    }
    @Override
    public GdSupplyDemandEntity detailSupplyDemand(GdSupplyDemandDTO gdSupplyDemand) {
        if (Func.isEmpty(gdSupplyDemand)) {
            throw new ServiceException("需求参数不能为空");
        }
        GdSupplyDemandEntity detail = baseMapper.selectGdSupplyDemandDetail(gdSupplyDemand, buildDeptIdList());
        if (detail == null) {
            throw new ServiceException("未查询到数据");
        }
        return detail;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean submitSupplyDemand(GdSupplyDemandEntity gdSupplyDemand) {
        if (Func.isEmpty(gdSupplyDemand)) {
            throw new ServiceException("需求参数不能为空");
        }
        // 获取前端传入的需求状态
        String demandStatus = gdSupplyDemand.getDemandStatus();
        if (StringUtil.isBlank(demandStatus)) {
            throw new ServiceException("需求状态不能为空");
        }
        // 校验状态值有效性
        if (!STATUS_DRAFT.equals(demandStatus) && !STATUS_APPLY.equals(demandStatus)) {
            throw new ServiceException("需求状态值无效,仅支持 0(草稿) 或 1(申请中)");
        }
        boolean isInsert = Func.isEmpty(gdSupplyDemand.getId());
        Long userId = AuthUtil.getUserId();
        Long deptId = Long.valueOf(AuthUtil.getDeptId());
        gdSupplyDemand.setUpdateUser(userId);
        boolean saved;
        if (isInsert) {
            gdSupplyDemand.setCreateUser(userId);
            gdSupplyDemand.setCreateDept(deptId);
            saved = baseMapper.insertGdSupplyDemand(gdSupplyDemand) > 0;
        } else {
            saved = baseMapper.updateGdSupplyDemand(gdSupplyDemand) > 0;
        }
        if (!saved) {
            return false;
        }
        // 仅当状态为"申请中"(1)时才创建审核记录
        if (STATUS_APPLY.equals(demandStatus)) {
            GdSupplyDemandEntity supplyDemand = loadSupplyDemandForAudit(gdSupplyDemand.getId());
            GdSupplyDemandAuditEntity auditEntity = buildAuditEntity(supplyDemand, AUDIT_PENDING, null);
            return gdSupplyDemandAuditService.saveSupplyDemandAudit(auditEntity);
        }
        // 状态为"草稿"(0)时直接返回成功
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean approveSupplyDemand(Long demandId, String auditOpinion, List<Long> attachIds) {
        GdSupplyDemandEntity supplyDemand = loadSupplyDemandForAudit(demandId);
        Long userId = AuthUtil.getUserId();
        GdSupplyDemandEntity update = new GdSupplyDemandEntity();
        update.setId(demandId);
        update.setDemandStatus(STATUS_APPROVED);
        update.setUpdateUser(userId);
        boolean updated = baseMapper.updateGdSupplyDemand(update) > 0;
        if (!updated) {
            return false;
        }
        GdSupplyDemandAuditEntity auditEntity = buildAuditEntity(supplyDemand, AUDIT_PASS, auditOpinion);
        boolean auditSaved = gdSupplyDemandAuditService.saveSupplyDemandAudit(auditEntity);
        if (!auditSaved) {
            return false;
        }
        if (Func.isEmpty(attachIds)) {
            return true;
        }
        List<GdSupplyDemandAuditAttachmentEntity> attachments = new ArrayList<>();
        Long deptId = Long.valueOf(AuthUtil.getDeptId());
        attachIds.forEach(attachId -> {
            if (attachId == null) {
                return;
            }
            GdSupplyDemandAuditAttachmentEntity attachment = new GdSupplyDemandAuditAttachmentEntity();
            attachment.setDemandId(demandId);
            attachment.setAttachId(attachId);
            attachment.setAreaCode(supplyDemand.getAreaCode());
            attachment.setCreateUser(userId);
            attachment.setCreateDept(deptId);
            attachments.add(attachment);
        });
        if (attachments.isEmpty()) {
            return true;
        }
        return gdSupplyDemandAuditAttachmentService.saveSupplyDemandAuditAttachments(attachments);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean rejectSupplyDemand(Long demandId, String auditOpinion) {
        if (StringUtil.isBlank(auditOpinion)) {
            throw new ServiceException("拒绝原因不能为空");
        }
        GdSupplyDemandEntity supplyDemand = loadSupplyDemandForAudit(demandId);
        Long userId = AuthUtil.getUserId();
        GdSupplyDemandEntity update = new GdSupplyDemandEntity();
        update.setId(demandId);
        update.setDemandStatus(STATUS_REJECTED);
        update.setUpdateUser(userId);
        boolean updated = baseMapper.updateGdSupplyDemand(update) > 0;
        if (!updated) {
            return false;
        }
        GdSupplyDemandAuditEntity auditEntity = buildAuditEntity(supplyDemand, AUDIT_REJECT, auditOpinion);
        return gdSupplyDemandAuditService.saveSupplyDemandAudit(auditEntity);
    }
    private GdSupplyDemandAuditEntity buildAuditEntity(GdSupplyDemandEntity supplyDemand, String auditStatus, String auditOpinion) {
        if (Func.isEmpty(supplyDemand) || supplyDemand.getId() == null) {
            throw new ServiceException("需求数据不存在");
        }
        Long userId = AuthUtil.getUserId();
        Long deptId = Long.valueOf(AuthUtil.getDeptId());
        GdSupplyDemandAuditEntity auditEntity = new GdSupplyDemandAuditEntity();
        auditEntity.setDemandId(supplyDemand.getId());
        auditEntity.setAuditStatus(auditStatus);
        auditEntity.setAuditOpinion(StringUtil.isBlank(auditOpinion) ? null : auditOpinion);
        auditEntity.setAreaCode(supplyDemand.getAreaCode());
        auditEntity.setCreateUser(userId);
        auditEntity.setCreateDept(deptId);
        return auditEntity;
    }
    private GdSupplyDemandEntity loadSupplyDemandForAudit(Long demandId) {
        if (demandId == null) {
            throw new ServiceException("需求编号不能为空");
        }
        GdSupplyDemandDTO query = new GdSupplyDemandDTO();
        query.setId(demandId);
        GdSupplyDemandEntity detail = baseMapper.selectGdSupplyDemandDetail(query, buildDeptIdList());
        if (detail == null) {
            throw new ServiceException("需求数据不存在");
        }
        return detail;
    }
    private List<Long> buildDeptIdList() {
        if (AuthUtil.isAdministrator()) {
            return null;
        }
        Long deptId = Long.valueOf(AuthUtil.getDeptId());
        List<Long> deptIdList = SysCache.getDeptChildIds(deptId);
        List<Long> result = new ArrayList<>();
        if (Func.isNotEmpty(deptIdList)) {
            result.addAll(deptIdList);
        }
        if (!result.contains(deptId)) {
            result.add(deptId);
        }
        return result;
    }
    @Override
    public List<GdSupplyDemandExcel> exportGdSupplyDemand(Wrapper<GdSupplyDemandEntity> queryWrapper) {