人员手机号检验规则
创建外包人员时,phone 字段会经过以下校验:
- 必填校验:手机号不能为空;为空时返回“手机号不能为空”。
- 格式校验:必须是 11 位中国大陆手机号,格式为
^1[3-9]\\d{9}$:以1开头,第二位为3至9,其余为 9 位数字;不符合时返回“手机号格式错误”。 - 唯一性校验:保存时由数据库唯一索引
uk_contact_number兜底限制,手机号重复时返回“手机号已存在”。
校验位置
- 请求字段的必填与格式:
CreateStaffCmd.phone。 - 请求参数校验:
OutsourcedStaffWebValidator.validateCreateStaff调用统一参数校验。 - 手机号唯一性:新增员工插入数据库时捕获唯一索引冲突并转换为业务提示。
人员批量导入提示“批次号不存在有效记录”
点击“批量保存员工”时,系统会按批次号查询导入明细中状态为 1 的记录;查询结果为空时,即提示“批量导入员工信息批次号不存在有效记录”。
常见触发情况:
- 该批次没有成功写入导入明细,例如文件导入未完成或未插入任何记录。
- 导入明细已被全部删除或清空。
- 导入的所有员工均校验失败,明细状态均为
0。 - 原本有效的明细在后续保存处理中因手机号与已有员工冲突等原因被标记为失败。
导入明细可能因机构或岗位不存在、无机构权限、手机号重复、证件类型或号码不合法、码值校验失败、线客必填项缺失,以及员工状态不允许关联当前项目等原因而变为失败。应先在批次明细页查看各条记录的失败原因,修正后重新导入。
校验代码:OutsourcedStaffBatchSaveHandler.getValidBatchRecords,查询条件为 batch_id = 当前批次号 且 status = 1。
人员批量导入提示“无此机构权限”
报错原因:本次导入传入的 permissionOrgCode 权限范围不包含待新增人员的归属部门/团队。
permissionOrgCode 可以管理哪些机构
permissionOrgCode 所属机构 | 可管理范围 |
|---|---|
总公司 210000000000 | 不限制机构范围 |
| 一级机构 | 本机构及下属二、三级机构 |
| 二级机构 | 本机构及下属三级机构 |
| 三级机构 | 本机构及其上级二级机构 |
如何查看机构级别
在组织中心按 permissionOrgCode 查询机构详情,查看 branchOrgLevel:
branchOrgLevel | 机构级别 |
|---|---|
0 | 总公司(一级机构) |
1 | 分公司(二级机构) |
2 | 中心支公司(三级机构) |
如果 permissionOrgCode 是团队编码,系统会向上找到最近的经营组织,再按该经营组织的级别计算权限。
示例
permissionOrgCode 属于 A 支公司,只能管理 A 支公司及其下属机构;Excel 中待新增人员填写的是 B 支公司的电融团队,因此超出权限范围,提示“无此机构权限”。
排查时只需对比:
- 导入请求中的
permissionOrgCode。 - Excel 中待新增人员的“人员归属部门/团队编码”(
branchOrgCode)。
如果一个批次的人员全部因此校验失败,保存时还会提示“批次号不存在有效记录”。
申请单不存在、任务实例为空或流程不存在
/项目运维/image/pasted-image-20260828164626.png)
/项目运维/image/pasted-image-20260828164618.png)
问题结论
“申请单不存在、任务实例为空或流程不存在”是流程中心在处理审批动作前返回的通用提示,表示申请单、当前任务实例或流程实例中至少有一项不满足操作条件,并不代表三个对象同时不存在。
本次账号 zhex08330100094 的实际情况是:离场申请已经驳回,当前审批任务已经结束,再次点击驳回时流程中心无法查询到可处理的当前任务实例,因此返回上述提示。与此同时,HRMS 人员状态没有随驳回结果恢复,形成了“申请已驳回、人员仍显示离场审批中”的数据不一致。
只读查询确认的关键数据如下:
| 数据 | 查询结果 |
|---|---|
| 人员状态 | 2,离场审批中 |
| 离场申请单ID | 7498996947656392704 |
| 流程实例ID | PS2026087498996981226729472 |
| 申请单状态 | rejected |
| 当前申请明细 | 0条 |
| 历史申请明细 | 1条 |
这些数据说明流程中心的第一次驳回已经完成,HRMS 也已将申请单改为驳回并将申请明细迁入历史表;页面再次驳回时,实际命中的是“任务实例为空”。
数据库排查顺序
本问题需要按照“人员 → 当前申请明细 → 历史申请明细 → 申请单主表 → 数量交叉验证”的顺序查询。前一步查出的人员ID或申请批次ID,是后一步的查询条件,不要直接用登录账号去所有表中模糊查询。
第一步:查询人员主表
表:outsourced_staff
重点字段:
| 字段 | 用途 |
|---|---|
id | HRMS 人员主键,后续申请明细表的 account_id 实际保存该值 |
account_id | 新一代账号名称,用于根据页面账号定位人员 |
staff_status | 人员当前状态;2 表示离场审批中,0 表示已入场 |
onboard_date | 入场日期,用于确认人员当前是否应处于已入场状态 |
offboard_date | 离场日期,用于判断离场是否已经实际生效 |
project_id | 当前关联项目编号,可用于辅助核对申请明细 |
agreement_id | 当前关联合同编号,可用于辅助核对申请明细 |
gmt_create、gmt_modified | 人员创建及最后修改时间,用于还原状态变化时间线 |
is_valid | 数据是否有效 |
本次查询:
SELECT id,
account_id,
staff_status,
onboard_date,
offboard_date,
project_id,
agreement_id,
gmt_create,
gmt_modified,
is_valid
FROM outsourced_staff
WHERE account_id = 'zhex08330100094';本次结果:人员ID为 7483405542666027008,staff_status = '2',说明 HRMS 仍认为该人员处于离场审批中。后续查询申请明细时,应使用人员ID 7483405542666027008,不能继续使用账号名称。
第二步:查询当前申请明细表
主表:outsourced_staff_onoffboard_application_record
关联表:outsourced_staff_onoffboard_application
重点字段:
| 字段 | 用途 |
|---|---|
id | 申请明细主键 |
application_batch_id | 申请单ID,用于关联申请单主表 |
account_id | 字段名虽然是账号ID,当前代码实际写入 outsourced_staff.id |
status | 明细处理状态:0 待处理、1 处理中、2 成功、3 失败 |
project_id、agreement_id | 申请时关联的项目和合同 |
error_msg | 明细处理失败原因 |
gmt_create、gmt_modified | 申请明细创建及修改时间 |
is_valid | 数据是否有效 |
申请单的 application_type | 用于区分当前明细属于入场申请还是离场申请 |
申请单的 status | 用于确认关联申请单当前处于审批中、通过还是驳回状态 |
申请单的 flow_instance_id | 流程实例ID,用于后续关联流程中心 |
只根据人员ID查询当前明细表,可能同时查到以前的入场申请或其他已完成记录,因此必须关联申请单主表,并通过 application_type = 'offboard' 明确筛选离场申请。
本次查询:
SELECT r.id,
r.application_batch_id,
r.account_id AS staff_id,
r.status AS record_status,
r.project_id,
r.agreement_id,
r.error_msg,
a.application_type,
a.status AS application_status,
a.flow_instance_id,
r.gmt_create,
r.gmt_modified,
r.is_valid
FROM outsourced_staff_onoffboard_application_record r
JOIN outsourced_staff_onoffboard_application a
ON a.id = r.application_batch_id
WHERE r.account_id = '7483405542666027008'
AND a.application_type = 'offboard'
ORDER BY r.gmt_create DESC;如果该查询有结果,说明目标离场申请明细仍保存在当前表,可以继续根据返回的 application_status 和 flow_instance_id 判断审批状态。如果该查询没有结果,只能说明“当前表没有该人员的离场申请明细”,不能说明该人员从未发起过离场申请;此时需要继续查询历史申请明细表。
本次查询没有结果。单独查询当前明细表虽然能看到该人员以前的入场记录,但入场记录不是本次排查目标,因此不能据此判断离场流程状态。
第三步:查询历史申请明细表
主表:outsourced_staff_onoffboard_application_record_history
关联表:outsourced_staff_onoffboard_application
该表字段含义与当前申请明细表基本一致,重点仍是 application_batch_id、account_id、status、project_id、agreement_id、error_msg、gmt_create 和 gmt_modified。
历史表也需要关联申请单主表并筛选 application_type = 'offboard',确保查到的是目标离场申请,而不是以前归档的入场记录。
本次查询:
SELECT r.id,
r.application_batch_id,
r.account_id AS staff_id,
r.status AS record_status,
r.project_id,
r.agreement_id,
r.error_msg,
a.application_type,
a.status AS application_status,
a.flow_instance_id,
r.gmt_create,
r.gmt_modified,
r.is_valid
FROM outsourced_staff_onoffboard_application_record_history r
JOIN outsourced_staff_onoffboard_application a
ON a.id = r.application_batch_id
WHERE r.account_id = '7483405542666027008'
AND a.application_type = 'offboard'
ORDER BY r.gmt_create DESC;本次在历史表中查到目标离场申请明细,application_batch_id = '7498996947656392704'、application_status = 'rejected'。这说明该离场明细已经从当前表迁入历史表,并且关联申请单已经驳回。下一步使用这个申请单ID单独查询申请单主表,核对完整流程字段和时间信息。
第四步:查询申请单主表
表:outsourced_staff_onoffboard_application
重点字段:
| 字段 | 用途 |
|---|---|
id | 申请单ID,对应明细表的 application_batch_id |
application_type | 申请类型:onboard 入场、offboard 离场 |
application_date | 申请入场或离场日期 |
status | 申请单状态:init、approving、approved、rejected 等 |
flow_req_id | 启动流程请求ID,审批回调代码使用该字段匹配流程实例ID |
flow_instance_id | 流程中心返回的流程实例ID,用于去流程中心继续排查任务 |
gmt_create、gmt_modified | 申请单创建及最后修改时间 |
is_valid | 数据是否有效 |
本次查询:
SELECT id,
application_type,
application_date,
status,
flow_req_id,
flow_instance_id,
gmt_create,
gmt_modified,
is_valid
FROM outsourced_staff_onoffboard_application
WHERE id = '7498996947656392704';本次结果为 application_type = 'offboard'、status = 'rejected',流程实例ID为 PS2026087498996981226729472。这能够证明 HRMS 申请单已经收到并处理过驳回结果。
第五步:交叉验证当前表、历史表和申请单状态
最后使用同一人员ID和申请单ID统计三处数据,避免只看单表得出错误结论:
SELECT
(SELECT COUNT(*)
FROM outsourced_staff_onoffboard_application_record
WHERE account_id = '7483405542666027008'
AND application_batch_id = '7498996947656392704') AS current_record_count,
(SELECT COUNT(*)
FROM outsourced_staff_onoffboard_application_record_history
WHERE account_id = '7483405542666027008'
AND application_batch_id = '7498996947656392704') AS history_record_count,
(SELECT COUNT(*)
FROM outsourced_staff_onoffboard_application
WHERE id = '7498996947656392704'
AND application_type = 'offboard'
AND status = 'rejected') AS rejected_application_count;本次结果为:
current_record_count = 0
history_record_count = 1
rejected_application_count = 1结合第一步的 staff_status = '2',最终确认数据不一致:申请单已经驳回、明细已经归档,但人员仍处于离场审批中。
第六步:使用流程实例ID排查流程中心
HRMS 库只能确认业务申请单和人员状态,不能直接证明流程中心当前任务实例是否存在。需要使用第四步得到的 flow_instance_id 去流程中心查询:
- 流程实例是否仍处于运行状态。
- 是否存在当前待办任务实例。
- 当前任务是否已经进入历史任务。
- 驳回操作是否已经产生
PROCESS_REJECT事件。
流程中心表结构不在 HRMS 项目和 hrms_ps_mysql 数据源中,因此不能根据 HRMS 代码猜测具体表名;应通过流程中心管理页面、日志、接口或其数据库查询。本次 HRMS 申请单已是 rejected 且明细已进入历史表,结合页面再次驳回时报错,可以判断当前可处理任务已经不存在。
根因
旧的 HRMS 驳回回调执行顺序存在一致性风险:
- 将申请单状态更新为
rejected。 - 删除当前申请明细并写入历史表。
- 最后才恢复人员状态。
申请单归档和人员状态恢复不是一个不可分割的操作,并且旧逻辑没有校验人员状态更新的影响行数。如果人员状态更新阶段发生异常、被中断或没有更新成功,就会留下以下半完成状态:
申请单:rejected
申请明细:已经迁入历史表
人员状态:仍为 2(离场审批中)数据库快照能够确认本次案例正处于上述不一致状态;但数据库本身不能证明当时人员更新失败的具体异常,需要结合对应时间段的应用日志进一步确认。
驳回结果处理链路
流程中心完成驳回
→ 发送 PROCESS_REJECT 消息
→ StaffOnOffboardFlowListener.staffOffboard 注册的 MessageListener.consume 接收消息
→ OutsourcedStaffOnOffboardRepository.confirmOnOffboardRequest
→ OutsourcedStaffApprovalHandler.confirmOnOffboardRequest
→ 根据流程实例ID查询申请单、申请明细和人员
→ 恢复人员基础状态
→ 将申请单和申请明细归档为驳回状态StaffOnOffboardFlowListener.staffOffboard 负责注册并启动入场、离场审批结果的 MQ 消费者,真正逐条接收消息的入口是内部的 MessageListener.consume。当消息事件为 PROCESS_REJECT 时,监听器将流程实例ID和 REJECTED 状态传给审批处理器。
修复方式
代码调整后的处理顺序如下:
- 先根据申请类型恢复人员状态。
- 离场驳回:
2/3 → 0,恢复为已入场。 - 入场驳回:
-3/-2 → -1;历史离场人员再次入场且已有账号时恢复为已离场。
- 离场驳回:
- 更新人员时同时限定人员ID和原审批状态,避免延迟或重复回调覆盖其他流程已经写入的新状态。
- 人员状态处理完成后,再将申请单改为
rejected,并将当前申请明细迁入历史表。 - 更新影响0行时记录告警,方便排查人员状态是否已被其他流程修改。
存量异常数据不会因为代码发布自动恢复,需要在确认申请单确实为 rejected 后,将对应人员的 staff_status 从 2 修正为 0。生产数据修复必须使用人员ID、账号、申请单ID、流程实例ID和当前状态共同限定,避免误修改其他人员或后续新流程产生的数据。
zhex08330100106 做离场
http://10.197.33.188/api/testprojectstable-aboss-hrms/platform/api/aboss/hrms/staff/offboard-request
{
“permissionOrgCode”: “210000000000”,
“staffIds”: [
“7500030617691373571”
]
}
{
“requestId”: “7500439710574854144”,
“applicationDate”: “2026-09-01”,
“remark”: “test”,
“permissionOrgCode”: “210000000000”
}