status: propose created: 2026-08-03 complexity: 🔴复杂 source:
code-copilot/changes/20260728需求/绩效打分系统_详细设计文档.mdresearch baseline: branchcodex/capability-guide-version-sync, commitbc96278b(进入/apply前必须按最新工作区复核)
客户希望不为绩效打分系统编写 KPI 专用 Java、Vue 或外部脚本,而是直接复用 Forge 低代码业务对象、主子表、Flowable、业务动作、公式、数据权限、任务中心和消息中心完成搭建。
当前平台已经具备普通 CRUD、主子表、字段公式、聚合公式、业务动作事务、FOREACH、CREATE_RECORD、流程绑定、角色数据权限和站内消息等底座。本变更不重复建设这些能力,而是补齐它们之间尚未闭合的运行链路,使实施人员只通过低代码配置即可完成以下绩效流程:
周期任务生成 -> 员工自评 -> 上级评分 -> 员工确认/申诉 -> HR 处理 -> 关闭
SUM(明细.weight) = 100,前后端均给出明确反馈,后端为最终可信边界。FOREACH/CREATE_RECORD 消费。FIELD:evaluatee_id 和 FIELD:evaluator_id。FOLLOW_SYSTEM、本人、本组织、本组织及下级组织、自定义组织和全部数据权限。unified-enterprise-collaboration 变更负责。| 能力 | 当前判断 | 本变更处理 |
|---|---|---|
| 指标、模板、任务、申诉等普通对象 CRUD | 已满足 | 直接使用低代码对象、主子表、字典、文件和自动编号 |
| 员工本人、考评人、HR 数据范围 | 已满足(需拆运行入口) | 复用 FOLLOW_SYSTEM,分别映射 evaluatee_id/evaluator_id,不重做数据权限 |
| 站内消息模板、发送、记录 | 已满足 | 继续复用消息中心 |
| 按任务记录字段选择消息接收人 | 部分满足 | 补 FIELD:<fieldCode>,不重做消息模块 |
| Flowable 流程绑定和普通主表字段权限 | 已满足 | 继续复用现有流程设计器和绑定链路 |
| 流程节点子表编辑、行操作、草稿/办理校验 | 不满足 | 补节点子表权限、受控保存和唯一办理命令 |
| 权重合计、条件必填、结果专属输入 | 不满足 | 补服务端声明式集合/条件校验和结果补充字段 |
| 节点业务文案、业务动作和失败补偿 | 部分满足 | 复用 Business Action,补节点结果绑定与补偿闭环 |
| 周期批量查询员工、查模板明细、生成任务快照 | 不满足 | 补周期上下文、可信身份、QUERY_RECORDS、主子表快照和批量运维 |
| 报表、动态指标列导出、外部企微/飞书/钉钉 | 本期排除 | 由独立变更负责,不影响本期核心绩效低代码验收 |
每个结论均以当前工作区源码为准;历史可行性文档中的成熟度描述不作为本 Spec 的代码事实。
本绩效场景所需的低代码数据权限已经具备
forge-admin-ui/src/views/app-center/components/designer/BusinessPermissionFlowPanel.vue 的 dataScopeOptions 已提供 TENANT/FOLLOW_SYSTEM 和本人字段、组织字段映射。forge-server/forge-framework/forge-plugin-parent/forge-plugin-generator/src/main/java/com/mdframe/forge/plugin/generator/service/DynamicDataScopeService.java#buildCondition 已实现 SELF/ORG/ORG_AND_CHILD/CUSTOM/TENANT_ALL/REGION 条件;DynamicCrudService#selectPage/#selectById/#updateById/#updateInternalFieldsById/#deleteById 均把该条件传入 Repository,覆盖查询和写操作。evaluatee_id 和 evaluator_id;本变更不扩展按角色动态切换本人字段。业务动作已有事务、幂等和循环底座
BusinessActionExecutionService#executeInternal 使用 TransactionTemplate 执行白名单步骤,并通过动作日志处理幂等命中。ForeachActionStepExecutor 已支持遍历集合和逐行执行子步骤。CreateRecordActionStepExecutor#execute 已能调用 DynamicCrudService.insertInternal 创建目标记录。ForeachActionStepExecutor#resolveItems 只能遍历 items/collection 或动作上下文中已有的 collectionPath;CreateRecordActionStepExecutor 也只读取现有上下文,当前没有受控 QUERY_RECORDS 步骤来按员工岗位加载模板及模板明细。BusinessActionExecutionController#logs 已支持失败日志分页查询;但 BusinessActionExecutionService#findReusableLog/#reserveLog 会拒绝使用相同幂等键重试 FAILED 日志,当前没有可信的失败重放入口。公式和父子聚合已有底座
FormulaType.AGGREGATE、StoredFormulaRuntime 和 StoredAggregateRefreshService 已支持保存态公式;DynamicCrudService#insert/#updateById/#deleteById 分别调用 refreshAfterChildInsert/#refreshAfterChildUpdate/#refreshAfterChildDelete 刷新父表聚合。站内消息动作已经可用
BusinessTriggerExecutor#executeSendMessageAction 和 SendMessageActionStepExecutor#execute 已复用消息模板、业务记录变量和现有消息中心。BusinessMessageChannelService#sendInternalMessage 已调用 MessageService 发送站内消息。流程待办子表固定只读
forge-admin-ui/src/views/flow/todo.vue 对 ChildTableEditor 固定传入 readonly。FormPermissionConfig.vue 的字段权限协议只有扁平 {field, readable, writable, required},无法表达子表字段及新增/删除行权限。BusinessTaskFormContextVO 虽包含 childrenConfig,但没有节点级子表权限结果。业务对象节点保存只更新主表字段
BusinessFlowService#saveTaskFormContext 收集扁平 writableFields 后调用 DynamicCrudService.updateInternalFieldsById,不会保存 children payload。BusinessTaskFormSaveDTO 只有一个扁平 data Map,没有草稿/办理校验阶段语义。子表校验只覆盖单行必填和数值范围
DynamicCrudService#validateChildRow 逐行检查 required/min/max,不支持 SUM(weight) = 100、至少一行、唯一明细或按办理阶段校验全部行。forge-admin-ui/src/components/lowcode-builder/shared/runtime-rules.js#resolveRuntimeControl 能在前端按条件改变 required,但后端动态 CRUD/流程保存没有执行同协议;绕过前端可跳过“分差过大必须填评语”等条件必填。待办动作仍固定为 approve/reject
BusinessFlowService#completeBusinessTask 只接受 approve/reject。forge-admin-ui/src/views/flow/todo.vue 固定显示“同意/驳回”。BusinessFlowService#executeFlowCallbackAction 在终态 APPROVED/REJECTED/CANCELED 执行,缺少按任务节点和办理结果执行对象动作的配置;现有动作日志能查询失败,但不能用同一逻辑幂等键安全补偿失败动作。定时触发器只能扫描已有到期记录
BusinessTriggerSchedulerService#scanSingleTrigger 要求配置 dueField,定位是对已有记录做临期/逾期扫描。CREATE_RECORD 也没有面向“目标主表 + 目标子表集合”的结构化快照映射配置。消息接收人不能选择任意记录字段
BusinessTriggerExecutor#resolveReceivers 和 SendMessageActionStepExecutor#resolveReceivers 只支持 STARTER/OWNER/CREATOR/USERS/ROLES/DEPTS/ALL。OWNER 无法覆盖不同节点的收件人。在职员工目录还没有冻结成低代码可查询来源
sys_user/sys_user_position 描述员工、岗位和上下级关系,但当前 Forge 的真实主岗位关系是 sys_user_post.is_main,名称和数据语义不一致。没有完整低代码应用配置包导入接口
relationCode + fieldCode 标识,不使用显示名称或数据库表名作为运行协议。readable/writable/requiredOnComplete/allowAdd/allowDelete;requiredOnComplete 表示办理节点时每个有效行均必填。required 只保留给条件校验规则的效果名,二者不得混用。PUT task-form-context 只承担 DRAFT 保存,不再接受 COMPLETE,避免出现两个办理入口。DRAFT 只校验已填写值的类型、范围和字段权限,不执行办理必填及集合完整性校验。POST task-action 是唯一办理命令,在同一请求中接收业务结果、主表和子表数据,依次完成权限/任务状态复核、服务端公式计算、COMPLETE 校验、数据保存和 Flowable 任务办理。taskId + idempotencyKey + requestDigest 防重;相同键但请求摘要不同必须拒绝,任务已被其他请求完成时返回明确的冲突结果。sys_flow_task 持久化办理命令状态,阶段固定为 RESERVED -> FORM_SAVED -> FLOW_COMPLETED -> ACTION_SUCCEEDED;后置动作失败进入 COMPENSATION_PENDING。命令同时固定结果、规范化流程变量、动作编码及已发布版本,不保存完整表单或评分正文。FORM_SAVED 继续;Flowable 已成功但响应丢失时先查询任务/历史并收敛为 FLOW_COMPLETED,不得再次办理;最终响应丢失时相同键和摘要直接返回已持久化结果。ROW_COUNT、SUM、UNIQUE、ALL_REQUIRED。EQ/NE/GT/GTE/LT/LTE,值类型限制为数字、字符串、布尔值和空值。SAVE/COMPLETE;绩效模板权重合计使用 SAVE,评分完整性使用节点 COMPLETE。SAVE;流程草稿只执行基础字段校验;唯一办理命令执行 COMPLETE。低代码对象/应用发布只静态检查规则引用、公式精度和依赖,不扫描业务记录。EQ/NE/GT/GTE/LT/LTE/EMPTY/NOT_EMPTY,效果首期只执行 required。required 必须同步为服务端条件校验协议;路由、URL、客户端用户对象和任意表达式不能作为服务端必填依据。approveLabel/rejectLabel,未配置时兼容“同意/驳回”。actionCode 及受控流程变量和值;业务状态更新、申诉创建、日志和消息均由该已发布动作完成,Flow Service 不再直接提供第二个状态写入口。BusinessActionExecutionService,幂等键至少包含租户、流程实例、任务 ID、节点、结果和动作编码。FAILED 执行恢复为 RUNNING,不得换幂等键制造第二次逻辑动作。COMPENSATE,不开放“忽略失败”或任意失败脚本。@ScheduledJob 自动扫描和人工重试,两者走同一 CAS 服务;达到最大次数后进入人工处理状态,不无限重放。APPROVED/REJECTED 的状态及副作用只归节点结果动作所有;同一绑定不得再配置历史 callbackActions.APPROVED/REJECTED 或 approvedActionCode/rejectedActionCode。发布检查发现共存必须阻断;CANCELED 终态回调可继续保留。SCHEDULE 触发器新增“批量源查询”模式,与现有“到期字段扫描”模式并存。DAY/WEEK/MONTH/QUARTER/YEAR、时区和周期偏移,生成只读上下文 period.type/key/start/end/label/scheduledTime,供字段映射、消息模板和幂等键引用。QUERY_RECORDS 动作步骤:只允许选择已发布对象、白名单字段/操作符、排序、上限和是否加载已声明子表,并把结果写入命名输出变量。QUERY_RECORDS 的条件值可以引用当前源记录和循环项,但不能写 SQL/脚本;查询必须使用触发器绑定身份的数据权限,默认最多返回一页,超上限失败关闭。按岗位匹配模板时必须包含启用条件、确定性排序和 expect=EXACTLY_ONE,零条或多条均明确失败,不能静默取第一条。QUERY_RECORDS + FOREACH + CREATE_RECORD 查模板、取模板明细并创建目标主表和子表。CREATE_RECORD 支持结构化 children 映射,将模板明细复制为任务明细快照。ai_business_action_execution_log 的唯一幂等键作为并发防重权威;触发器日志只记录每次尝试,目标低代码对象必须配置并发布对应复合唯一索引作为最终数据约束。BusinessActionRetryService 对原 FAILED 动作日志执行 CAS 重试;ai_business_trigger_log 必须关联 action_log_id,不得另建一套动作重放语义。pageNum/pageSize 分页查看失败源记录、失败阶段和脱敏错误,不保存敏感业务全文。period JSON、绑定用户/组织 ID、源查询版本/摘要和动作已发布版本;失败重试只使用原运行上下文,不读取触发器当前已编辑配置。用户/组织 ID 固定,但账号有效性和当前角色/数据权限仍在每次重试时实时校验,撤权后失败关闭。SEND_MESSAGE 增加 FIELD:<fieldCode> 和多个字段接收人配置;候选字段仅限用户选择组件、用户引用字段或设计器显式标记的接收人字段。FAIL/SKIP,默认失败关闭,不回退给操作人。evaluatee_id,“我的待考评”映射 evaluator_id;HR 角色使用全部数据权限。SUM(weight) = 100,自评/上级评分节点分别控制对应子表字段。ROW_COUNT >= 1 和 UNIQUE(indicator_id),证明白名单集合规则不是 KPI 专用 SUM 实现。schemaVersion、fixtureId 和测试命名空间;测试加载器只调用受权限保护的现有管理 API 按依赖顺序创建/更新配置,不直接写平台元数据表。fixtureId + namespace 管理的资源;与非 fixture 同编码资源冲突时失败关闭。清理命令只删除/逻辑删除该命名空间创建的合成配置和数据,并在非测试环境默认拒绝运行。权限规则
DynamicDataScopeService。草稿与办理规则
FORM_SAVED,随后完成 Flowable 任务和节点动作。计算规则
BigDecimal 语义,不使用浮点数。条件校验规则
节点动作规则
APPROVED/REJECTED 回调动作互斥;发布和运行时均失败关闭,避免同一状态或副作用被执行两次。CANCELED 继续由终态回调处理。批量规则
QUERY_RECORDS 与批量源查询使用同一白名单查询服务和可信执行身份,不允许形成绕过数据权限的第二查询通道。run 固定的配置/周期/身份/源查询/动作版本快照;触发器编辑后新运行使用新配置,旧运行不得漂移。验收 fixture 规则
消息规则
| 操作 | 表名/协议 | 字段/索引 | 说明 |
|---|---|---|---|
| 扩展 | sys_flow_task |
复用 action_idempotency_key/action_request_digest/action_type,新增 action_stage, action_code, action_version, action_context_json, action_result_json, action_update_time 及阶段索引 |
持久化唯一办理命令;上下文只保存结果、规范化流程变量和固定动作引用,结果只保存可安全重放的响应摘要,不保存完整表单/评分正文 |
| 新增 | ai_business_flow_action_compensation |
流程实例、任务、节点、业务对象/记录、结果、动作编码及已发布版本、规范化流程变量、原幂等键/摘要、action_log_id、状态、重试次数、下次重试时间、错误摘要及标准审计字段;唯一键 (tenant_id, task_id, outcome, action_code) |
不保存完整表单/评分正文,不提供普通行删除;仅承载节点动作补偿状态和重试上下文,ai_business_action_execution_log 仍是动作幂等权威 |
| 新增 | ai_business_trigger_run |
id, tenant_id, trigger_id, run_type, scheduled_time, execute_status, total_count, success_count, failed_count, skipped_count, idempotent_count, correlation_id, trigger_version_digest, period_json, run_as_user_id, run_as_org_id, source_config_digest, action_code, action_version, start_time, finish_time, error_summary 及标准审计字段 |
保存一次批量运行汇总并冻结失败重试上下文;不提供普通行删除接口,留存清理由专用策略处理 |
| 扩展 | ai_business_trigger_log |
run_id, source_record_id, idempotency_key, action_log_id, attempt_no, retryable |
关联运行批次和动作日志,逐次记录初次执行和失败重试;不作为唯一幂等占位 |
| 新增索引 | ai_business_trigger_log |
(tenant_id, run_id, execute_status)、普通索引 (tenant_id, trigger_id, idempotency_key, attempt_no) |
支持运行详情和尝试追踪,不设置幂等唯一索引 |
| 扩展 JSON | ai_business_trigger.event_condition |
schedule.mode=BATCH_QUERY、Cron、周期策略/时区/偏移、源对象查询、分页、上限、runAsUserId/runAsOrgId |
与现有 DUE_FIELD 模式兼容;执行时实时验证绑定身份 |
| 扩展 JSON | ai_business_trigger.action_config |
子表快照映射、幂等表达式、失败策略 | 复用现有业务动作协议 |
| 扩展 BPMN | 用户任务扩展属性 | 子表字段权限、行操作权限、按钮文案、节点结果动作 | 由真实流程设计器维护 |
| 扩展 Schema | 低代码关系/表单规则 | 集合校验规则及校验阶段 | 不新增任意脚本字段 |
| 字典/资源 | sys_dict_type/sys_dict_data/sys_resource |
批量运行状态、运行类型、失败策略及相关权限 | Flyway 使用 NOT EXISTS,tenant_id=1 |
所有正式结构和内置数据变更必须新增 forge-server/db/migration/V<next>__*.sql,不得修改已执行脚本。迁移版本在 /apply 开始时根据仓库最新版本分配。
| 操作 | 接口 | 方法 | 变更内容 |
|---|---|---|---|
| 扩展 | /ai/business/flow/task-form-context |
GET | 返回子表字段权限、行操作权限、校验阶段和节点结果文案 |
| 扩展 | /ai/business/flow/task-form-context |
PUT | 只保存 DRAFT 主子表数据,不办理任务 |
| 扩展 | /ai/business/flow/task-action |
POST | 唯一办理入口;接收业务结果及完整主子表数据,按持久化命令阶段执行或恢复,返回稳定的办理/节点动作/补偿状态 |
| 新增 | /ai/business/flow/action-compensations |
GET | 按 pageNum/pageSize 分页查询节点动作补偿记录 |
| 新增 | /ai/business/flow/action-compensations/{id} |
GET | 查询固定动作版本、流程/业务定位、尝试历史和脱敏错误,不返回表单/评分正文 |
| 新增 | /ai/business/flow/action-compensations/{id}/retry |
POST | 使用原请求摘要和逻辑幂等键 CAS 重试失败节点动作,不重复办理 Flowable 任务 |
| 新增 | /ai/business/trigger/{id}/preview |
POST | 预览批量源查询数量和脱敏样例,不执行动作 |
| 新增 | /ai/business/trigger/{id}/run |
POST | 手工执行批量触发器,返回 runId |
| 新增 | /ai/business/trigger/runs |
GET | 分页查询批量运行汇总,参数使用 pageNum/pageSize |
| 新增 | /ai/business/trigger/runs/{runId} |
GET | 查询运行汇总;失败明细通过分页日志接口按 runId 查询 |
| 新增 | /ai/business/trigger/runs/{runId}/retry-failed |
POST | 只重试可重试失败项 |
| 兼容 | /ai/business/trigger/logs |
GET | 保留原接口,并增加 runId/executeStatus 筛选 |
| 测试工具 | `fixtures/kpi-lowcode-fixture-loader.mjs load | cleanup` | CLI |
接口继续使用现有 @ApiEncrypt/@ApiDecrypt、Sa-Token 权限和 RespInfo 协议。所有 Long ID 在前端按字符串处理。
BusinessFlowService、任务表单 DTO/VO、待办页、子表编辑器。QUERY_RECORDS、CREATE_RECORD 子表映射和幂等上下文。sys_flow_task 办理命令阶段和最小恢复上下文。⚠️ 本变更涉及状态流转、权限控制和批量数据创建,必须人工审查。
sys_user_position 与 Forge 当前 sys_user_post 不一致,未冻结权威来源前不能进入批量生成人员查询实现。test-spec.md 和 execution-log.md 骨架,进入 /apply、每个 Phase 收尾和 /test 时按 code-copilot/rules/automated-testing-standard.md 增量维护。SEND_MESSAGE 规则、现有 CREATE_RECORD/FOREACH 和动态 CRUD 普通主子表不得回退。以下问题全部解决并写回 Spec 后才能进入
/apply。
evaluatee_id/evaluator_id?推荐:接受,不扩展按角色动态切换本人字段。sys_user + sys_user_post.is_main + 当前组织关系 暴露白名单字段;C. 客户现有权威 HR 业务对象。不得默认让 QUERY_RECORDS 直查原始 sys_user 表,也不得继续使用需求文档中并不存在于 Forge 的 sys_user_position 名称。tenant + evaluatee + cycle_type + cycle_key?推荐:是。Asia/Shanghai,由触发器显式配置周期偏移,禁止从 Cron 文本猜测。score / max_score * weight,以及中间精度、最终精度和舍入方式。final_score 如何解释。| 决策 | 选择 | 理由 |
|---|---|---|
| 实现定位 | 平台通用能力补齐,不开发 KPI 专用模块 | 用户目标是完全低代码,能力应可复用于评分、巡检、验收等主子表流程 |
| 节点配置归属 | 真实流程设计器/BPMN 用户任务 | 遵循 Forge 流程配置所有权,不建设第二套节点配置 UI |
| 子表权限协议 | 稳定 relationCode + fieldCode,行操作单独授权 |
避免表名耦合,支持字段级和结构级双重安全控制 |
| 校验协议 | 白名单声明式集合规则 | 满足权重、完整性和唯一性,同时避免任意脚本安全风险 |
| 条件必填 | 前端运行规则 + 同源服务端安全子集 + 节点结果必填 | 保留低代码交互,同时保证绕过前端也不能跳过评语、申诉和调分必填 |
| 办理入口 | PUT 只存草稿,POST task-action 单一承载 COMPLETE 数据与结果 | 消除双入口、重复保存和校验后数据变化风险 |
| 办理恢复 | sys_flow_task 持久化阶段 + CAS 前进 + Flowable 历史核对 |
处理业务数据已保存、Flowable 响应丢失和最终响应丢失,不依赖客户端猜测 |
| 公式能力 | 复用现有 Formula Runtime | 当前已有存储公式和聚合刷新,不重复实现计算引擎 |
| 状态/副作用 | 节点结果绑定现有 Business Action | 复用事务、幂等、执行日志、FOREACH 和消息步骤 |
| 关联数据加载 | 新增白名单 QUERY_RECORDS 动作步骤 |
现有 FOREACH 只能消费已有集合;统一复用动态 CRUD 查询和数据权限,不允许脚本/SQL |
| 跨服务一致性 | Flowable 完成 + 幂等后置动作 + 补偿重试 | Admin 与 Flowable 不能构造虚假本地事务,采用可观察的最终一致 |
| 动作补偿 | 新增流程节点补偿台账,复用原动作日志及幂等键 | 补偿表只负责编排重试,不成为第二套动作幂等权威 |
| 回调所有权 | 节点结果动作与绑定级 APPROVED/REJECTED 回调互斥,CANCELED 可保留 | 避免状态和消息等副作用重复执行,同时兼容取消终态 |
| 定时生成 | 扩展 Business Trigger 为批量源查询模式 | 复用 Forge Job、触发器、动作和日志,不使用外部 Python/crontab |
| 周期变量 | 结构化 period 上下文 | Cron 只控制触发时间;周期键、边界、消息和幂等使用统一服务端结果,不依赖脚本格式化 |
| 批量防重 | 动作日志占位 + 尝试日志审计 + 目标复合唯一索引 | 分离并发控制、可观测性和最终数据约束,允许失败重试保留完整历史 |
| 失败动作重试 | 流程补偿与批量重试共用 BusinessActionRetryService |
对原 FAILED 日志做同一套 CAS 恢复,触发日志仅引用动作日志 |
| 批量上下文 | run 固定触发器/源查询/周期/身份/动作版本 | 历史失败重试不受当前设计态配置编辑影响 |
| 员工目录 | HARD-GATE 选择已发布对象、系统目录适配器或客户 HR 对象 | 原需求表名与当前 Forge 不一致,禁止默认为系统原表开旁路查询 |
| 数据权限 | 复用现有 FOLLOW_SYSTEM,绩效拆运行入口 | 当前能力足够,避免把业务视图差异升级成权限引擎重构 |
| 消息范围 | 复用站内消息,仅新增 FIELD 接收人 | 当前需求文档建议 OA 内部消息,外部协同通道另有独立变更 |
| 报表范围 | 明确排除 | 用户本轮要求不考虑报表部分 |
| 验收 fixture | 版本化 Schema + 测试加载/清理工具,不冒充生产导入接口 | 当前没有完整应用配置导入 API;先保证验收可重复,生产配置包能力另立变更 |
| Task | 状态 | 实际改动文件 | 备注 |
|---|---|---|---|
| Research | completed | 无生产代码改动 | 已核对流程待办、动态 CRUD、公式、动作、触发器、数据权限和消息链路 |
| Proposal | completed | spec.md、tasks.md、test-spec.md、execution-log.md |
等待待澄清项和 HARD-GATE 确认 |
| Apply | pending | - | 未授权,不得修改生产代码 |
/apply,不适用。tasks.md 前,不得进入 /apply 或修改生产代码。