臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于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. 它自己负责什么

人员模块保存人员档案及其任职变化,主要负责:

  • 建立、修改、查询和批量导入外包人员档案;新建档案默认进入待入场状态;
  • 记录人员关联的项目、协议、组织、岗位等业务信息;
  • 发起入场、离场申请,接收审批结果后更新人员状态;
  • 入场审批通过且到达入场日期后请求账号中心创建外部账号,并在人员变更、返聘和离场时同步账号;
  • 保留人员在项目和协议之间的任职变动记录;
  • 按项目计算已入场人数、申请人数和剩余名额,供其他模块展示或校验;
  • 对外提供按账号、组织或人员编号查询人员及任职记录的接口。

这里的“人员档案”是当前信息;“任职变动记录”是历史信息。例如李四从项目 A 调到项目 B,档案展示当前归属 B,历史记录保留曾在 A 任职的事实。

外包人员模块协作与数量口径图

先看模块间实际调用、回调与消息通知:查看模块协作时序图

项目编制、待审批人数、已入场人数及离场释放的加减规则见:查看人员数量与项目编制增减图

入/离场申请的内部处理链路见:查看申请、生效与重试流程图

2. 与各模块的关系

先看总览。这里不再用一张总图,避免把“主动查询”和“对方主动通知”误画成同一种箭头。

关联模块人员模块如何协作人员模块实际取得或输出什么
项目模块人员模块主动查询;项目到期或终止时也会发消息通知人员模块。查询项目、业务线和编制;处理项目终止后受影响的人员。
协议模块人员模块主动查询协议信息;协议终止或编号变更时会发消息。补齐协议名称、期限和协议乙方(供应商)信息;处理受影响人员。
组织与权限人员模块主动查询或调用权限能力。机构名称、可查询的组织范围、业务线数据范围。
账号中心人员模块主动发起创建、更新、返聘、失效和详情查询。创建时返回账号 ID;查询时返回账号详情。
审批流程中心人员模块主动发起入/离场审批;审批中心回传结果。审批通过或驳回结果。
计划/编制模块计划模块主动向人员模块查询。人员模块按项目返回已入场人数。
其他系统其他系统主动向人员模块查询。人员详情和任职历史。

供应商信息通过协议模块进入人员模块,因此未写成“供应商直接向人员模块提供数据”。项目终止和协议终止对人员状态的影响,在后续对应小图中说明。

2.1 项目模块:项目是人员实际工作的落点

人员模块在办理入场或离场时,主动向项目模块查询项目编号对应的业务线、项目编制和计划年度。同一批申请人员必须归属同一个项目;查不到项目则不能继续办理。入场申请会把本次人数记为编制占用,并计算“项目编制 + 已申请占用 + 本次申请”后的剩余数;提交审批前剩余数小于零就不能提交。

项目详情反过来会主动查询人员模块:按项目编号取得已入场人数和申请中人数,其中已入场人数用于展示项目实际入场人数。也就是说,不是人员模块定时把人数推给项目模块,而是项目模块需要展示时来查询。

项目到期或终止时,项目模块会发送项目变更消息。人员模块监听到终止消息后,查询该项目下已入场人员,依次更新为离场、写入任职历史,并请求账号中心使其外部账号失效。

代码如何实现

对应的关键代码位置:

  • 人员申请按项目校验和查询项目编制:OutsourcedStaffApplicationHandler
  • 项目详情查询人员统计人数:ProjectServiceImpl 调用 queryStaffApplyProject
  • 人员统计已入场/申请中人数:OutsourcedStaffQueryHandler
  • 项目终止消息消费与人员离场处理:StaffProjectCodeChangeListenerOutsourcedStaffProjectChangeRepositoryImpl

UAT 真实数据示例

以下是 2026-08-26 的 UAT 查询快照,查询使用了 project_detail(项目)、outsourced_staff(人员档案)两张表;未读取姓名、账号、联系方式或证件信息。

  • 项目编号:XM202600000153;项目名称:四川分公司线上渠道服务外包;已审批版本的项目额度:442 人;项目年度:2024 至 2028。
  • 当前人员档案中,关联该项目的记录有 442 名状态为 0(已入场)、7 名状态为 -1(未入场)、16 名状态为 1(已离场)。

通俗地说,这个项目相当于有 442 个已审批的在场名额;UAT 快照中恰好查到 442 名当前已入场人员。若再为该项目新增人员入场,系统会在申请阶段重新按项目编制和已有申请占用计算剩余数;剩余数不足时就不允许提交。

这条快照能直接证明项目额度和当前人员状态确实有关联;它不能单独证明某一时刻的“申请中人数”或每一次入场审批时的剩余名额,因为这两项由入离场申请记录计算,本次 UAT 未查询到对应表。

**关系要点:**项目管理“能招多少人、归哪个业务线”,人员模块管理“名额最终被谁占用”。项目并不保存每个人的完整档案。

2.2 计划/编制模块:用人员在场结果衡量计划执行

计划模块会按项目批量向人员模块查询已入场人数,再按业务线和运营组织汇总,展示计划对应的实际入场人数。

因此,计划模块不是直接逐个管理人员;它使用人员模块提供的统计结果回答“计划编制是否已经落地为真实在场人员”。

代码如何实现

计划模块先查询某个计划年度下的项目清单,并按“业务线 + 运营组织”把这些项目分组;随后把所有项目编号一次性传给人员模块的批量查询方法。人员模块逐个项目统计已入场人数和申请中人数,返回项目维度的统计结果。计划模块最后只取每个项目的已入场人数,按原来的“业务线 + 运营组织”分组汇总并展示。

也就是说,计划模块并不保存人员名单,也不会自己根据档案逐条计算人数;它保存计划和项目的对应关系,人员模块保存入离场申请和人员状态,二者在查询时组合出“计划实际入场人数”。

对应的关键代码位置:

  • 计划模块批量调用人员统计:PlanDetailServiceImpl 调用 outsourcedStaffService.queryStaffApplyProjects(projectIds)
  • 人员模块按项目逐个返回统计:OutsourcedStaffQueryHandler.queryStaffApplyProjects
  • 单个项目的已入场/申请中人数来源:OutsourcedStaffQueryHandler.queryStaffApplyProject 查询入离场申请项目记录。

UAT 真实数据示例

以下是 2026-08-26 的 UAT 查询快照,查询使用了 plan_detail(计划明细)、project_detail(项目)和 outsourced_staff(人员档案)三张表;未读取个人信息。

  • 2026 年计划明细中,业务线 TX_XK、运营组织 215100000000 的记录显示:分配额度为 200、使用额度为 500。
  • 上述“四川分公司线上渠道服务外包”项目也属于同一业务线和运营组织;其已审批项目额度为 442,当前人员档案中有 442 名已入场人员。

通俗地说:计划明细先规定某个“业务线 + 运营组织”这一组可以使用多少编制;项目落在这个组里;人员再实际入场到项目中。页面需要看计划执行情况时,会先找到该组下的项目,再向人员模块查询各项目的人数并汇总,而不是直接拿计划额度当作已入场人数。

这条快照能直接证明计划、项目和人员能够通过相同的业务线与运营组织对应起来;它不能单独证明计划页面最终展示的“实际入场人数”必然是 442,因为代码中的该统计来自入离场申请项目记录,本次 UAT 未查询到对应表。计划额度字段的业务口径也应以计划模块说明为准,不能仅凭字段名推断为可用或已使用人数。

2.3 协议模块:协议是人员用工和结算的依据

人员档案记录协议编号及协议使用期间。人员详情查询时,系统会用协议模块的协议信息补齐协议名称、协议归属机构和供应商名称;按协议查询人员时,也会以人员的协议使用期间筛选。

协议发生终止或编号变更时,人员模块会接收相关消息并调整受影响的人员关系与历史记录。这里要区分两件事:

  • 协议模块管理协议本身,例如协议期限和签约主体;
  • 人员模块管理某个人在某段时间内实际使用该协议的事实。

代码如何实现

人员详情查询时,人员模块先从人员档案取出协议编号,再批量调用协议服务查询协议详情;随后把协议名称、协议归属机构和协议乙方名称补到人员详情中。这是人员模块主动查询协议,不是协议模块主动把资料推给人员模块。

协议相关业务需要找人员时,则由协议编号和结算期间查询人员模块保存的任职变动记录。只有“人员实际使用该协议的期间”与结算期间重叠的记录才会保留,再按项目和协议期间去重。因此,协议模块使用的是人员模块维护的实际任职事实,而不是仅看当前人员档案。

协议续延或终止时,协议模块发送协议变更消息。人员模块监听后:续延会把仍在场人员的协议编号换成新协议编号,并写入任职变动;终止会把该协议下仍在场的人员改为离场、写入任职历史,并请求账号中心使外部账号失效。

对应的关键代码位置:

  • 人员详情批量查询并补齐协议信息:OutsourcedStaffRPCServiceImpl
  • 按协议和期间筛选人员任职记录:OutsourcedStaffAgreementRepositoryImpl.queryBatchStaffDetailByAgreement
  • 协议变更消息消费:StaffAgreementNoChangeListener
  • 协议续延、终止后的人员和账号处理:OutsourcedStaffAgreementChangeRepositoryImpl

UAT 真实数据示例

以下是 2026-08-26 的 UAT 查询快照,查询使用了 agreement(协议)、outsourced_staff_movement(人员任职变动)和 project_detail(项目)三张表;未读取人员姓名、联系方式、证件号或账号。

  • 协议编号:H260709000000000000000121;协议名称:客服+理赔协议测试;状态:APPROVED;协议期限:2026-07-09 至 2026-09-30。
  • 项目编号:XM202600000217;项目名称:理赔外包测试;项目额度:10 人。
  • 查到 1 条任职变动记录:状态 0(已入场),变动类型 AGREEMENT,入场日期为 2026-07-09;该记录同时关联上述协议和项目。

通俗地说:系统里已经有一名不展示身份信息的外包人员,在 2026-07-09 入场后,被安排到“理赔外包测试”项目,并使用“客服+理赔协议测试”这份协议。假设现在结算 2026 年 8 月 的服务费用:8 月处于该协议的有效期内,且这名人员的任职记录从 7 月 9 日起已生效,因此按协议和期间查询人员时,这条记录会落入该协议的人员范围,可继续按项目归集到“理赔外包测试”。

这条数据能直接证明“协议—人员任职记录—项目”三者存在实际关联;它不能单独证明某次结算已经生成、金额是多少,或该项目实际已使用了多少个名额。这些需要结合结算记录和完整申请数据另行确认。

2.4 供应商模块:供应商不是人员档案的直接控制方

从现有代码可确认,人员详情中的供应商名称来自人员关联协议的乙方信息。也就是说,人员与供应商的业务联系通过协议建立:供应商信息先进入协议,再由协议供人员展示和使用。

因此不能简单理解为“一个供应商直接拥有一批人员”。人员是否能在场,仍由其项目归属、协议使用情况和入离场审批共同决定。

2.5 审批流程中心:决定申请能否生效

人员模块按项目所属业务线发起“外包人员入场申请”或“外包人员离场申请”。审批流程中心只负责流程处理并回传通过或驳回结果;人员模块才据此更新人员状态、保存任职变动,并同步项目申请记录。

日常说,审批中心负责“批不批准”,人员模块负责“批准后这批人到底入场还是离场”。

2.6 组织、权限与账号中心:分别决定“能看谁”和“能否登录”

组织信息用于补齐人员所属机构、运营机构和工作地点等名称,并支持按机构向下查询人员。人员查询还会按照运营机构范围和业务线范围过滤;入离场申请也携带可操作的组织范围。具体的组织与角色授权由权限体系维护,人员模块只使用其结果。

账号中心负责外包人员的外部账号(可理解为工号/登录账号)。新人员在入场审批通过且到达申请入场日期后,人员模块带着人员姓名、手机、机构、照片等信息请求账号中心创建外部账号,并保存返回的账号 ID;如果入场日期还未到,人员先处于“审批通过待入场”状态,账号会在后续定时处理时创建。已离场人员返聘时,人员模块请求恢复账号;在场人员资料变更时,会同步更新账号资料;正常离场,以及项目或协议终止导致的离场时,会请求账号中心将账号失效。

代码中确实存在“账号中心账号变更消息”的监听配置,但实际处理逻辑仍标注为 TODO,且无人员状态更新。因此,目前只能确认人员模块会接收这类消息,不能确认账号中心已能反向自动触发人员被动离场。账号中心也不管理人员的项目归属、协议归属或项目编制。

2.7 其他系统:把人员模块作为人员与任职信息的查询来源

人员模块发布对外查询能力,可按账号、人员编号、组织范围或条件分页查询人员详情,也可查询任职变动历史。返回内容会补齐项目、协议和组织的可读名称,供调用方使用。

这说明人员模块是“外包人员当前归属和历史任职”的查询出口;调用方通常读取结果,不应绕过它自行拼接人员状态。

3. 外包人员入离场流程:按阶段和代码顺序

创建外包人员不是“直接入场”。系统先保存人员档案,并将人员状态固定为 待入场(状态码 -1)。此时人员已经具备项目、协议和组织等归属信息,但还没有占用已入场人数、没有外部账号,也不会生成任职变动记录;只有后续入场申请真正生效后,才会形成在场事实。

3.1 待入场时需要做什么

这一阶段对应创建人员档案,代码按以下顺序执行:

会议上可以把这张图概括成一句话:先完成资料和业务归属校验,再以待入场状态建立人员档案;此时只是“建档”,还没有真正入场。

主调用链是:OutsourcedStaffController.createStaffOutsourcedStaffWebImpl.createStaffOutsourcedStaffServiceImpl.createStaffOutsourcedStaffOperationService.createStaffOutsourcedStaffBaseRepositoryImpl.createStaff。其中 Controller 和接口层主要负责接收、转发请求,真正的处理按方法分布如下。

  1. OutsourcedStaffWebImpl.createStaff:执行入口校验并调用领域服务

    1. 调用 OutsourcedStaffWebValidator.validateCreateStaff,校验创建请求和当前登录信息。
    2. 未关联历史内部账号时,校验照片不能为空。
    3. 校验通过后,调用 OutsourcedStaffService.createStaff
  2. OutsourcedStaffServiceImpl.createStaff:转发到人员基础操作服务

    1. 该方法本身不执行业务校验。
    2. 将请求转给 OutsourcedStaffOperationService.createStaff
  3. OutsourcedStaffOperationService.createStaff:校验和补齐人员基础资料

    1. 校验证件号码与年龄是否匹配。
    2. 校验学历、政治面貌、业务分类、民族等码值是否合法。
    3. 校验归属部门,并补齐经营组织等组织信息。
    4. 校验工作岗位。
    5. 工作地点为空时,默认使用归属组织编码。
    6. 自动授权标识为空时,默认设为“否”(0)。
    7. 根据工作岗位补齐标准岗位编码。
    8. 确认关联历史内部账号时,校验账号与证件是否一致、账号是否已被占用;未上传照片时,从内部账号补充照片。
    9. 基础资料处理完成后,调用 OutsourcedStaffBaseRepository.createStaff
  4. OutsourcedStaffBaseRepositoryImpl.createStaff:校验业务归属并保存人员档案

    1. 将创建请求转换为人员实体,并在实体中将人员状态设为 待入场-1)。
    2. 查询已审批协议;协议不存在时终止创建,存在时将协议乙方回填为供应商。
    3. 查询项目;项目不存在时终止创建,存在时回填项目编号和业务线。
    4. 调用 OutsourcedStaffMapper.insert 保存人员档案。
    5. 返回新建人员 ID。

保存成功后只代表人员档案已建立:此时不创建外部账号、不写任职变动、不计入已入场人数,也不能办理离场。人员资料可以继续修改;没有未完成入场申请时可以删除。

3.2 入场前需要做什么(进入审批前)

这一阶段由两个接口分段完成:onboard-request 负责筛选人员和初始化申请数据,onboard-request-apply 负责补充申请信息并正式提交审批。

相关表怎么关联

入场申请以 outsourced_staff_onoffboard_application 为主表,其余申请数据都通过同一个 requestId 挂在主表下面。代码按字段建立业务关联;当前代码未体现数据库层面的外键约束。

作用关联关系
outsourced_staff_onoffboard_application入离场申请主单id = requestId,保存申请类型、计划入场日期、备注、流程实例 ID 和申请状态
outsourced_staff_onoffboard_application_project本次申请的项目编制快照application_batch_id → 申请主表 idproject_idproject_detail.project_code
outsourced_staff_onoffboard_application_record本次申请包含的人员明细application_batch_id → 申请主表 idaccount_idoutsourced_staff.idproject_idproject_detail.project_codeagreement_idagreement.agreement_no
attachment提交审批时上传的附件biz_id → 申请主表 id,并以 type = outsourced_staff 区分业务类型
outsourced_staff人员当前档案id 被申请人员明细的 account_id 引用;自身的 project_idagreement_id 分别记录当前项目编号、协议编号

容易误解的一点:申请人员明细字段名虽然叫 account_id,实际保存的是 outsourced_staff.id,不是人员档案中的新一代账号字段 outsourced_staff.account_id

可以把它理解成一张申请主单下面挂两组明细:一组回答“这次涉及哪些项目、占多少编制”,另一组回答“这次具体有哪些人”;附件也通过同一个申请主单 ID 归档。主表与项目编制明细、人员申请明细、附件都是一对多关系;不过当前入场接口要求同一批人员属于同一项目,所以一张入场申请通常只有一条项目编制明细。人员明细与项目编制明细之间不直接互相引用,而是共同使用 application_batch_id 归属于同一张申请主单,并通过 project_id 指向同一个项目编号。

实际保存顺序

第一次调用 onboard-request 初始化申请时:

  1. 先保存申请主表 outsourced_staff_onoffboard_application,生成一张状态为 INIT 的入场申请主单。
  2. 再保存项目编制明细表 outsourced_staff_onoffboard_application_project,记录项目总编制、本次占用数和申请后剩余编制。
  3. 最后批量保存人员申请明细表 outsourced_staff_onoffboard_application_record,把每个人、项目和协议挂到该申请主单下,明细状态为待处理。
  4. 此时不修改 outsourced_staff 的人员状态,也不保存附件;人员仍是待入场或已离场返聘状态。

携带同一 requestId 再次调用 onboard-request 调整人员时:

  1. 复用原来的 INIT 申请主单,不新增主表记录。
  2. 先删除该申请原有的人员申请明细。
  3. 再删除该申请原有的项目编制明细。
  4. 先重新保存项目编制明细,再重新保存人员申请明细。

代码对新旧 requestId 统一执行明细清理逻辑;第一次新建时没有旧明细,因此删除操作实际影响 0 行。采用“清旧后全量重建”是为了让项目编制和本次最终有效人员保持一致。

调用 onboard-request-apply 正式提交审批时:

  1. 先读取并校验上述主单、人员明细和项目编制明细;计划入场日期和备注先写入主单实体内存,此时尚未更新数据库主表。
  2. 有附件时,先保存 attachment,使用申请主单 ID 作为 biz_id
  3. 再调用审批中心创建流程。
  4. 流程创建成功后,先将 outsourced_staff.staff_status 更新为入场审批中(-3)。
  5. 最后更新申请主表:保存计划入场日期、备注、流程请求 ID、流程实例 ID,并将主单状态改为 APPROVING

项目编制明细和人员申请明细在正式提交时不会再次重建;它们沿用初始化阶段的数据,只做提交前复核。

会议上可以把这张图概括成一句话:第一个接口先筛人并生成初始化申请数据,第二个接口再做提交前校验并发起审批;此时人员只是进入审批中,还没有真正入场。

两个接口都会经过 OutsourcedStaffWebImplOutsourcedStaffServiceImplOutsourcedStaffOnOffboardServiceOutsourcedStaffOnOffboardRepositoryImpl。这几层主要负责入口校验和请求转发,入场申请的核心处理集中在 OutsourcedStaffApplicationHandler 中。

  1. OutsourcedStaffWebImpl.onboardRequest:校验并发起初始化请求

    1. 调用 OutsourcedStaffWebValidator.validateOnboardRequest,校验请求参数和当前登录信息。
    2. 校验本次人员 ID 数量在 1—100 之间。
    3. 校验通过后,逐层转发给 OutsourcedStaffApplicationHandler.onboardRequest
  2. OutsourcedStaffApplicationHandler.onboardRequest:筛选人员并初始化申请数据

    1. 根据人员 ID 查询人员;未查到人员时终止初始化。

    2. 校验同一批人员必须属于同一项目。

    3. 新生成或复用 requestId,初始化错误人员列表,然后调用 validateAndClassifyStaff:先批量查询项目、已审批协议、业务线权限和组织管理路径,再逐人校验状态、业务归属和权限,并将失败人员记入错误列表。

      展开查看这一步的 validateAndClassifyStaff 具体做了什么

      这个方法本身不创建申请单,也不保存人员;它会先批量准备校验资料,再逐人校验,并把不符合入场条件的人员收集到 errorStaffList

      三个入参

      • permissionOrgCode:当前登录账号的默认 ACL 权限机构编码,表示本次操作的机构权限范围起点。
      • allStaffList:本次选中的全部外包人员。
      • errorStaffList:用于收集校验失败人员和失败原因的结果列表。

      先批量准备四份校验资料

      1. projectInfoMap:按人员的项目编号批量查询项目和编制信息,后续用于判断项目是否存在以及项目属于哪条业务线。
      2. agreementMap:批量查询人员关联的协议;此处传入 true,所以只保留已审批协议。
      3. businessLinePermissions:根据当前登录账号查询可操作的业务线。这里传入 null 表示不额外指定某条业务线,不是表示没有权限。
      4. orgCodeMap:收集所有人员的 operatingOrgCode 和当前账号的 permissionOrgCode,批量查询后整理成“机构编码 → 组织管理路径”的映射。

      validateStaff 逐人校验的内容

      1. 当前账号是否具有业务线权限。
      2. 人员状态是否允许发起入场:待入场(-1)和已离场(1,返聘)可以继续;入场、已入场或离场流程中的人员不允许继续。
      3. 人员关联的项目是否存在。
      4. 人员关联的已审批协议是否存在。
      5. 人员项目所属业务线是否在当前账号权限内。
      6. 人员的 operatingOrgCode 是否位于 permissionOrgCode 的组织管理路径下;不在范围内时返回“无此机构权限”。

      例如,permissionOrgCode 对应“江苏分公司”,人员的 operatingOrgCode 对应“南京中心支公司”,而南京中心支公司在江苏分公司管理路径下,则机构校验通过。

      校验失败后怎么处理

      某个人校验失败时,方法不会立即终止整批处理,而是将其账号 ID、姓名、手机号和失败原因加入 errorStaffList。已离场人员返聘时记录历史手机号,其他人员记录当前手机号。方法返回后,后续的 filterValidStaff 才会从全部人员中筛出没有错误且状态为待入场或已离场的人员。

    4. 调用 filterValidStaff:只保留状态为待入场(-1)或已离场(1,返聘),并且未记入错误列表的人员。

    5. 如果过滤后没有有效人员,直接返回空的有效人员列表和错误原因,不创建入场申请单。

    6. 有有效人员时,调用 createApplication,使用第 3 步的 requestId 新建或复用状态为初始化的入场申请单。

      展开查看这一步的 createApplication 具体做了什么

      这个方法只负责取得一张可继续编辑的申请主单:调用方没有传入 requestId 时新建申请单,传入了 requestId 时校验并复用指定申请单。它不会在这里保存申请人员、项目编制、计划入场日期或附件,也不会发起审批。

      三个入参

      • requestId:本次请求原本传入的申请单 ID,用于判断是新建还是复用。
      • requestDTO:初始化接口的返回对象,里面已经保存了第 3 步生成或复用的 requestId
      • applicationType:申请类型;当前入场场景传入 ONBOARD,同一个方法也供离场申请传入 OFFBOARD 使用。

      实际处理顺序

      1. 先创建一个尚未保存的申请单实体,并从 requestDTO 中取出 requestId 设置为申请单 ID。
      2. 如果请求原本传入了 requestId,说明本次要继续编辑已有申请单:根据该 ID 查询申请主单。
      3. 已有申请单不存在,或者状态不是初始化(INIT)时,终止处理并提示“申请单不存在或者状态异常”。审批中或已经结束的申请单不能通过这里重新初始化。
      4. 已有申请单存在且状态为 INIT 时,直接返回这张申请单,不再新增申请主单。
      5. 如果请求没有传入 requestId,则使用第 3 步新生成的 ID,设置申请类型为入场(ONBOARD)、状态为初始化(INIT),保存一张新的申请主单并返回。

      这里的“复用”不是系统自动寻找任意一张初始化申请单,而是只复用本次请求明确指定的 requestId。代码当前在复用分支中只校验申请单是否存在以及状态是否为 INIT,没有再次校验已有申请单的 applicationType 是否为入场。

      简单理解:createApplication 只先建立或找回一个“申请单外壳”;下一步 processStaffOnboardRecordsOptimized 才会把人员明细和项目编制明细装进去。

      办理场景示例(requestId 为示意值)

      1. 第一次选择人员办理入场:业务人员选择 3 名待入场人员,第一次调用 onboard-request 时没有传 requestId。系统先生成 R001createApplication 再保存一张 ID 为 R001、类型为入场(ONBOARD)、状态为初始化(INIT)的申请主单。随后下一步把这 3 人的人员明细和项目编制明细保存到 R001 下,并将 R001 返回给前端。
      2. 提交审批前重新调整人员:本次请求带回 requestId = R001createApplication 查询到 R001 仍是 INIT 状态后,直接返回原申请主单,不再新建 R002。下一步会删除 R001 原有的人员明细和项目编制明细,再按照本次有效人员重新生成,因此整个过程仍属于同一张申请草稿。
      3. 申请已经提交审批:如果 R001 已不再是 INIT 状态,再使用它调用初始化接口时,createApplication 会提示“申请单不存在或者状态异常”,不允许重新覆盖审批中的申请数据。

      从后端代码可以确认它支持“使用同一 requestId 重新初始化申请明细”;具体由前端哪个页面操作触发重新初始化,还需要结合前端代码确认。

    7. 调用 processStaffOnboardRecordsOptimized:再次排除项目无效或已被其他入场申请占用的人员,然后生成并保存申请人员明细和项目编制明细。

      展开查看这一步的 processStaffOnboardRecordsOptimized 具体做了什么

      这个方法负责把前面初步筛选通过的人员再检查一遍,并为当前 requestId 生成两类初始化数据:申请人员明细项目编制明细。它不会在这里发起审批,也不会修改人员状态。

      五个入参

      • validStaffList:经过 validateAndClassifyStafffilterValidStaff 初步筛选后剩下的人员。
      • staffMap:人员 ID 与人员档案的映射,人员被其他申请占用时,用于回填姓名、手机号等错误信息。
      • applicationEntity:第 6 步新建或复用的初始化申请单。
      • errorStaffList:继续收集这一阶段发现的无效人员及原因。
      • onboardRequestDTO:接收最终有效人员列表,并提供当前 requestId

      实际处理顺序

      1. 如果没有有效人员,直接结束,不生成任何明细。
      2. 根据人员的项目编号批量查询项目和编制;查询不到项目的人员会从 validStaffList 中移除,并记入错误列表,原因是“员工所属项目不存在”。
      3. 查询这些人员是否已经存在于其他申请单的待处理人员明细中。查询时排除本次 requestId;已被其他申请占用的人员会记入错误列表,原因是“已在入场申请流程中”,并从本次有效人员中排除。
      4. 将最终剩下的人员写入接口返回值中的有效人员列表。
      5. 为每个人组装一条申请人员明细,记录当前申请单 ID、人员 ID、项目 ID、协议 ID,并将明细状态设为待处理(PENDING)。
      6. 如果本次是在重新初始化已有的 requestId,先删除该申请单原有的人员明细,避免旧数据和新选择的人员重复。
      7. 调用 processProjectInfo 重建项目编制明细:先删除当前申请单原有的项目编制明细,再按项目统计本次申请人数,查询项目总编制、其他申请已经占用或释放的编制,以及项目计划年度。
      8. 批量保存新的项目编制明细和申请人员明细。

      项目编制明细中的数字怎么计算

      • projectNum:项目当前总编制。
      • availableNum:项目总编制加上其他申请累计产生的编制变动,即本次申请开始前的可用编制。
      • requestNum:本次入场人数的负数;入场会占用编制,例如本次入场 2 人时记录为 -2
      • remainNumavailableNum + requestNum,表示本次申请完成后的剩余编制。
      • projectYear:项目所属计划年度。

      例如,项目总编制为 10,该项目已有申请编制明细累计记录的编制变动为 -3,本次申请入场 2 人,那么 availableNum = 10 - 3 = 7requestNum = -2remainNum = 5。这里的已有编制明细在本方法中没有再按申请单状态过滤。

      这里即使算出 remainNum < 0,也不会在初始化阶段终止;真正的编制不足校验发生在后续 onboardRequestApply 提交审批时。

      方法名中的 Optimized 主要表示它采用批量查询、批量组装和批量保存,业务作用仍然是“再次筛人并生成申请明细”。

    8. 返回 requestId、最终有效人员列表和被排除人员及其原因。

到这里,onboard-request 接口执行结束:人员筛选和申请初始化数据已经完成。下面开始补充申请信息,并通过 onboard-request-apply 正式提交审批。

  1. 业务人员补充申请信息

    1. 根据初始化接口返回的 requestId,填写计划入场日期和备注。
    2. 根据需要上传附件。
    3. 调用 onboard-request-apply 提交入场申请。
  2. OutsourcedStaffWebImpl.onboardRequestApply:校验并发起提交请求

    1. 调用 OutsourcedStaffWebValidator.validateOnboardRequestApply,校验 requestId、计划入场日期、机构权限编码和当前登录信息。
    2. 校验通过后,逐层转发给 OutsourcedStaffApplicationHandler.onboardRequestApply
  3. OutsourcedStaffApplicationHandler.onboardRequestApply:重新校验并提交审批

    1. 读取入场申请单,校验申请类型和初始化状态,并写入计划入场日期和备注。
    2. 读取申请人员明细和项目编制明细。
    3. 校验本次申请后的剩余编制不能小于 0。
    4. 重新查询人员档案,校验照片是否存在,以及人员是否已进入其他入离场状态。
    5. 保存附件。
    6. 使用 permissionOrgCode 查询机构层级,再结合项目业务线组装审批变量,向审批中心发起“外包人员入场申请”。
    7. 审批流程创建成功后,保存流程实例 ID,将申请单改为审批中,并将人员状态改为“入场审批中”(-3)。

如果申请被驳回、取消或放弃,人员会恢复为待入场,可以补充资料后重新申请。

3.3 入场审批通过后需要做什么

这一阶段由审批结果消息触发:先判断计划入场日期是否已经到达;日期已到时立即办理正式入场,日期未到时先等待定时任务。

会议上可以把这张图概括成一句话:审批通过不一定立即入场;只有计划入场日期已经到达,系统才会创建或恢复账号,并把人员状态、任职记录和下游消息处理成正式入场结果。

  1. StaffOnOffboardFlowListener.staffOffboard:接收入场审批结果

    1. 监听审批中心发送的入离场流程变更消息。
    2. 校验消息中的流程实例 ID、流程编码和事件;关键字段缺失时记录错误并结束本次消费。
    3. 收到审批完成事件(FINISH)时,将其转换成审批通过(APPROVED),调用 confirmOnOffboardRequest 处理;审批拒绝走恢复和清理分支,不进入下面的正式入场流程。
  2. OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:根据流程实例找到申请并分派处理

    1. 根据流程实例 ID 查询入场申请主单;申请单不存在时只记录错误并结束。
    2. 查询该申请下状态为待处理(PENDING)或失败(FAIL)的人员明细,再查询对应人员档案。
    3. 审批结果为通过时,先按申请类型分派;入场申请继续调用 handleOnboardApplication
  3. OutsourcedStaffApprovalHandler.handleOnboardApplication:先判断现在是否应该正式入场

    1. 将申请单中的计划入场日期与当天比较。
    2. 计划入场日期小于或等于当天时,立即进入正式入场处理。
    3. 计划入场日期晚于当天时,不创建或恢复账号,也不写任职记录;只把人员状态改为“审批通过待入场”(-2),把申请单状态改为已批准(APPROVED),等待日期到达。
  4. UpdateStaffStatusHandler.handleOutsourcedStaffApprovalHandler.handleOnOffboardRequest:让待入场申请到期后继续执行

    1. 定时任务触发后,查询类型为入场、状态为已批准,并且计划入场日期小于或等于当前时间的申请单。
    2. 对每张满足条件的申请,再次调用 confirmOnOffboardRequest,重新进入同一个 handleOnboardApplication
    3. 这次日期条件已经满足,因此继续执行正式入场,不再停留在“审批通过待入场”。定时任务的具体执行频率由任务平台配置,当前处理类中没有写死。
  5. OutsourcedStaffApprovalHandler.handleOnboardApplication:逐人完成正式入场

    1. 清除人员来源标识,将计划入场日期写为实际入场日期,并把默认离场日期设置为 9999-12-31
    2. 人员没有外部账号 ID 时,调用 createNewStaffAccount 创建账号;已有外部账号 ID 时按返聘处理,调用 rehireStaff 恢复账号并同步姓名、机构、业务分类、照片和历史内部账号关联等资料。
    3. 账号处理成功后,将申请人员明细改为成功(SUCCESS),再将人员状态改为“已入场”(0)。
    4. 调用 handleStaffMovementForApproval 新增入场任职记录,保存本次项目、协议及任职起止时间。
    5. 调用 OutsourcedStaffChangeHandler.handle 发送人员变更消息,通知下游人员已经正式入场。
    6. 单个人员处理抛出业务异常时,将该人员明细改为失败并记录原因,同时按人员业务线和所属机构查找通知对象,发送失败通知。
    7. 全部人员成功时,将申请单改为成功(SUCCESS);任意人员失败时,将申请单改为失败(FAIL)。
    展开查看这一步的账号处理具体做了什么

    首次入场,没有外部账号 ID

    createNewStaffAccount 会把人员 ID、姓名、入场和默认离场日期、手机号、所属机构、经营组织、业务分类、照片、外部负责人以及已确认的历史内部账号关联发送给账号中心。账号中心创建成功后,返回的外部账号 ID 会写回人员档案。

    返聘,已经有外部账号 ID

    rehireStaff 先请求账号中心恢复原账号并重新设置账号有效期,再同步最新人员资料和历史内部账号关联;成功后,将历史手机号恢复为当前手机号。

    这里判断首次入场还是返聘的直接依据是人员档案中 accountId 是否为空,而不是再次根据人员状态判断。

例如,某人的计划入场日期为 9 月 15 日:如果审批在 9 月 10 日通过,系统先把他改为“审批通过待入场”(-2),等定时任务在 9 月 15 日或之后扫描到申请再正式入场;如果审批在 9 月 15 日当天通过,则审批回调会直接办理正式入场,不需要等待定时任务。

完成以上处理后,项目和计划模块再次查询人数时,该人员才会按“已入场”口径计入项目实际在场人数和计划实际执行人数。

3.4 离场审批通过后需要做什么

这一阶段同样由审批结果消息触发,但离场日期的判断比入场更严格:只有计划离场日期早于当天才正式离场,等于当天仍先等待。

会议上可以把这张图概括成一句话:离场审批通过后,人员先等待计划离场日结束;到计划日之后,系统才失效外部账号、更新人员和任职信息、通知下游,并写入释放编制的正数明细。

  1. StaffOnOffboardFlowListener.staffOffboard:接收离场审批结果

    1. 监听审批中心发送的入离场流程变更消息。
    2. 校验消息中的流程实例 ID、流程编码和事件;关键字段缺失时记录错误并结束本次消费。
    3. 收到审批完成事件(FINISH)时,将其转换成审批通过(APPROVED),调用 confirmOnOffboardRequest 处理;审批拒绝走恢复和清理分支,不进入下面的正式离场流程。
  2. OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:根据流程实例找到申请并分派处理

    1. 根据流程实例 ID 查询离场申请主单;申请单不存在时只记录错误并结束。
    2. 查询该申请下状态为待处理(PENDING)或失败(FAIL)的人员明细,再查询对应人员档案。
    3. 审批结果为通过时,先按申请类型分派;离场申请继续调用 handleOffboardApplication
  3. OutsourcedStaffApprovalHandler.handleOffboardApplication:先判断现在是否应该正式离场

    1. 将申请单中的计划离场日期与当天比较。
    2. 只有计划离场日期早于当天时,才立即进入正式离场处理。
    3. 计划离场日期晚于或等于当天时,不失效账号,也不更新离场日期和任职记录;只把人员状态改为“审批通过待离场”(3),把申请单状态改为已批准(APPROVED),等待日期条件满足。
  4. UpdateStaffStatusHandler.handleOutsourcedStaffApprovalHandler.handleOnOffboardRequest:让待离场申请到期后继续执行

    1. 定时任务触发后,查询类型为离场、状态为已批准,并且计划离场日期小于当前时间的申请单。
    2. 对每张查询到的申请,再次调用 confirmOnOffboardRequest,重新进入同一个 handleOffboardApplication
    3. handleOffboardApplication 最终仍按日期比较,因此计划离场日期等于当天时不会正式离场;到下一天后才满足“早于当天”。定时任务的具体执行频率由任务平台配置,当前处理类中没有写死。
  5. OutsourcedStaffApprovalHandler.handleOffboardApplication:逐人完成正式离场

    1. 调用 invalidateAccountIfNeeded 检查并处理外部账号:账号已经失效时跳过;账号仍有效时请求账号中心使其失效;查询账号状态异常时也继续尝试失效。
    2. 账号处理成功后,将申请人员明细改为成功(SUCCESS)。
    3. 调用 updateStaffOffboard 保存原手机号到历史手机号,用新生成的占位值替换当前手机号,写入实际离场日期,将人员状态改为“已离场”(1),并清除人员来源标识。
    4. 调用 handleStaffMovementForApproval 更新上一条任职记录的结束时间,并新增本次离场任职记录。
    5. 调用 OutsourcedStaffChangeHandler.handle 发送人员变更消息,通知下游人员已经离场。
    6. 单个人员处理抛出业务异常时,将该人员明细改为失败并记录原因,同时按人员业务线和所属机构查找通知对象,发送失败通知。
    7. 全部人员成功时,将申请单改为成功(SUCCESS);任意人员失败时,将申请单改为失败(FAIL)。
    8. 人员循环结束后调用 processProjectInfo,按项目写入离场编制明细;本次离场人数使用正数 requestNum,用于抵消入场时写入的负数占用。
    展开查看这一步的账号失效和编制释放具体做了什么

    账号失效:invalidateAccountIfNeeded

    • 人员存在外部账号 ID 时,先查询账号中心的账号状态。
    • 账号已经是无效状态时,认为账号处理已经完成,不再重复调用失效接口。
    • 账号仍有效时,调用账号失效接口。
    • 查询账号状态发生异常时,不让查询异常直接卡住离场流程,而是继续调用账号失效接口兜底。
    • 如果人员的外部账号 ID 为空,当前代码仍会组装失效请求并调用账号中心;正常已入场人员原则上应已有外部账号 ID,这种异常数据如何处理需要结合账号中心接口确认。

    编制释放:processProjectInfo

    方法按项目统计离场申请人员数,重新查询项目总编制和已有申请编制变动,然后写入一条新的项目编制明细。与入场的负数占用相反,离场人数以正数记录;例如离场 2 人时,requestNum = 2,表示释放 2 个名额。

    需要注意:当前代码使用本申请的全部人员明细统计离场人数,没有按照人员明细的成功或失败状态再次过滤;即使本批存在失败人员,仍会在更新申请单状态后执行这次编制明细写入。

例如,某人的计划离场日期为 9 月 15 日:如果审批在 9 月 10 日通过,系统先把他改为“审批通过待离场”(3);9 月 15 日当天仍不会正式离场,到 9 月 16 日才满足“计划离场日期早于当天”,随后由定时任务触发账号失效和正式离场处理。如果审批到 9 月 16 日才通过,则审批回调会直接办理正式离场。

离场生效后,人员不再按“已入场”口径计入项目和计划的实际在场人数;历史任职记录和原手机号仍保留,用于后续查询及返聘处理。

3.5 离场审批未通过后需要做什么

这一阶段处理审批中心明确返回拒绝(REJECT)的情况。离场申请不会生效,人员恢复为已入场,当前申请明细转入历史记录。

会议上可以把这张图概括成一句话:离场审批被驳回后不执行真正离场,只把申请标记为驳回、把人员恢复为已入场,并将本次申请明细转入历史。

  1. StaffOnOffboardFlowListener.staffOffboard:接收离场审批拒绝结果

    1. 监听审批中心发送的流程变更消息。
    2. 收到拒绝事件(REJECT)时,将其转换为已拒绝状态(REJECTED)。
    3. 调用 confirmOnOffboardRequest,并传入流程实例 ID 和拒绝状态。
  2. OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:找到需要结束的离场申请

    1. 根据流程实例 ID 查询申请主单;申请单不存在时记录错误并结束。
    2. 查询状态为待处理(PENDING)或失败(FAIL)的人员申请明细和对应人员档案;没有申请明细时记录错误并结束。
    3. 审批状态不是通过时,不调用 handleOffboardApplication,而是调用 cleanupApplicationData(applicationId, false)
  3. OutsourcedStaffApprovalHandler.cleanupApplicationData(applicationId, false):恢复人员并归档本次申请明细

    1. 将离场申请主单状态改为已拒绝(REJECTED),申请主单本身继续保留。
    2. 查询本申请的人员明细,删除当前明细,并转换后保存到人员申请历史表。
    3. 将本次申请涉及的人员从“离场审批中”(2)恢复为“已入场”(0)。
    4. 查询本申请的项目编制明细,删除当前明细,并转换后保存到项目编制历史表。
    5. 不调用账号失效接口,不写实际离场日期,不替换手机号,不新增离场任职记录,也不发送正式离场的人员变更消息。
    6. 不写正数离场编制明细,因此不会按正式离场释放项目名额。人员恢复后可以重新发起离场申请。

这里的方法名虽然叫 cleanupApplicationData,但传入 false 时不是删除整张申请单,而是保留申请主单并标记为驳回,同时把当前人员明细和项目编制明细转存到历史表。

例如,某人原本是“已入场”(0),提交离场审批后变为“离场审批中”(2)。如果审批被驳回,他会恢复为“已入场”(0),外部账号继续有效,项目实际在场人数也不会减少;之后可以修改原因或日期,再重新发起离场申请。

4. 一次入场,关系如何串起来

假设“客服外包项目”还有 1 个可用名额,王某的档案已关联该项目和一份有效协议。

  1. 人员模块读取王某的项目归属,确认同批申请人员都属于该项目。
  2. 它根据项目给出的业务线发起入场审批,并检查照片、人员当前状态和剩余编制。
  3. 审批中心回传通过后,如入场日期已到,人员模块创建或恢复外部账号、把王某更新为已入场并写入任职变动记录;如日期未到,则先保留为审批通过待入场,待日期到达后再生效。
  4. 项目模块查询时,看到该项目的已入场人数增加;计划模块汇总时,也把这 1 人计入对应业务线和组织的实际入场人数。
  5. 协议模块在按协议查询人员或进行相关处理时,可以识别王某实际使用该协议的期间。

这个例子里,项目、协议、审批各自没有被人员模块替代:人员模块只是把它们共同作用的结果落实到了王某身上。

5. 使用与维护注意事项

  • 修改人员项目或协议时,要同时关注任职历史,不能只看当前档案;历史记录用于还原人员曾在哪个项目、使用哪份协议。
  • 新建人员默认是待入场,不代表已占用项目实际在场名额;必须经入场申请、审批和日期生效后才成为已入场人员。
  • 项目编制是入场校验的重要前提。项目可用名额不足时,不能仅靠修改人员状态绕过入场流程。
  • 协议终止、项目到期或终止会影响已关联人员,应检查消息处理和人员状态是否完成同步。
  • 组织范围用于查询和办理申请时的范围控制;它与“人员属于哪个项目”“协议属于哪个供应商”是不同维度,不能混为同一种关系。
  • 当前代码能确认人员模块接收项目、协议和账号变更消息;这些消息的跨系统投递可靠性及数据库外键约束,需结合运行配置和数据库结构另行确认。

6. 总结

外包人员模块是外包用工链路的执行台账:

  • 项目给名额和业务归属;
  • 协议给用工和结算依据;
  • 审批决定入离场申请是否生效;
  • 人员模块保存每个人最终的在场状态、项目/协议归属和任职历史;
  • 项目与计划再使用这些结果观察编制的实际占用。

所以它既是项目、协议等主数据的使用者,也是“实际在场人数”和“人员任职事实”的提供者。