fix(aihr): reject duplicate organization identities
This commit is contained in:
+20
@@ -27,8 +27,10 @@ import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -105,6 +107,7 @@ public class AihrOrgSyncService {
|
||||
}
|
||||
int phoneLinked = (int) rows.stream().filter(row -> !row.personPhone().isBlank()).count();
|
||||
int suspectText = (int) rows.stream().filter(AihrOrgSyncService::hasSuspectText).count();
|
||||
int duplicatePartyIds = countDuplicatePartyIds(rows.stream().map(OrgRow::extPartyId).toList());
|
||||
if (!rows.isEmpty() && phoneLinked < rows.size()) {
|
||||
warnings.add("外部员工快照手机号覆盖 " + phoneLinked + "/" + rows.size() + ",移动端正式登录仅可映射已带手机号人员");
|
||||
}
|
||||
@@ -114,9 +117,15 @@ public class AihrOrgSyncService {
|
||||
if (maskedPhone > 0) {
|
||||
warnings.add("外部员工快照存在 " + maskedPhone + " 条脱敏/不可用手机号,不能用于移动端正式登录映射");
|
||||
}
|
||||
if (duplicatePartyIds > 0) {
|
||||
warnings.add("外部员工快照存在 " + duplicatePartyIds + " 条重复 ext_party_id,写入会被唯一键折叠,请先修复上游快照");
|
||||
}
|
||||
if (!dryRun && replaceExisting && rows.isEmpty()) {
|
||||
throw new IllegalArgumentException("外部员工快照为空,已阻止覆盖本地组织人员快照");
|
||||
}
|
||||
if (!dryRun && duplicatePartyIds > 0) {
|
||||
throw new IllegalArgumentException("外部员工快照存在重复 ext_party_id,已阻止写入组织人员快照;请先修复上游身份数据");
|
||||
}
|
||||
if (!dryRun && replaceExisting && !allowPartialReplace
|
||||
&& hasUnsafeReplaceData(employeeItems.size(), rows.size(), skipped, phoneLinked, maskedPhone, suspectText)) {
|
||||
throw new IllegalArgumentException("外部员工快照存在不完整或疑似异常数据,已阻止覆盖本地组织人员快照;请先 dry-run,确认后显式传 allowPartialReplace=true");
|
||||
@@ -150,6 +159,17 @@ public class AihrOrgSyncService {
|
||||
|| suspectText > 0;
|
||||
}
|
||||
|
||||
static int countDuplicatePartyIds(List<String> extPartyIds) {
|
||||
Set<String> seen = new HashSet<>();
|
||||
int duplicates = 0;
|
||||
for (String extPartyId : extPartyIds) {
|
||||
if (!seen.add(extPartyId)) {
|
||||
duplicates++;
|
||||
}
|
||||
}
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
public OrgSnapshotResponse snapshot(String keyword, String projectCode, String positionLevel, String status,
|
||||
Integer pageNum, Integer pageSize, Integer limit) {
|
||||
requireSnapshotTable();
|
||||
|
||||
+8
@@ -11,6 +11,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -37,6 +38,13 @@ public class AihrOrgSyncServiceTest {
|
||||
assertFalse(AihrOrgSyncService.hasUnsafeReplaceData(22, 22, 0, 22, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicateExternalPartyIdsAreCountedAfterTheFirstOccurrence() {
|
||||
assertEquals(1, AihrOrgSyncService.countDuplicatePartyIds(List.of("EMP-1", "EMP-2", "EMP-1")));
|
||||
assertEquals(2, AihrOrgSyncService.countDuplicatePartyIds(List.of("EMP-1", "EMP-1", "EMP-1")));
|
||||
assertEquals(0, AihrOrgSyncService.countDuplicatePartyIds(List.of("EMP-1", "EMP-2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unusablePhoneCandidateDetectsMaskedPhone() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
Reference in New Issue
Block a user