|
|
@@ -0,0 +1,219 @@
|
|
|
+package com.yimeng.okr.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.yimeng.okr.dto.WorkflowActionDto;
|
|
|
+import com.yimeng.okr.dto.WorkspaceOverviewDto;
|
|
|
+import com.yimeng.okr.entity.AssessmentPeriod;
|
|
|
+import com.yimeng.okr.enums.PeriodStatus;
|
|
|
+import com.yimeng.okr.mapper.AssessmentPeriodMapper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class WorkspaceService {
|
|
|
+
|
|
|
+ private final JdbcTemplate jdbcTemplate;
|
|
|
+ private final AssessmentPeriodMapper periodMapper;
|
|
|
+
|
|
|
+ public WorkspaceOverviewDto getOverview(Long userId, Long periodId) {
|
|
|
+ AssessmentPeriod period;
|
|
|
+ if (periodId != null) {
|
|
|
+ period = periodMapper.selectById(periodId);
|
|
|
+ } else {
|
|
|
+ period = findRecommendedPeriod();
|
|
|
+ }
|
|
|
+ if (period == null) {
|
|
|
+ return emptyOverview();
|
|
|
+ }
|
|
|
+
|
|
|
+ WorkspaceOverviewDto dto = new WorkspaceOverviewDto();
|
|
|
+
|
|
|
+ WorkspaceOverviewDto.RecommendedPeriod rp = new WorkspaceOverviewDto.RecommendedPeriod();
|
|
|
+ rp.setId(period.getId());
|
|
|
+ rp.setName(period.getName());
|
|
|
+ rp.setStatus(period.getStatus().name());
|
|
|
+ rp.setStartDate(period.getStartDate() != null ? period.getStartDate().toString() : null);
|
|
|
+ rp.setEndDate(period.getEndDate() != null ? period.getEndDate().toString() : null);
|
|
|
+ dto.setRecommendedPeriod(rp);
|
|
|
+
|
|
|
+ List<AssessmentPeriod> allPeriods = periodMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<AssessmentPeriod>()
|
|
|
+ .ne(AssessmentPeriod::getStatus, PeriodStatus.ARCHIVED)
|
|
|
+ .orderByDesc(AssessmentPeriod::getStartDate));
|
|
|
+ List<WorkspaceOverviewDto.PeriodOption> options = new ArrayList<>();
|
|
|
+ for (AssessmentPeriod p : allPeriods) {
|
|
|
+ WorkspaceOverviewDto.PeriodOption opt = new WorkspaceOverviewDto.PeriodOption();
|
|
|
+ opt.setId(p.getId());
|
|
|
+ opt.setName(p.getName());
|
|
|
+ opt.setStatus(p.getStatus().name());
|
|
|
+ options.add(opt);
|
|
|
+ }
|
|
|
+ dto.setAvailablePeriods(options);
|
|
|
+ dto.setCurrentPhase(period.getStatus().name());
|
|
|
+ dto.setMyTodos(buildTodos(userId, period));
|
|
|
+ dto.setTeamSummary(buildTeamSummary(userId, period.getId()));
|
|
|
+ dto.setRisks(buildRisks(period.getId()));
|
|
|
+ dto.setUpcomingDeadlines(buildDeadlines(period));
|
|
|
+ dto.setAllowedActions(buildAllowedActions(period));
|
|
|
+
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ private AssessmentPeriod findRecommendedPeriod() {
|
|
|
+ var periods = periodMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<AssessmentPeriod>()
|
|
|
+ .in(AssessmentPeriod::getStatus, Arrays.asList(PeriodStatus.OKR_ALIGN, PeriodStatus.EXECUTING, PeriodStatus.ASSESSING))
|
|
|
+ .orderByDesc(AssessmentPeriod::getStartDate));
|
|
|
+ if (!periods.isEmpty()) return periods.get(0);
|
|
|
+
|
|
|
+ periods = periodMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<AssessmentPeriod>()
|
|
|
+ .ne(AssessmentPeriod::getStatus, PeriodStatus.ARCHIVED)
|
|
|
+ .orderByDesc(AssessmentPeriod::getStartDate));
|
|
|
+ return periods.isEmpty() ? null : periods.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<WorkspaceOverviewDto.TodoItem> buildTodos(Long userId, AssessmentPeriod period) {
|
|
|
+ List<WorkspaceOverviewDto.TodoItem> todos = new ArrayList<>();
|
|
|
+ String status = period.getStatus().name();
|
|
|
+
|
|
|
+ if ("OKR_ALIGN".equals(status)) {
|
|
|
+ Integer count = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM okr_objective WHERE period_id = ? AND user_id = ? AND (deleted IS NULL OR deleted = 0)",
|
|
|
+ Integer.class, period.getId(), userId);
|
|
|
+ if (count == null || count == 0) {
|
|
|
+ todos.add(todo("DRAFT_OKR", "制定 OKR", "/okr/my", period.getId()));
|
|
|
+ } else {
|
|
|
+ todos.add(todo("EDIT_OKR", "修改 OKR", "/okr/my", period.getId()));
|
|
|
+ }
|
|
|
+ Integer pending = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM okr_objective o JOIN sys_user u ON o.user_id = u.id " +
|
|
|
+ "WHERE o.period_id = ? AND u.superior_id = ? AND o.status = 'SUBMITTED'",
|
|
|
+ Integer.class, period.getId(), userId);
|
|
|
+ if (pending != null && pending > 0) {
|
|
|
+ todos.add(todo("REVIEW_OKR", "审核下属 OKR", "/okr/team", period.getId(), null, pending));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ("EXECUTING".equals(status)) {
|
|
|
+ Integer krCount = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM kr_key_result k JOIN okr_objective o ON k.objective_id = o.id " +
|
|
|
+ "WHERE o.period_id = ? AND o.user_id = ? AND k.status != 'COMPLETED'",
|
|
|
+ Integer.class, period.getId(), userId);
|
|
|
+ if (krCount != null && krCount > 0) {
|
|
|
+ todos.add(todo("UPDATE_PROGRESS", "更新 KR 进度", "/okr/execution", period.getId(), null, krCount));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ("ASSESSING".equals(status)) {
|
|
|
+ Integer selfCount = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM performance_score WHERE period_id = ? AND user_id = ? AND type = 'SELF'",
|
|
|
+ Integer.class, period.getId(), userId);
|
|
|
+ if (selfCount == null || selfCount == 0) {
|
|
|
+ todos.add(todo("SELF_SCORE", "提交自评", "/scores/my", period.getId()));
|
|
|
+ }
|
|
|
+ Integer finalCount = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM sys_user WHERE superior_id = ? AND deleted = 0",
|
|
|
+ Integer.class, userId);
|
|
|
+ if (finalCount != null && finalCount > 0) {
|
|
|
+ Integer pendingFinal = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM performance_score s JOIN sys_user u ON s.user_id = u.id " +
|
|
|
+ "WHERE s.period_id = ? AND u.superior_id = ? AND s.type = 'SELF' AND s.status = 'SUBMITTED'",
|
|
|
+ Integer.class, period.getId(), userId);
|
|
|
+ if (pendingFinal != null && pendingFinal > 0) {
|
|
|
+ todos.add(todo("FINAL_SCORE", "完成终评", "/scores/review", period.getId(), null, pendingFinal));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return todos;
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkspaceOverviewDto.TodoItem todo(String type, String label, String url, Long periodId) {
|
|
|
+ return todo(type, label, url, periodId, null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkspaceOverviewDto.TodoItem todo(String type, String label, String url, Long periodId, Long objectId, Integer count) {
|
|
|
+ WorkspaceOverviewDto.TodoItem item = new WorkspaceOverviewDto.TodoItem();
|
|
|
+ item.setType(type);
|
|
|
+ item.setLabel(label);
|
|
|
+ item.setTargetUrl(url + "?periodId=" + periodId);
|
|
|
+ item.setPeriodId(periodId);
|
|
|
+ item.setObjectId(objectId);
|
|
|
+ item.setCount(count);
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkspaceOverviewDto.TeamSummary buildTeamSummary(Long userId, Long periodId) {
|
|
|
+ WorkspaceOverviewDto.TeamSummary ts = new WorkspaceOverviewDto.TeamSummary();
|
|
|
+ Integer total = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(*) FROM sys_user WHERE superior_id = ? AND deleted = 0", Integer.class, userId);
|
|
|
+ ts.setTotalMembers(total != null ? total : 0);
|
|
|
+
|
|
|
+ if (ts.getTotalMembers() > 0) {
|
|
|
+ Integer okrSub = jdbcTemplate.queryForObject(
|
|
|
+ "SELECT COUNT(DISTINCT o.user_id) FROM okr_objective o JOIN sys_user u ON o.user_id = u.id " +
|
|
|
+ "WHERE o.period_id = ? AND u.superior_id = ?",
|
|
|
+ Integer.class, periodId, userId);
|
|
|
+ ts.setOkrSubmitted(okrSub != null ? okrSub : 0);
|
|
|
+ }
|
|
|
+ return ts;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<WorkspaceOverviewDto.RiskItem> buildRisks(Long periodId) {
|
|
|
+ List<WorkspaceOverviewDto.RiskItem> risks = new ArrayList<>();
|
|
|
+ List<Map<String, Object>> blocked = jdbcTemplate.queryForList(
|
|
|
+ "SELECT k.title, u.real_name FROM kr_key_result k " +
|
|
|
+ "JOIN okr_objective o ON k.objective_id = o.id " +
|
|
|
+ "JOIN sys_user u ON o.user_id = u.id " +
|
|
|
+ "WHERE o.period_id = ? AND k.status = 'BLOCKED' LIMIT 5",
|
|
|
+ periodId);
|
|
|
+ for (Map<String, Object> row : blocked) {
|
|
|
+ WorkspaceOverviewDto.RiskItem risk = new WorkspaceOverviewDto.RiskItem();
|
|
|
+ risk.setType("BLOCKED_KR");
|
|
|
+ risk.setLabel((String) row.get("title"));
|
|
|
+ risk.setSeverity("WARNING");
|
|
|
+ risk.setUserName((String) row.get("real_name"));
|
|
|
+ risks.add(risk);
|
|
|
+ }
|
|
|
+ return risks;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<WorkspaceOverviewDto.DeadlineItem> buildDeadlines(AssessmentPeriod period) {
|
|
|
+ List<WorkspaceOverviewDto.DeadlineItem> deadlines = new ArrayList<>();
|
|
|
+ if (period.getEndDate() != null) {
|
|
|
+ WorkspaceOverviewDto.DeadlineItem d = new WorkspaceOverviewDto.DeadlineItem();
|
|
|
+ d.setLabel("周期结束");
|
|
|
+ d.setDate(period.getEndDate().toString());
|
|
|
+ long days = ChronoUnit.DAYS.between(LocalDate.now(), period.getEndDate());
|
|
|
+ d.setType(days <= 7 ? "URGENT" : "NORMAL");
|
|
|
+ deadlines.add(d);
|
|
|
+ }
|
|
|
+ return deadlines;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<WorkflowActionDto> buildAllowedActions(AssessmentPeriod period) {
|
|
|
+ List<WorkflowActionDto> actions = new ArrayList<>();
|
|
|
+ actions.add(new WorkflowActionDto("VIEW_PERIOD", "查看周期详情", "/period", period.getId(), null));
|
|
|
+ actions.add(new WorkflowActionDto("VIEW_OKR", "查看 OKR", "/okr/my", period.getId(), null));
|
|
|
+ actions.add(new WorkflowActionDto("VIEW_SCORES", "查看绩效", "/scores/my", period.getId(), null));
|
|
|
+ return actions;
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkspaceOverviewDto emptyOverview() {
|
|
|
+ WorkspaceOverviewDto dto = new WorkspaceOverviewDto();
|
|
|
+ dto.setCurrentPhase("NO_PERIOD");
|
|
|
+ dto.setMyTodos(Collections.emptyList());
|
|
|
+ dto.setAvailablePeriods(Collections.emptyList());
|
|
|
+ dto.setAllowedActions(Collections.emptyList());
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+}
|