SpringBoot+Vue3 绩效管理系统设计:KPI/OKR + 360 评估 + 绩效校准与申诉全流程
🌐 演示地址:http://ruoyioffice.com | 📦 源码1·GitHub:ruoyi-office | 📦 源码2·GitCode:ruoyi-office | 📦 源码3·Gitee:ruoyi-office | 💬 微信:17156169080(备注「RuoYi Office」)
绩效管理是企业 HRM 里最"得罪人"也最难做对的模块——评什么、谁来评、分怎么算、等级怎么定、员工不服气怎么办,每一步都牵动着钱和人心。多数企业还停留在"季末发一张 Excel、主管凭印象打分、HR 手工汇总"的阶段:标准不透明、评分无依据、等级靠拍脑袋、申诉无渠道。RuoYi Office 用 15 张表把绩效拆成"配置层 + 执行层 + 结果层"三段,KPI/OKR 双评价模式、自评/上级评/360 加权汇总、HR 校准对齐正态分布、结果确认与申诉双 BPM 流程闭环,让绩效从"凭印象"升级为有数据、有留痕、可追溯的引擎。

▲ 绩效管理功能架构全景:①配置区(周期/方案/模板/指标库/等级规则)→ ②流程区(计划→自评→上级评→360→HR校准→结果确认→申诉)→ ③数据模型(15 张表三层结构)→ ④四大设计亮点
引言:绩效管理到底难在哪?
“不就是给员工打个分吗?”——没真正做过绩效系统的人都这么想。但只要落地一次,就会撞上一连串难题:
评价方法多样,一套表撑不住:有的部门用 KPI(结果指标,如"销售额 ≥ 100 万"),有的团队用 OKR(目标指标,如"上线 3 个核心功能"),还有的混合了行为能力、纪律执行、专项贡献。如果为每种方法各做一套表,系统会迅速膨胀到无法维护。
评分要有依据,不能凭印象:员工自评打 95 分、主管打 60 分,到底听谁的?没有逐项指标、没有权重、没有过程证据,最后变成"谁嗓门大谁赢"。绩效得分必须由指标 × 权重加权算出来,而不是拍一个总分。
等级要对齐,避免"全员优秀":如果各主管各自打分,结果往往是"人人 A、个个优秀",绩效奖金池根本不够分。需要 HR 做一轮校准(Calibration),按部门拉齐 S/A/B/C/D 的比例,对齐正态分布。
结果要确认,异议要有出口:绩效结果直接关联奖金、调薪,员工有权知情并签字确认。一旦不认可,必须有正规的申诉渠道,而不是私下找领导吵。申诉还得二次校准、二次确认,全程留痕。
全程要可追溯:自评分、上级评分、校准分、最终分——每一级都要留底;校准前后、申诉前后的分数变化要留原因;用了哪条等级规则、系数多少、绩效金额多少,都要快照保存,方便复盘和合规审计。
本文以 RuoYi Office 的 yudao-module-hrm 绩效模块为例,完整拆解其数据建模、四级评分加权、等级规则匹配、HR 校准与申诉双流程的设计方案。所有结论均来自真实源码。
一、业务设计:配置层 + 执行层 + 结果层
1.1 核心理念:配置驱动,规则与数据分离
绩效管理 = 一次配置(规则),多次执行(评价),自动产出(结果)。RuoYi Office 把整个模块抽象为三层:
| 配置层 | 定义"评什么、怎么算" | 周期 / 方案 / 模板 / 指标库 / 等级规则 | HR 管理员,一次配置 |
| 执行层 | 承载"谁在评、评多少" | 计划 / 计划项 / 评价单 / 评审记录 / 校准记录 | 员工、主管、HR 协同 |
| 结果层 | 产出"得几分、什么级、多少钱" | 结果 / 批次结果审批单 | 系统自动 + HR 确认 |
这种分层的价值:HR 配好一套"乐高积木"(指标库 + 等级规则 + 模板),每个考核周期直接复用,不必重复造轮子。
1.2 KPI / OKR 双评价模式
绩效方案 PerformanceSchemeDO 用一个 evaluationMode 字段切换评价方法论,配合统一的指标库支撑多种考核风格:
| 1 | KPI | 销售、生产等结果导向岗位 |
| 2 | OKR | 研发、创新等目标导向团队 |
| 3 | KPI + 行为混合 | 既看结果又看价值观的管理岗 |
| 4 | OKR + KPI 混合 | 项目制、矩阵式团队 |
指标库 PerformanceIndicatorDO 的 indicatorType 把指标统一分为五类,无论 KPI 还是 OKR 都从同一个库里挑:
| 1 | 结果指标(KPI) | 可量化的业绩结果 |
| 2 | 目标指标(OKR) | 阶段性关键目标 |
| 3 | 行为能力 | 协作、沟通、专业能力 |
| 4 | 纪律执行 | 考勤、制度遵守 |
| 5 | 专项贡献 | 临时项目、创新加分 |
关键设计:方案还定义了 scoreMode(1 百分制 / 2 等级制)和 salaryLinkMode(1 系数制 / 2 金额制 / 3 人工确认),决定了最终分如何换算成绩效工资——一套引擎跑两套方法论、三种薪资联动。
1.3 评价单状态机:贯穿全流程的主线
执行层的核心是绩效考核评价单 PerformanceAssessmentBillDO,它的 status 字段是一条贯穿始终的状态主线(取自源码常量):
10 待自评 → 20 待上级评 → 30 待校准 → 40 待结果确认 → 50 已完成
│
员工不认可,发起申诉
▼
60 待发起 → 61 待审核 → 62 待校准 → 63 待确认 → 64 申诉完成
| 10 | 待自评 | self_review 自评填报 | 员工本人 |
| 20 | 待上级评 | leader_review 评审填报 | 直属主管 |
| 30 | 待校准 | hr_calibration 校准填报 | HR / 校准人 |
| 40 | 待结果确认 | result_confirm 结果确认 | 员工本人 |
| 50 | 已完成 | —(终态) | — |
| 61~64 | 申诉审核/校准/确认/完成 | appeal_* 系列 | 主管 / HR / 员工 |
二、系统设计:模块职责与设计决策
2.1 模块组成
绩效管理位于 HRM 人力资源 → 绩效管理 目录下,由配置、执行、结果三类共 13 个页面组成:
| 设置 | 指标库 / 模板 / 方案 / 等级规则 | 维护考核规则 | HR 管理员 |
| 周期 | 周期列表 / 详情 | 创建考核周期、设置截止日期 | HR 管理员 |
| 计划 | 计划列表 / 详情 / 过程证据 | 制定指标计划、目标确认、同步工作汇报 | HR + 员工 |
| 自评 | 自评待办 | 员工逐项打分、填写说明 | 员工本人 |
| 上级评 | 评审待办 | 主管逐项复评 | 直属主管 |
| HR 校准 | 校准列表 | 调整分数、对齐等级分布 | HR / 校准人 |
| 结果 | 结果列表 / 详情 | 查看得分、等级、绩效金额 | HR + 员工 |
| 结果确认 | 批次审批 / 个人确认 | 批次走审批、个人签字确认 | HR + 员工 |
| 申诉 | 申诉列表 | 发起申诉、审核、二次校准 | 员工 + 主管 + HR |
| 统计 | 绩效统计 | 周期数、参与人数、平均分、金额合计 | HR 管理员 |
2.2 核心设计决策
| 评价方法 | 方案级 evaluationMode 切换 KPI/OKR | 一套引擎适配多种方法论,指标库统一承载 |
| 评分留痕 | 计划项存自评/上级/校准/最终四级分 | 每一级独立留底,可追溯、可复盘 |
| 成绩计算 | ∑(有效分 × 权重) / ∑权重,HALF_UP 两位 | 加权汇总有据可依,不是拍总分 |
| 取值优先级 | finalScore > calibratedScore > leaderScore > selfScore | 越靠后的环节越权威,自动覆盖前序 |
| 等级判定 | 等级规则区间 [scoreStart, scoreEnd] 匹配 | 分数→等级→系数全自动,HR 可配 |
| 流程编排 | 主流程 + 申诉流程两条独立 BPM 实例 | 申诉是支线,独立留痕不污染主流程 |
| 结果快照 | resultSnapshotJson 存指标明细+校准记录 | 合规审计、历史复盘不依赖实时计算 |
三、PC 端功能实现
3.1 绩效方案配置
绩效方案是整个考核体系的"总开关"——它定义了评估方法(KPI / OKR / 360)、周期类型(月度/季度/年度)、评分模式(百分制)与联薪方式(系数制),并关联一套指标模板,后续的批次、计划、评价单都基于方案派生。
▲ 绩效方案列表:“标准月度绩效考核方案"采用 KPI+行为混合评估模式、百分制评分、系数制联薪,关联"通用月度绩效考核模板”;另有标准季度方案,状态均为启用
方案设计要点:
- 评估模式可切换:方案级 evaluationMode 支持 KPI、OKR、KPI+行为混合等多种方法论,一套引擎适配不同考核体系。
- 周期与模板绑定:方案绑定周期类型(月度/季度/年度)与指标模板,批次按方案批量生成员工计划,避免重复配置。
- 联薪方式系数制:评分换算为等级后映射绩效系数,再与薪资联动,把"考核结果"真正落到"绩效金额"。
3.2 绩效计划与 HR 校准
绩效计划是执行层的起点——HR 按模板为每个员工生成"指标 + 权重 + 目标值"的考核计划,员工确认目标后正式进入考核;自评、上级评完成后,HR 校准是绩效系统区别于"简单打分工具"的关键环节,HR 站在全局视角调整分数与等级,对齐部门正态分布。
计划与校准设计要点:
- 计划项即考核指标:每个计划含若干 plan_item,每项有指标名、权重、目标值,以及自评分/上级分/校准分四级留痕。
- 目标确认触发评价单:confirmTarget 将计划状态置为"已确认",自动调用 createFromPlanIfAbsent 生成评价单、启动主流程。
- 同步工作汇报取数:syncWorkReport 拉取 OA 工作汇报作为过程证据,对"系统取数"类指标自动算自评分。
- 校准前后双留痕:PerformanceCalibrationDO 同时存 beforeScore/beforeLevel 与 afterScore/afterLevel/afterCoefficient,HR 只填校准后分数,后端 resolveGradeSummary 自动匹配等级规则换算等级与系数,且 reason 必填,杜绝暗箱操作。
3.3 绩效统计与结果
绩效统计是结果层的全局视图——基于一期已落地的绩效批次、目标确认、考核结果和确认单据实时聚合批次数、参与人数、平均得分、绩效金额合计,并直接展示绩效结果明细。 
▲ 绩效统计看板:7 个绩效批次、22 名参与人、平均得分 83.75、绩效金额合计 5,600,下方"绩效结果明细"逐行展示员工得分、等级(S/A/B/C/D)、系数、绩效金额与结果状态
统计与结果设计要点:
- 结果由计划自动汇总:generatePerformanceResultsByPeriod 遍历周期内已校准的计划,按最终分匹配等级规则生成结果,得分取值优先级为 finalScore > calibratedScore > leaderScore > selfScore。
- 多状态分离:publishStatus(发布)/ signStatus(签字)/ salarySyncStatus(薪资同步)相互独立,对应不同操作节点。
- 结果快照:resultSnapshotJson 完整保存每项指标的有效分、得分来源、加权贡献、校准记录,详情页直接渲染,不依赖实时计算,满足合规审计与历史复盘。
四、流程设计:主流程 + 申诉流程双 BPM 编排
4.1 主流程:自评 → 上级评 → 校准 → 结果确认
当员工确认目标后,系统自动生成评价单并启动主流程(HRM_PERFORMANCE_ASSESSMENT_BILL)。整条流程由 BPM 节点驱动,每个节点对应一个状态:
[制定计划] 目标确认
│ createFromPlanIfAbsent → 生成评价单 + startMainProcess
▼
[self_review 自评填报] 员工逐项打分 → submitSelfReview → status=20
▼
[leader_review 评审填报] 主管逐项复评 → submitLeaderReview → status=30
▼
[hr_calibration 校准填报] HR 校准 → submitCalibration → status=40
▼
[result_confirm 结果确认] 员工确认 → confirmResult
├─ 认可 → status=50 已完成
└─ 不认可 → startAppeal → 进入申诉支线
关键设计:评价单生成时显式设置 PROCESS_SKIP_START_USER_NODE = false,确保流程不会自动跳过发起人节点,而是稳稳停在"自评填报"等待员工处理。
4.2 申诉流程:独立 BPM 实例,二次校准闭环
员工对结果有异议时,不在主流程里"回退",而是启动一条独立的申诉流程(HRM_PERFORMANCE_APPEAL_BILL):
[result_confirm] 员工不认可 → startAppeal → status=61 待审核
▼
[appeal_audit 申诉审核] 主管审核 → submitAppealAudit
├─ 驳回 → status=50 已完成(维持原结果)
└─ 通过 → status=62 待校准
▼
[appeal_calibration 申诉校准] HR 二次校准 → status=63 待确认
▼
[appeal_result_confirm 申诉结果确认] 员工确认 → status=64 申诉完成
评价单上分别记录了主流程与申诉流程的实例 ID 和状态——mainProcessInstanceId/mainProcessStatus 与 appealProcessInstanceId/appealProcessStatus,两条流程各自独立留痕、互不污染。
4.3 批次结果审批:FlowBillService 回调发布
周期结束后,HR 创建一张批次结果审批单 PerformanceResultConfirmBillDO,走标准 BPM 审批。它实现了 FlowBillService 接口,审批通过时自动回调发布整批结果:
@Override
@Transactional(rollbackFor = Exception.class)
public void onProcessApproved(String businessKey) {
Long id = Long.parseLong(businessKey);
PerformanceResultConfirmBillDO bill = performanceResultConfirmBillMapper.selectById(id);
if (bill == null) {
return;
}
// 审批通过 → 按周期发布全部绩效结果,并把周期状态推进到"已发布"(5)
performanceResultService.publishPerformanceResultsByPeriod(bill.getPeriodId());
performancePeriodMapper.updateById(new PerformancePeriodDO().setId(bill.getPeriodId()).setStatus(5));
}
提交批次审批前,还会校验该周期下所有评价单是否都已走到终态(50 已完成 / 64 申诉完成),否则拒绝提交——避免"有人还没评完就发奖金"。
五、后端核心实现
5.1 成绩计算与等级匹配(核心)
绩效得分不是拍一个总分,而是按"指标 × 权重"加权汇总。calculateStageScore 是整个模块的心脏——遍历计划项,按阶段取有效分,加权平均后保留两位小数:
private BigDecimal calculateStageScore(List<PerformancePlanItemDO> items, String stageKey) {
if (items == null || items.isEmpty()) {
return null;
}
BigDecimal totalWeight = BigDecimal.ZERO;
BigDecimal totalScore = BigDecimal.ZERO;
for (PerformancePlanItemDO item : items) {
if (item.getWeight() == null) {
continue;
}
BigDecimal score = resolveStageItemScore(item, stageKey); // 按阶段取分
if (score == null) {
continue;
}
totalWeight = totalWeight.add(item.getWeight());
totalScore = totalScore.add(score.multiply(item.getWeight()));
}
if (BigDecimal.ZERO.compareTo(totalWeight) == 0) {
return null;
}
// 加权平均:∑(分×权重) / ∑权重
return totalScore.divide(totalWeight, 2, RoundingMode.HALF_UP);
}
不同阶段取不同的"有效分"——这是四级评分留痕的精髓。resolveStageItemScore 用阶段 key 决定取自评分、上级分还是校准分,且后序环节缺分时自动降级取前序分:
private BigDecimal resolveStageItemScore(PerformancePlanItemDO item, String stageKey) {
return switch (stageKey) {
case "self_review" -> item.getSelfScore();
case "leader_review" -> item.getLeaderScore() != null ? item.getLeaderScore() : item.getSelfScore();
case "hr_calibration", "result_confirm", "appeal_calibration", "appeal_result_confirm" ->
item.getCalibratedScore() != null ? item.getCalibratedScore()
: item.getLeaderScore() != null ? item.getLeaderScore() : item.getSelfScore();
default -> item.getFinalScore() != null ? item.getFinalScore()
: item.getCalibratedScore() != null ? item.getCalibratedScore()
: item.getLeaderScore() != null ? item.getLeaderScore() : item.getSelfScore();
};
}
得分算出来后,由 matchGradeRule 在等级规则里找到第一条命中区间的规则,换算等级与系数:
private PerformanceGradeRuleDO matchGradeRule(BigDecimal score, List<PerformanceGradeRuleDO> gradeRules) {
if (score == null || gradeRules == null || gradeRules.isEmpty()) {
return null;
}
return gradeRules.stream()
.filter(rule -> rule.getScoreStart() != null && rule.getScoreEnd() != null
&& score.compareTo(rule.getScoreStart()) >= 0 // score >= start
&& score.compareTo(rule.getScoreEnd()) <= 0) // score <= end
.findFirst()
.orElse(null);
}
等级规则默认按"S(95-100, 系数1.5) / A(85-94.99, 1.2) / B(75-84.99, 1.0) / C(65-74.99, 0.8) / D(0-64.99, 0.5)"五档配置,HR 可按方案自定义区间与系数。
5.2 自评 / 上级评:360 评分回写与加权汇总
自评和上级评提交时,前端传一段评分快照 JSON(每项 planItemId + score + comment)。applyReviewScores 把分数回写到对应计划项,再加权汇总成计划的总分:
private void applyReviewScores(PerformanceReviewDO review) {
// 仅自评(1)/上级评(2)需要回写分数
if (!Integer.valueOf(1).equals(review.getReviewType()) && !Integer.valueOf(2).equals(review.getReviewType())) {
return;
}
PerformancePlanDO plan = performancePlanMapper.selectById(review.getPlanId());
if (plan == null) {
return;
}
List<ScoreSnapshotItem> items = parseScoreItems(review.getScoreSnapshotJson());
for (ScoreSnapshotItem item : items) {
if (item.getPlanItemId() == null) {
continue;
}
PerformancePlanItemDO update = new PerformancePlanItemDO().setId(item.getPlanItemId());
// 自评写 selfScore,上级评写 leaderScore —— 两套分数互不覆盖
if (Integer.valueOf(1).equals(review.getReviewType())) {
update.setSelfScore(item.getScore());
} else {
update.setLeaderScore(item.getScore());
}
if (StringUtils.isNotBlank(item.getComment())) {
update.setScoreComment(item.getComment());
}
performancePlanItemMapper.updateById(update);
}
// 重新加权汇总,更新计划总分与状态
List<PerformancePlanItemDO> planItems = performancePlanItemMapper.selectListByPlanId(plan.getId());
BigDecimal score = calculateWeightedScore(planItems, Integer.valueOf(1).equals(review.getReviewType()));
PerformancePlanDO updatePlan = new PerformancePlanDO().setId(plan.getId()).setFinalScore(score);
if (Integer.valueOf(1).equals(review.getReviewType())) {
updatePlan.setSelfReviewTime(review.getSubmitTime()).setStatus(4);
} else {
updatePlan.setLeaderReviewTime(review.getSubmitTime()).setStatus(5);
}
performancePlanMapper.updateById(updatePlan);
updatePeriodStatus(plan.getPeriodId()); // 全员评完则推进周期状态
}
自评提交前还有一道完整性校验——必须每项指标都打了分、写了说明,否则报错并提示缺失的指标名,杜绝"敷衍打分":
private void validateSelfReviewPayload(PerformanceAssessmentBillDO bill, String scoreSnapshotJson) {
List<ScoreSnapshotItem> snapshotItems = parseScoreSnapshotItems(scoreSnapshotJson);
Map<Long, ScoreSnapshotItem> scoreMap = /* 按 planItemId 建索引 */;
for (PerformancePlanItemDO item : performancePlanItemMapper.selectListByPlanId(bill.getPlanId())) {
ScoreSnapshotItem scoreItem = scoreMap.get(item.getId());
if (scoreItem == null || scoreItem.getScore() == null || StringUtils.isBlank(scoreItem.getComment())) {
throw exception(PERFORMANCE_ASSESSMENT_BILL_SELF_REVIEW_ITEM_INCOMPLETE, item.getIndicatorName());
}
}
}
5.3 HR 校准:填分自动算等级,结果同步回写
HR 校准时只需填校准后分数,submitCalibration 负责把它换算成等级与系数、记录校准留痕、刷新结果、推进流程:
@Override
@Transactional(rollbackFor = Exception.class)
public void submitCalibration(PerformanceAssessmentBillCalibrationReqVO reqVO) {
PerformanceAssessmentBillDO bill = validateBillForAction(reqVO.getBillId(), "hr_calibration");
// 1. 校准后分数 → 自动匹配等级规则得出等级、系数
StageScoreSummary summary = resolveGradeSummary(resolvePlan(bill.getPlanId()), reqVO.getAfterScore());
// 2. 记录校准前后双留痕
PerformanceCalibrationSaveReqVO calibrationReqVO = new PerformanceCalibrationSaveReqVO();
calibrationReqVO.setAssessmentBillId(bill.getId());
calibrationReqVO.setCalibrationType(CALIBRATION_TYPE_MAIN);
calibrationReqVO.setBeforeScore(resolvePlanScore(bill.getPlanId()));
calibrationReqVO.setBeforeLevel(resolvePlan(bill.getPlanId()).getFinalLevel());
calibrationReqVO.setAfterScore(reqVO.getAfterScore());
calibrationReqVO.setAfterLevel(summary.level);
calibrationReqVO.setAfterCoefficient(summary.coefficient);
calibrationReqVO.setReason(reqVO.getReason());
performanceCalibrationService.createPerformanceCalibration(calibrationReqVO); // 回写计划 finalScore/Level
refreshBillFinalResult(bill.getId(), RESULT_TYPE_ORIGINAL, reqVO.getReason()); // 生成原始结果
// 3. 推进到"结果确认",经办人改为员工本人
performanceAssessmentBillMapper.updateById(new PerformanceAssessmentBillDO().setId(bill.getId())
.setStatus(BILL_STATUS_WAIT_RESULT_CONFIRM)
.setCurrentNodeKey("result_confirm")
.setCurrentAssigneeId(resolveEmployeeUserId(bill.getEmployeeId()))
.setFinalScore(reqVO.getAfterScore())
.setFinalLevel(summary.level)
.setFinalCoefficient(summary.coefficient));
}
校准记录落库时,applyCalibration 会把校准后的分数、等级、系数同步回写到计划,并在全员校准完成后自动推进周期状态:
private void applyCalibration(PerformanceCalibrationDO calibration) {
PerformancePlanDO plan = performancePlanMapper.selectById(calibration.getPlanId());
if (plan == null) {
return;
}
performancePlanMapper.updateById(new PerformancePlanDO().setId(plan.getId())
.setFinalScore(calibration.getAfterScore())
.setFinalLevel(calibration.getAfterLevel())
.setFinalCoefficient(calibration.getAfterCoefficient())
.setStatus(6)); // 已校准
List<PerformancePlanDO> plans = performancePlanMapper.selectListByPeriodId(plan.getPeriodId());
boolean allCalibrated = !plans.isEmpty() && plans.stream().allMatch(item -> item.getStatus() != null && item.getStatus() >= 6);
if (allCalibrated) {
performancePeriodMapper.updateById(new PerformancePeriodDO().setId(plan.getPeriodId()).setStatus(4));
}
}
5.4 批次结果汇总:等级分布与绩效金额
批次结果审批单生成时,buildBill 会按周期汇总全员结果——计算平均分、绩效金额合计,并按等级分组统计分布,为审批人提供决策依据:
List<PerformanceResultDO> results = performanceResultMapper.selectListByPeriodId(saveReqVO.getPeriodId());
// 平均分
BigDecimal avgScore = results.isEmpty() ? BigDecimal.ZERO : results.stream()
.map(item -> item.getScore() == null ? BigDecimal.ZERO : item.getScore())
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(results.size()), 2, RoundingMode.HALF_UP);
// 绩效金额合计
BigDecimal totalAmount = results.stream()
.map(item -> item.getPerformanceAmount() == null ? BigDecimal.ZERO : item.getPerformanceAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 等级分布:S:3, A:12, B:25, C:6, D:1
String distribution = results.stream()
.collect(Collectors.groupingBy(item -> item.getLevel() == null ? "未定级" : item.getLevel(), Collectors.counting()))
.entrySet().stream()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining(", "));
这个等级分布字符串会随流程变量带入审批流,审批人一眼就能看出"S/A 是否超标、是否符合正态分布"。
六、RuoYi Office 的创新设计
6.1 四级评分:自评/上级/校准/最终独立留痕
传统打分系统往往只存一个"最终分",改了就找不回原值。RuoYi Office 在 plan_item 上设了 selfScore / leaderScore / calibratedScore / finalScore 四个独立字段。每一级评分都在自己的字段里,互不覆盖,任何环节都能回溯"员工自己打了多少、主管打了多少、HR 校准成多少"。
计算总分时按 finalScore > calibratedScore > leaderScore > selfScore 的优先级降级取值——越靠后的环节越权威,缺分时自动回退到前序,永远有分可算。
6.2 KPI/OKR 双模:一个库、五类指标、两套方法论
不为 KPI 和 OKR 各做一套表,而是用统一的指标库(五类 indicatorType)+ 方案级 evaluationMode 切换。同一个指标库里,"销售额"是 KPI 结果指标,"上线核心功能"是 OKR 目标指标,"团队协作"是行为能力——HR 按方案自由组合,引擎完全不变。
6.3 HR 校准:对齐正态分布,治理"全员优秀"
如果各主管自行打分,结果一定是"人人 A"。RuoYi Office 把校准做成独立环节:HR 横向对比同部门所有人的分数,按 S/A/B/C/D 的合理比例拉齐,对齐正态分布。校准前后分数、等级、系数全部留痕,原因必填——既治理了分数通胀,又保证了透明可申诉。
6.4 主流程 + 申诉流程:双 BPM 实例不互相污染
申诉不是在主流程里"回退节点",而是启动一条独立的申诉流程实例。评价单上 mainProcessInstanceId 与 appealProcessInstanceId 分开记录,主流程的审批历史保持干净,申诉的审核-校准-确认全程独立留痕。这样既符合"申诉是例外支线"的业务语义,又方便合规审计。
6.5 结果快照:合规审计不依赖实时计算
绩效结果生成时,resultSnapshotJson 完整保存了每项指标的有效分、得分来源(员工自评/上级评分/校准分/最终确认分)、加权贡献、所有校准记录。哪怕日后指标库、等级规则发生变更,历史结果详情依然能原样还原——这对"年终复盘、调薪依据、劳动仲裁举证"等场景至关重要。
七、数据结构
7.1 表结构:hrm_performance_assessment_bill(评价单 · 核心执行表)
| id | bigint | 主键 |
| bill_code | varchar | 评价单编号 |
| plan_id | bigint | 关联绩效计划 |
| period_id | bigint | 关联周期 |
| employee_id / employee_name | bigint/varchar | 被考核员工 |
| leader_user_id / leader_user_name | bigint/varchar | 直属主管 |
| current_node_key / current_node_name | varchar | 当前节点 |
| current_assignee_id / current_assignee_name | bigint/varchar | 当前经办人 |
| main_process_instance_id / main_process_status | varchar/int | 主流程实例 |
| appeal_process_instance_id / appeal_process_status | varchar/int | 申诉流程实例 |
| status | int | 状态(10/20/30/40/50/61~64) |
| final_score / final_level / final_coefficient | decimal/varchar/decimal | 最终分/级/系数 |
| result_confirm_status | int | 结果确认状态 |
| appeal_status | int | 申诉状态 |
| appeal_start_time / appeal_result_confirm_time | datetime | 申诉时间留痕 |
7.2 表结构:hrm_performance_plan_item(计划项 · 四级评分留痕)
| id | bigint | 主键 |
| plan_id | bigint | 关联计划 |
| indicator_id / indicator_code / indicator_name | bigint/varchar | 指标 |
| item_type | int | 指标类型(结果/目标/行为/纪律/专项) |
| target_value / actual_value | varchar | 目标值 / 实际值 |
| weight | decimal | 权重 |
| data_source_type | int | 数据来源(1手工 2系统取数 3外部联动) |
| self_score | decimal | 自评分 |
| leader_score | decimal | 上级评分 |
| calibrated_score | decimal | 校准分 |
| final_score | decimal | 最终确认分 |
| score_comment | varchar | 评分说明 |
| sort | int | 排序 |
7.3 表结构:hrm_performance_grade_rule(等级规则)
| id | bigint | 主键 |
| scheme_id / scheme_name | bigint/varchar | 所属方案 |
| rule_name | varchar | 规则名称 |
| score_start / score_end | decimal | 得分区间 [start, end] |
| grade_level | varchar | 等级(S/A/B/C/D) |
| coefficient | decimal | 绩效系数 |
| fixed_amount | decimal | 固定绩效金额 |
| sort / enabled | int | 排序 / 是否启用 |
7.4 表结构:hrm_performance_calibration(校准记录)
| id | bigint | 主键 |
| assessment_bill_id / plan_id | bigint | 关联评价单/计划 |
| calibration_type | int | 校准类型(1主校准 2申诉校准) |
| before_score / before_level | decimal/varchar | 校准前分/级 |
| after_score / after_level / after_coefficient | decimal/varchar/decimal | 校准后分/级/系数 |
| reason | varchar | 校准原因(必填) |
| calibration_user_id / calibration_user_name | bigint/varchar | 校准人 |
| calibration_time | datetime | 校准时间 |
7.5 设计要点
- 四级评分字段并存:self/leader/calibrated/final 四个分数字段互不覆盖,是全程可追溯的根基
- @TableField(exist=false) 运行时计算:评价单 DO 上的 currentScore/selfReviewScore/leaderReviewScore/calibrationScore 等不落库,在 enrichBill 时按计划项实时加权算出
- BigDecimal 精度统一:所有得分计算用 divide(…, 2, RoundingMode.HALF_UP),权重/系数用更高精度,避免浮点误差影响奖金
- 双流程实例字段分离:主流程与申诉流程的实例 ID/状态各占一组字段,互不干扰
八、技术亮点总结
| 三层架构 | 配置层 / 执行层 / 结果层,15 张表 | 规则与数据分离,HR 一次配置全员复用 |
| KPI/OKR 双模 | 方案级 evaluationMode + 五类指标库 | 一套引擎跑两套方法论 |
| 四级评分留痕 | self/leader/calibrated/final 四字段 | 每级独立留底,全程可追溯 |
| 加权成绩计算 | ∑(分×权重)/∑权重,HALF_UP 两位 | 评分有据可依,不是拍总分 |
| 取值优先级降级 | final>calibrated>leader>self | 越后越权威,缺分自动回退 |
| 等级规则匹配 | 区间 [start,end] 命中 → 等级+系数 | 分数→等级→薪资全自动 |
| 自评完整性校验 | 逐项校验分数+说明非空 | 杜绝敷衍打分 |
| HR 校准 | 校准前后双留痕 + 原因必填 | 对齐正态分布,治理分数通胀 |
| 双 BPM 流程 | 主流程 + 申诉流程独立实例 | 申诉支线不污染主流程 |
| 结果快照 | resultSnapshotJson 存明细+校准 | 合规审计不依赖实时计算 |
| 批次审批回调 | FlowBillService.onProcessApproved | 审批通过自动发布整批结果 |
| 周期状态自动推进 | 全员评完/校准完自动进下一阶段 | 减少 HR 手工操作 |
九、快速体验
在线演示:http://ruoyioffice.com/web/(账号 admin / 密码 admin123)
操作路径:HRM 人力资源 → 绩效管理
推荐体验流程:
源码仓库:
| GitHub | https://github.com/yuqing2026/ruoyi-office |
| GitCode | https://gitcode.com/zhouzhongyan/ruoyi-office |
| Gitee | https://gitee.com/yqzy1688/ruoyi-office |
结语
绩效管理的难点从来不是"打分"本身,而是让打分有依据、让等级有标准、让结果有出口、让全程可追溯。RuoYi Office 的设计给出了一套务实答案:用三层架构把规则和数据分开,用四级评分字段让每一步打分都留底,用加权成绩 + 等级规则把"主观印象"换成"客观算法",用 HR 校准对齐正态分布治理分数通胀,用主流程 + 申诉流程双 BPM 编排让异议有正规出口。
这套"配置驱动 + 多级留痕 + 规则匹配 + 流程闭环"的思路不止适用于绩效,也能推广到能力评估、述职评审、供应商考评、项目验收打分等所有"多人评分 + 加权汇总 + 等级判定 + 结果确认"的场景。
如果你正在设计企业 HRM 的绩效模块,或者对"KPI/OKR 双模、多级评分留痕、校准与申诉流程"感兴趣,欢迎参考源码实现。你们公司的绩效是怎么做校准的?是按部门拉正态分布,还是主管说了算?欢迎在评论区聊聊。
常见问题(FAQ)
RuoYi Office 的绩效管理同时支持 KPI 和 OKR 吗?
支持。绩效方案 PerformanceSchemeDO 提供 evaluationMode 字段,可选 KPI、OKR、KPI+行为混合、OKR+KPI 混合四种模式;指标库统一把指标分为结果(KPI)/目标(OKR)/行为/纪律/专项五类,同一套引擎适配两种方法论。
绩效得分是怎么算出来的?
按"指标 × 权重"加权汇总:∑(有效分 × 权重) / ∑权重,保留两位小数(HALF_UP)。有效分按 finalScore > calibratedScore > leaderScore > selfScore 优先取值,再用等级规则的区间 [scoreStart, scoreEnd] 匹配出等级 S/A/B/C/D 与对应系数。
什么是"绩效校准",为什么需要它?
绩效校准(Calibration)是 HR 在主管评分后做的一轮全局调整,用于对齐部门内 S/A/B/C/D 的比例、对齐正态分布,治理"全员优秀"的分数通胀。RuoYi Office 的校准会记录校准前后的分数、等级、系数,并要求填写校准原因,全程透明可追溯。
员工对绩效结果不认可怎么办?
可以发起申诉。RuoYi Office 用一条独立的申诉 BPM 流程处理:员工发起 → 上级审核 → HR 二次校准 → 员工确认申诉结果。申诉流程与主流程是两条独立的流程实例,各自留痕,互不污染。
绩效结果能和薪资联动吗?
能。方案的 salaryLinkMode 支持系数制、金额制、人工确认三种模式;等级规则配置了系数和固定金额,结果生成时自动换算绩效金额,并提供"薪资同步"状态位对接薪资模块。
这套绩效系统是开源免费的吗?
是。基于 RuoYi-Vue-Pro / Yudao 架构,后端 Spring Boot 3.5 + 前端 Vue3 + 工作流 Flowable,开源可商用,本地约 10 分钟即可启动。
💡 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
📦 源码仓库:GitHub | GitCode | Gitee
💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!


