外包人员模块:把“人”接入外包用工体系
外包人员模块管理的是具体哪位外包人员、当前能否在场、归属哪个项目、使用哪份协议。它不是项目、协议或供应商的附属页面,而是把这些业务条件落实到每个人身上的执行模块。
可以把它理解为“人员在场台账”:项目给出可用名额和业务归属,协议给出用工依据,人员模块据此办理入场、离场和变更,并把实际在场人数反馈给项目与计划。
1. 它自己负责什么
人员模块保存人员档案及其任职变化,主要负责:
- 建立、修改、查询和批量导入外包人员档案;新建档案默认进入待入场状态;
- 记录人员关联的项目、协议、组织、岗位等业务信息;
- 发起入场、离场申请,接收审批结果后更新人员状态;
- 入场审批通过且到达入场日期后请求账号中心创建外部账号,并在人员变更、返聘和离场时同步账号;
- 保留人员在项目和协议之间的任职变动记录;
- 按项目计算已入场人数、申请人数和剩余名额,供其他模块展示或校验;
- 对外提供按账号、组织或人员编号查询人员及任职记录的接口。
这里的“人员档案”是当前信息;“任职变动记录”是历史信息。例如李四从项目 A 调到项目 B,档案展示当前归属 B,历史记录保留曾在 A 任职的事实。
外包人员模块协作与数量口径图
先看模块间实际调用、回调与消息通知:查看模块协作时序图。
项目编制、待审批人数、已入场人数及离场释放的加减规则见:查看人员数量与项目编制增减图。
入/离场申请的内部处理链路见:查看申请、生效与重试流程图。
2. 与各模块的关系
先看总览。这里不再用一张总图,避免把“主动查询”和“对方主动通知”误画成同一种箭头。
| 关联模块 | 人员模块如何协作 | 人员模块实际取得或输出什么 |
|---|---|---|
| 项目模块 | 人员模块主动查询;项目到期或终止时也会发消息通知人员模块。 | 查询项目、业务线和编制;处理项目终止后受影响的人员。 |
| 协议模块 | 人员模块主动查询协议信息;协议终止或编号变更时会发消息。 | 补齐协议名称、期限和协议乙方(供应商)信息;处理受影响人员。 |
| 组织与权限 | 人员模块主动查询或调用权限能力。 | 机构名称、可查询的组织范围、业务线数据范围。 |
| 账号中心 | 人员模块主动发起创建、更新、返聘、失效和详情查询。 | 创建时返回账号 ID;查询时返回账号详情。 |
| 审批流程中心 | 人员模块主动发起入/离场审批;审批中心回传结果。 | 审批通过或驳回结果。 |
| 计划/编制模块 | 计划模块主动向人员模块查询。 | 人员模块按项目返回已入场人数。 |
| 其他系统 | 其他系统主动向人员模块查询。 | 人员详情和任职历史。 |
供应商信息通过协议模块进入人员模块,因此未写成“供应商直接向人员模块提供数据”。项目终止和协议终止对人员状态的影响,在后续对应小图中说明。
2.1 项目模块:项目是人员实际工作的落点
人员模块在办理入场或离场时,主动向项目模块查询项目编号对应的业务线、项目编制和计划年度。同一批申请人员必须归属同一个项目;查不到项目则不能继续办理。入场申请会把本次人数记为编制占用,并计算“项目编制 + 已申请占用 + 本次申请”后的剩余数;提交审批前剩余数小于零就不能提交。
项目详情反过来会主动查询人员模块:按项目编号取得已入场人数和申请中人数,其中已入场人数用于展示项目实际入场人数。也就是说,不是人员模块定时把人数推给项目模块,而是项目模块需要展示时来查询。
项目到期或终止时,项目模块会发送项目变更消息。人员模块监听到终止消息后,查询该项目下已入场人员,依次更新为离场、写入任职历史,并请求账号中心使其外部账号失效。
代码如何实现
对应的关键代码位置:
- 人员申请按项目校验和查询项目编制:
OutsourcedStaffApplicationHandler; - 项目详情查询人员统计人数:
ProjectServiceImpl调用queryStaffApplyProject; - 人员统计已入场/申请中人数:
OutsourcedStaffQueryHandler; - 项目终止消息消费与人员离场处理:
StaffProjectCodeChangeListener、OutsourcedStaffProjectChangeRepositoryImpl。
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.createStaff → OutsourcedStaffWebImpl.createStaff → OutsourcedStaffServiceImpl.createStaff → OutsourcedStaffOperationService.createStaff → OutsourcedStaffBaseRepositoryImpl.createStaff。其中 Controller 和接口层主要负责接收、转发请求,真正的处理按方法分布如下。
-
OutsourcedStaffWebImpl.createStaff:执行入口校验并调用领域服务- 调用
OutsourcedStaffWebValidator.validateCreateStaff,校验创建请求和当前登录信息。 - 未关联历史内部账号时,校验照片不能为空。
- 校验通过后,调用
OutsourcedStaffService.createStaff。
- 调用
-
OutsourcedStaffServiceImpl.createStaff:转发到人员基础操作服务- 该方法本身不执行业务校验。
- 将请求转给
OutsourcedStaffOperationService.createStaff。
-
OutsourcedStaffOperationService.createStaff:校验和补齐人员基础资料- 校验证件号码与年龄是否匹配。
- 校验学历、政治面貌、业务分类、民族等码值是否合法。
- 校验归属部门,并补齐经营组织等组织信息。
- 校验工作岗位。
- 工作地点为空时,默认使用归属组织编码。
- 自动授权标识为空时,默认设为“否”(
0)。 - 根据工作岗位补齐标准岗位编码。
- 确认关联历史内部账号时,校验账号与证件是否一致、账号是否已被占用;未上传照片时,从内部账号补充照片。
- 基础资料处理完成后,调用
OutsourcedStaffBaseRepository.createStaff。
-
OutsourcedStaffBaseRepositoryImpl.createStaff:校验业务归属并保存人员档案- 将创建请求转换为人员实体,并在实体中将人员状态设为 待入场(
-1)。 - 查询已审批协议;协议不存在时终止创建,存在时将协议乙方回填为供应商。
- 查询项目;项目不存在时终止创建,存在时回填项目编号和业务线。
- 调用
OutsourcedStaffMapper.insert保存人员档案。 - 返回新建人员 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 → 申请主表 id;project_id → project_detail.project_code |
outsourced_staff_onoffboard_application_record | 本次申请包含的人员明细 | application_batch_id → 申请主表 id;account_id → outsourced_staff.id;project_id → project_detail.project_code;agreement_id → agreement.agreement_no |
attachment | 提交审批时上传的附件 | biz_id → 申请主表 id,并以 type = outsourced_staff 区分业务类型 |
outsourced_staff | 人员当前档案 | id 被申请人员明细的 account_id 引用;自身的 project_id 和 agreement_id 分别记录当前项目编号、协议编号 |
容易误解的一点:申请人员明细字段名虽然叫
account_id,实际保存的是outsourced_staff.id,不是人员档案中的新一代账号字段outsourced_staff.account_id。
可以把它理解成一张申请主单下面挂两组明细:一组回答“这次涉及哪些项目、占多少编制”,另一组回答“这次具体有哪些人”;附件也通过同一个申请主单 ID 归档。主表与项目编制明细、人员申请明细、附件都是一对多关系;不过当前入场接口要求同一批人员属于同一项目,所以一张入场申请通常只有一条项目编制明细。人员明细与项目编制明细之间不直接互相引用,而是共同使用 application_batch_id 归属于同一张申请主单,并通过 project_id 指向同一个项目编号。
实际保存顺序
第一次调用 onboard-request 初始化申请时:
- 先保存申请主表
outsourced_staff_onoffboard_application,生成一张状态为INIT的入场申请主单。 - 再保存项目编制明细表
outsourced_staff_onoffboard_application_project,记录项目总编制、本次占用数和申请后剩余编制。 - 最后批量保存人员申请明细表
outsourced_staff_onoffboard_application_record,把每个人、项目和协议挂到该申请主单下,明细状态为待处理。 - 此时不修改
outsourced_staff的人员状态,也不保存附件;人员仍是待入场或已离场返聘状态。
携带同一 requestId 再次调用 onboard-request 调整人员时:
- 复用原来的
INIT申请主单,不新增主表记录。 - 先删除该申请原有的人员申请明细。
- 再删除该申请原有的项目编制明细。
- 先重新保存项目编制明细,再重新保存人员申请明细。
代码对新旧 requestId 统一执行明细清理逻辑;第一次新建时没有旧明细,因此删除操作实际影响 0 行。采用“清旧后全量重建”是为了让项目编制和本次最终有效人员保持一致。
调用 onboard-request-apply 正式提交审批时:
- 先读取并校验上述主单、人员明细和项目编制明细;计划入场日期和备注先写入主单实体内存,此时尚未更新数据库主表。
- 有附件时,先保存
attachment,使用申请主单 ID 作为biz_id。 - 再调用审批中心创建流程。
- 流程创建成功后,先将
outsourced_staff.staff_status更新为入场审批中(-3)。 - 最后更新申请主表:保存计划入场日期、备注、流程请求 ID、流程实例 ID,并将主单状态改为
APPROVING。
项目编制明细和人员申请明细在正式提交时不会再次重建;它们沿用初始化阶段的数据,只做提交前复核。
会议上可以把这张图概括成一句话:第一个接口先筛人并生成初始化申请数据,第二个接口再做提交前校验并发起审批;此时人员只是进入审批中,还没有真正入场。
两个接口都会经过 OutsourcedStaffWebImpl → OutsourcedStaffServiceImpl → OutsourcedStaffOnOffboardService → OutsourcedStaffOnOffboardRepositoryImpl。这几层主要负责入口校验和请求转发,入场申请的核心处理集中在 OutsourcedStaffApplicationHandler 中。
-
OutsourcedStaffWebImpl.onboardRequest:校验并发起初始化请求- 调用
OutsourcedStaffWebValidator.validateOnboardRequest,校验请求参数和当前登录信息。 - 校验本次人员 ID 数量在 1—100 之间。
- 校验通过后,逐层转发给
OutsourcedStaffApplicationHandler.onboardRequest。
- 调用
-
OutsourcedStaffApplicationHandler.onboardRequest:筛选人员并初始化申请数据-
根据人员 ID 查询人员;未查到人员时终止初始化。
-
校验同一批人员必须属于同一项目。
-
新生成或复用
requestId,初始化错误人员列表,然后调用validateAndClassifyStaff:先批量查询项目、已审批协议、业务线权限和组织管理路径,再逐人校验状态、业务归属和权限,并将失败人员记入错误列表。展开查看这一步的 validateAndClassifyStaff 具体做了什么
这个方法本身不创建申请单,也不保存人员;它会先批量准备校验资料,再逐人校验,并把不符合入场条件的人员收集到
errorStaffList。三个入参
permissionOrgCode:当前登录账号的默认 ACL 权限机构编码,表示本次操作的机构权限范围起点。allStaffList:本次选中的全部外包人员。errorStaffList:用于收集校验失败人员和失败原因的结果列表。
先批量准备四份校验资料
projectInfoMap:按人员的项目编号批量查询项目和编制信息,后续用于判断项目是否存在以及项目属于哪条业务线。agreementMap:批量查询人员关联的协议;此处传入true,所以只保留已审批协议。businessLinePermissions:根据当前登录账号查询可操作的业务线。这里传入null表示不额外指定某条业务线,不是表示没有权限。orgCodeMap:收集所有人员的operatingOrgCode和当前账号的permissionOrgCode,批量查询后整理成“机构编码 → 组织管理路径”的映射。
validateStaff逐人校验的内容- 当前账号是否具有业务线权限。
- 人员状态是否允许发起入场:待入场(
-1)和已离场(1,返聘)可以继续;入场、已入场或离场流程中的人员不允许继续。 - 人员关联的项目是否存在。
- 人员关联的已审批协议是否存在。
- 人员项目所属业务线是否在当前账号权限内。
- 人员的
operatingOrgCode是否位于permissionOrgCode的组织管理路径下;不在范围内时返回“无此机构权限”。
例如,
permissionOrgCode对应“江苏分公司”,人员的operatingOrgCode对应“南京中心支公司”,而南京中心支公司在江苏分公司管理路径下,则机构校验通过。校验失败后怎么处理
某个人校验失败时,方法不会立即终止整批处理,而是将其账号 ID、姓名、手机号和失败原因加入
errorStaffList。已离场人员返聘时记录历史手机号,其他人员记录当前手机号。方法返回后,后续的filterValidStaff才会从全部人员中筛出没有错误且状态为待入场或已离场的人员。 -
调用
filterValidStaff:只保留状态为待入场(-1)或已离场(1,返聘),并且未记入错误列表的人员。 -
如果过滤后没有有效人员,直接返回空的有效人员列表和错误原因,不创建入场申请单。
-
有有效人员时,调用
createApplication,使用第 3 步的requestId新建或复用状态为初始化的入场申请单。展开查看这一步的 createApplication 具体做了什么
这个方法只负责取得一张可继续编辑的申请主单:调用方没有传入
requestId时新建申请单,传入了requestId时校验并复用指定申请单。它不会在这里保存申请人员、项目编制、计划入场日期或附件,也不会发起审批。三个入参
requestId:本次请求原本传入的申请单 ID,用于判断是新建还是复用。requestDTO:初始化接口的返回对象,里面已经保存了第 3 步生成或复用的requestId。applicationType:申请类型;当前入场场景传入ONBOARD,同一个方法也供离场申请传入OFFBOARD使用。
实际处理顺序
- 先创建一个尚未保存的申请单实体,并从
requestDTO中取出requestId设置为申请单 ID。 - 如果请求原本传入了
requestId,说明本次要继续编辑已有申请单:根据该 ID 查询申请主单。 - 已有申请单不存在,或者状态不是初始化(
INIT)时,终止处理并提示“申请单不存在或者状态异常”。审批中或已经结束的申请单不能通过这里重新初始化。 - 已有申请单存在且状态为
INIT时,直接返回这张申请单,不再新增申请主单。 - 如果请求没有传入
requestId,则使用第 3 步新生成的 ID,设置申请类型为入场(ONBOARD)、状态为初始化(INIT),保存一张新的申请主单并返回。
这里的“复用”不是系统自动寻找任意一张初始化申请单,而是只复用本次请求明确指定的
requestId。代码当前在复用分支中只校验申请单是否存在以及状态是否为INIT,没有再次校验已有申请单的applicationType是否为入场。简单理解:
createApplication只先建立或找回一个“申请单外壳”;下一步processStaffOnboardRecordsOptimized才会把人员明细和项目编制明细装进去。办理场景示例(
requestId为示意值)- 第一次选择人员办理入场:业务人员选择 3 名待入场人员,第一次调用
onboard-request时没有传requestId。系统先生成R001,createApplication再保存一张 ID 为R001、类型为入场(ONBOARD)、状态为初始化(INIT)的申请主单。随后下一步把这 3 人的人员明细和项目编制明细保存到R001下,并将R001返回给前端。 - 提交审批前重新调整人员:本次请求带回
requestId = R001。createApplication查询到R001仍是INIT状态后,直接返回原申请主单,不再新建R002。下一步会删除R001原有的人员明细和项目编制明细,再按照本次有效人员重新生成,因此整个过程仍属于同一张申请草稿。 - 申请已经提交审批:如果
R001已不再是INIT状态,再使用它调用初始化接口时,createApplication会提示“申请单不存在或者状态异常”,不允许重新覆盖审批中的申请数据。
从后端代码可以确认它支持“使用同一
requestId重新初始化申请明细”;具体由前端哪个页面操作触发重新初始化,还需要结合前端代码确认。 -
调用
processStaffOnboardRecordsOptimized:再次排除项目无效或已被其他入场申请占用的人员,然后生成并保存申请人员明细和项目编制明细。展开查看这一步的 processStaffOnboardRecordsOptimized 具体做了什么
这个方法负责把前面初步筛选通过的人员再检查一遍,并为当前
requestId生成两类初始化数据:申请人员明细和项目编制明细。它不会在这里发起审批,也不会修改人员状态。五个入参
validStaffList:经过validateAndClassifyStaff和filterValidStaff初步筛选后剩下的人员。staffMap:人员 ID 与人员档案的映射,人员被其他申请占用时,用于回填姓名、手机号等错误信息。applicationEntity:第 6 步新建或复用的初始化申请单。errorStaffList:继续收集这一阶段发现的无效人员及原因。onboardRequestDTO:接收最终有效人员列表,并提供当前requestId。
实际处理顺序
- 如果没有有效人员,直接结束,不生成任何明细。
- 根据人员的项目编号批量查询项目和编制;查询不到项目的人员会从
validStaffList中移除,并记入错误列表,原因是“员工所属项目不存在”。 - 查询这些人员是否已经存在于其他申请单的待处理人员明细中。查询时排除本次
requestId;已被其他申请占用的人员会记入错误列表,原因是“已在入场申请流程中”,并从本次有效人员中排除。 - 将最终剩下的人员写入接口返回值中的有效人员列表。
- 为每个人组装一条申请人员明细,记录当前申请单 ID、人员 ID、项目 ID、协议 ID,并将明细状态设为待处理(
PENDING)。 - 如果本次是在重新初始化已有的
requestId,先删除该申请单原有的人员明细,避免旧数据和新选择的人员重复。 - 调用
processProjectInfo重建项目编制明细:先删除当前申请单原有的项目编制明细,再按项目统计本次申请人数,查询项目总编制、其他申请已经占用或释放的编制,以及项目计划年度。 - 批量保存新的项目编制明细和申请人员明细。
项目编制明细中的数字怎么计算
projectNum:项目当前总编制。availableNum:项目总编制加上其他申请累计产生的编制变动,即本次申请开始前的可用编制。requestNum:本次入场人数的负数;入场会占用编制,例如本次入场 2 人时记录为-2。remainNum:availableNum + requestNum,表示本次申请完成后的剩余编制。projectYear:项目所属计划年度。
例如,项目总编制为 10,该项目已有申请编制明细累计记录的编制变动为
-3,本次申请入场 2 人,那么availableNum = 10 - 3 = 7,requestNum = -2,remainNum = 5。这里的已有编制明细在本方法中没有再按申请单状态过滤。这里即使算出
remainNum < 0,也不会在初始化阶段终止;真正的编制不足校验发生在后续onboardRequestApply提交审批时。方法名中的
Optimized主要表示它采用批量查询、批量组装和批量保存,业务作用仍然是“再次筛人并生成申请明细”。 -
返回
requestId、最终有效人员列表和被排除人员及其原因。
-
到这里,
onboard-request接口执行结束:人员筛选和申请初始化数据已经完成。下面开始补充申请信息,并通过onboard-request-apply正式提交审批。
-
业务人员补充申请信息
- 根据初始化接口返回的
requestId,填写计划入场日期和备注。 - 根据需要上传附件。
- 调用
onboard-request-apply提交入场申请。
- 根据初始化接口返回的
-
OutsourcedStaffWebImpl.onboardRequestApply:校验并发起提交请求- 调用
OutsourcedStaffWebValidator.validateOnboardRequestApply,校验requestId、计划入场日期、机构权限编码和当前登录信息。 - 校验通过后,逐层转发给
OutsourcedStaffApplicationHandler.onboardRequestApply。
- 调用
-
OutsourcedStaffApplicationHandler.onboardRequestApply:重新校验并提交审批- 读取入场申请单,校验申请类型和初始化状态,并写入计划入场日期和备注。
- 读取申请人员明细和项目编制明细。
- 校验本次申请后的剩余编制不能小于 0。
- 重新查询人员档案,校验照片是否存在,以及人员是否已进入其他入离场状态。
- 保存附件。
- 使用
permissionOrgCode查询机构层级,再结合项目业务线组装审批变量,向审批中心发起“外包人员入场申请”。 - 审批流程创建成功后,保存流程实例 ID,将申请单改为审批中,并将人员状态改为“入场审批中”(
-3)。
如果申请被驳回、取消或放弃,人员会恢复为待入场,可以补充资料后重新申请。
3.3 入场审批通过后需要做什么
这一阶段由审批结果消息触发:先判断计划入场日期是否已经到达;日期已到时立即办理正式入场,日期未到时先等待定时任务。
会议上可以把这张图概括成一句话:审批通过不一定立即入场;只有计划入场日期已经到达,系统才会创建或恢复账号,并把人员状态、任职记录和下游消息处理成正式入场结果。
-
StaffOnOffboardFlowListener.staffOffboard:接收入场审批结果- 监听审批中心发送的入离场流程变更消息。
- 校验消息中的流程实例 ID、流程编码和事件;关键字段缺失时记录错误并结束本次消费。
- 收到审批完成事件(
FINISH)时,将其转换成审批通过(APPROVED),调用confirmOnOffboardRequest处理;审批拒绝走恢复和清理分支,不进入下面的正式入场流程。
-
OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:根据流程实例找到申请并分派处理- 根据流程实例 ID 查询入场申请主单;申请单不存在时只记录错误并结束。
- 查询该申请下状态为待处理(
PENDING)或失败(FAIL)的人员明细,再查询对应人员档案。 - 审批结果为通过时,先按申请类型分派;入场申请继续调用
handleOnboardApplication。
-
OutsourcedStaffApprovalHandler.handleOnboardApplication:先判断现在是否应该正式入场- 将申请单中的计划入场日期与当天比较。
- 计划入场日期小于或等于当天时,立即进入正式入场处理。
- 计划入场日期晚于当天时,不创建或恢复账号,也不写任职记录;只把人员状态改为“审批通过待入场”(
-2),把申请单状态改为已批准(APPROVED),等待日期到达。
-
UpdateStaffStatusHandler.handle→OutsourcedStaffApprovalHandler.handleOnOffboardRequest:让待入场申请到期后继续执行- 定时任务触发后,查询类型为入场、状态为已批准,并且计划入场日期小于或等于当前时间的申请单。
- 对每张满足条件的申请,再次调用
confirmOnOffboardRequest,重新进入同一个handleOnboardApplication。 - 这次日期条件已经满足,因此继续执行正式入场,不再停留在“审批通过待入场”。定时任务的具体执行频率由任务平台配置,当前处理类中没有写死。
-
OutsourcedStaffApprovalHandler.handleOnboardApplication:逐人完成正式入场- 清除人员来源标识,将计划入场日期写为实际入场日期,并把默认离场日期设置为
9999-12-31。 - 人员没有外部账号 ID 时,调用
createNewStaffAccount创建账号;已有外部账号 ID 时按返聘处理,调用rehireStaff恢复账号并同步姓名、机构、业务分类、照片和历史内部账号关联等资料。 - 账号处理成功后,将申请人员明细改为成功(
SUCCESS),再将人员状态改为“已入场”(0)。 - 调用
handleStaffMovementForApproval新增入场任职记录,保存本次项目、协议及任职起止时间。 - 调用
OutsourcedStaffChangeHandler.handle发送人员变更消息,通知下游人员已经正式入场。 - 单个人员处理抛出业务异常时,将该人员明细改为失败并记录原因,同时按人员业务线和所属机构查找通知对象,发送失败通知。
- 全部人员成功时,将申请单改为成功(
SUCCESS);任意人员失败时,将申请单改为失败(FAIL)。
展开查看这一步的账号处理具体做了什么
首次入场,没有外部账号 ID
createNewStaffAccount会把人员 ID、姓名、入场和默认离场日期、手机号、所属机构、经营组织、业务分类、照片、外部负责人以及已确认的历史内部账号关联发送给账号中心。账号中心创建成功后,返回的外部账号 ID 会写回人员档案。返聘,已经有外部账号 ID
rehireStaff先请求账号中心恢复原账号并重新设置账号有效期,再同步最新人员资料和历史内部账号关联;成功后,将历史手机号恢复为当前手机号。这里判断首次入场还是返聘的直接依据是人员档案中
accountId是否为空,而不是再次根据人员状态判断。 - 清除人员来源标识,将计划入场日期写为实际入场日期,并把默认离场日期设置为
例如,某人的计划入场日期为 9 月 15 日:如果审批在 9 月 10 日通过,系统先把他改为“审批通过待入场”(-2),等定时任务在 9 月 15 日或之后扫描到申请再正式入场;如果审批在 9 月 15 日当天通过,则审批回调会直接办理正式入场,不需要等待定时任务。
完成以上处理后,项目和计划模块再次查询人数时,该人员才会按“已入场”口径计入项目实际在场人数和计划实际执行人数。
3.4 离场审批通过后需要做什么
这一阶段同样由审批结果消息触发,但离场日期的判断比入场更严格:只有计划离场日期早于当天才正式离场,等于当天仍先等待。
会议上可以把这张图概括成一句话:离场审批通过后,人员先等待计划离场日结束;到计划日之后,系统才失效外部账号、更新人员和任职信息、通知下游,并写入释放编制的正数明细。
-
StaffOnOffboardFlowListener.staffOffboard:接收离场审批结果- 监听审批中心发送的入离场流程变更消息。
- 校验消息中的流程实例 ID、流程编码和事件;关键字段缺失时记录错误并结束本次消费。
- 收到审批完成事件(
FINISH)时,将其转换成审批通过(APPROVED),调用confirmOnOffboardRequest处理;审批拒绝走恢复和清理分支,不进入下面的正式离场流程。
-
OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:根据流程实例找到申请并分派处理- 根据流程实例 ID 查询离场申请主单;申请单不存在时只记录错误并结束。
- 查询该申请下状态为待处理(
PENDING)或失败(FAIL)的人员明细,再查询对应人员档案。 - 审批结果为通过时,先按申请类型分派;离场申请继续调用
handleOffboardApplication。
-
OutsourcedStaffApprovalHandler.handleOffboardApplication:先判断现在是否应该正式离场- 将申请单中的计划离场日期与当天比较。
- 只有计划离场日期早于当天时,才立即进入正式离场处理。
- 计划离场日期晚于或等于当天时,不失效账号,也不更新离场日期和任职记录;只把人员状态改为“审批通过待离场”(
3),把申请单状态改为已批准(APPROVED),等待日期条件满足。
-
UpdateStaffStatusHandler.handle→OutsourcedStaffApprovalHandler.handleOnOffboardRequest:让待离场申请到期后继续执行- 定时任务触发后,查询类型为离场、状态为已批准,并且计划离场日期小于当前时间的申请单。
- 对每张查询到的申请,再次调用
confirmOnOffboardRequest,重新进入同一个handleOffboardApplication。 handleOffboardApplication最终仍按日期比较,因此计划离场日期等于当天时不会正式离场;到下一天后才满足“早于当天”。定时任务的具体执行频率由任务平台配置,当前处理类中没有写死。
-
OutsourcedStaffApprovalHandler.handleOffboardApplication:逐人完成正式离场- 调用
invalidateAccountIfNeeded检查并处理外部账号:账号已经失效时跳过;账号仍有效时请求账号中心使其失效;查询账号状态异常时也继续尝试失效。 - 账号处理成功后,将申请人员明细改为成功(
SUCCESS)。 - 调用
updateStaffOffboard保存原手机号到历史手机号,用新生成的占位值替换当前手机号,写入实际离场日期,将人员状态改为“已离场”(1),并清除人员来源标识。 - 调用
handleStaffMovementForApproval更新上一条任职记录的结束时间,并新增本次离场任职记录。 - 调用
OutsourcedStaffChangeHandler.handle发送人员变更消息,通知下游人员已经离场。 - 单个人员处理抛出业务异常时,将该人员明细改为失败并记录原因,同时按人员业务线和所属机构查找通知对象,发送失败通知。
- 全部人员成功时,将申请单改为成功(
SUCCESS);任意人员失败时,将申请单改为失败(FAIL)。 - 人员循环结束后调用
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)的情况。离场申请不会生效,人员恢复为已入场,当前申请明细转入历史记录。
会议上可以把这张图概括成一句话:离场审批被驳回后不执行真正离场,只把申请标记为驳回、把人员恢复为已入场,并将本次申请明细转入历史。
-
StaffOnOffboardFlowListener.staffOffboard:接收离场审批拒绝结果- 监听审批中心发送的流程变更消息。
- 收到拒绝事件(
REJECT)时,将其转换为已拒绝状态(REJECTED)。 - 调用
confirmOnOffboardRequest,并传入流程实例 ID 和拒绝状态。
-
OutsourcedStaffApprovalHandler.confirmOnOffboardRequest:找到需要结束的离场申请- 根据流程实例 ID 查询申请主单;申请单不存在时记录错误并结束。
- 查询状态为待处理(
PENDING)或失败(FAIL)的人员申请明细和对应人员档案;没有申请明细时记录错误并结束。 - 审批状态不是通过时,不调用
handleOffboardApplication,而是调用cleanupApplicationData(applicationId, false)。
-
OutsourcedStaffApprovalHandler.cleanupApplicationData(applicationId, false):恢复人员并归档本次申请明细- 将离场申请主单状态改为已拒绝(
REJECTED),申请主单本身继续保留。 - 查询本申请的人员明细,删除当前明细,并转换后保存到人员申请历史表。
- 将本次申请涉及的人员从“离场审批中”(
2)恢复为“已入场”(0)。 - 查询本申请的项目编制明细,删除当前明细,并转换后保存到项目编制历史表。
- 不调用账号失效接口,不写实际离场日期,不替换手机号,不新增离场任职记录,也不发送正式离场的人员变更消息。
- 不写正数离场编制明细,因此不会按正式离场释放项目名额。人员恢复后可以重新发起离场申请。
- 将离场申请主单状态改为已拒绝(
这里的方法名虽然叫 cleanupApplicationData,但传入 false 时不是删除整张申请单,而是保留申请主单并标记为驳回,同时把当前人员明细和项目编制明细转存到历史表。
例如,某人原本是“已入场”(0),提交离场审批后变为“离场审批中”(2)。如果审批被驳回,他会恢复为“已入场”(0),外部账号继续有效,项目实际在场人数也不会减少;之后可以修改原因或日期,再重新发起离场申请。
4. 一次入场,关系如何串起来
假设“客服外包项目”还有 1 个可用名额,王某的档案已关联该项目和一份有效协议。
- 人员模块读取王某的项目归属,确认同批申请人员都属于该项目。
- 它根据项目给出的业务线发起入场审批,并检查照片、人员当前状态和剩余编制。
- 审批中心回传通过后,如入场日期已到,人员模块创建或恢复外部账号、把王某更新为已入场并写入任职变动记录;如日期未到,则先保留为审批通过待入场,待日期到达后再生效。
- 项目模块查询时,看到该项目的已入场人数增加;计划模块汇总时,也把这 1 人计入对应业务线和组织的实际入场人数。
- 协议模块在按协议查询人员或进行相关处理时,可以识别王某实际使用该协议的期间。
这个例子里,项目、协议、审批各自没有被人员模块替代:人员模块只是把它们共同作用的结果落实到了王某身上。
5. 使用与维护注意事项
- 修改人员项目或协议时,要同时关注任职历史,不能只看当前档案;历史记录用于还原人员曾在哪个项目、使用哪份协议。
- 新建人员默认是待入场,不代表已占用项目实际在场名额;必须经入场申请、审批和日期生效后才成为已入场人员。
- 项目编制是入场校验的重要前提。项目可用名额不足时,不能仅靠修改人员状态绕过入场流程。
- 协议终止、项目到期或终止会影响已关联人员,应检查消息处理和人员状态是否完成同步。
- 组织范围用于查询和办理申请时的范围控制;它与“人员属于哪个项目”“协议属于哪个供应商”是不同维度,不能混为同一种关系。
- 当前代码能确认人员模块接收项目、协议和账号变更消息;这些消息的跨系统投递可靠性及数据库外键约束,需结合运行配置和数据库结构另行确认。
6. 总结
外包人员模块是外包用工链路的执行台账:
- 项目给名额和业务归属;
- 协议给用工和结算依据;
- 审批决定入离场申请是否生效;
- 人员模块保存每个人最终的在场状态、项目/协议归属和任职历史;
- 项目与计划再使用这些结果观察编制的实际占用。
所以它既是项目、协议等主数据的使用者,也是“实际在场人数”和“人员任职事实”的提供者。