diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java index 054ce95a..875004d0 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java @@ -302,21 +302,34 @@ public class AihrOrgSyncService { } private static ExternalDirectoryNode directoryNode(JsonNode item, String nodeType) { - String externalId = switch (nodeType) { + String rawExternalId = switch (nodeType) { case "GROUP" -> firstNonBlank(text(item, "id", "group_id", "groupId")); case "COMPANY" -> firstNonBlank(text(item, "id", "company_id", "companyId")); case "PROJECT" -> firstNonBlank(text(item, "id", "project_id", "projectId")); case "DEPARTMENT" -> firstNonBlank(text(item, "id", "department_id", "departmentId", "dept_id", "deptId")); default -> ""; }; - if (externalId.isBlank()) { + if (rawExternalId.isBlank()) { return null; } + String externalId = stableDirectoryId(nodeType, rawExternalId); String parentExternalId = switch (nodeType) { - case "GROUP" -> firstNonBlank(text(item, "parent_group_id", "parentGroupId", "parent_id", "parentId")); - case "COMPANY" -> firstNonBlank(text(item, "group_id", "groupId", "parent_group_id", "parentGroupId", "parent_id", "parentId")); - case "PROJECT" -> firstNonBlank(text(item, "company_id", "companyId", "group_id", "groupId", "parent_id", "parentId")); - case "DEPARTMENT" -> firstNonBlank(text(item, "project_id", "projectId", "company_id", "companyId", "group_id", "groupId", "parent_id", "parentId")); + case "GROUP" -> stableDirectoryId("GROUP", firstNonBlank(text(item, "parent_group_id", "parentGroupId", "parent_id", "parentId"))); + case "COMPANY" -> firstNonBlank( + stableDirectoryId("GROUP", text(item, "group_id", "groupId", "parent_group_id", "parentGroupId")), + stableDirectoryId("GROUP", text(item, "parent_id", "parentId")) + ); + case "PROJECT" -> firstNonBlank( + stableDirectoryId("COMPANY", text(item, "company_id", "companyId")), + stableDirectoryId("GROUP", text(item, "group_id", "groupId")), + stableDirectoryId("COMPANY", text(item, "parent_id", "parentId")) + ); + case "DEPARTMENT" -> firstNonBlank( + stableDirectoryId("PROJECT", text(item, "project_id", "projectId")), + stableDirectoryId("COMPANY", text(item, "company_id", "companyId")), + stableDirectoryId("GROUP", text(item, "group_id", "groupId")), + stableDirectoryId("DEPARTMENT", text(item, "parent_id", "parentId")) + ); default -> ""; }; if (externalId.length() > 128 || parentExternalId.length() > 128) { @@ -332,6 +345,26 @@ public class AihrOrgSyncService { return new ExternalDirectoryNode(externalId, parentExternalId, nodeType, truncate(name, 200)); } + static String rawDirectoryId(String stableId, String nodeType) { + String prefix = directoryIdPrefix(nodeType); + return stableId != null && stableId.startsWith(prefix) ? stableId.substring(prefix.length()) : clean(stableId); + } + + private static String stableDirectoryId(String nodeType, String rawId) { + String value = clean(rawId); + return value.isBlank() ? "" : directoryIdPrefix(nodeType) + value; + } + + private static String directoryIdPrefix(String nodeType) { + return switch (nodeType) { + case "GROUP" -> "G:"; + case "COMPANY" -> "C:"; + case "PROJECT" -> "P:"; + case "DEPARTMENT" -> "D:"; + default -> throw new IllegalArgumentException("不支持的组织节点类型:" + nodeType); + }; + } + private List fetchSnapshot(String baseUrl, String token, SyncRequest req, String resourceType, int pageSize, int maxPages) { List items = new ArrayList<>(); diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrTenantOrgGovernanceService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrTenantOrgGovernanceService.java index 1d8e967c..e64e0ef5 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrTenantOrgGovernanceService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrTenantOrgGovernanceService.java @@ -227,10 +227,11 @@ public class AihrTenantOrgGovernanceService { String groupId = null; String companyId = null; String departmentId = null; + String sourceExternalId = AihrOrgSyncService.rawDirectoryId(binding.rootExternalId(), binding.rootNodeType()); switch (binding.rootNodeType()) { - case "GROUP" -> groupId = binding.rootExternalId(); - case "COMPANY" -> companyId = binding.rootExternalId(); - case "DEPARTMENT" -> departmentId = binding.rootExternalId(); + case "GROUP" -> groupId = sourceExternalId; + case "COMPANY" -> companyId = sourceExternalId; + case "DEPARTMENT" -> departmentId = sourceExternalId; default -> throw new ServiceException("ORG_SCOPE_TYPE_NOT_SYNCABLE", HttpStatus.CONFLICT); } SyncRequest source = requested == null diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java index a185d03c..2bf4c636 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java @@ -296,10 +296,10 @@ public class AihrOrgSyncServiceTest { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/api/open/v1/sync/snapshot", exchange -> { String query = exchange.getRequestURI().getRawQuery(); - String items = query.contains("resource_type=group") ? "[{\"id\":\"G-1\",\"name\":\"银城集团\"}]" - : query.contains("resource_type=company") ? "[{\"id\":\"C-1\",\"group_id\":\"G-1\",\"name\":\"南京公司\"}]" - : query.contains("resource_type=project") ? "[{\"id\":\"P-1\",\"company_id\":\"C-1\",\"name\":\"翡翠湾项目\"}]" - : query.contains("resource_type=department") ? "[{\"id\":\"D-1\",\"project_id\":\"P-1\",\"name\":\"客服部\"}]" : "[]"; + String items = query.contains("resource_type=group") ? "[{\"id\":\"1\",\"name\":\"银城集团\"}]" + : query.contains("resource_type=company") ? "[{\"id\":\"1\",\"group_id\":\"1\",\"name\":\"南京公司\"}]" + : query.contains("resource_type=project") ? "[{\"id\":\"1\",\"company_id\":\"1\",\"name\":\"翡翠湾项目\"}]" + : query.contains("resource_type=department") ? "[{\"id\":\"1\",\"project_id\":\"1\",\"name\":\"客服部\"}]" : "[]"; byte[] body = ("{\"data\":{\"items\":" + items + ",\"total\":1,\"has_more\":false}}").getBytes(StandardCharsets.UTF_8); exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); exchange.sendResponseHeaders(200, body.length); @@ -319,9 +319,10 @@ public class AihrOrgSyncServiceTest { var snapshot = service.directorySnapshot(); assertEquals(4, snapshot.nodes().size()); - assertTrue(snapshot.nodes().stream().anyMatch(node -> "GROUP".equals(node.nodeType()) && "G-1".equals(node.externalId()))); + assertEquals(4, snapshot.nodes().stream().map(node -> node.externalId()).distinct().count()); + assertTrue(snapshot.nodes().stream().anyMatch(node -> "GROUP".equals(node.nodeType()) && "G:1".equals(node.externalId()))); assertTrue(snapshot.nodes().stream().anyMatch(node -> "DEPARTMENT".equals(node.nodeType()) - && "P-1".equals(node.parentExternalId()))); + && "P:1".equals(node.parentExternalId()))); } finally { server.stop(0); } diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrTenantOrgGovernanceServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrTenantOrgGovernanceServiceTest.java index 7bdb9517..61ee5fff 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrTenantOrgGovernanceServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrTenantOrgGovernanceServiceTest.java @@ -104,10 +104,10 @@ class AihrTenantOrgGovernanceServiceTest { when(resultSet.getString("tenant_id")).thenReturn("000000"); when(resultSet.getString("tenant_name")).thenReturn("平台租户"); when(resultSet.getString("source_code")).thenReturn(AihrTenantOrgGovernanceService.SOURCE_CODE); - when(resultSet.getString("root_external_id")).thenReturn("C-1"); + when(resultSet.getString("root_external_id")).thenReturn("C:C-1"); when(resultSet.getString("root_name")).thenReturn("南京公司"); when(resultSet.getString("root_node_type")).thenReturn("COMPANY"); - when(resultSet.getString("root_path")).thenReturn("G-1/C-1"); + when(resultSet.getString("root_path")).thenReturn("G:G-1/C:C-1"); when(resultSet.getString("mode")).thenReturn(mode); when(resultSet.getString("status")).thenReturn(AihrTenantOrgGovernanceService.ACTIVE); when(resultSet.getString("directory_version")).thenReturn("v1");