iwork 人员组织同步相关

This commit is contained in:
chenbowen
2025-11-26 10:42:24 +08:00
parent 4c79ac8a6d
commit 12ba2cf756
8 changed files with 77 additions and 54 deletions

View File

@@ -14,6 +14,9 @@ public class AdminUserRespDTO implements VO {
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id; private Long id;
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zhangsan")
private String username;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王") @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
private String nickname; private String nickname;

View File

@@ -114,17 +114,23 @@ public class IWorkIntegrationController {
// ----------------- 同步到本地 ----------------- // ----------------- 同步到本地 -----------------
@PostMapping("/hr/full-sync") @PostMapping("/hr/full-sync")
@Operation(summary = "手动触发 iWork 组织/人员全量同步") @Operation(summary = "手动触发 iWork 组织/人员同步")
public CommonResult<IWorkFullSyncRespVO> fullSync(@Valid @RequestBody IWorkFullSyncReqVO reqVO) { public CommonResult<IWorkFullSyncRespVO> fullSync(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
return success(syncService.fullSync(reqVO)); return success(syncService.fullSync(reqVO));
} }
@PostMapping("/hr/departments/full-sync") @PostMapping("/hr/departments/full-sync")
@Operation(summary = "手动触发 iWork 部门全量同步") @Operation(summary = "手动触发 iWork 部门同步")
public CommonResult<IWorkFullSyncRespVO> fullSyncDepartments(@Valid @RequestBody IWorkFullSyncReqVO reqVO) { public CommonResult<IWorkFullSyncRespVO> fullSyncDepartments(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
return success(syncService.fullSyncDepartments(reqVO)); return success(syncService.fullSyncDepartments(reqVO));
} }
@PostMapping("/hr/subcompanies/full-sync")
@Operation(summary = "手动触发 iWork 分部同步")
public CommonResult<IWorkFullSyncRespVO> fullSyncSubcompanies(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
return success(syncService.fullSyncSubcompanies(reqVO));
}
@PostMapping("/hr/job-titles/full-sync") @PostMapping("/hr/job-titles/full-sync")
@Operation(summary = "手动触发 iWork 岗位全量同步") @Operation(summary = "手动触发 iWork 岗位全量同步")
public CommonResult<IWorkFullSyncRespVO> fullSyncJobTitles(@Valid @RequestBody IWorkFullSyncReqVO reqVO) { public CommonResult<IWorkFullSyncRespVO> fullSyncJobTitles(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {

View File

@@ -159,13 +159,9 @@ public class IWorkHrDepartmentPageRespVO {
private String supsubcomname; private String supsubcomname;
@Schema(description = "父部门 ID") @Schema(description = "父部门 ID")
@JsonProperty("parentdeptid") @JsonProperty("supdepid")
@JsonDeserialize(using = LenientIntegerDeserializer.class) @JsonDeserialize(using = LenientIntegerDeserializer.class)
private Integer parentdeptid; private Integer supdepid;
@Schema(description = "父部门名称")
@JsonProperty("parentdeptname")
private String parentdeptname;
@Schema(description = "层级路径") @Schema(description = "层级路径")
@JsonProperty("alllevel") @JsonProperty("alllevel")

View File

@@ -5,11 +5,10 @@ import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -50,9 +49,10 @@ public class IWorkHrSubcompanyPageRespVO {
@Schema(description = "分部信息") @Schema(description = "分部信息")
public static class Subcompany { public static class Subcompany {
@Schema(description = "分部唯一 ID") @Schema(description = "部门 IDiWork 主键)")
@JsonProperty("subcompanyid1") @JsonProperty("id")
private Integer subcompanyid1; @JsonDeserialize(using = LenientIntegerDeserializer.class)
private Integer id;
@Schema(description = "分部编码") @Schema(description = "分部编码")
@JsonProperty("subcompanycode") @JsonProperty("subcompanycode")

View File

@@ -74,16 +74,23 @@ public class DeptServiceImpl implements DeptService {
// 校验部门名的唯一性 // 校验部门名的唯一性
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
// 生成并校验部门编码 // 生成并校验部门编码
Long effectiveParentId = normalizeParentId(createReqVO.getParentId()); boolean isIWorkSource = Objects.equals(createReqVO.getDeptSource(), DeptSourceEnum.IWORK.getSource());
boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT); if (isIWorkSource) {
String resolvedCode; // iWork 来源直接使用提供的编码,不再生成
if (isTopLevel) { String providedCode = StrUtil.blankToDefault(createReqVO.getCode(), null);
resolvedCode = resolveTopLevelCode(null, createReqVO.getCode()); createReqVO.setCode(providedCode);
} else { } else {
resolvedCode = generateDeptCode(effectiveParentId); Long effectiveParentId = normalizeParentId(createReqVO.getParentId());
validateDeptCodeUnique(null, resolvedCode); boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT);
String resolvedCode;
if (isTopLevel) {
resolvedCode = resolveTopLevelCode(null, createReqVO.getCode());
} else {
resolvedCode = generateDeptCode(effectiveParentId);
validateDeptCodeUnique(null, resolvedCode);
}
createReqVO.setCode(resolvedCode);
} }
createReqVO.setCode(resolvedCode);
// 插入部门 // 插入部门
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
@@ -110,28 +117,35 @@ public class DeptServiceImpl implements DeptService {
// 校验部门名的唯一性 // 校验部门名的唯一性
validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
// 如果上级发生变化,需要重新生成编码并同步子级 // 如果上级发生变化,需要重新生成编码并同步子级
boolean isIWorkSource = Objects.equals(originalDept.getDeptSource(), DeptSourceEnum.IWORK.getSource());
Long newParentId = normalizeParentId(updateReqVO.getParentId()); Long newParentId = normalizeParentId(updateReqVO.getParentId());
Long oldParentId = normalizeParentId(originalDept.getParentId()); Long oldParentId = normalizeParentId(originalDept.getParentId());
boolean parentChanged = !Objects.equals(newParentId, oldParentId); boolean parentChanged = !Objects.equals(newParentId, oldParentId);
if (parentChanged) { if (isIWorkSource) {
String newCode; // iWork 来源直接使用提供的编码,不再生成
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) { String providedCode = StrUtil.blankToDefault(updateReqVO.getCode(), null);
newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode()); updateReqVO.setCode(providedCode);
} else {
newCode = generateDeptCode(updateReqVO.getParentId());
validateDeptCodeUnique(updateReqVO.getId(), newCode);
}
updateReqVO.setCode(newCode);
} else { } else {
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) { if (parentChanged) {
String requestedCode = updateReqVO.getCode(); String newCode;
if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) { if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode)); newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode());
} else {
newCode = generateDeptCode(updateReqVO.getParentId());
validateDeptCodeUnique(updateReqVO.getId(), newCode);
}
updateReqVO.setCode(newCode);
} else {
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
String requestedCode = updateReqVO.getCode();
if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) {
updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode));
} else {
updateReqVO.setCode(originalDept.getCode());
}
} else { } else {
updateReqVO.setCode(originalDept.getCode()); updateReqVO.setCode(originalDept.getCode());
} }
} else {
updateReqVO.setCode(originalDept.getCode());
} }
} }

View File

@@ -16,10 +16,15 @@ public interface IWorkSyncService {
IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO); IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO);
/** /**
* 仅同步部门(会自动包含依赖的分部) * 仅同步部门
*/ */
IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO); IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO);
/**
* 仅同步分部
*/
IWorkFullSyncRespVO fullSyncSubcompanies(IWorkFullSyncReqVO reqVO);
/** /**
* 仅同步岗位 * 仅同步岗位
*/ */

View File

@@ -28,14 +28,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Slf4j @Slf4j
@@ -72,12 +65,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
while (iterator.hasNext()) { while (iterator.hasNext()) {
IWorkHrSubcompanyPageRespVO.Subcompany sub = iterator.next(); IWorkHrSubcompanyPageRespVO.Subcompany sub = iterator.next();
if (shouldSkipByCanceled(sub.getCanceled(), options)) { if (shouldSkipByCanceled(sub.getCanceled(), options)) {
logSkip("分部", sub.getSubcompanyid1(), "iWork 标记为失效且当前不同步失效记录"); logSkip("分部", sub.getId(), "iWork 标记为失效且当前不同步失效记录");
result.increaseSkipped(); result.increaseSkipped();
iterator.remove(); iterator.remove();
continue; continue;
} }
Integer externalId = sub.getSubcompanyid1(); Integer externalId = sub.getId();
if (externalId == null) { if (externalId == null) {
log.warn("[iWork] 分部缺少标识,跳过:{}", sub.getSubcompanyname()); log.warn("[iWork] 分部缺少标识,跳过:{}", sub.getSubcompanyname());
result.increaseFailed(); result.increaseFailed();
@@ -95,7 +88,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
options); options);
applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname()); applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname());
} catch (Exception ex) { } catch (Exception ex) {
log.error("[iWork] 同步分部失败: id={} name={}", sub.getSubcompanyid1(), sub.getSubcompanyname(), ex); log.error("[iWork] 同步分部失败: id={} name={}", sub.getId(), sub.getSubcompanyname(), ex);
result.increaseFailed(); result.increaseFailed();
result.withMessage("同步分部失败: " + ex.getMessage()); result.withMessage("同步分部失败: " + ex.getMessage());
} }
@@ -108,7 +101,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
} }
if (!queue.isEmpty()) { if (!queue.isEmpty()) {
for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) { for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) {
log.warn("[iWork] 分部因父级缺失未同步: id={} name={}", remaining.getSubcompanyid1(), remaining.getSubcompanyname()); log.warn("[iWork] 分部因父级缺失未同步: id={} name={}", remaining.getId(), remaining.getSubcompanyname());
result.increaseFailed(); result.increaseFailed();
} }
} }
@@ -397,7 +390,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
DeptSaveReqVO req = new DeptSaveReqVO(); DeptSaveReqVO req = new DeptSaveReqVO();
req.setId(deptId); req.setId(deptId);
req.setName(limitLength(StrUtil.blankToDefault(data.getSubcompanyname(), "未命名分部"), 30)); req.setName(limitLength(StrUtil.blankToDefault(data.getSubcompanyname(), "未命名分部"), 30));
req.setShortName(limitLength(data.getSubcompanyname(), 20)); // req.setShortName(limitLength(data.getSubcompanyname(), 20));
req.setCode(trimToNull(data.getSubcompanycode())); req.setCode(trimToNull(data.getSubcompanycode()));
req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId); req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId);
req.setSort(defaultSort(data.getShoworder())); req.setSort(defaultSort(data.getShoworder()));
@@ -415,7 +408,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
DeptSaveReqVO req = new DeptSaveReqVO(); DeptSaveReqVO req = new DeptSaveReqVO();
req.setId(deptId); req.setId(deptId);
req.setName(limitLength(StrUtil.blankToDefault(data.getDepartmentname(), "未命名部门"), 30)); req.setName(limitLength(StrUtil.blankToDefault(data.getDepartmentname(), "未命名部门"), 30));
req.setShortName(limitLength(StrUtil.blankToDefault(data.getDepartmentmark(), data.getDepartmentname()), 20)); // req.setShortName(limitLength(StrUtil.blankToDefault(data.getDepartmentmark(), data.getDepartmentname()), 20));
req.setCode(trimToNull(data.getDepartmentcode())); req.setCode(trimToNull(data.getDepartmentcode()));
req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId); req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId);
req.setSort(defaultSort(data.getShoworder())); req.setSort(defaultSort(data.getShoworder()));
@@ -472,8 +465,8 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
} }
private ParentHolder resolveDepartmentParent(IWorkHrDepartmentPageRespVO.Department dept) { private ParentHolder resolveDepartmentParent(IWorkHrDepartmentPageRespVO.Department dept) {
Long parentDeptId = toLong(dept.getParentdeptid()); Long parentDeptId = toLong(dept.getSupdepid());
if (parentDeptId != null) { if (parentDeptId != null && parentDeptId > 0) {
return new ParentHolder(parentDeptId); return new ParentHolder(parentDeptId);
} }
Long subcompanyId = toLong(dept.getSubcompanyid1()); Long subcompanyId = toLong(dept.getSubcompanyid1());

View File

@@ -37,6 +37,11 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT)); return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT));
} }
@Override
public IWorkFullSyncRespVO fullSyncSubcompanies(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.SUBCOMPANY));
}
@Override @Override
public IWorkFullSyncRespVO fullSyncJobTitles(IWorkFullSyncReqVO reqVO) { public IWorkFullSyncRespVO fullSyncJobTitles(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.JOB_TITLE)); return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.JOB_TITLE));
@@ -56,6 +61,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
boolean syncUsers = scopes.contains(IWorkSyncEntityTypeEnum.USER); boolean syncUsers = scopes.contains(IWorkSyncEntityTypeEnum.USER);
boolean syncDepartments = scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT); boolean syncDepartments = scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT);
boolean syncSubcompanies = scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY); boolean syncSubcompanies = scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY);
boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE);
int processedPages = 0; int processedPages = 0;
IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO); IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO);
if (syncSubcompanies) { if (syncSubcompanies) {
@@ -64,7 +70,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
if (syncDepartments) { if (syncDepartments) {
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats); processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats);
} }
if (scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE)) { if (syncJobTitle) {
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats); processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
} }
if (syncUsers) { if (syncUsers) {