Skip to content

SpringBoot+Vue3 绩效“谁评谁”设计:1类主评、2种规则、3类来源破解评审错派

🌐 演示地址https://ruoyioffice.com | 📦 源码1·GitHubruoyi-office | 📦 源码2·GitCoderuoyi-office | 📦 源码3·Giteeruoyi-office | 💬 微信:17156169080(备注「RuoYi Office」)

绩效系统里最危险的错误,往往不是 90 分算成了 89.9 分,而是评价任务发给了错误的人。主管调整、部门负责人缺失、指定评委变更、批次重生成覆盖人工结果,都会让“谁评谁”失真。RuoYi Office 把评估关系做成独立能力:方案层定义规则,周期层固化实例,发布前先预览,异常统一进池,人工改派再同步计划、评价单与必要的 BPM 变量。

绩效评估关系设计 - 从方案规则到执行链同步的全景图

▲ 绩效评估关系全景:L1 方案规则 → 发布前预览 → L2 周期实例 → 异常池/人工修正 → 计划、评价单与 BPM 同步;当前边界是单主评,不是完整 360 多角色。

结论前置:先建立可执行关系,再启动评价流程

核心结论只有一句话:绩效流程不能靠临时推断主评人,必须先生成一条可审计、可修正、可同步的“被评人 → 主评人”关系。

当前产品已落地:

  1. relationType=1,当前关系类型是主评
  2. assessorType=1/2,当前主评规则支持部门负责人指定用户
  3. source=1/2/3,关系来源支持 AUTO / MANUAL / IMPORT
  4. 草稿批次发布前可预览 matchedCount / unmatchedCount / relationCount / abnormalCount
  5. 无法解析主评时保留异常码和异常说明,并进入工作台异常池。
  6. 已锁定、手工或导入的关系在重生成时会被保留。
  7. HR 可更换主评人,系统同步回写计划、评价单,并在必要时更新 BPM 的 leaderUserId
  8. 校准及之后阶段禁止改边,避免历史责任链被重写。

但边界也必须说清楚:

当前落地的是“单主评关系闭环”,不是完整 360 多角色评价。

代码里虽然已经把关系抽象成独立表,也保留了 relationTyperoleLabel 等扩展位置,但目前不能据此宣称同级、下属、跨部门评委、多角色权重汇总已经全部完成。


二、两层模型:L1 定规则,L2 固化本周期结果

2.1 为什么要拆成两层?

L1 解决“通常应该由谁评”,L2 解决“这个周期最终由谁评”。

如果只有 L1:

  • 每次查询都要重新解析组织关系;
  • 负责人变化会让历史批次的主评漂移;
  • 无法记录手工改派;
  • 无法对异常关系做闭环处置。

如果只有 L2:

  • 每个周期都要手工维护全部关系;
  • 无法复用方案规则;
  • 批量生成成本过高;
  • 很难保证不同批次口径一致。

两层组合后,规则可复用,实例可修正。

2.2 L1:PerformanceRelationRuleDO

L1 规则挂在绩效方案 schemeId 下。

当前真实字段表达的能力如下:

字段当前语义已落地取值
schemeId所属绩效方案方案 ID
assesseeScopeType被评人范围1=ALL
assessorType主评人解析类型1=部门负责人2=指定用户
assessorParam解析参数指定用户 ID 等
sort规则顺序当前按顺序取首条
fallbackPolicy找不到主评时的策略1=EXCEPTION

这张表的角色是“规则模板”,不是本周期最终名单。

当方案没有配置关系规则时,服务会构造一条默认规则:

  • 被评范围为全部;
  • 主评人按部门负责人解析;
  • 找不到时记录异常。

2.3 L2:PerformanceEvalRelationDO

L2 关系挂在绩效周期 periodId 下。

它记录的是发布后真正参与执行的数据:

字段作用业务价值
periodId归属周期隔离不同考核批次
assesseeEmployeeId被考核员工关系左端
assessorUserId主评用户关系右端
relationType关系类型当前固定为主评
source来源区分自动、手工、导入
locked是否锁定控制重生成是否覆盖
status有效/排除支持关系状态治理
roleLabel角色标签当前默认“主评”
exceptionCode异常码机器可识别原因
exceptionMsg异常说明HR 可读原因

2.4 真实源码:当前数据边界

下面片段来自 PerformanceEvalRelationDO,裁剪后保留了最关键的边界字段。

java
public class PerformanceEvalRelationDO extends BaseDO {

    @TableId
    private Long id;
    private Long periodId;
    private Long assesseeEmployeeId;
    private Long assesseeUserId;
    private Long assessorUserId;
    private String assessorUserName;

    /** 关系类型:1=主评 */
    private Integer relationType;
    /** 来源:1=AUTO 2=MANUAL 3=IMPORT */
    private Integer source;
    /** 锁定/手工边在自动生成时保留 */
    private Boolean locked;
    /** 状态:0=有效 1=排除 */
    private Integer status;
    private String roleLabel;
    private String exceptionCode;
    private String exceptionMsg;
}

这段代码明确证明了三件事:

  1. 当前关系实例是周期级数据;
  2. 当前关系类型只定义了主评;
  3. 异常与来源都作为正式字段落库。

三、两种主评规则:部门负责人或指定用户

当前主评规则只有两种:默认按员工所属部门负责人解析,或从 assessorParam 读取指定用户 ID。解析失败不会静默猜人,而是返回异常码,交由 HR 处理。

3.3 当前异常码

异常码触发条件建议处理
EMPLOYEE_NOT_FOUND被考核员工记录不存在检查计划与员工档案
DEPT_MISSING员工未配置部门完善员工组织归属
DEPT_LEADER_NOT_FOUND部门未配置负责人补部门负责人或人工改派
DESIGNATED_USER_INVALID指定用户参数无法解析修正规则参数
DESIGNATED_USER_NOT_FOUND指定用户不存在更换有效用户

异常码面向程序筛选,异常说明面向 HR 阅读。

两者同时存在,异常池才能既可统计又可处置。


四、发布前预览:把失败提前到批次落库之前

4.1 预览不是“看看名单”

发布前预览是一次不落库的关系彩排。

它同时回答四个问题:

  1. 有多少员工匹配到绩效模板?
  2. 有多少员工未匹配模板?
  3. 能预生成多少条主评关系?
  4. 其中有多少条关系异常?

对应响应字段分别是:

  • matchedCount
  • unmatchedCount
  • relationCount
  • abnormalCount

除此之外,响应还带:

  • 参与人明细 participants
  • 关系明细 relations
  • 是否需要目标确认 requireGoalConfirm
  • 发布时是否自动确认 autoConfirmOnPublish

4.2 为什么先匹配模板,再预览关系?

绩效周期不是把所有在职员工都直接塞进流程。

系统先判断员工是否匹配绩效模板。

只有匹配成功的员工,才进入主评关系预览。

因此:

  • unmatchedCount 解决“谁没有考核内容”;
  • abnormalCount 解决“有考核内容但找不到谁来评”。

这两类异常不能混成一个数字。

前者通常要补模板或调整范围。

后者通常要补组织负责人、修规则或人工改派。

4.3 真实源码:逐人试算主评

下面是 previewPrimaryRelations 的核心路径。

java
public List<PerformancePeriodPreviewRespVO.RelationItem>
        previewPrimaryRelations(Long schemeId, List<EmployeeDO> employees) {
    PerformanceRelationRuleDO primaryRule = resolvePrimaryRule(schemeId);
    Map<Long, DeptRespDTO> deptCache = new HashMap<>();
    List<PerformancePeriodPreviewRespVO.RelationItem> rows = new ArrayList<>();
    if (employees == null || employees.isEmpty()) {
        return rows;
    }
    for (EmployeeDO employee : employees) {
        AssessorResolveResult result =
                resolveAssessor(primaryRule, employee, deptCache);
        PerformancePeriodPreviewRespVO.RelationItem row =
                new PerformancePeriodPreviewRespVO.RelationItem();
        row.setAssesseeEmployeeId(employee.getId());
        row.setAssesseeEmployeeName(employee.getName());
        row.setAssessorUserId(result.assessorUserId());
        row.setAssessorUserName(result.assessorUserName());
        row.setRoleLabel("主评");
        row.setExceptionCode(result.exceptionCode());
        row.setExceptionMsg(result.exceptionMsg());
        rows.add(row);
    }
    return rows;
}

这里还做了一个朴素但有效的优化:

deptCache 在批次预览期间缓存部门信息。

同一部门有多名员工时,不必重复请求部门接口。

4.4 工作台如何呈现预览

草稿状态下,前端不会读取已落库关系。

它把预览响应中的 relations 映射为临时展示行:

  • 临时负数 ID 仅用于表格行键;
  • roleLabel 默认“主评”;
  • source 展示为自动;
  • 异常码非空的行进入异常池;
  • 发布后才允许更换主评。

绩效批次工作台总览 - 参与人数、评估关系、异常关系与阶段信息

▲ 绩效批次工作台总览:草稿期先展示参与人数、关系数量与异常数量;发布动作建立在预览结果之上,避免“先启动、后补洞”。

4.5 发布门禁

当前前端至少要求:

text
草稿状态 && matchedCount > 0

才允许点击发布。

如果没有员工匹配模板,按钮会给出明确提示:

“当前范围下无在职员工可参与考核,请调整公司/部门/人员状态后再发布。”

这比发布后生成一个空批次更可控。


五、L2 生成:自动关系允许失败,但失败必须留下原因

5.2 真实源码:生成与保留

下面代码来自 generateByPeriod,保留了生成、异常落库和回写计划三条主线。

java
public void generateByPeriod(Long periodId) {
    PerformanceRelationRuleDO rule =
            resolvePrimaryRule(validatePeriodExists(periodId).getSchemeId());
    List<PerformancePlanDO> plans = performancePlanMapper.selectListByPeriodId(periodId);
    Map<Long, DeptRespDTO> deptCache = new HashMap<>();
    for (PerformancePlanDO plan : plans) {
        PerformanceEvalRelationDO existed = performanceEvalRelationMapper
                .selectByPeriodIdAndAssesseeEmployeeId(periodId, plan.getEmployeeId());
        if (existed != null && shouldPreserve(existed)) {
            fillPlanLeaderFromRelation(plan, existed);
            continue;
        }
        EmployeeDO employee = employeeMapper.selectById(plan.getEmployeeId());
        AssessorResolveResult result = resolveAssessor(rule, employee, deptCache);
        PerformanceEvalRelationDO relation = new PerformanceEvalRelationDO()
                .setPeriodId(periodId)
                .setAssesseeEmployeeId(plan.getEmployeeId())
                .setAssessorUserId(result.assessorUserId())
                .setRelationType(RELATION_TYPE_PRIMARY)
                .setSource(SOURCE_AUTO)
                .setExceptionCode(result.exceptionCode())
                .setExceptionMsg(result.exceptionMsg());
        fillPlanLeaderFromRelation(plan, relation);
    }
}

5.3 为什么关系要回写计划?

独立关系表是主评治理入口。

但计划和评价单仍需要冗余主评字段:

  • 列表查询不必每次联表;
  • 评价单创建时可直接得到上级评办理人;
  • BPM 启动变量可以从业务单据准备;
  • 历史执行记录更容易还原。

因此,这不是“重复存储没设计好”。

它是一种有同步约束的执行冗余。

关键不在于是否冗余,而在于修改主评时必须同步


六、重生成策略:为什么“尽量保留”比全量覆盖更安全?

6.1 三类需要保留的关系

shouldPreserve 的真实逻辑非常明确:locked=true,或来源为 SOURCE_MANUAL / SOURCE_IMPORT 时返回 true

也就是说,以下关系不会被自动重生成覆盖:

  1. locked=true 的关系;
  2. source=MANUAL 的关系;
  3. source=IMPORT 的关系。
关系来源/状态重生成行为原因
AUTO 且未锁定允许重算跟随当前规则与组织数据
MANUAL保留尊重 HR 明确修正
IMPORT保留尊重外部确认名单
locked=true保留显式冻结本周期关系

七、异常池:让“找不到主评”成为可处理工单

7.1 异常池不是错误日志

异常池是批次工作台内的业务处置区,不是开发人员看的服务器日志。

前端通过 displayRelations.filter((item) => !!item.exceptionCode) 把异常码非空的关系筛进异常池。

这让 HR 不需要理解堆栈或数据库。

HR 看到的是:

  • 被考核人;
  • 当前主评人;
  • 角色;
  • 来源;
  • 异常码;
  • 异常说明;
  • 更换主评操作。

绩效评估关系列表 - 主评来源、异常码与更换主评入口

▲ 绩效评估关系列表:关系来源区分自动、手工与导入;异常码和异常说明帮助 HR 定位问题,发布后可通过“更换主评”完成修正。

7.2 异常池的运营顺序

建议 HR 按以下顺序处理:

  1. 先看 unmatchedCount,补绩效模板覆盖;
  2. 再看 abnormalCount,判断组织配置还是规则参数问题;
  3. 可修主数据的,优先补部门或负责人;
  4. 本周期必须立即推进的,直接人工更换主评;
  5. 修复后重新生成关系;
  6. 确认异常池归零或剩余异常已有明确处理意见;
  7. 再进入自评、上级评与校准运营。

7.3 为什么不自动找上级部门负责人?

当前真实代码没有实现“本部门无负责人就逐级向上找”的逻辑。

这不是遗漏描述,而是事实边界。

自动向上寻找看似聪明,实际可能产生新的歧义:

  • 子部门绩效是否应该由事业部负责人评价?
  • 空部门负责人是配置错误还是业务规则?
  • 跨层级主评是否符合绩效制度?
  • 上级部门负责人是否有权限查看被评人的全部指标?

在规则未明确前,进入异常池比擅自决定更安全。


八、人工改派:一次操作同步关系、计划、评价单与 BPM

8.1 改派不是只更新一列

更换主评人的真正难点是保持执行链一致。

如果只改 hrm_performance_eval_relation.assessor_user_id

  • 计划详情仍显示旧主管;
  • 评价单仍记录旧主评;
  • 当前办理人可能仍是旧人;
  • BPM 变量仍可能把任务派给旧人。

所以改派必须是一条事务链。

8.2 真实源码:主评人同步

下面片段裁剪自 updatePrimaryAssessor,展示业务单据和流程变量同步。

java
performanceEvalRelationMapper.updateById(
        new PerformanceEvalRelationDO()
                .setId(relation.getId())
                .setAssessorUserId(newAssessorUserId)
                .setSource(SOURCE_MANUAL)
                .setLocked(true)
                .setExceptionCode(null)
                .setExceptionMsg(null));

performancePlanMapper.updateById(new PerformancePlanDO()
        .setId(plan.getId())
        .setLeaderUserId(newAssessorUserId));
performanceAssessmentBillMapper.updateById(new PerformanceAssessmentBillDO()
        .setId(bill.getId())
        .setLeaderUserId(newAssessorUserId));
if (StringUtils.isNotBlank(bill.getMainProcessInstanceId())) {
    processInstanceApi.updateProcessInstanceVariables(
            bill.getMainProcessInstanceId(),
            Map.of("leaderUserId", newAssessorUserId));
}

真实实现还包含一个更细的条件:

如果评价单当前正处于 leader_review,或者状态为待上级评 20,系统还会同步:

  • currentAssigneeId
  • currentAssigneeName

这样工作台办理人与主评关系立即一致。

8.3 为什么 BPM 更新是“必要时尝试”?

代码仅在 mainProcessInstanceId 非空时更新流程变量。

并且流程变量更新放在 try/catch 中:

  • 成功:BPM 的 leaderUserId 与业务关系一致;
  • 失败:记录 warn 日志,不回滚已经完成的关系和业务表更新。

因此,准确表述应是:

更换主评会回写关系、计划与评价单;主流程已存在时,系统会尝试更新 BPM leaderUserId

不应写成“任何情况下 BPM 任务都必然自动改派成功”。

流程变量变化与已创建用户任务的实际候选人刷新,还受流程引擎节点状态和任务实现影响。

8.4 改派时机边界

当前服务允许在校准前修改关系。

以下状态禁止改边:

  • 待校准 30
  • 待结果确认 40
  • 已完成及更后状态 >=50

原因是:

主评责任已经完成后再换人,会让“实际评分人”和“关系表当前主评”不一致。

禁止改边是为了保护审计语义。


十三、技术亮点总结

设计点实现方式业务价值
L1/L2 两层关系方案规则 + 周期实例兼顾复用与历史快照
单主评边界明确relationType=1不夸大当前能力
两种主评规则部门负责人 / 指定用户覆盖层级组织与特殊评委
三类来源AUTO / MANUAL / IMPORT可审计、可制定覆盖策略
发布前四项预览matched/unmatched/relation/abnormal把失败提前暴露
显式异常池code + message + 独立 TabHR 可定位、可处置
重生成保护locked / MANUAL / IMPORT 保留不覆盖人工确认结果
改派同步relation / plan / bill业务数据一致
节点办理人同步上级评阶段更新 assignee防止待办仍指向旧人
BPM 变量同步必要时更新 leaderUserId流程候选人与业务关系对齐
校准后禁改边状态门禁保护历史责任链
部门缓存deptCache降低批量解析重复调用

技术栈锚点:

  • 后端:Spring Boot 3.5、MyBatis-Plus、Flowable;
  • 前端:Vue3、TypeScript、Vben Admin、Ant Design Vue;
  • 业务位置:HRM 绩效方案、周期工作台、评估关系与评价单。

十四、快速体验

14.1 在线演示

地址:https://ruoyioffice.com/web/

账号:admin

密码:admin123

14.2 推荐体验路径

操作路径:HRM 人力资源 → 绩效管理 → 绩效周期 → 批次工作台

建议按以下顺序体验:

  1. 新建或打开一个草稿绩效周期;
  2. 进入批次工作台;
  3. 查看参与人匹配数量;
  4. 打开“评估关系”页签;
  5. 对照被评人、主评人、角色与异常说明;
  6. 查看未匹配模板与异常关系是否分开提示;
  7. 发布批次,让预览关系正式落库;
  8. 修改部门负责人后尝试重新生成自动关系;
  9. 选择一条关系执行“更换主评”;
  10. 再次重生成,观察手工锁定关系是否保留;
  11. 查看计划与评价单上的主评是否同步;
  12. 在待上级评阶段核对当前办理人。

14.3 源码仓库

平台地址适合场景
GitHubhttps://github.com/yuqing2026/ruoyi-office浏览源码、Issue 与 Star
GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office国内网络访问
Giteehttps://gitee.com/yqzy1688/ruoyi-office国内镜像与协作

说明:在线演示用于功能体验;不同版本的模块范围、源码授权、商业能力与持续维护政策,请以仓库说明和正式咨询结果为准。


十五、常见问题(FAQ)

绩效评估关系到底解决什么问题?

绩效评估关系解决“某个绩效周期里,谁负责评价谁”的执行问题。

它把主评人从临时组织查询结果,升级为周期级业务数据,支持生成、预览、异常、改派、锁定和同步。

当前是否已经支持完整 360 度评价?

没有。

当前真实落地的是 relationType=主评 的单主评关系,主评可来自部门负责人或指定用户。

完整的同级、下属、多角色、多权重 360 关系仍属于后续扩展方向。

部门没有负责人时,系统会自动找上级部门负责人吗?

当前不会。

系统会生成 DEPT_LEADER_NOT_FOUND 异常,并在工作台异常池展示,由 HR 补组织配置或人工更换主评。

重新生成关系会覆盖 HR 手工改派吗?

按当前 shouldPreserve 逻辑,已锁定、手工来源或导入来源的关系会被保留。

未锁定的自动关系允许按最新规则和组织数据重新计算。

更换主评后,绩效计划和流程会同步吗?

会同步计划与评价单的主评字段。

如果评价单处于上级评节点,还会同步当前办理人;主流程实例存在时,系统会尝试更新 BPM 变量 leaderUserId

为什么校准后不能再更换主评?

因为主评责任已经完成。

校准后再改关系,会导致历史评分人与当前关系不一致,破坏审计语义,所以服务在待校准、待结果确认和完成阶段禁止改边。


结语:绩效系统先要把责任链做对

绩效系统的第一性问题,不是“用了 KPI 还是 OKR”,也不是“有几套评分公式”,而是:

每一位员工的评价责任,是否在流程启动前就已经明确。

这套设计用一条很务实的路径回答了这个问题:

  • L1 方案规则定义通常由谁评;
  • 发布前预览暴露模板与关系异常;
  • L2 周期实例固化本批次主评;
  • 异常池承接无法自动判断的情况;
  • 手工改派锁定 HR 的最终决定;
  • 计划、评价单与 BPM 同步保持执行一致;
  • 状态门禁保护已经完成的责任链。

它没有假装一次性解决完整 360 多角色评价。

但它把最基础、也最容易卡住绩效运营的“谁评谁”做成了可执行闭环。

这种“规则模板 + 周期快照 + 异常显式化 + 人工兜底 + 执行链同步”的模式,也可以迁移到:

  • 试用期转正评估;
  • 人才盘点评委分配;
  • 项目成员评价;
  • 供应商考评;
  • 能力测评;
  • 述职评审。

如果你正在建设绩效系统,建议先问团队一个问题:

当主管调岗、部门负责人缺失、HR 临时换评委时,你们的系统能否准确解释“现在是谁评、为什么是他、改完后哪些地方同步了”?

欢迎在评论区分享你们遇到过的评审错派、主管变更和批次重生成问题。


💡 想要体验 RuoYi Office 的绩效批次工作台?

🌐 在线演示https://ruoyioffice.com/web/(账号 admin / admin123)

📦 源码仓库GitHub | GitCode | Gitee

💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」

如果本文对你设计绩效“谁评谁”有帮助,欢迎关注项目并给个 Star。

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版