Просмотр исходного кода

华创启德质量指标:新增推送的定时任务

ZhuJiaHao 1 день назад
Родитель
Сommit
e4022931d1

+ 243 - 0
ibps-model-root/modules/org-model/src/main/java/com/lc/ibps/org/party/persistence/entity/QualityMetricsForHcqdDto.java

@@ -0,0 +1,243 @@
+package com.lc.ibps.org.party.persistence.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import java.util.Date;
+
+/**
+ * 质量指标计划DTO
+ * 包含计划表和填写记录表的公用字段
+ */
+public class QualityMetricsForHcqdDto {
+    
+    // 计划表字段
+    private String id;                // 主键
+    private String tenantId;          // 租户ID
+    private String ip;                // IP地址
+    private String createBy;          // 创建人
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;          // 创建时间
+    private String updateBy;          // 更新人
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;          // 更新时间
+    private String shiFouGuoShen;     // 是否过审
+    private String diDian;            // 地点
+    private String bianZhiRen;        // 编制人
+    private String bianZhiBuMen;      // 编制部门
+    private String bianZhiShiJian;    // 编制时间
+    private String kuaiZhao;          // 快照
+    private String suoShuBuMen;       // 所属部门
+    private String zhouQiLeiXing;     // 周期类型
+    private String zhouQiCanShu1;     // 周期参数1
+    private String zhouQiCanShu2;     // 周期参数2
+    private String zhouQiBiaoDa;      // 周期表达式
+    private String shiFouQiYong;      // 是否启用
+    private String biaoGeMoBan;       // 表格模板
+    private String jiHuaMingCheng;    // 计划名称
+    private String zhouQiXiangQin;    // 周期详情
+    private String xiaCiTuiSongT;     // 下次推送时间
+    
+    // 填写记录表字段
+    private String jiHuaBiaoId;       // 计划表id(填写记录表专用)
+    private String biaoGeNeiRong;     // 表格内容(填写记录表专用)
+
+    // Getters and Setters
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(String tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getShiFouGuoShen() {
+        return shiFouGuoShen;
+    }
+
+    public void setShiFouGuoShen(String shiFouGuoShen) {
+        this.shiFouGuoShen = shiFouGuoShen;
+    }
+
+    public String getDiDian() {
+        return diDian;
+    }
+
+    public void setDiDian(String diDian) {
+        this.diDian = diDian;
+    }
+
+    public String getBianZhiRen() {
+        return bianZhiRen;
+    }
+
+    public void setBianZhiRen(String bianZhiRen) {
+        this.bianZhiRen = bianZhiRen;
+    }
+
+    public String getBianZhiBuMen() {
+        return bianZhiBuMen;
+    }
+
+    public void setBianZhiBuMen(String bianZhiBuMen) {
+        this.bianZhiBuMen = bianZhiBuMen;
+    }
+
+    public String getBianZhiShiJian() {
+        return bianZhiShiJian;
+    }
+
+    public void setBianZhiShiJian(String bianZhiShiJian) {
+        this.bianZhiShiJian = bianZhiShiJian;
+    }
+
+    public String getKuaiZhao() {
+        return kuaiZhao;
+    }
+
+    public void setKuaiZhao(String kuaiZhao) {
+        this.kuaiZhao = kuaiZhao;
+    }
+
+    public String getSuoShuBuMen() {
+        return suoShuBuMen;
+    }
+
+    public void setSuoShuBuMen(String suoShuBuMen) {
+        this.suoShuBuMen = suoShuBuMen;
+    }
+
+    public String getZhouQiLeiXing() {
+        return zhouQiLeiXing;
+    }
+
+    public void setZhouQiLeiXing(String zhouQiLeiXing) {
+        this.zhouQiLeiXing = zhouQiLeiXing;
+    }
+
+    public String getZhouQiCanShu1() {
+        return zhouQiCanShu1;
+    }
+
+    public void setZhouQiCanShu1(String zhouQiCanShu1) {
+        this.zhouQiCanShu1 = zhouQiCanShu1;
+    }
+
+    public String getZhouQiCanShu2() {
+        return zhouQiCanShu2;
+    }
+
+    public void setZhouQiCanShu2(String zhouQiCanShu2) {
+        this.zhouQiCanShu2 = zhouQiCanShu2;
+    }
+
+    public String getZhouQiBiaoDa() {
+        return zhouQiBiaoDa;
+    }
+
+    public void setZhouQiBiaoDa(String zhouQiBiaoDa) {
+        this.zhouQiBiaoDa = zhouQiBiaoDa;
+    }
+
+    public String getShiFouQiYong() {
+        return shiFouQiYong;
+    }
+
+    public void setShiFouQiYong(String shiFouQiYong) {
+        this.shiFouQiYong = shiFouQiYong;
+    }
+
+    public String getBiaoGeMoBan() {
+        return biaoGeMoBan;
+    }
+
+    public void setBiaoGeMoBan(String biaoGeMoBan) {
+        this.biaoGeMoBan = biaoGeMoBan;
+    }
+
+    public String getJiHuaMingCheng() {
+        return jiHuaMingCheng;
+    }
+
+    public void setJiHuaMingCheng(String jiHuaMingCheng) {
+        this.jiHuaMingCheng = jiHuaMingCheng;
+    }
+
+    public String getZhouQiXiangQin() {
+        return zhouQiXiangQin;
+    }
+
+    public void setZhouQiXiangQin(String zhouQiXiangQin) {
+        this.zhouQiXiangQin = zhouQiXiangQin;
+    }
+
+    public String getXiaCiTuiSongT() {
+        return xiaCiTuiSongT;
+    }
+
+    public void setXiaCiTuiSongT(String xiaCiTuiSongT) {
+        this.xiaCiTuiSongT = xiaCiTuiSongT;
+    }
+
+    public String getJiHuaBiaoId() {
+        return jiHuaBiaoId;
+    }
+
+    public void setJiHuaBiaoId(String jiHuaBiaoId) {
+        this.jiHuaBiaoId = jiHuaBiaoId;
+    }
+
+    public String getBiaoGeNeiRong() {
+        return biaoGeNeiRong;
+    }
+
+    public void setBiaoGeNeiRong(String biaoGeNeiRong) {
+        this.biaoGeNeiRong = biaoGeNeiRong;
+    }
+}

+ 92 - 0
ibps-model-root/modules/org-model/src/main/resources/com/lc/ibps/org/party/persistence/mapping/QualityMetricsForHcqd.map.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.lc.ibps.platform.dao.QualityMetricsForHcqdDao">
+
+    <!-- 结果映射:计划表 -->
+    <resultMap id="planResultMap" type="com.lc.ibps.org.party.persistence.entity.QualityMetricsForHcqdDto">
+        <id property="id" column="id_" />
+        <result property="tenantId" column="tenant_id_" />
+        <result property="ip" column="ip_" />
+        <result property="createBy" column="create_by_" />
+        <result property="createTime" column="create_time_" />
+        <result property="updateBy" column="update_by_" />
+        <result property="updateTime" column="update_time_" />
+        <result property="shiFouGuoShen" column="shi_fou_guo_shen_" />
+        <result property="diDian" column="di_dian_" />
+        <result property="bianZhiRen" column="bian_zhi_ren_" />
+        <result property="bianZhiBuMen" column="bian_zhi_bu_men_" />
+        <result property="bianZhiShiJian" column="bian_zhi_shi_jian" />
+        <result property="kuaiZhao" column="kuai_zhao_" />
+        <result property="suoShuBuMen" column="suo_shu_bu_men_" />
+        <result property="zhouQiLeiXing" column="zhou_qi_lei_xing_" />
+        <result property="zhouQiCanShu1" column="zhou_qi_can_shu_1" />
+        <result property="zhouQiCanShu2" column="zhou_qi_can_shu_2" />
+        <result property="zhouQiBiaoDa" column="zhou_qi_biao_da_" />
+        <result property="shiFouQiYong" column="shi_fou_qi_yong_" />
+        <result property="biaoGeNeiRong" column="biao_ge_nei_rong_" />
+        <result property="jiHuaMingCheng" column="ji_hua_ming_cheng" />
+        <result property="zhouQiXiangQin" column="zhou_qi_xiang_qin" />
+        <result property="xiaCiTuiSongT" column="xia_ci_tui_song_t" />
+    </resultMap>
+
+    <!-- 获取所有启用的计划 -->
+    <select id="findEnabledPlans" resultMap="planResultMap">
+        SELECT 
+            id_, tenant_id_, ip_, create_by_, create_time_, update_by_, update_time_,
+            shi_fou_guo_shen_, di_dian_, bian_zhi_ren_, bian_zhi_bu_men_, bian_zhi_shi_jian,
+            kuai_zhao_, suo_shu_bu_men_, zhou_qi_lei_xing_, zhou_qi_can_shu_1, zhou_qi_can_shu_2,
+            zhou_qi_biao_da_, shi_fou_qi_yong_, biao_ge_nei_rong_, ji_hua_ming_cheng,
+            zhou_qi_xiang_qin, xia_ci_tui_song_t
+        FROM t_zlzbjhb
+        WHERE shi_fou_qi_yong_ = 'Y'
+    </select>
+
+    <!-- 插入填写记录 -->
+    <insert id="insertRecord" parameterType="com.lc.ibps.org.party.persistence.entity.QualityMetricsForHcqdDto">
+        INSERT INTO t_zlzbtxjlb (
+            id_, tenant_id_, ip_, create_by_, create_time_, update_by_, update_time_,
+            shi_fou_guo_shen_, di_dian_, bian_zhi_ren_, bian_zhi_bu_men_, bian_zhi_shi_jian,
+            kuai_zhao_, ji_hua_biao_id_, biao_ge_nei_rong_, ji_hua_ming_cheng,
+            zhou_qi_lei_xing_, zhou_qi_xiang_qin
+        ) VALUES (
+            #{id}, #{tenantId}, #{ip}, #{createBy}, NOW(), #{updateBy}, #{updateTime},
+            #{shiFouGuoShen}, #{diDian}, #{bianZhiRen}, #{bianZhiBuMen}, #{bianZhiShiJian},
+            #{kuaiZhao}, #{jiHuaBiaoId}, #{biaoGeNeiRong}, #{jiHuaMingCheng},
+            #{zhouQiLeiXing}, #{zhouQiXiangQin}
+        )
+    </insert>
+
+    <!-- 检查填写记录是否已存在 -->
+    <select id="countRecordByPlanAndDate" resultType="int">
+        SELECT COUNT(*) 
+        FROM t_zlzbtxjlb 
+        WHERE ji_hua_biao_id_ = #{jiHuaBiaoId} 
+          AND bian_zhi_shi_jian = #{bianZhiShiJian}
+    </select>
+
+    <!-- 获取指定计划的所有填写记录日期 -->
+    <select id="findRecordDatesByPlan" resultType="string">
+        SELECT bian_zhi_shi_jian
+        FROM t_zlzbtxjlb 
+        WHERE ji_hua_biao_id_ = #{jiHuaBiaoId}
+        ORDER BY bian_zhi_shi_jian ASC
+    </select>
+
+    <!-- 更新计划的下次推送时间 -->
+    <update id="updateNextPushTime">
+        UPDATE t_zlzbjhb
+        SET xia_ci_tui_song_t = #{nextPushTime},
+            update_time_ = NOW(),
+            update_by_ = 'system_job'
+        WHERE id_ = #{id}
+    </update>
+
+    <!-- 获取指定计划的最新填写记录日期 -->
+    <select id="findLatestRecordDateByPlan" resultType="string">
+        SELECT MAX(bian_zhi_shi_jian)
+        FROM t_zlzbtxjlb 
+        WHERE ji_hua_biao_id_ = #{jiHuaBiaoId}
+    </select>
+
+</mapper>

+ 56 - 0
ibps-provider-root/modules/provider-platform-default/src/main/java/com/lc/ibps/platform/dao/QualityMetricsForHcqdDao.java

@@ -0,0 +1,56 @@
+package com.lc.ibps.platform.dao;
+
+import com.lc.ibps.org.party.persistence.entity.QualityMetricsForHcqdDto;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface QualityMetricsForHcqdDao {
+    
+    /**
+     * 获取所有启用的计划
+     * @return 计划列表
+     */
+    List<QualityMetricsForHcqdDto> findEnabledPlans();
+    
+    /**
+     * 插入填写记录
+     * @param record 填写记录数据
+     * @return 影响行数
+     */
+    int insertRecord(QualityMetricsForHcqdDto record);
+    
+    /**
+     * 检查填写记录是否已存在
+     * @param jiHuaBiaoId 计划表ID
+     * @param bianZhiShiJian 编制时间(yyyy-MM-dd)
+     * @return 存在数量
+     */
+    int countRecordByPlanAndDate(@Param("jiHuaBiaoId") String jiHuaBiaoId, 
+                                 @Param("bianZhiShiJian") String bianZhiShiJian);
+    
+    /**
+     * 获取指定计划的所有填写记录日期
+     * @param jiHuaBiaoId 计划表ID
+     * @return 日期列表(yyyy-MM-dd格式)
+     */
+    List<String> findRecordDatesByPlan(@Param("jiHuaBiaoId") String jiHuaBiaoId);
+    
+    /**
+     * 获取指定计划的最新填写记录日期
+     * @param jiHuaBiaoId 计划表ID
+     * @return 最新日期(yyyy-MM-dd格式),如果没有记录返回null
+     */
+    String findLatestRecordDateByPlan(@Param("jiHuaBiaoId") String jiHuaBiaoId);
+    
+    /**
+     * 更新计划的下次推送时间
+     * @param id 计划ID
+     * @param nextPushTime 下次推送时间
+     * @return 影响行数
+     */
+    int updateNextPushTime(@Param("id") String id, 
+                           @Param("nextPushTime") String nextPushTime);
+}

+ 639 - 0
ibps-provider-root/modules/provider-platform/src/main/java/com/lc/ibps/platform/plan/job/QualityMetricsForHcqdJob.java

@@ -0,0 +1,639 @@
+package com.lc.ibps.platform.plan.job;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.lc.ibps.base.core.util.AppUtil;
+import com.lc.ibps.base.core.util.BeanUtils;
+import com.lc.ibps.base.core.util.JacksonUtil;
+import com.lc.ibps.org.party.persistence.entity.QualityMetricsForHcqdDto;
+import com.lc.ibps.platform.dao.QualityMetricsForHcqdDao;
+import org.apache.commons.lang3.StringUtils;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 华创启德专用质量指标推送任务
+ * 该定时任务用于处理华创启德相关的质量指标计算和推送
+ * 
+ * 主要业务流程:
+ * 1. 获取所有启用的质量指标计划(shi_fou_qi_yong_='Y')
+ * 2. 解析每个计划的周期参数(zhou_qi_can_shu_1 JSON格式)
+ * 3. 计算从计划创建到今天的所有周期日期
+ * 4. 检查每个周期是否已经推送过(避免重复)
+ * 5. 判断今天是否需要推送(周期日期=今天且日期偏移满足条件)
+ * 6. 插入填写记录到t_zlzbtxjlb表
+ * 7. 刷新计划的下次推送时间(xia_ci_tui_song_t)
+ * 
+ * 核心方法调用链:
+ * executeJob() → processAllPlans() → processSinglePlan() → processCyclePush()
+ */
+public class QualityMetricsForHcqdJob extends AbstractJob {
+
+
+    private static final Logger logger = LoggerFactory.getLogger(QualityMetricsForHcqdJob.class);
+    
+    // 日期格式化器
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    
+    @Override
+    public void executeJob(JobExecutionContext context) throws Exception {
+        JobDataMap dataMap = context.getMergedJobDataMap();
+        logger.warn("group={} job={} trigger={} is running.",
+                context.getJobDetail().getKey().getGroup(),
+                context.getJobDetail().getKey().getName(),
+                context.getTrigger().getKey().getName());
+        logger.warn("jobDataMap=is {}.", JacksonUtil.toJsonString(dataMap.getWrappedMap()));
+        
+        logger.warn("QualityMetricsForHcqdJob定时任务开始执行...");
+        
+        try {
+            // 1. 获取DAO实例
+            QualityMetricsForHcqdDao qualityMetricsDao = AppUtil.getBean(QualityMetricsForHcqdDao.class);
+            
+            // 2. 获取所有启用的计划
+            List<QualityMetricsForHcqdDto> enabledPlans = qualityMetricsDao.findEnabledPlans();
+            if (BeanUtils.isEmpty(enabledPlans)) {
+                logger.warn("没有找到启用的质量指标计划");
+                return;
+            }
+            
+            logger.warn("找到 {} 个启用的质量指标计划", enabledPlans.size());
+            
+            // 3. 处理所有计划
+            processAllPlans(enabledPlans, qualityMetricsDao);
+            
+        } catch (Exception e) {
+            logger.error("QualityMetricsForHcqdJob执行异常: {}", e.getMessage(), e);
+            throw e;
+        }
+    }
+    
+    /**
+     * 处理所有计划的主方法
+     */
+    private void processAllPlans(List<QualityMetricsForHcqdDto> plans, QualityMetricsForHcqdDao dao) {
+        LocalDate today = LocalDate.now();
+        int totalProcessed = 0;
+        
+        for (QualityMetricsForHcqdDto plan : plans) {
+            try {
+                //执行推送
+                int processed = processSinglePlan(plan, today, dao);
+                totalProcessed += processed;
+                
+                // 刷新下次推送时间
+                refreshNextPushTime(plan, dao);
+                
+            } catch (Exception e) {
+                logger.error("处理计划 {} 时发生异常: {}", plan.getJiHuaMingCheng(), e.getMessage(), e);
+            }
+        }
+        
+        logger.warn("QualityMetricsForHcqdJob定时任务执行完成,共处理了 {} 条记录", totalProcessed);
+    }
+    
+    /**
+     * 处理单个计划
+     */
+    private int processSinglePlan(QualityMetricsForHcqdDto plan, LocalDate today, QualityMetricsForHcqdDao dao) {
+        // 1. 解析周期参数
+        CycleParams cycleParams = parseCycleParams(plan.getZhouQiCanShu1(), plan.getZhouQiLeiXing());
+        if (cycleParams == null) {
+            logger.warn("计划 {} 的周期参数解析失败", plan.getJiHuaMingCheng());
+            return 0;
+        }
+        
+        // 2. 获取计划的起始时间(编制时间)
+        LocalDate StartDate = getPlanStartDate(plan);
+        if (StartDate == null) {
+            logger.warn("计划 {} 的创建时间无效", plan.getJiHuaMingCheng());
+            return 0;
+        }
+        
+        // 3. 获取已推送日期集合
+        Set<String> existingDateSet = getExistingRecordDates(plan.getId(), dao);
+        
+        // 4. 计算所有周期日期
+        List<LocalDate> cycleDates = calculateAllCycleDates(StartDate, today, cycleParams);
+        
+        // 5. 处理周期推送
+        return processCyclePush(plan, cycleDates, today, cycleParams, existingDateSet, dao);
+    }
+    
+    /**
+     * 刷新下次推送时间
+     */
+    private void refreshNextPushTime(QualityMetricsForHcqdDto plan, QualityMetricsForHcqdDao dao) {
+        try {
+            // 检查计划是否启用,只有启用的计划才刷新下次推送时间
+            if (!"Y".equals(plan.getShiFouQiYong())) {
+                logger.debug("计划 {} 未启用,不刷新下次推送时间", plan.getJiHuaMingCheng());
+                return;
+            }
+            
+            // 1. 解析周期参数
+            CycleParams cycleParams = parseCycleParams(plan.getZhouQiCanShu1(), plan.getZhouQiLeiXing());
+            if (cycleParams == null) {
+                logger.warn("计划 {} 的周期参数解析失败,无法刷新下次推送时间", plan.getJiHuaMingCheng());
+                return;
+            }
+            
+            // 2. 计算下次推送时间
+            LocalDate nextPushDate = calculateNextPushDate(plan, cycleParams, dao);
+            
+            // 3. 更新数据库
+            String nextPushStr = nextPushDate.format(DATE_FORMATTER);
+            int result = dao.updateNextPushTime(plan.getId(), nextPushStr);
+            
+            if (result > 0) {
+                logger.debug("计划 {} 的下次推送时间已更新为: {}", plan.getJiHuaMingCheng(), nextPushStr);
+            } else {
+                logger.warn("计划 {} 的下次推送时间更新失败", plan.getJiHuaMingCheng());
+            }
+            
+        } catch (Exception e) {
+            logger.error("刷新计划 {} 的下次推送时间时发生异常: {}", plan.getJiHuaMingCheng(), e.getMessage(), e);
+        }
+    }
+    
+    /**
+     * 计算下次推送时间
+     */
+    private LocalDate calculateNextPushDate(QualityMetricsForHcqdDto plan, CycleParams cycleParams, QualityMetricsForHcqdDao dao) {
+        LocalDate today = LocalDate.now();
+        logger.debug("calculateNextPushDate: 计划={}, 今天={}", plan.getJiHuaMingCheng(), today);
+        
+        LocalDate nextPushDate;
+        
+        // 获取已推送的最近日期
+        LocalDate latestRecordDate = getLatestRecordDate(plan, dao);
+        logger.debug("  最近记录日期: {}", latestRecordDate);
+        
+        if (latestRecordDate != null && !latestRecordDate.isBefore(today)) {
+            // 如果最近记录日期在今天或之后,从最近记录日期计算
+            logger.debug("  最近记录日期在今天或之后,从最近记录日期计算");
+            nextPushDate = calculateNextCycleDate(latestRecordDate, cycleParams);
+            logger.debug("  从最近记录日期计算的下次推送日期: {}", nextPushDate);
+        } else {
+            // 否则从今天开始计算
+            logger.debug("  从今天开始计算第一个有效日期");
+            nextPushDate = calculateFirstValidDate(today, cycleParams);
+            logger.debug("  计算出的第一个有效日期: {}", nextPushDate);
+            
+            // 如果今天已经达到或超过推送日,计算下一个周期
+            if (isTodayAfterOrEqualPushDate(nextPushDate, today)) {
+                // 已经推送过或今天就是推送日,计算下一个周期
+                logger.debug("  今天已达到或超过推送日,计算下一个周期");
+                nextPushDate = calculateNextCycleDate(nextPushDate, cycleParams);
+                logger.debug("  下一个周期日期: {}", nextPushDate);
+            } else {
+                logger.debug("  今天未达到推送日,使用当前计算日期");
+            }
+        }
+        
+        logger.debug("  最终下次推送日期: {}", nextPushDate);
+        return nextPushDate;
+    }
+    
+    /**
+     * 获取计划的最新记录日期
+     */
+    private LocalDate getLatestRecordDate(QualityMetricsForHcqdDto plan, QualityMetricsForHcqdDao dao) {
+        try {
+            String latestDateStr = dao.findLatestRecordDateByPlan(plan.getId());
+            if (StringUtils.isNotBlank(latestDateStr)) {
+                return LocalDate.parse(latestDateStr, DATE_FORMATTER);
+            }
+        } catch (Exception e) {
+            logger.warn("获取计划 {} 的最新记录日期失败: {}", plan.getJiHuaMingCheng(), e.getMessage());
+        }
+        return null;
+    }
+    
+    /**
+     * 计算第一个符合条件的日期(从指定日期开始)
+     */
+    private LocalDate calculateFirstValidDate(LocalDate startDate, CycleParams params) {
+        LocalDate currentDate = startDate;
+        
+        logger.debug("calculateFirstValidDate: start={}, type={}, monthOffset={}, dayOffset={}",
+            startDate, params.cycleType, params.monthOffset, params.dayOffset);
+        
+        // 步骤1:根据周期类型调整到周期起始点
+        // - 季度计划:调整到所在季度的第一天(1月1日、4月1日等)
+        // - 半年度计划:调整到所在半年度的第一天(1月1日或7月1日)
+        // - 年度计划:调整到当年的第一天(1月1日)
+        // - 月度计划:不需要调整,直接使用传入的日期
+        switch (params.cycleType) {
+            case "季度":
+                currentDate = adjustToQuarterStart(currentDate);
+                logger.debug("  调整到季度开始: {}", currentDate);
+                break;
+            case "半年度":
+                currentDate = adjustToHalfYearStart(currentDate);
+                logger.debug("  调整到半年度开始: {}", currentDate);
+                break;
+            case "年度":
+                currentDate = adjustToYearStart(currentDate);
+                logger.debug("  调整到年度开始: {}", currentDate);
+                break;
+            default:
+                // 月度计划不需要调整周期开始
+                logger.debug("  月度计划,不调整周期开始");
+                break;
+        }
+        
+        // 步骤2:应用月份偏移
+        // monthOffset表示在周期内的第几个月执行(1表示第1个月,0表示不使用月份偏移)
+        // 例如:monthOffset=1 表示第1个月(加0个月),monthOffset=2 表示第2个月(加1个月)
+        // 对于季度计划:monthOffset=2 表示季度的第2个月(需要加1个月)
+        // 对于年度计划:monthOffset=6 表示年度第6个月(需要加5个月)
+        // monthOffset=0 表示不使用月份偏移(如月度计划)
+        if (params.monthOffset > 0) {
+            // monthOffset=1表示第1个月,需要加0个月
+            // monthOffset=2表示第2个月,需要加1个月
+            // 以此类推:需要加 (monthOffset - 1) 个月
+            int monthsToAdd = params.monthOffset - 1;
+            currentDate = currentDate.plusMonths(monthsToAdd);
+            logger.debug("  应用月份偏移: +{}个月 = {}", monthsToAdd, currentDate);
+        } else {
+            logger.debug("  月份偏移为0,不应用月份偏移");
+        }
+        
+        // 步骤3:应用日期偏移
+        // dayOffset表示在月份内的第几天执行(从1开始)
+        // 例如:dayOffset=15 表示每月15日
+        // 需要确保日期不超过当月的最后一天
+        if (params.dayOffset > 0) {
+            // 确保日期不超过当月最后一天
+            int day = Math.min(params.dayOffset, currentDate.lengthOfMonth());
+            currentDate = LocalDate.of(currentDate.getYear(), currentDate.getMonth(), day);
+            logger.debug("  应用日期偏移: 第{}天 = {}", day, currentDate);
+        } else {
+            logger.debug("  日期偏移为0,不应用日期偏移");
+        }
+        
+        // 步骤4:处理开始日期已过推送日的情况
+        // 例如:计划创建于1月20日,但推送日是每月15日
+        // 那么第一个推送日应该是下个月的15日,而不是上个月的15日
+        if (startDate.isAfter(currentDate)) {
+            logger.debug("  开始日期{}已过推送日{},计算下一个周期", startDate, currentDate);
+            currentDate = calculateNextCycleDate(currentDate, params);
+        }
+        
+        logger.debug("  最终计算出的第一个有效推送日期: {}", currentDate);
+        return currentDate;
+    }
+    
+    // ==================== 以下是原有的辅助方法,保持原有逻辑 ====================
+    
+    /**
+     * 获取计划的始时间(编制时间)
+     */
+    private LocalDate getPlanStartDate(QualityMetricsForHcqdDto plan) {
+        // 获取原始日期字符串
+        String dateStr = plan.getBianZhiShiJian();
+        // 如果长度是10,说明只有日期部分,补上时间 00:00:00
+        if (dateStr.length() == 10) {
+            dateStr = dateStr + " 00:00:00";
+        }
+        LocalDateTime ldt = LocalDateTime.parse(dateStr, DATETIME_FORMATTER);
+        Date startTime = Date.from(ldt.atZone(java.time.ZoneId.systemDefault()).toInstant());
+        if (startTime == null) {
+            return null;
+        }
+        return startTime.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate();
+    }
+    
+    /**
+     * 获取已存在的记录日期集合
+     */
+    private Set<String> getExistingRecordDates(String planId, QualityMetricsForHcqdDao dao) {
+        List<String> existingDates = dao.findRecordDatesByPlan(planId);
+        return new HashSet<>(existingDates);
+    }
+    
+    /**
+     * 处理周期推送
+     */
+    private int processCyclePush(QualityMetricsForHcqdDto plan, List<LocalDate> cycleDates, 
+                               LocalDate today, CycleParams cycleParams, 
+                               Set<String> existingDateSet, QualityMetricsForHcqdDao dao) {
+        int processedCount = 0;
+        
+        // 遍历所有计算出的周期日期
+        // cycleDates包含从计划创建到今天的所有应该推送的日期
+        for (LocalDate cycleDate : cycleDates) {
+            // 将日期格式化为字符串,用于数据库比较
+            String cycleDateStr = cycleDate.format(DATE_FORMATTER);
+            
+            // 步骤1:检查该周期是否已经推送过
+            // 通过比对已存在的记录日期集合,避免重复插入
+            if (existingDateSet.contains(cycleDateStr)) {
+                continue; // 已存在,跳过处理
+            }
+            
+            // 步骤2:插入填写记录
+            // cycleDate已经通过calculateFirstValidDate正确计算,应该直接推送
+            // 取消shouldPushDate检查,避免大小月问题
+            // 将计划数据复制到填写记录表,关联计划ID和周期日期
+            if (insertRecordForPlan(plan, cycleDateStr, dao)) {
+                processedCount++;
+                logger.warn("计划 {} 的 {} 周期记录已插入", plan.getJiHuaMingCheng(), cycleDateStr);
+            }
+        }
+        
+        // 记录处理结果
+        if (processedCount > 0) {
+            logger.warn("计划 {} 处理完成,新增了 {} 条记录", plan.getJiHuaMingCheng(), processedCount);
+        }
+        
+        return processedCount;
+    }
+    
+    /**
+     * 解析周期参数
+     */
+    private CycleParams parseCycleParams(String cycleParamStr, String cycleType) {
+        if (StringUtils.isBlank(cycleParamStr)) {
+            return null;
+        }
+        
+        try {
+            JSONObject json = JSON.parseObject(cycleParamStr);
+            int[] params = null;
+            
+            switch (cycleType) {
+                case "月度":
+                    params = parseIntArray(json.getJSONArray("month"));
+                    break;
+                case "季度":
+                    params = parseIntArray(json.getJSONArray("quarter"));
+                    break;
+                case "半年度":
+                    params = parseIntArray(json.getJSONArray("halfYear"));
+                    break;
+                case "年度":
+                    params = parseIntArray(json.getJSONArray("year"));
+                    break;
+                default:
+                    logger.warn("未知的周期类型: {}", cycleType);
+                    return null;
+            }
+            
+            if (params == null || params.length < 2) {
+                return null;
+            }
+            
+            // 记录解析结果用于调试
+            logger.debug("解析周期参数: type={}, monthOffset={}, dayOffset={}, raw={}", 
+                cycleType, params[0], params[1], cycleParamStr);
+            
+            return new CycleParams(cycleType, params[0], params[1]);
+            
+        } catch (Exception e) {
+            logger.error("解析周期参数失败: {}, 原始参数: {}", e.getMessage(), cycleParamStr);
+            return null;
+        }
+    }
+    
+    /**
+     * 解析FastJSON数组为Java整型数组
+     * 主要处理周期参数中的月份和日期偏移量,数组固定长度为2
+     * 支持null值处理,null转换为0表示不使用偏移(对于月份)或第1天(对于日期)
+     * 确保即使JSON数组长度不足也能安全处理
+     * 
+     * 注意:对于月份偏移,0表示第1个月或不使用月份偏移(月度计划)
+     *       对于日期偏移,0表示第1天
+     */
+    private int[] parseIntArray(com.alibaba.fastjson.JSONArray jsonArray) {
+        if (jsonArray == null || jsonArray.isEmpty()) {
+            return null;
+        }
+        
+        int[] result = new int[2];
+        for (int i = 0; i < 2 && i < jsonArray.size(); i++) {
+            Object value = jsonArray.get(i);
+            if (value == null) {
+                result[i] = 0;
+            } else {
+                result[i] = jsonArray.getInteger(i);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * 计算指定时间范围内的所有周期日期
+     * 从开始日期找到第一个符合条件的日期作为起始点
+     * 按周期类型连续计算下一个日期,直到超过结束日期
+     * 返回所有符合条件的日期列表,用于判断哪些周期需要推送
+     * 
+     * 周期计算示例:
+     * 1. 月度计划(每月15日,开始日期2024-01-01,结束日期2024-12-31):
+     *    → 2024-01-15, 2024-02-15, ..., 2024-12-15
+     * 2. 季度计划(每季度第2个月第5天):
+     *    → 2024-02-05, 2024-05-05, 2024-08-05, 2024-11-05
+     * 3. 半年度计划(每半年第5个月第8天):
+     *    → 2024-05-08, 2024-11-08
+     * 4. 年度计划(每年第6个月第16天):
+     *    → 2024-06-16
+     * 
+     * 关键处理逻辑:
+     * - 考虑月份偏移(monthOffset):在周期内的第几个月(0表示第1个月)
+     * - 考虑日期偏移(dayOffset):在月份内的第几天(1表示第1天)
+     * - 考虑周期类型(cycleType):月/季/半年/年
+     * - 避免重复计算:通过existingDateSet检查
+     * - 时间边界处理:确保不计算超过结束日期的日期
+     * 
+     * 注意:此方法使用当前周期参数计算历史日期
+     * 如果周期配置变更(如从每月15日改为17日),会重新计算历史周期
+     * 这可能产生"补推"效果,但也可能导致同一时间段内多个记录
+     * 例如:原配置每月15日已推送1月15日记录,新配置每月17日会补推1月17日记录
+     * 这取决于业务需求:是否允许补推变更前的周期
+     */
+    private List<LocalDate> calculateAllCycleDates(LocalDate startDate, LocalDate endDate, CycleParams params) {
+        List<LocalDate> dates = new ArrayList<>();
+        
+        // 步骤1:找到第一个有效的周期日期
+        // 从计划的创建日期开始,根据周期类型、月份偏移和日期偏移
+        // 计算出第一个应该推送的日期
+        // 例如:月度计划每月15日,季度计划每季度第2个月第5天等
+        LocalDate currentDate = calculateFirstValidDate(startDate, params);
+        
+        // 步骤2:循环生成所有周期日期
+        // 从第一个有效日期开始,按周期类型(月/季/半年/年)递增
+        // 直到生成的日期超过指定的结束日期(通常是今天)
+        while (!currentDate.isAfter(endDate)) {
+            // 将当前周期日期添加到结果列表
+            dates.add(currentDate);
+            
+            // 步骤3:计算下一个周期日期
+            // 根据周期类型增加相应的时间间隔:
+            // - 月度:增加1个月
+            // - 季度:增加3个月  
+            // - 半年度:增加6个月
+            // - 年度:增加1年
+            currentDate = calculateNextCycleDate(currentDate, params);
+        }
+        
+        // 步骤4:返回所有计算出的周期日期
+        // 这个列表包含了从计划创建到指定结束日期之间
+        // 所有应该触发推送的日期
+        return dates;
+    }
+    
+    /**
+     * 调整日期到当前季度的开始
+     * 将任意日期调整为所在季度的第1个月第1天
+     * 用于季度计划的周期计算,确保从季度起始点开始
+     * 1-3月调整为1月1日,4-6月调整为4月1日,以此类推
+     */
+    private LocalDate adjustToQuarterStart(LocalDate date) {
+        int monthValue = date.getMonthValue();
+        if (monthValue <= 3) {
+            return LocalDate.of(date.getYear(), 1, 1);
+        } else if (monthValue <= 6) {
+            return LocalDate.of(date.getYear(), 4, 1);
+        } else if (monthValue <= 9) {
+            return LocalDate.of(date.getYear(), 7, 1);
+        } else {
+            return LocalDate.of(date.getYear(), 10, 1);
+        }
+    }
+    
+    /**
+     * 调整日期到当前半年度的开始
+     * 将任意日期调整为所在半年度的第1个月第1天
+     * 用于半年度计划的周期计算,确保从半年度起始点开始
+     * 1-6月调整为1月1日,7-12月调整为7月1日
+     */
+    private LocalDate adjustToHalfYearStart(LocalDate date) {
+        int monthValue = date.getMonthValue();
+        if (monthValue <= 6) {
+            return LocalDate.of(date.getYear(), 1, 1);
+        } else {
+            return LocalDate.of(date.getYear(), 7, 1);
+        }
+    }
+    
+    /**
+     * 调整日期到当前年度的开始
+     * 将任意日期调整为当年的1月1日
+     * 用于年度计划的周期计算,确保从年度起始点开始
+     * 无论输入日期是哪一天,都返回该年的第一天
+     */
+    private LocalDate adjustToYearStart(LocalDate date) {
+        return LocalDate.of(date.getYear(), 1, 1);
+    }
+    
+    /**
+     * 根据周期类型计算下一个周期日期
+     * 支持四种周期类型:月度加1个月,季度加3个月,半年度加6个月,年度加1年
+     */
+    private LocalDate calculateNextCycleDate(LocalDate currentDate, CycleParams params) {
+        switch (params.cycleType) {
+            case "月度":
+                return currentDate.plusMonths(1);
+            case "季度":
+                return currentDate.plusMonths(3);
+            case "半年度":
+                return currentDate.plusMonths(6);
+            case "年度":
+                return currentDate.plusYears(1);
+            default:
+                return currentDate.plusMonths(1);
+        }
+    }
+    
+    /**
+     * 判断今天是否已经达到或超过推送日
+     * 专门用于计算下次推送时间的逻辑
+     * 检查今天的日期是否已经达到或超过计算出的推送日期
+     * 用于决定是否需要计算下一个周期
+     */
+    private boolean isTodayAfterOrEqualPushDate(LocalDate pushDate, LocalDate today) {
+        // 检查今天是否已经达到或超过推送日期
+        return !today.isBefore(pushDate);
+    }
+    
+    /**
+     * 为计划插入填写记录到数据库
+     * 首先检查相同计划相同日期是否已有记录,避免重复插入
+     * 生成去除横杠的UUID作为主键,确保唯一性
+     * 复制计划表的模板内容和属性到填写记录表
+     * 包含完整的错误处理和日志记录,确保数据一致性
+     */
+    private boolean insertRecordForPlan(QualityMetricsForHcqdDto plan, String cycleDateStr, QualityMetricsForHcqdDao dao) {
+        try {
+            // 检查是否已经存在
+            int existingCount = dao.countRecordByPlanAndDate(plan.getId(), cycleDateStr);
+            if (existingCount > 0) {
+                logger.warn("计划 {} 的 {} 记录已存在,跳过", plan.getJiHuaMingCheng(), cycleDateStr);
+                return false;
+            }
+            
+            // 创建填写记录
+            QualityMetricsForHcqdDto record = new QualityMetricsForHcqdDto();
+            
+            // 生成ID(去除横杠的UUID)
+            String uuid = UUID.randomUUID().toString().replace("-", "");
+            record.setId(uuid);
+            
+            // 复制字段
+            record.setTenantId(plan.getTenantId());
+            record.setIp(plan.getIp());
+            record.setCreateBy(plan.getCreateBy());
+            record.setCreateTime(new Date());
+            record.setUpdateBy(plan.getUpdateBy());
+            record.setUpdateTime(new Date());
+            record.setShiFouGuoShen(plan.getShiFouGuoShen());
+            record.setDiDian(plan.getDiDian());
+            record.setBianZhiRen(plan.getBianZhiRen());
+            record.setBianZhiBuMen(plan.getSuoShuBuMen());
+            record.setBianZhiShiJian(cycleDateStr);
+            record.setKuaiZhao(plan.getKuaiZhao());
+            record.setJiHuaBiaoId(plan.getId());
+            record.setBiaoGeNeiRong(plan.getBiaoGeNeiRong());
+            record.setJiHuaMingCheng(plan.getJiHuaMingCheng());
+            record.setZhouQiLeiXing(plan.getZhouQiLeiXing());
+            record.setZhouQiXiangQin(plan.getZhouQiXiangQin());
+            
+            // 插入记录
+            int result = dao.insertRecord(record);
+            return result > 0;
+            
+        } catch (Exception e) {
+            logger.error("插入填写记录失败,计划: {}, 日期: {}, 错误: {}", 
+                    plan.getJiHuaMingCheng(), cycleDateStr, e.getMessage(), e);
+            return false;
+        }
+    }
+    
+    /**
+     * 周期参数内部类,封装计划周期的核心参数
+     * 用于统一管理周期类型、月份偏移和日期偏移三个相关参数
+     * 提供类型安全的数据结构,避免参数传递时的错误
+     * 作为周期计算的基础数据单元,贯穿整个定时任务流程
+     */
+    private static class CycleParams {
+        String cycleType;   // 周期类型:月度、季度、半年度、年度
+        int monthOffset;    // 月份偏移:1表示第1个月,2表示第2个月,以此类推;0表示不使用月份偏移(月度计划专用)
+        int dayOffset;      // 日期偏移:1表示第1天,15表示第15天
+        
+        CycleParams(String cycleType, int monthOffset, int dayOffset) {
+            this.cycleType = cycleType;
+            this.monthOffset = monthOffset;
+            this.dayOffset = dayOffset;
+        }
+    }
+}