Procházet zdrojové kódy

feat: implement KR_LAGGING notification — daily check for KRs behind timeline by 20%+

wangkangyjy před 2 měsíci
rodič
revize
ecacd1e159

+ 50 - 1
backend/src/main/java/com/yimeng/okr/schedule/PeriodScheduler.java

@@ -2,10 +2,14 @@ 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;
@@ -15,7 +19,6 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
-import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.List;
 
@@ -27,6 +30,8 @@ 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 * * ?")
@@ -58,4 +63,48 @@ public class PeriodScheduler {
             }
         }
     }
+
+    @Scheduled(cron = "0 7 10 * * ?")
+    public void checkKrLagging() {
+        log.info("Checking KR lagging...");
+        List<AssessmentPeriod> periods = periodMapper.selectList(
+                new LambdaQueryWrapper<AssessmentPeriod>()
+                        .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<OkrObjective> objectives = objectiveMapper.selectList(
+                        new LambdaQueryWrapper<OkrObjective>()
+                                .eq(OkrObjective::getPeriodId, p.getId())
+                                .eq(OkrObjective::getStatus, "APPROVED"));
+                for (OkrObjective obj : objectives) {
+                    List<KrKeyResult> krs = krMapper.selectList(
+                            new LambdaQueryWrapper<KrKeyResult>()
+                                    .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());
+            }
+        }
+    }
 }

+ 1 - 0
frontend/src/views/notification/NotificationView.vue

@@ -51,6 +51,7 @@ function typeLabel(type) {
     PERIOD_DEADLINE: '截止提醒',
     PERIOD_OKR_ALIGN: '周期发布', PERIOD_EXECUTING: '开始执行',
     PERIOD_ASSESSING: '考评开始', PERIOD_ARCHIVED: '周期归档',
+    KR_LAGGING: '进度滞后',
     FEEDBACK_NOTIFY: '面谈通知'
   }
   return map[type] || type