臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速Obsidian、双向链接与知识图谱
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
中华财险-公司项目
对外接口文档
基础数据码表对外接口(仅支持rpc调用)业务码表对外接口应用主数据对外接口
基础运营查询版本更新日志详情消息中心对外接口站内信对外接口站内信模板配置手册
权限中心权限中心对外接口文档(最新)权限中心对外接口文档前端ACL-CORE FACADE依赖版本
审计中心审计中心对外接口文档audit-center-facade版本
审批中心工作流迁审批流现状工作流迁审批流API能力替换方案老审批流接口文档审批中心接口文档审批中心业务回调FAQ
账号中心内部系统对接单点登录内部系统对接认证中心三方网页应用登录授权账号中心对外接口文档前端账号中心RPC接口文档账户变更对外广播消息文档账户中心对外接口HRMS外部员工变更广播消息文档HRMS外部员工对外接口
组织员工岗位🔥机构映射SDK接口文档员工岗位变更通知说明组织机构管营一体概念与用法组织员工对外广播消息文档组织员工岗位对外接口文档组织员工岗位对外接口文档前端组织员工岗位数据模型组织员工主数据业务场景案例organization-facade版本
hrms(大型人力资源外包管理系统)
项目架构说明HRMS Maven模块与依赖说明
项目说明0.流程中心模块0.组织模块说明1.业务线模块2.计划模块3.项目模块4.协议模块5.供应商模块6.合约域模块7.外包人员模块*核心:外包人员生命周期
项目运维外包人员项目编制差异排查与修复2.HRMS相关问题排查4.修改externalId(externalId和accountId不一致)
需求-系分
1.内部转外包0723外包人员关联历史内部账号系分新增外包人员关联历史账号内容(紧急0723上线)需求
2.工作岗位0820工作岗位0820需求工作岗位0820需求-系分
3.用工模式调整0917内部人员转外包用工系统需求2内部人员转外包用工系统需求-系分
sso(账号中心-单点登录)
项目架构说明aboss-sso项目架构入门AOP统一日志打印链路Maven多模块项目高级知识OAuth2.0
项目说明1.SSO-OAuth2.0与IDaaS登录流程2.外部账号创建流水号并发问题分析
AI
使用说明
第三方插件&技能简介Archify使用与安装指南Ponytail使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.完成可运行聊天接口
GitGit常用命令与Obsidian推送排查
Java
面试题Java基础与集合面试题
AtomicInteger原子计数与并发安全ConcurrentHashMap并发安全与计数Java线程、线程池与Future
python
基础Python基础语法
HTTPXHRMS员工详情接口调用(Python HTTPX)

阅读建议:本流程同时涉及外包人员、项目、协议、业务线、年度计划、项目编制和审批等模块。建议先理解这些模块的概念,再阅读下面的完整流程图。这样更容易看懂人员为什么需要校验、项目编制如何变化,以及申请在审批、生效和失败重试之间如何流转。------------本文是该项目的核心。

1. 业务流程图(产品和测试版,不含数据库读写)

本图隐藏表名、字段、状态码和系统接口,仅呈现用户操作、业务校验、审批、生效、异常及重试流程,供产品和测试理解、评审及设计用例。

2. 完整流程图(含数据库读写)

图中“查”表示读取本地 HRMS 数据库,“写”包含新增、更新、删除和归档;流程中心、SSO、ACL、消息中心属于外部系统调用,不对应 HRMS 本地表。

2.1 查校验数据的用途

项目:project_detail,project_code = project_id
协议:agreement,agreement_no = agreement_id
机构:t_abs_org_basic
业务线:business_line;角色权限另查 ACL 外部系统

这些数据主要用于校验“当前人员是否有资格发起这次申请”,不是单纯为了页面展示。

查询对象查出来的用途
project_detail根据人员的 project_id 确认项目存在;取得项目额度、业务线和项目年度;入场时计算申请后剩余编制
agreement根据 agreement_id 确认人员关联的协议存在;入场申请还要求协议状态为已生效;协议编号同时写入人员申请明细作为快照
t_abs_org_basic查询人员所属机构和本次操作机构的管理路径,判断当前用户是否有权操作该机构下的人员
business_line取得项目所属业务线信息,并与当前用户可管理的业务线进行比较
ACL 外部系统判断当前账号是否属于 HR 等特殊角色;如果不是,则限制其只能操作有权限的业务线

整体校验逻辑可以理解为:

人员关联的项目是否存在?
→ 人员关联的协议是否有效?
→ 当前操作人是否有人员所属机构的权限?
→ 当前操作人是否有项目所属业务线的权限?
→ 全部通过后,人员才能进入本次入场/离场申请

其中:

  • 项目和协议属于“业务数据有效性校验”。
  • 机构、业务线和 ACL 属于“操作权限校验”。
  • project_detail.project_quota 还用于入场编制计算,并非只做存在性校验。

2.2 为什么要防止人员重复申请

查询条件实际表示:

account_id IN (本次选择的 staffId 集合)
AND application_batch_id != 本次 requestId
AND status = 0

各条件含义:

  • account_id IN staffIds:查本次选中的这些人员。这里的 account_id 实际保存的是 outsourced_staff.id,也就是 staffId
  • status = 0:该人员申请明细仍处于“待处理”,还没有实际入场或离场完成。
  • application_batch_id != 本次 requestId:排除当前正在编辑的这张申请,避免把自己误判为重复申请。

例如:

人员:张三,staffId = S001
已有申请:requestId = R001,status = 0
当前新申请:requestId = R002

系统查询到张三已经在 R001 中待处理,就不会再把他加入 R002,并提示:

已在入场申请流程中

如果当前只是在继续编辑 R001,因为查询排除了本次 requestId = R001,所以不会误报重复。

核心目的就是:避免同一个人同时存在于两张未完成的申请中,防止两条审批流程同时修改人员状态、账号和项目编制。

2.3 requestId 的用途

requestId 是 HRMS 为这一次入场或离场申请生成的业务申请单编号,用于把整张申请涉及的数据关联起来。

它的主要用途有:

  1. 作为申请主表主键
outsourced_staff_onoffboard_application.id = requestId

一张入场或离场申请对应一个 requestId

  1. 关联人员申请明细
outsourced_staff_onoffboard_application_record.application_batch_id
= requestId

通过它可以知道这张申请包含哪些人员。

  1. 关联项目编制明细
outsourced_staff_onoffboard_application_project.application_batch_id
= requestId

通过它可以知道这张申请占用或释放了多少项目编制。

  1. 关联附件
attachment.biz_id = requestId

用于查询这张申请上传的附件。

  1. 支持后续操作

前端后续查询、继续编辑、正式提交、取消申请和失败重试,都通过 requestId 定位申请单。

  1. 关联审批中心

正式提交时,将它传给流程中心:

businessNum = requestId
审批变量 request_id = requestId

这样流程中心的审批记录可以反查 HRMS 业务申请。

例如:

requestId = R001

申请主表
└── id = R001

人员明细
├── 张三,application_batch_id = R001
└── 李四,application_batch_id = R001

项目编制明细
└── application_batch_id = R001,request_num = -2

附件
└── biz_id = R001

简单理解:requestId 就是整张入场或离场申请的“订单号”,负责串联申请主表、人员明细、项目编制、附件和审批流程。

它和 flowInstanceId 不一样:

requestId      = HRMS 业务申请编号,初始化申请时生成
flowInstanceId = 流程中心审批实例编号,正式提交审批后生成

2.4 project_quota 的用途

这一步是在计算:项目总编制是多少、已经占用了多少、本次入场后还剩多少。

系统没有直接修改 project_detail.project_quota,而是使用项目申请明细表中的正负数流水计算编制。

2.4.1 查询项目总编制

project_detail.project_code = 人员的 project_id

读取:

  • project_quota:项目总编制。
  • business_line_code:所属业务线,用于权限校验和确定审批业务线。
  • 起止年度:确定本次申请对应的项目年度。

例如:

project_code = P001
project_quota = 10

表示项目 P001 总共允许配置 10 人。

2.4.2 汇总项目已有的编制变动

查询:

outsourced_staff_onoffboard_application_project
WHERE project_id = P001

然后汇总:

SUM(request_num)

request_num 使用正负数表示编制变化:

场景request_num含义
入场 1 人-1占用一个编制
入场 3 人-3占用三个编制
离场 1 人+1释放一个编制
离场 2 人+2释放两个编制

2.4.3 计算本次申请前后的剩余编制

计算方式:

本次申请前可用编制
= project_quota + 已有 request_num 汇总值

本次申请后剩余编制
= 本次申请前可用编制 + 本次 request_num

例如项目总编制为 10 人:

历史入场 6 人:request_num = -6
历史离场 1 人:request_num = +1

已有 request_num 汇总
= -6 + 1
= -5

当前可用编制:

available_num
= 10 + (-5)
= 5

现在又申请入场 2 人:

本次 request_num = -2

remain_num
= 5 + (-2)
= 3

最终写入本次项目申请明细:

project_num  = 10   项目总编制
available_num = 5   本次申请前可用编制
request_num  = -2   本次占用两个编制
remain_num   = 3    本次申请后剩余编制

核心目的就是:project_detail.project_quota 作为总额度,通过汇总项目申请明细的 request_num,计算本次入场是否会超出项目编制。

如果计算结果:

remain_num < 0

正式提交入场审批时就会报错:

本次申请后剩余编制小于 0

2.5 business_line_code 和项目起止年度的用途

可以把 business_line_code 理解成:项目属于哪个业务条线的标签

比如公司可能有:

车险业务线
非车险业务线
农险业务线
健康险业务线

某个外包项目属于“车险业务线”,那么项目表中可能保存:

project_id = P001
business_line_code = CAR

办理人员入场时,系统主要用它做两件事。

2.5.1 判断当前用户是否有权办理

例如:

项目 P001 属于车险业务线
当前用户只能管理非车险业务线

那么系统会阻止当前用户为这个项目办理人员入场。

如果当前用户拥有车险业务线权限,才能继续申请。

2.5.2 决定申请由谁审批

正式提交后,系统把项目的 business_line_code 传给流程中心。

流程中心根据业务线找到对应的审批人:

车险项目
→ 走车险业务线审批人员

农险项目
→ 走农险业务线审批人员

所以它并不负责计算编制人数,而是负责:

business_line_code
├── 判断当前用户能不能操作这个项目
└── 告诉流程中心应该由哪条业务线的人审批

一句话理解:business_line_code 就像项目的“所属部门标签”,既用来限制操作权限,也用来找到正确的审批人。

关键点是:“启用年度”和 start_year 不是两个同等优先级的选择。启用年度是首选,start_year 只是当前代码的兜底值。

2.5.3 什么是启用年度

plan_year 表保存公司已经创建的年度计划:

2025,status = 0,停用
2026,status = 1,启用
2027,status = 0,停用

status = 1 表示当前公司正在使用、正在核算编制的计划年度。

可以把它理解成:

启用年度 = 当前打开使用的年度账本

系统设计上通常只有一个启用年度。启用新年度时,会先把原来的启用年度改成停用。

2.5.4 正常情况下为什么取启用年度

假设项目有效期是:

start_year = 2025
end_year   = 2027

当前启用年度是 2026:

2025—2027 包含 2026
→ 本次人员申请归入 2026 年度
→ project_year = 2026

意思是:

虽然项目跨了三年,但这次入场申请发生在当前启用的 2026 年度,所以给申请标记 2026。

2.5.5 为什么又能取 start_year

假设年度表是:

2025,status = 0
2026,status = 0
2027,status = 0

项目范围内有年度计划,但是没有任何年度处于启用状态。

当前代码没有报错,而是兜底:

project_year = start_year = 2025

这里并不表示 2025 已经启用,也不表示 2025 一定是正确的业务年度。

它只是表示:

没有找到启用年度
→ 为了保证 project_year 有值
→ 暂时使用项目开始年度

2.5.6 三种情况对比

年度表情况最终取值原因
2026 是启用年度,并且位于项目起止范围内2026正常归入当前启用年度
项5—2027 年度都存在,但全部停用2025没有启用年度,代码用 start_year 兜底
2025—2027 一个年度计划都没有创建报错项目范围内没有任何年度计划

2.5.7 这个兜底合理吗

从代码上看,它能保证程序继续执行;但从业务语义上看,有一点不严谨:

没有启用年度
却把申请记录到已经停用的 start_year

因此它不是“启用年度和开始年度都可以随便选”,而是:

优先:项目范围内的启用年度
兜底:项目 start_year
完全没有年度计划:报错

如果业务要求“人员申请必须属于当前启用年度”,更严格的处理应该是:

项目起止范围内没有启用年度
→ 直接报错
→ 提示先启用计划年度

而不是回退到 start_year

另外,当前入离场流程计算剩余编制时只按 project_id 汇总,没有按 project_year 分年度计算。因此这里的 project_year 更像一个年度归属标签或快照字段,不会因为取了 2025 或 2026 就重新生成一套项目编制。

2.6 申请确认页为什么要查四张表

这是什么申请?
申请里有哪些人?
人员详细信息是什么?
本次申请占用多少编制?
申请后还剩多少编制?

所以需要查询多张表。

2.6.1 四张表分别负责什么

查询表回答的问题
申请主表这是一张什么申请?
人员申请明细这张申请选了哪些人?
人员表这些人的详细资料是什么?
项目申请明细这次申请占用多少编制?

1. 申请主表

outsourced_staff_onoffboard_application

根据 requestId 查询:

application_type:入场还是离场
status:草稿、审批中、成功还是失败
application_date:计划入场或离场日期
remark:申请备注
flow_instance_id:对应哪个审批流程

它回答的是:

这是一张什么申请,目前进行到哪一步?

2. 人员申请明细

outsourced_staff_onoffboard_application_record

查询条件:

application_batch_id = requestId

它保存:

这张申请包含哪些 staffId
人员申请处理成功还是失败
失败原因是什么
申请时所属的项目和协议

它回答的是:

这张申请选了哪些人?

只查询 outsourced_staff 不够,因为人员表里没有 requestId,无法判断某个人属于哪张申请。

3. 人员表

outsourced_staff

人员申请明细里主要只有 staffId,没有完整的人员资料,所以还要回查人员表,取得:

姓名
手机号
人员状态
所属机构
所属项目
所属协议
工作地点
其他人员信息

两张表的分工是:

申请人员明细
→ 确定这张申请选了哪些 staffId

人员表
→ 根据 staffId 查询这些人的完整资料

4. 项目申请明细

outsourced_staff_onoffboard_application_project

它保存这张申请生成时的编制快照:

project_num:项目总编制
available_num:本次申请前可用编制
request_num:本次申请人数
remain_num:本次申请后剩余编制
project_year:归属计划年度
business_line_code:所属业务线

它回答的是:

这次申请会占用或释放多少项目编制?

仅查询人员名单,只能知道申请了几个人,不能直接还原发起申请时的总编制和剩余编制。

2.6.2 举个例子

requestId = R001
申请类型 = 入场

申请主表告诉系统:

R001 是一张入场申请
当前状态是 init

人员申请明细告诉系统:

R001 包含 S001、S002 两个人

人员表告诉系统:

S001 = 张三
S002 = 李四

项目申请明细告诉系统:

项目总编制 = 10
申请前可用 = 5
本次申请 = 2
申请后剩余 = 3

确认页最终展示的是:

入场申请 R001
├── 人员:张三、李四
├── 本次申请:2 人
├── 项目总编制:10 人
├── 申请前可用:5 人
└── 申请后剩余:3 人

2.6.3 最简单的理解

申请主表
→ 查“什么申请”

人员申请明细
→ 查“申请选了谁”

人员表
→ 查“这些人是谁”

项目申请明细
→ 查“用了多少编制”

因此,不是所有页面都必须查这么多表。如果页面只展示人员名单,确实只需要:

人员申请明细 → 人员表

当前流程图把它们放在一起,是因为描述的是完整的申请确认页面,而不只是人员名单查询。

2.7 为什么入场是 application_date ≤ 今天,离场是 application_date < 今天

因为当前代码把两个日期理解成不同的边界:

入场日期 = 第一天
离场日期 = 最后一天

2.7.1 入场:application_date ≤ 今天

到入场日期当天,人员就应该生效。

例如:

入场日期:9 月 10 日
今天:9 月 10 日

当天就创建或返聘账号,并把人员状态修改为“已入场”。

2.7.2 离场:application_date < 今天

离场日期被当作人员的“最后有效日”。离场日期当天人员仍然有效,等这一天结束、日期过去后,才真正执行离场。

例如:

离场日期:9 月 10 日
9 月 10 日:仍是在场状态
9 月 11 日:离场日期已经小于今天,执行账号失效并改为“已离场”

因此可以简单理解为:

入场日期:从这一天开始生效
离场日期:到这一天结束为止

这不是技术上必须如此,而是当前代码采用的业务口径。如果产品定义的“离场日期”表示“当天一到就离场”,判断条件就应该改成 application_date ≤ 今天;但按照当前实现,它表示最后工作日或最后有效日。

2.8 为什么入场任职快照的 event_type = AGREEMENT

这里的 AGREEMENT 不是说“本次入场发生了协议变更”,而是表示:这条任职快照记录的是人员使用某份协议的任职区间。

人员入场后,系统会形成一段关系:

某个人
在某个项目下
使用某份协议
从入场日期开始任职

因此,入场时会向 outsourced_staff_movement 写入:

event_type = AGREEMENT
agreement_id = 当前协议编号
agreement_start_date = 入场日期
agreement_end_date = 离场日期,入场时通常为 9999-12-31

例如:

人员:张三
协议:A001
入场日期:2026-09-10

写入的任职快照相当于:

张三使用协议 A001
任职开始:2026-09-10
任职结束:9999-12-31
event_type:AGREEMENT

以后张三离场时,系统会按照以下条件找到这条协议任职记录:

account_id
+ agreement_id
+ event_type = AGREEMENT

找到记录后,再把 agreement_end_date 修改为实际离场日期。

这类记录还有一个重要用途:后续按协议结算或查询人员时,可以根据以下信息判断某个人在结算周期内是否使用过这份协议:

agreement_id
+ event_type = AGREEMENT
+ agreement_start_date / agreement_end_date

所以它的实际含义更接近:

AGREEMENT = 协议任职区间记录

而不是:

AGREEMENT = 本次发生了协议变更

流程图中的这一步可以理解为:

写任职快照表:outsourced_staff_movement
 
新增人员本次协议任职区间:
event_type = AGREEMENT
agreement_id = 当前协议
agreement_start_date = 入场日期
agreement_end_date = 默认离场日期
 
用途:
后续按协议查询任职历史
离场时找到并结束当前协议任职区间

需要注意:实体注释虽然定义了 DEFAULT / AGREEMENT / PROJECT,但当前代码中多个场景都写成了 AGREEMENT,包括部分项目续延和终止处理。因此,当前实现中的 AGREEMENT 已经带有“协议维度任职记录”的泛化含义,并不完全是严格的事件来源。

2.9 为什么部分人员离场失败仍释放整批编制

核心原因是:当前代码按“整张离场申请”释放编制,而不是按“实际离场成功人数”释放编制。

执行过程是:

逐个人员办理离场
├── 成功:人员明细 status = 1
└── 失败:人员明细 status = 2
 
全部人员处理结束
→ 不判断是否全部成功
→ 仍然调用项目编制处理
→ 按申请人员明细总数写入 request_num

代码统计的是申请内全部人员:

按 project_id 对所有申请人员明细计数

离场时:

request_num = +申请人员总数

它没有先过滤:

人员明细.status = 1

例如,一张离场申请有 3 人:

张三:离场成功
李四:离场成功
王五:SSO 账号失效失败

当前代码仍然写入:

request_num = +3

而不是:

request_num = +2

可能的设计意图是:

  • 离场申请已经审批通过并到达生效日,业务上认为整批人员都应退出项目;
  • 单个人员失败通常被当成 SSO、消息或数据处理异常,不改变其应该离场的业务决定;
  • 编制只在首次离场执行时统一释放一次;
  • 后续失败重试不会再次增加 request_num,避免重复释放编制。

因此,目前的口径是:

编制释放依据 = 已生效的离场申请人数
不是 = 技术处理成功人数

但这里存在一个风险:

人员实际离场失败
但项目编制已经释放

如果失败人员一直没有重试成功,就会出现:

人员仍未完成离场
项目却已经可以使用这个空出的编制

所以,这只能说明“当前代码为什么这么执行”,不能证明业务规则一定合理。如果实际要求是“人员真正离场成功后才能释放编制”,当前实现需要调整为:

首次处理:只释放 status = 1 的成功人数
失败重试成功:再释放本次新增成功人数
同时增加防重复释放控制

流程图中的这一步可以理解为:

当前实现:按整张离场申请释放项目编制
 
查 project_detail
查 outsourced_staff_onoffboard_application_project
并按 project_id 汇总 request_num
 
写项目申请明细:
request_num = +申请人员总数
 
不按人员处理结果过滤
部分人员失败时仍释放整批编制
失败重试不再重复释放
 
注意:
如果业务要求实际离场成功后才释放编制,
当前实现存在提前释放风险,需要进一步确认