init
parent
b313663589
commit
76070ba58a
|
|
@ -342,11 +342,17 @@ public class ComplianceCheckUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* 行业专业能力人员要求校验
|
||||
* 行业专业能力人员要求校验(一人一岗)
|
||||
* <p>
|
||||
* 每个人员(sourcePersonnelId)可能具备多个专业能力,但只能被分配到一个专业能力岗位。
|
||||
* 使用最大流算法(Dinic)求解最优分配方案,判断是否能满足所有专业能力的人数要求。
|
||||
* <p>
|
||||
* 核心约束:certCapabilityList 中 personnelId == industryProfessionalPersonCos 中 sourcePersonnelId 标识同一人,
|
||||
* 一个人最多只能占用一个专业能力名额。
|
||||
*
|
||||
* @param standardsDOList 人员要求标准(按 行业code + 专业能力code 分类的人数要求)
|
||||
* @param industryProfessionalPersonCos 当前实际人员数(按 行业code + 专业能力code 分类汇总)
|
||||
* @param certCapabilityList certCapabilityList
|
||||
* @param industryProfessionalPersonCos 当前实际人员(用于确定参检人员范围)
|
||||
* @param certCapabilityList 人员证书专业能力信息(提供人员→专业能力映射)
|
||||
* @return Pair<Boolean, String> key 是否通过 ,value 失败消息(通过时为 null)
|
||||
*/
|
||||
public static Pair<Boolean, String> doIndustryProfessionalStandardsValid(List<IndustryProfessionalStandardsDO> standardsDOList
|
||||
|
|
@ -355,38 +361,212 @@ public class ComplianceCheckUtil {
|
|||
return Pair.of(true, null);
|
||||
}
|
||||
|
||||
// 当前人员数按 行业code + 专业能力code 分类汇总
|
||||
Map<String, Long> currentCountMap = industryProfessionalPersonCos.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
c -> groupKey(c.getIndustryCode(), c.getProfessionalCapabilityCode()),
|
||||
Collectors.summingLong(c -> c.getPersonNum() == null ? 0L : c.getPersonNum())));
|
||||
// 1. 汇总每个专业能力code的要求人数
|
||||
Map<String, Integer> requiredMap = standardsDOList.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IndustryProfessionalStandardsDO::getProfessionalCapabilityCode,
|
||||
s -> s.getPersonNum() != null ? s.getPersonNum().intValue() : 0,
|
||||
Integer::sum,
|
||||
LinkedHashMap::new));
|
||||
|
||||
// 2. 获取本行业备案人员集合(确定参检人员范围)
|
||||
Set<Long> filingPersonIds = industryProfessionalPersonCos.stream()
|
||||
.map(IndustryProfessionalPersonCo::getSourcePersonnelId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 3. 构建人员→可担任专业能力code集合(同一人可能具备多个专业能力)
|
||||
Map<Long, Set<String>> personCapabilityMap = new HashMap<>();
|
||||
for (OrgPersonnelCertCapabilityComplianceCheckCO cert : certCapabilityList) {
|
||||
Long pid = cert.getPersonnelId();
|
||||
if (pid != null && filingPersonIds.contains(pid) && cert.getProfessionalCapabilityCode() != null) {
|
||||
personCapabilityMap.computeIfAbsent(pid, k -> new HashSet<>())
|
||||
.add(cert.getProfessionalCapabilityCode());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 一人一岗最优分配
|
||||
Map<String, Integer> assignedMap = assignPersonnelByMaxFlow(personCapabilityMap, requiredMap);
|
||||
|
||||
// 5. 判断是否所有专业能力都达标,并生成失败消息
|
||||
boolean allMet = true;
|
||||
IndustryEnum industryEnum = IndustryEnum.ofCode(standardsDOList.get(0).getIndustryCode());
|
||||
String industryName = industryEnum != null ? industryEnum.getValue() : standardsDOList.get(0).getIndustryCode();
|
||||
|
||||
// 要求按 行业code + 专业能力code 分类汇总,并记录专业能力名称
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
for (IndustryProfessionalStandardsDO s : standardsDOList) {
|
||||
String key = groupKey(s.getIndustryCode(), s.getProfessionalCapabilityCode());
|
||||
Long currentPerson = currentCountMap.getOrDefault(key, 0L);
|
||||
if (s.getPersonNum() > currentPerson) {
|
||||
IndustryEnum industryEnum = IndustryEnum.ofCode(s.getIndustryCode());
|
||||
ProfessionalCapabilityEnum professionalCapabilityEnum = ProfessionalCapabilityEnum.ofCode(s.getProfessionalCapabilityCode());
|
||||
failureMsg.append(industryEnum.getValue())
|
||||
for (Map.Entry<String, Integer> entry : requiredMap.entrySet()) {
|
||||
String capCode = entry.getKey();
|
||||
int required = entry.getValue();
|
||||
int assigned = assignedMap.getOrDefault(capCode, 0);
|
||||
if (assigned < required) {
|
||||
allMet = false;
|
||||
ProfessionalCapabilityEnum capEnum = ProfessionalCapabilityEnum.ofCode(capCode);
|
||||
String capName = capEnum != null ? capEnum.getValue() : capCode;
|
||||
failureMsg.append(industryName)
|
||||
.append(":")
|
||||
.append(professionalCapabilityEnum.getValue())
|
||||
.append(capName)
|
||||
.append(" 需要")
|
||||
.append(s.getPersonNum())
|
||||
.append("人,当前")
|
||||
.append(currentPerson)
|
||||
.append("人");
|
||||
.append(required)
|
||||
.append("人,因一人一岗最多可分配")
|
||||
.append(assigned)
|
||||
.append("人;");
|
||||
}
|
||||
}
|
||||
return failureMsg.length() > 0 ? Pair.of(false, failureMsg.toString()) : Pair.of(true, null);
|
||||
return allMet ? Pair.of(true, null) : Pair.of(false, failureMsg.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 行业code + 专业能力code 分组键
|
||||
* 一人一岗最优分配算法(最大流 / Dinic)
|
||||
* <p>
|
||||
* 每个人员最多分配到一个专业能力岗位,每个岗位有最低人数要求,
|
||||
* 求解在"一人一岗"约束下各专业能力实际可分配到的人数。
|
||||
* <p>
|
||||
* 网络结构: source → 人员节点(cap=1) → 专业能力节点(cap=1) → sink(cap=要求人数)
|
||||
*
|
||||
* @param personCapabilityMap 人员→可担任专业能力code集合(同一人可能具备多个专业能力)
|
||||
* @param requiredMap 专业能力code→要求人数
|
||||
* @return 专业能力code→实际分配人数
|
||||
*/
|
||||
private static String groupKey(String industryCode, String professionalCapabilityCode) {
|
||||
return (industryCode == null ? "" : industryCode) + "_"
|
||||
+ (professionalCapabilityCode == null ? "" : professionalCapabilityCode);
|
||||
private static Map<String, Integer> assignPersonnelByMaxFlow(Map<Long, Set<String>> personCapabilityMap
|
||||
, Map<String, Integer> requiredMap) {
|
||||
List<Long> personIds = new ArrayList<>(personCapabilityMap.keySet());
|
||||
List<String> capCodes = new ArrayList<>(requiredMap.keySet());
|
||||
Map<String, Integer> capIndex = new HashMap<>();
|
||||
for (int j = 0; j < capCodes.size(); j++) {
|
||||
capIndex.put(capCodes.get(j), j);
|
||||
}
|
||||
|
||||
int numPersons = personIds.size();
|
||||
int numCaps = capCodes.size();
|
||||
int source = 0;
|
||||
int sink = numPersons + numCaps + 1;
|
||||
MaxFlow mf = new MaxFlow(sink + 1);
|
||||
|
||||
// source → person (capacity 1: 每人最多分配一个岗位)
|
||||
for (int i = 0; i < numPersons; i++) {
|
||||
mf.addEdge(source, 1 + i, 1);
|
||||
}
|
||||
// person → capability (capacity 1: 该人可担任该专业能力)
|
||||
for (int i = 0; i < numPersons; i++) {
|
||||
Set<String> caps = personCapabilityMap.get(personIds.get(i));
|
||||
if (caps != null) {
|
||||
for (String cap : caps) {
|
||||
Integer cj = capIndex.get(cap);
|
||||
if (cj != null) {
|
||||
mf.addEdge(1 + i, 1 + numPersons + cj, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// capability → sink (capacity = 该专业能力要求人数)
|
||||
int[] capEdgeIds = new int[numCaps];
|
||||
for (int j = 0; j < numCaps; j++) {
|
||||
capEdgeIds[j] = mf.addEdgeReturnId(1 + numPersons + j, sink, requiredMap.get(capCodes.get(j)));
|
||||
}
|
||||
|
||||
// 求解最大流
|
||||
mf.maxFlow(source, sink);
|
||||
|
||||
// 提取每个专业能力的实际分配人数
|
||||
Map<String, Integer> assignedMap = new LinkedHashMap<>();
|
||||
for (int j = 0; j < numCaps; j++) {
|
||||
assignedMap.put(capCodes.get(j), mf.getFlow(capEdgeIds[j]));
|
||||
}
|
||||
return assignedMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dinic 最大流算法(内部使用)
|
||||
* <p>
|
||||
* 用于求解"一人一岗"分配问题:将人员分配到专业能力岗位,
|
||||
* 每人最多分配一个岗位,每个岗位有最低人数要求。
|
||||
* <p>
|
||||
* 网络结构: source → 人员节点(cap=1) → 专业能力节点(cap=1) → sink(cap=要求人数)
|
||||
* 若最大流 = 所有人数要求之和,则存在合法分配方案。
|
||||
*/
|
||||
private static class MaxFlow {
|
||||
private final int n;
|
||||
private final List<int[]> edges;
|
||||
private final List<List<Integer>> adj;
|
||||
private final int[] level;
|
||||
private final int[] iter;
|
||||
|
||||
MaxFlow(int n) {
|
||||
this.n = n;
|
||||
this.edges = new ArrayList<>();
|
||||
this.adj = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
adj.add(new ArrayList<>());
|
||||
}
|
||||
this.level = new int[n];
|
||||
this.iter = new int[n];
|
||||
}
|
||||
|
||||
void addEdge(int from, int to, int cap) {
|
||||
addEdgeReturnId(from, to, cap);
|
||||
}
|
||||
|
||||
int addEdgeReturnId(int from, int to, int cap) {
|
||||
int edgeId = edges.size();
|
||||
adj.get(from).add(edgeId);
|
||||
edges.add(new int[]{to, cap});
|
||||
adj.get(to).add(edgeId + 1);
|
||||
edges.add(new int[]{from, 0});
|
||||
return edgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某条正向边的实际流量
|
||||
*/
|
||||
int getFlow(int edgeId) {
|
||||
return edges.get(edgeId ^ 1)[1];
|
||||
}
|
||||
|
||||
int maxFlow(int s, int t) {
|
||||
int flow = 0;
|
||||
while (bfs(s, t)) {
|
||||
Arrays.fill(iter, 0);
|
||||
int f;
|
||||
while ((f = dfs(s, t, Integer.MAX_VALUE)) > 0) {
|
||||
flow += f;
|
||||
}
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
|
||||
private boolean bfs(int s, int t) {
|
||||
Arrays.fill(level, -1);
|
||||
Queue<Integer> queue = new ArrayDeque<>();
|
||||
level[s] = 0;
|
||||
queue.add(s);
|
||||
while (!queue.isEmpty()) {
|
||||
int v = queue.poll();
|
||||
for (int ei : adj.get(v)) {
|
||||
int[] e = edges.get(ei);
|
||||
if (e[1] > 0 && level[e[0]] < 0) {
|
||||
level[e[0]] = level[v] + 1;
|
||||
queue.add(e[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return level[t] >= 0;
|
||||
}
|
||||
|
||||
private int dfs(int v, int t, int f) {
|
||||
if (v == t) return f;
|
||||
for (; iter[v] < adj.get(v).size(); iter[v]++) {
|
||||
int ei = adj.get(v).get(iter[v]);
|
||||
int[] e = edges.get(ei);
|
||||
if (e[1] > 0 && level[v] < level[e[0]]) {
|
||||
int d = dfs(e[0], t, Math.min(f, e[1]));
|
||||
if (d > 0) {
|
||||
e[1] -= d;
|
||||
edges.get(ei ^ 1)[1] += d;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue