外包人员项目编制差异排查与修复
1. 问题是什么
外包人员入场提示“项目人员已满”,但实际人员数小于项目额度。
例:项目 XM202600000150
项目额度:28
实际在场人员:27
申请记录净占用:-28
系统可用编制:28 + (-28) = 0系统依据入离场申请记录判断已占用 28 个编制,所以不允许入场;人员表实际只有 27 人,说明历史申请记录多占用了 1 个编制。
2. 核心口径
入场:request_num 为负数,例如 -1
离场:request_num 为正数,例如 1
diff = 实际人数 actual_count + 申请记录汇总 total_request_num| diff | 表示什么 | 修复方向 |
|---|---|---|
| 0 | 两套数据一致 | 不处理 |
| 小于 0 | 申请记录比实际人员多占用了编制 | 补离场记录,request_num 为正数 |
| 大于 0 | 实际人员比申请记录多 | 补入场记录,request_num 为负数 |
例如:
actual_count = 27
total_request_num = -28
diff = -1说明申请记录多占 1 个编制。补一条 offboard/+1 后:
27 + (-28 + 1) = 03. 差异检查 SQL
SELECT
t.project_id,
SUM(t.actual_count) AS actual_count,
SUM(t.total_request_num) AS total_request_num,
SUM(t.actual_count + t.total_request_num) AS diff
FROM (
SELECT
project_id,
COUNT(*) AS actual_count,
0 AS total_request_num
FROM outsourced_staff
WHERE staff_status IN ('0', '2', '3', '-3', '-2')
GROUP BY project_id
UNION ALL
SELECT
project_id,
0 AS actual_count,
COALESCE(SUM(request_num), 0) AS total_request_num
FROM outsourced_staff_onoffboard_application_project
GROUP BY project_id
) t
GROUP BY t.project_id
HAVING SUM(t.actual_count + t.total_request_num) != 0;第一段统计实际外包人员;第二段统计所有入离场申请的净编制变化;最外层只显示不一致的项目。
3.1 查询结果示例
以下是一次实际查询结果:
| project_id | actual_count | total_request_num | diff | 说明与处理方向 |
|---|---|---|---|---|
| XM202600000082 | 23 | -22 | 1 | 实际人员比申请记录多 1 人;排除在途申请后,补 1 条 onboard,request_num=-1。 |
| XM202600000112 | 1 | -2 | -1 | 申请记录比实际人员多占 1 个编制;排除在途申请后,补 1 条 offboard,request_num=1。 |
| XM202600000114 | 1 | -2 | -1 | 与 XM202600000112 相同:排除在途申请后,补 offboard/+1。 |
| XM202600000010 | 193 | -195 | -2 | 申请记录比实际人员多占 2 个编制;排除在途申请后,补 1 条 offboard,request_num=2。 |
以 XM202600000010 为例:
修复前:193 + (-195) = -2
补离场:request_num = 2
修复后:193 + (-195 + 2) = 0上表的“补数方向”只在没有正常在途申请时成立;具体判断和检查 SQL 见下一节。
4. 补数据前必须排除在途申请
新人已创建入场申请、但人员尚未真正入场时,申请会先占用编制。这种情况出现 diff=-1 是正常现象,不能补离场记录。
SELECT
a.id,
a.application_type,
a.status,
a.remark,
p.request_num,
p.gmt_create
FROM outsourced_staff_onoffboard_application_project p
JOIN outsourced_staff_onoffboard_application a
ON a.id = p.application_batch_id
WHERE p.project_id = '项目编码'
AND a.status IN ('init', 'approving')
ORDER BY p.gmt_create DESC;| 排查结果 | 应该怎么做 |
|---|---|
| 有正常的在途入场申请 | 不补,申请正在预占编制 |
| 有废弃或重复草稿 | 清理或终止旧申请,不额外造离场记录 |
| 没有在途申请且 diff 小于 0 | 补离场修正记录 |
| 没有在途申请且 diff 大于 0 | 补入场修正记录 |
| 修正后仍无可用编制 | 项目额度不足,应调整项目额度 |
同时查询项目最新配置:
SELECT
project_code,
project_quota,
business_line_code,
start_year,
end_year,
version,
status
FROM project_detail
WHERE project_code = '项目编码'
ORDER BY version DESC
LIMIT 1;可用编制 = project_quota + SUM(request_num)5. 两种补数写法
5.1 diff 小于 0:补离场记录,释放编制
适用:申请记录多占了 N 个编制。
application_type = offboard
request_num = NINSERT INTO outsourced_staff_onoffboard_application
(
id, application_type, application_date, remark,
flow_req_id, flow_instance_id, status,
gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid
)
VALUES
(
'申请流水号',
'offboard',
'实际修正日期',
'历史数据迁移差异修正记录项目编码',
'申请流水号',
'申请流水号',
'success',
NOW(), NOW(),
'1010001233', '1010001233', 'cic', 1
);5.2 diff 大于 0:补入场记录,补齐占用
适用:实际人员比申请记录多 N 人。
application_type = onboard
request_num = -NINSERT INTO outsourced_staff_onoffboard_application
(
id, application_type, application_date, remark,
flow_req_id, flow_instance_id, status,
gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid
)
VALUES
(
'申请流水号',
'onboard',
'实际修正日期',
'历史数据迁移差异修正记录项目编码',
'申请流水号',
'申请流水号',
'success',
NOW(), NOW(),
'1010001233', '1010001233', 'cic', 1
);两个字段的关系固定如下:
主表.id = 项目明细.application_batch_id
主表.id = flow_req_id = flow_instance_id历史迁移场景没有真实流程,所以后两个字段用主表流水号占位。
6. 项目明细怎么填
INSERT INTO outsourced_staff_onoffboard_application_project
(
id, application_batch_id, project_id, business_line_code, project_year,
project_num, available_num, request_num, remain_num,
gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid
)
VALUES
(
'项目明细流水号',
'申请流水号',
'项目编码',
'项目业务线编码',
'项目计划年度',
'项目额度或历史迁移快照人数',
'修正前可用编制',
'本次修正数量',
'修正后可用编制',
NOW(), NOW(),
'1010001233', '1010001233', 'cic', 1
);| 字段 | 含义 |
|---|---|
| application_batch_id | 主表 outsourced_staff_onoffboard_application.id |
| project_id | 项目编码,例如 XM202600000150 |
| business_line_code | 从项目最新 project_detail.business_line_code 获取 |
| project_year | 计划年度字符串,不是完整终止日期;按项目年度区间内启用的计划年度取值 |
| project_num | 正常业务记录中是项目额度;历史迁移旧记录可能按迁移人数保存,保持同类记录口径一致 |
| available_num | 修正前可用编制 |
| request_num | 入场负、离场正 |
| remain_num | 修正后可用编制 |
若本次修复的目的就是让新人能入场,应使用一致的快照公式:
project_num = project_quota
available_num = project_quota + 修正前 SUM(request_num)
remain_num = available_num + request_num例如项目额度 28、当前申请汇总 -28,补一条离场 +1:
project_num = 28
available_num = 0
request_num = 1
remain_num = 1旧历史迁移数据中常见 available_num=0、remain_num=0,那是迁移快照口径;如果目标是立即释放可用编制,以上述计算结果为准。
7. 可直接使用的修复模板
以下模板不使用存储过程、不使用变量、不自动查询项目数据。按查询结果手工替换每个单引号中的占位内容后,依次执行两条 INSERT。
7.1 diff 小于 0 或 request_num 需要补正数:使用离场主表
例如 diff=-1,则项目明细 request_num 填 1;diff=-2,则填 2。
INSERT INTO outsourced_staff_onoffboard_application
(id, application_type, application_date, remark, flow_req_id, flow_instance_id, status, gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid)
VALUES
(
'申请主表流水号',
'offboard',
'实际修正日期,例如2026-07-31',
'历史数据迁移申请记录项目编码',
'申请主表流水号',
'申请主表流水号',
'success',
NOW(),
NOW(),
'1010001233',
'1010001233',
'cic',
1
);7.2 diff 大于 0 或 request_num 需要补负数:使用入场主表
例如 diff=1,则项目明细 request_num 填 -1;diff=2,则填 -2。
INSERT INTO outsourced_staff_onoffboard_application
(id, application_type, application_date, remark, flow_req_id, flow_instance_id, status, gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid)
VALUES
(
'申请主表流水号',
'onboard',
'实际修正日期,例如2026-07-31',
'历史数据迁移申请记录项目编码',
'申请主表流水号',
'申请主表流水号',
'success',
NOW(),
NOW(),
'1010001233',
'1010001233',
'cic',
1
);7.3 两种情况共用的项目明细表
INSERT INTO outsourced_staff_onoffboard_application_project
(id, application_batch_id, project_id, business_line_code, project_year, project_num, available_num, request_num, remain_num, gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid)
VALUES
(
'项目明细流水号',
'申请主表流水号',
'项目编码,例如XM202600000112',
'项目对应的business_line_code',
'项目计划年度,例如2026',
'该项目的历史迁移快照人数',
0,
'本次修正值:离场填正数、入场填负数',
0,
NOW(),
NOW(),
'1010001233',
'1010001233',
'cic',
1
);填写规则:
申请主表流水号:32位 UUID 或项目使用的流水号;主表 id、flow_req_id、flow_instance_id 必须相同。
项目明细流水号:另一个新的、不重复的流水号。
application_batch_id:必须等于申请主表流水号。
project_id:差异检查 SQL 返回的项目编码。
business_line_code:查询项目对应业务线后填写。
project_year:填写项目计划年度;不要填写完整日期。
project_num:按同项目既有历史迁移记录的口径填写,通常是该项目当时的迁移人员总数。
request_num:diff 的相反数。diff=-N 填 N;diff=N 填 -N。执行后重新跑第 3 节的差异检查 SQL,确认该项目 diff=0。若不是 0,不要重复插入,先检查参数和在途申请。
7.4 具体示例:XM202600000112,diff=-1
该项目查询结果为实际人数 1、申请记录汇总 -2、diff=-1。确认无在途申请后,需要补 1 个正数,因此使用离场记录:offboard / request_num=1。
先在数据库生成两个不同的 32 位 UUID:
SELECT REPLACE(UUID(), '-', '');示例生成的两个 ID:
申请主表流水号:e10b18db8fdc11f1acdd6805caca9fe8
项目明细流水号:ab3210138fde11f1acdd6805caca9fe8执行以下两条 SQL:
INSERT INTO outsourced_staff_onoffboard_application
(
id, application_type, application_date, remark,
flow_req_id, flow_instance_id, status,
gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid
)
VALUES
(
'e10b18db8fdc11f1acdd6805caca9fe8',
'offboard',
'2026-07-31',
'历史数据迁移申请记录XM202600000112',
'e10b18db8fdc11f1acdd6805caca9fe8',
'e10b18db8fdc11f1acdd6805caca9fe8',
'success',
NOW(),
NOW(),
'1010001233',
'1010001233',
'cic',
1
);
INSERT INTO outsourced_staff_onoffboard_application_project
(
id, application_batch_id, project_id, business_line_code,
project_year, project_num, available_num, request_num, remain_num,
gmt_create, gmt_modified, modifier, creator, tenant_code, is_valid
)
VALUES
(
'ab3210138fde11f1acdd6805caca9fe8',
'e10b18db8fdc11f1acdd6805caca9fe8',
'XM202600000112',
'TX_LP',
2029,
3,
0,
1,
0,
NOW(),
NOW(),
'1010001233',
'1010001233',
'cic',
1
);本例字段对应关系:
主表 id = flow_req_id = flow_instance_id
项目明细 application_batch_id = 主表 id
request_num=1,所以 application_type 必须是 offboard执行后重新跑第 3 节差异检查 SQL。预期该项目由:
1 + (-2) = -1变为:
1 + (-2 + 1) = 0示例中的 TX_LP、2029、3 来自该项目已确认的历史迁移口径;用于其他项目时必须替换为目标项目的实际值。两个 UUID 也不能重复使用。
8. 示例中的 28、27、-28、0 是怎么得到的
以项目 XM202600000150 为例:
项目额度:28
实际在场人员:27
申请记录净占用:-28
系统可用编制:28 + (-28) = 0这四个数字来自两套不同的数据。实际人员数用于核对;入场申请是否允许,代码主要依据项目额度和入离场申请明细计算。
8.0 按顺序单独查询
以 XM202600000150 为例,按下面顺序执行,阅读和核对最直接:
-- 1. 项目额度
SELECT project_quota
FROM project_detail
WHERE project_code = 'XM202600000150'
ORDER BY version DESC
LIMIT 1;-- 2. 实际在场人数
SELECT COUNT(*) AS actual_count
FROM outsourced_staff
WHERE project_id = 'XM202600000150'
AND staff_status IN ('0', '2', '3', '-3', '-2');-- 3. 申请记录净占用
SELECT COALESCE(SUM(request_num), 0) AS total_request_num
FROM outsourced_staff_onoffboard_application_project
WHERE project_id = 'XM202600000150';-- 4. 手算可用编制
项目额度 + 申请记录净占用
28 + (-28) = 08.1 项目额度 28
数据来源:
project_detail.project_quota查询 SQL:
SELECT project_quota
FROM project_detail
WHERE project_code = 'XM202600000150'
ORDER BY version DESC
LIMIT 1;入场处理查询项目详情后,使用项目额度:
projectEntity.setProjectNum(projectYearAndQuotaDTO.getProjectQuota());8.2 实际在场人员 27
来自第 3 节核对 SQL 的第一段:
SELECT COUNT(*) AS actual_count
FROM outsourced_staff
WHERE project_id = 'XM202600000150'
AND staff_status IN ('0', '2', '3', '-3', '-2');结果为 27,表示人员表中当前归属该项目、且状态符合条件的人员数量。
这个数字用于发现数据差异;入场申请处理不会直接用 COUNT(outsourced_staff) 来扣减额度。
8.3 申请记录净占用 -28
来自第 3 节核对 SQL 的第二段:
SELECT COALESCE(SUM(request_num), 0) AS total_request_num
FROM outsourced_staff_onoffboard_application_project
WHERE project_id = 'XM202600000150';入场是负数、离场是正数。汇总为 -28,表示申请记录认为项目累计净入场并占用了 28 个编制。
入场处理代码也会按项目取出所有历史项目明细,并按 project_id 汇总 request_num:
Collectors.summingInt(
OutsourcedStaffOnOffboardApplicationProjectEntity::getRequestNum
)8.4 系统可用编制 0
入场处理中的计算公式:
projectEntity.setAvailableNum(
projectYearAndQuotaDTO.getProjectQuota() + requestedNum
);代入当前数据:
projectQuota = 28
requestedNum = -28
availableNum = 28 + (-28) = 0新人再申请入场 1 人时,本次 request_num 会是 -1,申请后的剩余数会变成:
remainNum = 28 + (-28) + (-1) = -1因此系统无法再分配编制,页面显示“项目人员已满”。
8.5 为什么补 offboard/+1 能释放名额
确认没有在途申请后,补一条离场修正记录:
application_type = offboard
request_num = 1申请记录汇总从 -28 变为 -27:
修复后可用编制 = 28 + (-27) = 1新人正常入场后再产生 -1:
实际人员 = 28
申请记录汇总 = -28
可用编制 = 0
diff = 28 + (-28) = 0也就是说,补数不修改人员表的 27 人,而是修正申请明细表中多占用的 1 个编制。
9. 与项目模块的关联
项目模块不直接统计 outsourced_staff 表,而是通过项目编码查询入离场申请明细:
project_detail.project_code
=
outsourced_staff_onoffboard_application_project.project_id项目审批发起时,将以下值写入流程中心:
play_remain_num 计划剩余额度
entrance_people_num 入离场申请记录汇总得到的已用编制
origin_play_num 原项目编制项目处于 APPROVING 时读取流程快照;审批完成后重新实时汇总申请记录。因此审批中的项目,即使修复了外包人员数据,审批页面仍可能展示发起审批时的旧快照。
10. 最短操作清单
- 跑差异检查 SQL,拿到 diff。
- 查在途申请;有在途申请先处理申请,不补数。
- 查项目额度。
- diff 小于 0 补 offboard/+N;diff 大于 0 补 onboard/-N。
- 在事务内执行,验证 diff=0 后再提交。
- 同一项目的同一笔修正不能重复执行。