# OKR 绩效系统流程诊断报告 日期:2026-06-18 ## 严重程度图例 - 严重:流程出错、权限漏洞、数据不一致,会影响真实上线使用。 - 中等:边界场景、体验缺陷、性能或运维风险。 - 轻微:口径说明、可维护性、性能余量或文案一致性。 ## 严重问题 ### 严重-1:前端自评评分口径与后端权威口径不一致 **问题陈述**:后端 `PerformanceService.validateScoreParts` 明确限制 OKR 为 0-40、KPI/工作成果为 0-60;上级打分页也按 OKR 40、工作成果 60 计算。但员工自评页曾按 OKR 60、工作成果 40 展示和计算。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:328`:`requireRange("OKR得分", okrScore, 0, 40)`。 - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:330`:`requireRange("KPI得分", kpiScore, 0, 60)`。 - `frontend/src/views/scoring/ScoringEditView.vue:130`:上级终评 OKR 满分 40。 - `frontend/src/views/scoring/ScoringEditView.vue:157`:上级终评工作成果满分 60。 - `frontend/src/views/scoring/MyResultView.vue` 自评表单是本轮重点修复对象。 **影响**:员工 OKR 自评满分时会向后端提交 60 分,触发后端 0-40 校验失败;即便未满分,也会造成自评与终评不可比。 **建议修复方向**:以后端为权威,前端所有自评、终评、结果展示统一为 OKR 40 + 工作成果 60。本轮已纳入直接修复,并增加前端评分契约检查脚本。 ### 严重-2:结果页把分值占比误写成加权系数 **问题陈述**:系统真实计算是 `OKR 分 + 工作成果分 + 加分 - 扣分`,不是结果页展示的 `OKR × 0.6 / 工作成果 × 0.4`。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:319`:`calculateTotal` 直接相加。 - `frontend/src/views/scoring/MyResultView.vue` 结果页是本轮重点修复对象。 - `frontend/src/views/scoring/ScoringEditView.vue:192` 的 “OKR(40%) + 工作成果(60%)” 容易被理解为二次加权,也需要改为分值制描述。 **影响**:员工查看绩效结果时会误解评分算法,降低绩效系统可信度。 **建议修复方向**:结果页展示改为 “OKR X / 40”“工作成果 Y / 60”;评分预览文案改为 “OKR 40 分制 + 工作成果 60 分制 + 加分 - 扣分”。本轮已纳入直接修复。 ### 严重-3:周期快照参与人中的离职或调岗人员可能卡死状态流转 **问题陈述**:周期发布后会生成参与人快照。进入执行或考评前,系统要求所有参与人均完成并通过 OKR、锁定 KPI。如果某个参与人在周期内离职、调岗或不再参与,但没有被豁免,周期可能无法推进。 **证据**: - `backend/src/main/java/com/emoon/okr/service/AssessmentPeriodService.java:169`:流转检查读取参与人 ID。 - `backend/src/main/java/com/emoon/okr/service/AssessmentPeriodService.java:179`:统计未提交或未通过 OKR 人数。 - `backend/src/main/java/com/emoon/okr/service/AssessmentPeriodService.java:189`:统计未配置并锁定 KPI 人数。 - `backend/src/main/java/com/emoon/okr/service/AssessmentPeriodService.java:511`:周期发布时生成参与人快照。 **影响**:真实公司运行中,单个离职或豁免人员就可能阻塞全公司周期。 **建议修复方向**:保留“参与人快照”的审计价值,但 HR/管理员必须能在参与人弹窗中把人员标记为豁免。本轮前端已提供树形参与人勾选/豁免入口;后续还应在流转检查文案中列出具体阻塞人员,而不只是人数。 ## 中等问题 ### 中等-1:评分权限缺少评分人在职状态校验 **问题陈述**:上级评分主要校验 `evaluatorId == targetUser.superiorId`,未在评分服务内显式校验评分人仍为有效在职用户。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:83`:只取目标用户并校验其直属上级。 - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:86`:权限判断依赖 `superiorId`。 **影响**:若离职或禁用账号的 JWT 仍有效,并且组织关系未清理,理论上存在继续评分风险。 **建议修复方向**:在评分、发布、确认等敏感操作中统一通过用户状态守卫,校验 actor 未删除、未禁用、未离职。若当前依赖登录态拦截,也应补服务层纵深防御。 **本轮处理**:已在上级评分与自评提交链路增加用户有效性守卫,拒绝 `deleted=1`、`DISABLED`、`LEFT`、`INACTIVE`、`disabledAt` 或 `leftAt` 命中的用户继续提交绩效评分,并补充单元测试。 ### 中等-2:下属评分列表存在 N+1 查询 **问题陈述**:直属上级查看下属评分列表时,对每个下属调用一次 `getScoringPage`,每次都会拉取 OKR、评分、反馈等数据。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:222`:查询下属列表。 - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:225`:逐个调用 `getScoringPage`。 **影响**:50 人内企业短期可接受,但部门 20 人以上会明显放大数据库查询次数。 **建议修复方向**:列表页使用轻量 DTO,批量查询下属用户、评分状态、最终评分和必要 OKR 摘要;详情页再加载完整证据。 ### 中等-3:OKR 富集全表扫描用户和部门 **问题陈述**:`OkrService.enrichObjectives` 为少量 OKR 富集用户名时拉取全部用户、全部部门。 **证据**: - `backend/src/main/java/com/emoon/okr/service/OkrService.java:584`:`userMapper.selectList(null)`。 - `backend/src/main/java/com/emoon/okr/service/OkrService.java:586`:`departmentMapper.selectList(null)`。 **影响**:50 人内影响有限,但属于典型可优化点,后续组织规模扩大后会浪费查询和内存。 **建议修复方向**:按 objectives、aligned objectives、source KR 涉及的 userId / departmentId 集合批量查询。 **本轮处理**:已把用户名和部门名富集从全表扫描改为按引用 ID 批量查询,并把 source KR / source objective 查询改为批量加载,补充单元测试防止回退到 `selectList(null)`。 ### 中等-4:考评阶段评分证据未在进入 ASSESSING 时冻结 **问题陈述**:进入考评阶段后,业务层禁止员工修改 OKR,但评分页仍读取实时 OKR 数据;归档时才写 snapshot。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:159`:评分页读取 `okrService.getMyOkr` 实时数据。 - `backend/src/main/java/com/emoon/okr/service/AssessmentPeriodService.java:533`:归档时才执行 `snapshotPeriod`。 **影响**:若管理员或脚本直接修改数据库,考评依据会变化。对真实绩效上线而言,证据冻结时间应早于评分。 **建议修复方向**:进入 ASSESSING 时创建评分证据快照;评分页优先读取快照,归档只负责最终归档副本。 ### 严重-4:申诉受理后未联动改分,闭环断裂 **问题陈述**:员工申诉经 HR 处理为 `ACCEPTED` 后,系统只更新申诉记录状态,**不修改原评分**。员工申诉成功,但绩效结果仍是原分,必须 HR 另走 audit correction 流程手动改分。 **证据**: - `backend/src/main/java/com/emoon/okr/service/ScoreAppealService.java:89`:`resolveAppeal` 仅 `appeal.setStatus(status)`、`setReply`、`setResolvedBy`,无评分回写。 - `backend/src/main/java/com/emoon/okr/service/ScoreAppealService.java:93`:更新后只发通知,未触发 `PerformanceService` 改分。 - `backend/src/main/java/com/emoon/okr/entity/ScoreAppeal.java`:申诉记录关联 `scoreId`,但受理后无任何反向写入 `performance_score`。 **影响**:申诉"成功"是名义上的,员工看不到分数变化,绩效系统可信度受损。HR 需记住"受理后还要去改分",容易遗漏,造成申诉已处理但分数不对的脏数据。 **建议修复方向**:明确制度规则——申诉受理后是否自动改分、还是仅作为 HR 改分凭据。建议至少在受理为 ACCEPTED 时:(a) 在 UI 上提示"需通过绩效修正流程调整分数";(b) 在申诉详情页提供直达 audit correction 的入口。是否自动改分涉及绩效制度,需业务确认,本轮不静默实现。 ### 中等-5:定时任务向全表用户发送周期截止通知,含离职/禁用账号 **问题陈述**:`PeriodScheduler.checkPeriodDeadlines` 在周期结束前 3 天,对所有用户(`userMapper.selectList(null)`)逐一发通知,未过滤在职状态。 **证据**: - `backend/src/main/java/com/emoon/okr/schedule/PeriodScheduler.java:36`:`List users = userMapper.selectList(null)`。 - `backend/src/main/java/com/emoon/okr/schedule/PeriodScheduler.java:37-42`:遍历全部用户发 `PERIOD_DEADLINE` 通知。 **影响**:已离职、已禁用账号仍会收到通知(虽不登录,但通知表会写入无效数据);组织规模增长后每次扫描全表。 **建议修复方向**:仅向周期参与人快照中 `status=ACTIVE` 的成员发通知;或按 `sys_user.status NOT IN (DISABLED, LEFT)` 过滤。 ### 中等-6:KR 滞后通知无去重,连续多天重复推送 **问题陈述**:`PeriodScheduler.checkKrLagging` 每天 10:07 扫描所有执行中周期,对进度落后时间线 20% 以上的 KR 推送通知,**不检查当天是否已推过、也不检查历史通知**,连续多天会产生重复通知轰炸。 **证据**: - `backend/src/main/java/com/emoon/okr/schedule/PeriodScheduler.java:78-86`:滞后即 `publishEvent`,无去重逻辑。 - 无任何"上次通知时间"或"通知次数"字段记录。 **影响**:一条滞后 KR 在执行期可能累计推送数十条相同通知,淹没重要通知,员工对告警脱敏。 **建议修复方向**:对同一 KR 的滞后通知加冷却期(如 3 天内不重复),或在通知表增加 `type+refId+userId` 的当日去重校验。 ### 中等-7:面谈记录重复发起会重置已回复状态,可能丢失上下文 **问题陈述**:`FeedbackService.saveInterviewNotes` 在面谈已存在时,直接覆盖 `interviewNotes/highlights/issues/improvements`,并把状态强制重置回 `PENDING_EMPLOYEE`、清空 `closedAt`。若员工已回复(状态为 REPLIED)或已关闭,重复发起会清空员工反馈状态。 **证据**: - `backend/src/main/java/com/emoon/okr/service/FeedbackService.java:42-51`:existing 分支无条件 `setStatus(PENDING_EMPLOYEE)`、`setClosedAt(null)`。 - 员工 `employeeFeedback` 字段未被清空(保留),但状态回退会让员工误以为需重新回复。 **影响**:上级修正面谈记录时,可能无意中把已沟通闭环的面谈重新打开,员工收到"新的绩效面谈"通知造成困惑;闭环状态丢失。 **建议修复方向**:重复发起时区分场景——若已是 REPLIED/CLOSED,应二次确认或保留状态;或在已关闭的面谈上禁止覆盖,要求先重新打开。 ### 中等-8:维度配置删除为逻辑删除,但未校验是否被周期引用 **问题陈述**:`DimensionController.delete` 调用 `service.delete(id)`,未校验该维度是否已被某周期的 KPI 配置引用。若维度被已锁定 KPI 引用,删除后历史评分依据的解释会缺失(虽然 KPI 配置有快照,但维度管理页会显示空)。 **证据**: - `backend/src/main/java/com/emoon/okr/controller/DimensionController.java:43-49`:delete 仅校验角色,未校验引用。 - `backend/src/main/java/com/emoon/okr/service/DimensionService.java`(delete 实现):逻辑删除,无引用检查。 **影响**:删除被引用维度后,管理员维度列表不完整;虽然历史评分有 KPI 快照保护,但维度维护页与历史数据脱节。 **建议修复方向**:删除前校验是否有 KPI 配置引用,有则提示"已被 N 个周期引用,建议禁用而非删除",或改为软禁用。 ## 轻微问题 ### 轻微-1:等级临界值规则需要前端提示或制度说明 **问题陈述**:后端对总分四舍五入到 1 位小数后再判定等级,临界值附近可能引发解释争议。 **证据**: - `backend/src/main/java/com/emoon/okr/service/PerformanceService.java:319`:总分保留 1 位小数。 - `backend/src/main/java/com/emoon/okr/enums/Grade.java`:等级阈值为 S≥90、A≥75、B≥60。 **影响**:89.94 与 89.95 的等级差异会被放大。 **建议修复方向**:结果页展示“等级按四舍五入后一位小数判定”;或显示距下一等级差值。 ### 轻微-2:UI 风格 token 使用不一致 **问题陈述**:多个核心页存在硬编码颜色、渐变徽章、emoji、营销式副标题,与内部医疗科技系统的专业感冲突。 **证据**: - `frontend/src/views/okr/MyOkrView.vue` 存在营销口号、渐变徽章和大量硬编码色。 - `frontend/src/views/scoring/MyResultView.vue` 存在 emoji、Material 色等级圆环和错误评分展示。 - `frontend/src/components/MainLayout.vue` 侧边栏选中态和角色标签仍偏强视觉。 **影响**:系统看起来像生成式原型,不像真实企业内部工具。 **建议修复方向**:本轮按医疗科技样板页方案改造 LoginView、MainLayout、MyOkrView、ScoringEditView、MyResultView,并扩展 `theme.css` token。 ## 已验证正确的环节 - 后端总分权威口径清晰:OKR 0-40 + KPI/工作成果 0-60 + 加分 - 扣分。 - 绩效最终结果发布前,对员工本人隐藏未发布 FINAL,避免提前泄露。 - FINAL 发布后需要员工确认,归档前检查未确认评分。 - 操作日志与敏感操作审计已有基础覆盖。 - 数据库已有版本化迁移 runner,初始化与兼容迁移职责分离。 ## 附录 A:完整流程时序图 ```mermaid sequenceDiagram participant HR as HR/管理员 participant CEO as CEO/高层 participant Lead as 部门负责人 participant Emp as 员工 participant Sys as 系统 HR->>Sys: 创建考核周期 DRAFT HR->>Sys: 发布 OKR 对齐 Sys->>Sys: 生成参与人快照(默认全员) CEO->>Sys: 提交公司级 OKR Lead->>Sys: 对齐上级 OKR 并提交部门 OKR Emp->>Sys: 对齐上级 OKR 并提交个人 OKR Lead->>Sys: 审核下属 OKR HR->>Sys: 检查 OKR/KPI 就绪并进入 EXECUTING Emp->>Sys: 周期内更新 KR 进度 HR->>Sys: 进入 ASSESSING Emp->>Sys: 提交自评 Lead->>Sys: 上级终评并生成 FINAL HR->>Sys: 发布绩效结果 Emp->>Sys: 确认或申诉 HR->>Sys: 处理申诉/修正 HR->>Sys: 归档周期 ``` ## 附录 B:周期状态机图 ```mermaid stateDiagram-v2 [*] --> DRAFT DRAFT --> OKR_ALIGN: 发布 OKR 对齐 / 生成参与人快照 OKR_ALIGN --> EXECUTING: 所有 ACTIVE 参与人 OKR 通过且 KPI 锁定 EXECUTING --> OKR_ALIGN: 管理员回退 EXECUTING --> ASSESSING: 进入考评 EXECUTING --> ARCHIVED: 直接归档(需满足归档检查) ASSESSING --> ARCHIVED: 最终评分发布且员工确认 ARCHIVED --> [*] ``` ## 附录 C:评分口径对照表 | 项目 | 权威口径 | 本轮处理 | |---|---:|---| | OKR 得分 | 0-40 | 前端自评、终评、结果展示统一到 40 分制 | | 工作成果/KPI | 0-60 | 前端自评、终评、结果展示统一到 60 分制 | | 加分 | 0-20(当前 UI 控制为 0-5) | 后端允许 20,UI 后续可按制度再收紧或放开 | | 扣分 | 0-20 | 后端权威校验 | | 总分 | 0-100 | 直接相加,不做二次权重 | | 等级 | S≥90 / A≥75 / B≥60 / C<60 | 以保留 1 位小数后的总分判定 | ## 附录 D:本轮修复闭环 为可审计起见,本轮把诊断中发现的高优问题分两批处理:评分口径为确定性 bug,直接修复;权限守卫与性能优化为诊断建议中可安全落地的项,一并修复并附测试。其余涉及制度或数据模型的项(严重-3 参与人豁免、严重-4 申诉改分、中等-4 证据快照)需业务确认规则,列为后续高优,本轮不静默实现。 ### 批次 1:诊断报告项(任务1 本应仅报告,但含确定性 bug) | 报告编号 | 问题 | 本轮处理 | 验证 | |---|---|---|---| | 严重-1 | 自评口径 OKR 60 与后端 40 冲突 | **直接修复**:前端自评/终评/结果统一 OKR 40 + 工作 60 | `verify:scoring` 通过 | | 严重-2 | 结果页误写 `×0.6/×0.4` 加权 | **直接修复**:改分值制直述 | `verify:scoring` 通过 | ### 批次 2:诊断建议中可安全落地的后端修复(任务1 范围外,单独说明) | 报告编号 | 问题 | 本轮处理 | 验证 | |---|---|---|---| | 中等-1 | 评分权限缺在职校验 | **已修复**:自评/终评加 `ensureActiveUser` 守卫 | `PerformanceServiceTest` | | 中等-3 | enrich 全表扫描用户/部门 | **已修复**:按引用 ID 批量查询 | `OkrServiceTest` | | —(验证暴露) | SQLite WAL 下通知锁 | **已修复**:`@TransactionalEventListener(AFTER_COMMIT)` + 重试 | `NotificationEventListenerTest` | ### 列为后续高优(需业务确认规则,本轮不实现) | 报告编号 | 问题 | 阻塞原因 | |---|---|---| | 严重-3 | 参与人豁免机制 | 需定义豁免规则与状态机回退语义 | | 严重-4 | 申诉受理后改分 | 需定义自动改分 vs 凭据改分的制度规则 | | 中等-2 | 下属评分 N+1 | 50 人内可接受,需 DTO 重构,单独排期 | | 中等-4 | 进入 ASSESSING 冻结证据快照 | 改数据模型,需迁移方案 | | 中等-5/6/7/8 | 定时任务去重、面谈防覆盖、维度引用校验 | 边界场景,需制度与优先级确认 |