package com.yimeng.okr.schedule; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.yimeng.okr.entity.AssessmentPeriod; import com.yimeng.okr.entity.KrKeyResult; import com.yimeng.okr.entity.OkrObjective; import com.yimeng.okr.entity.SysUser; import com.yimeng.okr.enums.PeriodStatus; import com.yimeng.okr.event.NotificationEvent; import com.yimeng.okr.mapper.AssessmentPeriodMapper; import com.yimeng.okr.mapper.KrKeyResultMapper; import com.yimeng.okr.mapper.OkrObjectiveMapper; import com.yimeng.okr.mapper.SysUserMapper; import com.yimeng.okr.service.AssessmentPeriodService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class PeriodScheduler { private final AssessmentPeriodService periodService; private final AssessmentPeriodMapper periodMapper; private final SysUserMapper userMapper; private final OkrObjectiveMapper objectiveMapper; private final KrKeyResultMapper krMapper; private final ApplicationEventPublisher publisher; @Scheduled(cron = "0 0 2 * * ?") public void autoAdvancePeriods() { log.info("Running period auto-advance check..."); periodService.checkAndAutoAdvance(); } @Scheduled(cron = "0 0 10 * * ?") public void checkPeriodDeadlines() { log.info("Checking period deadlines..."); List periods = periodMapper.selectList( new LambdaQueryWrapper() .eq(AssessmentPeriod::getStatus, PeriodStatus.EXECUTING)); LocalDate today = LocalDate.now(); for (AssessmentPeriod p : periods) { try { if (ChronoUnit.DAYS.between(today, p.getEndDate()) == 3) { List users = userMapper.selectList(null); for (SysUser u : users) { publisher.publishEvent(new NotificationEvent( this, u.getId(), "PERIOD_DEADLINE", "周期即将结束", "考核周期「" + p.getName() + "」将于3天后结束", "period", p.getId())); } } } catch (Exception e) { log.warn("Failed to check deadline for period {}: {}", p.getId(), e.getMessage()); } } } @Scheduled(cron = "0 7 10 * * ?") public void checkKrLagging() { log.info("Checking KR lagging..."); List periods = periodMapper.selectList( new LambdaQueryWrapper() .eq(AssessmentPeriod::getStatus, PeriodStatus.EXECUTING)); LocalDate today = LocalDate.now(); for (AssessmentPeriod p : periods) { try { long totalDays = ChronoUnit.DAYS.between(p.getStartDate(), p.getEndDate()); if (totalDays <= 0) continue; long elapsedDays = ChronoUnit.DAYS.between(p.getStartDate(), today); if (elapsedDays < 3) continue; // ignore first 3 days double elapsedRatio = Math.min(1.0, (double) elapsedDays / totalDays); double expectedProgress = elapsedRatio * 100; List objectives = objectiveMapper.selectList( new LambdaQueryWrapper() .eq(OkrObjective::getPeriodId, p.getId()) .eq(OkrObjective::getStatus, "APPROVED")); for (OkrObjective obj : objectives) { List krs = krMapper.selectList( new LambdaQueryWrapper() .eq(KrKeyResult::getObjectiveId, obj.getId())); for (KrKeyResult kr : krs) { if ("COMPLETED".equals(kr.getStatus())) continue; double actualProgress = kr.getTargetValue() > 0 ? (kr.getCurrentValue() / kr.getTargetValue()) * 100 : 0; if (actualProgress < expectedProgress - 20) { publisher.publishEvent(new NotificationEvent( this, obj.getUserId(), "KR_LAGGING", "KR 进度滞后", "「" + kr.getTitle() + "」进度落后于时间线(当前 " + Math.round(actualProgress) + "%,预期 " + Math.round(expectedProgress) + "%),请及时更新", "objective", obj.getId())); } } } } catch (Exception e) { log.warn("Failed to check KR lagging for period {}: {}", p.getId(), e.getMessage()); } } } }