From d81413e239ccf37838a45ad7fbf9bc5a91f7c746 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 17 Dec 2025 14:39:23 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=20databus=20=E5=9C=A8?= =?UTF-8?q?=E5=A4=9A=E5=B1=82=E5=B5=8C=E5=A5=97=E7=9A=84=20json=20?= =?UTF-8?q?=E6=8A=A5=E6=96=87=EF=BC=8C=E7=AD=BE=E5=90=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dept/vo/dept/DeptSaveReqVO.java | 3 + .../controller/admin/user/UserController.java | 5 +- .../admin/user/vo/user/UserPageReqVO.java | 3 + .../system/dal/mysql/dept/DeptMapper.java | 13 +- .../dal/mysql/user/AdminUserMapper.java | 10 +- .../system/service/dept/DeptServiceImpl.java | 351 +++++++++++------- .../iwork/impl/IWorkSyncProcessorImpl.java | 54 ++- .../system/service/user/AdminUserService.java | 8 +- .../service/user/AdminUserServiceImpl.java | 4 +- .../service/dept/DeptServiceImplTest.java | 185 ++++++++- 10 files changed, 472 insertions(+), 164 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java index a850f311..7d4c6a09 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java @@ -76,4 +76,7 @@ public class DeptSaveReqVO { @Schema(description = "部门来源类型", example = "1") private Integer deptSource; + @Schema(description = "内部使用:延迟生成部门编码", hidden = true) + private Boolean delayCodeGeneration; + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/UserController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/UserController.java index 4ca16850..98463401 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/UserController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/UserController.java @@ -94,8 +94,9 @@ public class UserController { @GetMapping({"/list-all-simple", "/simple-list"}) @Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项") - public CommonResult> getSimpleUserList() { - List list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus(), SIMPLE_LIST_LIMIT); + public CommonResult> getSimpleUserList( + @RequestParam(value = "keyword", required = false) String keyword) { + List list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus(), SIMPLE_LIST_LIMIT, keyword); return success(UserConvert.INSTANCE.convertSimpleList(list)); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java index fc9b19de..d2a7faa4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java @@ -23,6 +23,9 @@ public class UserPageReqVO extends PageParam { @Schema(description = "用户账号,模糊匹配", example = "zt") private String username; + @Schema(description = "用户昵称,模糊匹配", example = "张三") + private String nickname; + @Schema(description = "工号,模糊匹配", example = "A00123") private String workcode; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java index fbd7c2c0..76943edf 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java @@ -114,12 +114,15 @@ public interface DeptMapper extends BaseMapperX { * @param parentId 父部门ID * @return 编码最大的子部门 */ - default DeptDO selectLastChildByCode(Long parentId) { - return selectOne(new LambdaQueryWrapper() + default DeptDO selectLastChildByCode(Long parentId, String prefix) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(DeptDO::getParentId, parentId) - .isNotNull(DeptDO::getCode) - .orderByDesc(DeptDO::getCode) - .last("LIMIT 1")); + .isNotNull(DeptDO::getCode); + if (StrUtil.isNotBlank(prefix)) { + wrapper.likeRight(DeptDO::getCode, prefix); + } + wrapper.orderByDesc(DeptDO::getCode).last("LIMIT 1"); + return selectOne(wrapper); } /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java index e2bc5820..bea01b83 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java @@ -40,6 +40,7 @@ public interface AdminUserMapper extends BaseMapperX { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); query.leftJoin(UserDeptDO.class, UserDeptDO::getUserId, AdminUserDO::getId); query.likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername()); + query.likeIfPresent(AdminUserDO::getNickname, reqVO.getNickname()); query.likeIfPresent(AdminUserDO::getWorkcode, reqVO.getWorkcode()); query.likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile()); query.eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus()); @@ -70,9 +71,16 @@ public interface AdminUserMapper extends BaseMapperX { return selectList(new LambdaQueryWrapperX().like(AdminUserDO::getNickname, nickname)); } - default List selectListByStatus(Integer status, Integer limit) { + default List selectListByStatus(Integer status, Integer limit, String keyword) { LambdaQueryWrapperX query = new LambdaQueryWrapperX() .eq(AdminUserDO::getStatus, status); + if (StrUtil.isNotBlank(keyword)) { + String trimmed = keyword.trim(); + query.and(w -> w.like(AdminUserDO::getNickname, trimmed) + .or().like(AdminUserDO::getUsername, trimmed) + .or().like(AdminUserDO::getMobile, trimmed) + .or().like(AdminUserDO::getWorkcode, trimmed)); + } if (limit != null && limit > 0) { query.last("LIMIT " + limit); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index 7a4e6bbe..4c1799b8 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.google.common.annotations.VisibleForTesting; +import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import com.zt.plat.framework.common.enums.CommonStatusEnum; import com.zt.plat.framework.common.pojo.CompanyDeptInfo; import com.zt.plat.framework.common.util.object.BeanUtils; @@ -11,18 +12,13 @@ import com.zt.plat.framework.datapermission.core.annotation.DataPermission; import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; -import com.zt.plat.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO; -import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; -import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO; import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO; import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper; import com.zt.plat.module.system.dal.redis.RedisKeyConstants; import com.zt.plat.module.system.enums.dept.DeptSourceEnum; -import com.zt.plat.module.system.enums.DictTypeConstants; -import com.zt.plat.module.system.service.dict.DictDataService; -import com.zt.plat.module.system.service.dict.DictTypeService; +import com.zt.plat.module.system.service.permission.PermissionService; import org.apache.seata.spring.annotation.GlobalTransactional; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -57,17 +53,15 @@ public class DeptServiceImpl implements DeptService { @Resource private UserDeptMapper userDeptMapper; @Resource + private PermissionService permissionService; + @Resource private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; - @Resource - private DeptExternalCodeService deptExternalCodeService; - @Resource - private DictTypeService dictTypeService; - @Resource - private DictDataService dictDataService; private static final String ROOT_CODE_PREFIX = "ZT"; + private static final String EXTERNAL_CODE_PREFIX = "CU"; private static final int CODE_SEGMENT_LENGTH = 3; private static final int MAX_SEQUENCE = 999; + private static final int BATCH_SIZE = 1000; private static final Comparator DEPT_COMPARATOR = Comparator .comparing(DeptDO::getSort, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(DeptDO::getId, Comparator.nullsLast(Comparator.naturalOrder())); @@ -82,27 +76,38 @@ public class DeptServiceImpl implements DeptService { createReqVO.setParentId(normalizeParentId(createReqVO.getParentId())); // 创建时默认有效 createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + // 默认部门来源:未指定时视为外部部门 + if (createReqVO.getDeptSource() == null) { + createReqVO.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + } // 校验父部门的有效性 validateParentDept(null, createReqVO.getParentId()); // 校验部门名的唯一性 validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); // 生成并校验部门编码 - Long effectiveParentId = normalizeParentId(createReqVO.getParentId()); - String resolvedCode = generateDeptCode(effectiveParentId); - validateDeptCodeUnique(null, resolvedCode); - createReqVO.setCode(resolvedCode); + boolean isIWorkSource = Objects.equals(createReqVO.getDeptSource(), DeptSourceEnum.IWORK.getSource()); + if (isIWorkSource) { + // iWork 来源直接使用提供的编码,不再生成 + String providedCode = StrUtil.blankToDefault(createReqVO.getCode(), null); + createReqVO.setCode(providedCode); + } else { + if (Boolean.TRUE.equals(createReqVO.getDelayCodeGeneration())) { + createReqVO.setCode(null); + } else { + String resolvedCode = generateDeptCode(createReqVO.getParentId(), createReqVO.getDeptSource()); + validateDeptCodeUnique(null, resolvedCode); + createReqVO.setCode(resolvedCode); + } + } // 插入部门 DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); - // 设置部门来源:如果未指定,默认为外部部门 + // 设置部门来源(前置已默认化,此处兜底) if (dept.getDeptSource() == null) { dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); } deptMapper.insert(dept); - // 维护外部系统编码映射(若有传入) - upsertExternalCodeMapping(createReqVO, dept.getId()); - // 发布部门创建事件 databusChangeProducer.sendDeptCreatedMessage(dept); @@ -122,17 +127,29 @@ public class DeptServiceImpl implements DeptService { validateParentDept(updateReqVO.getId(), updateReqVO.getParentId()); // 校验部门名的唯一性 validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); - Long newParentId = normalizeParentId(updateReqVO.getParentId()); - Long oldParentId = normalizeParentId(originalDept.getParentId()); - boolean parentChanged = !Objects.equals(newParentId, oldParentId); - String existingCode = originalDept.getCode(); - boolean needRegenerateCode = StrUtil.isBlank(existingCode); - String resolvedCode = existingCode; - if (needRegenerateCode) { - resolvedCode = generateDeptCode(newParentId); - validateDeptCodeUnique(updateReqVO.getId(), resolvedCode); + boolean isIWorkSource = Objects.equals(originalDept.getDeptSource(), DeptSourceEnum.IWORK.getSource()); + if (isIWorkSource) { + // iWork 来源直接使用提供的编码,不再生成 + String providedCode = StrUtil.blankToDefault(updateReqVO.getCode(), originalDept.getCode()); + updateReqVO.setCode(providedCode); + } else { + Integer source = ObjectUtil.defaultIfNull(updateReqVO.getDeptSource(), originalDept.getDeptSource()); + if (source == null) { + source = DeptSourceEnum.EXTERNAL.getSource(); + } + String existingCode = originalDept.getCode(); + if (StrUtil.isBlank(existingCode)) { + if (Boolean.TRUE.equals(updateReqVO.getDelayCodeGeneration())) { + updateReqVO.setCode(null); + } else { + String newCode = generateDeptCode(updateReqVO.getParentId(), source); + validateDeptCodeUnique(updateReqVO.getId(), newCode); + updateReqVO.setCode(newCode); + } + } else { + updateReqVO.setCode(existingCode); + } } - updateReqVO.setCode(resolvedCode); // 更新部门 DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class); @@ -144,12 +161,6 @@ public class DeptServiceImpl implements DeptService { databusChangeProducer.sendDeptUpdatedMessage(updatedDept); } - if (needRegenerateCode) { - refreshChildCodesRecursively(updateObj.getId(), updateReqVO.getCode()); - } - - // 维护外部系统编码映射(若有传入) - upsertExternalCodeMapping(updateReqVO, updateReqVO.getId()); } @Override @@ -167,9 +178,6 @@ public class DeptServiceImpl implements DeptService { DeptDO dept = deptMapper.selectById(id); Long tenantId = (dept != null) ? dept.getTenantId() : null; - // 级联删除外部编码映射并清理缓存 - deptExternalCodeService.deleteDeptExternalCodesByDeptId(id); - // 删除部门 deptMapper.deleteById(id); @@ -268,26 +276,16 @@ public class DeptServiceImpl implements DeptService { } } - private String generateDeptCode(Long parentId) { + private String generateDeptCode(Long parentId, Integer deptSource) { Long effectiveParentId = normalizeParentId(parentId); - Long codeParentId = effectiveParentId; - String prefix = ROOT_CODE_PREFIX; - if (!DeptDO.PARENT_ID_ROOT.equals(effectiveParentId)) { - DeptDO parentDept = deptMapper.selectById(effectiveParentId); - if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) { - codeParentId = DeptDO.PARENT_ID_ROOT; - } else { - prefix = parentDept.getCode(); - } - } - - int nextSequence = determineNextSequence(codeParentId, prefix); + String prefix = resolveCodePrefix(effectiveParentId, deptSource); + int nextSequence = determineNextSequence(effectiveParentId, prefix); assertSequenceRange(nextSequence); return prefix + formatSequence(nextSequence); } private int determineNextSequence(Long parentId, String prefix) { - DeptDO lastChild = deptMapper.selectLastChildByCode(parentId); + DeptDO lastChild = deptMapper.selectLastChildByCode(parentId, prefix); Integer sequence = parseSequence(lastChild != null ? lastChild.getCode() : null, prefix); if (sequence != null) { return sequence + 1; @@ -365,12 +363,36 @@ public class DeptServiceImpl implements DeptService { candidate = candidate.trim(); } if (StrUtil.isBlank(candidate)) { - candidate = generateDeptCode(DeptDO.PARENT_ID_ROOT); + candidate = generateDeptCode(DeptDO.PARENT_ID_ROOT, DeptSourceEnum.EXTERNAL.getSource()); } validateDeptCodeUnique(currentDeptId, candidate); return candidate; } + private String resolveCodePrefix(Long parentId, Integer deptSource) { + boolean isExternal = Objects.equals(deptSource, DeptSourceEnum.EXTERNAL.getSource()); + if (DeptDO.PARENT_ID_ROOT.equals(parentId)) { + return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX; + } + + DeptDO parentDept = deptMapper.selectById(parentId); + if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) { + return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX; + } + + String parentCode = parentDept.getCode(); + if (isExternal) { + if (parentCode.startsWith(EXTERNAL_CODE_PREFIX)) { + return parentCode; + } + if (parentCode.startsWith(ROOT_CODE_PREFIX)) { + return EXTERNAL_CODE_PREFIX + parentCode.substring(ROOT_CODE_PREFIX.length()); + } + return EXTERNAL_CODE_PREFIX; + } + return parentCode; + } + @Override public DeptDO getDept(Long id) { return deptMapper.selectById(id); @@ -558,37 +580,59 @@ public class DeptServiceImpl implements DeptService { @Override public List getTopLevelDeptList() { - // 获取当前用户所属的部门列表 - Set deptIds = userDeptMapper.selectValidListByUserIds(singleton(getLoginUserId())) - .stream() - .map(UserDeptDO::getDeptId) - .collect(Collectors.toSet()); - + Long loginUserId = getLoginUserId(); + + // 当前用户所属部门 + Set userDeptIds = Optional.ofNullable(userDeptMapper.selectValidListByUserIds(singleton(loginUserId))) + .orElseGet(Collections::emptyList) + .stream() + .map(UserDeptDO::getDeptId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 数据权限部门 + DeptDataPermissionRespDTO dataPerm = permissionService.getDeptDataPermission(loginUserId); + Set permDeptIds = Optional.ofNullable(dataPerm) + .map(DeptDataPermissionRespDTO::getDeptIds) + .orElse(Collections.emptySet()); + + // all=true 直接返回根级启用部门 + if (dataPerm != null && Boolean.TRUE.equals(dataPerm.getAll())) { + List roots = deptMapper.selectListByParentId(DeptDO.PARENT_ID_ROOT, CommonStatusEnum.ENABLE.getStatus()); + roots.sort(DEPT_COMPARATOR); + return roots; + } + + // 合并两类部门 ID,仅在并集为空时返回空 + Set deptIds = new HashSet<>(); + deptIds.addAll(userDeptIds); + deptIds.addAll(Optional.ofNullable(permDeptIds).orElse(Collections.emptySet())); + if (CollUtil.isEmpty(deptIds)) { - // 如果用户没有关联任何部门,返回空列表 return Collections.emptyList(); } - - // 获取用户所属部门的最顶层祖先部门 - Set topLevelDeptIds = new HashSet<>(); - for (Long deptId : deptIds) { - DeptDO dept = getDept(deptId); - if (dept != null && CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) { - // 找到该部门的最顶层祖先 - DeptDO topLevelDept = findTopLevelAncestor(dept); - if (topLevelDept != null) { - topLevelDeptIds.add(topLevelDept.getId()); - } - } - } - - // 根据顶层部门ID获取部门详情 - return topLevelDeptIds.stream() - .map(this::getDept) - .filter(Objects::nonNull) - .filter(dept -> CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) - .distinct() - .collect(Collectors.toList()); + + // 缓存已加载的部门,避免重复 IO + Map deptCache = new HashMap<>(); + + // 批量解析最顶层祖先(到 ROOT 或上级禁用即停),减少循环 IO + Map topLevelMap = findTopLevelAncestorIdsBatch(deptIds, deptCache); + + // 汇总顶层部门 ID 并取实体(使用缓存避免再查) + Set topLevelDeptIds = topLevelMap.values().stream() + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + List topLevelDepts = topLevelDeptIds.stream() + .map(id -> deptCache.computeIfAbsent(id, this::getDept)) + .filter(Objects::nonNull) + .filter(dept -> CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) + .distinct() + .collect(Collectors.toList()); + + // 按 sort(nullsLast)再按 id 排序 + topLevelDepts.sort(DEPT_COMPARATOR); + return topLevelDepts; } /** @@ -741,64 +785,111 @@ public class DeptServiceImpl implements DeptService { return dept; } - private void upsertExternalCodeMapping(DeptSaveReqVO reqVO, Long deptId) { - if (reqVO == null || deptId == null) { - return; + /** + * 批量查找部门的最顶层祖先(到 ROOT 或遇到禁用/缺失的父部门即停止) + * 使用 1000 条分片批量查询,减少循环 IO + * + * @param deptIds 待解析的部门 ID 集合 + * @param deptCache 部门缓存(可复用外部缓存) + * @return 原始部门 ID -> 顶层祖先部门 ID 映射(若未找到则为 null) + */ + private Map findTopLevelAncestorIdsBatch(Set deptIds, Map deptCache) { + Map result = new HashMap<>(); + if (CollUtil.isEmpty(deptIds)) { + return result; } - String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode()); - String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode()); - if (StrUtil.isBlank(systemCode) || StrUtil.isBlank(externalCode)) { - return; + + // 当前指针:原始部门 -> 当前向上追溯的部门 ID + Map cursorMap = new HashMap<>(); + for (Long id : deptIds) { + cursorMap.put(id, id); } - // 缺失的外部系统字典类型或数据会自动补齐 - ensureExternalSystemDict(systemCode); - deptExternalCodeService.saveOrUpdateDeptExternalCode( - deptId, - systemCode, - externalCode, - reqVO.getExternalDeptName(), - CommonStatusEnum.ENABLE.getStatus()); + + // 预先加载首批部门 + loadDeptBatch(cursorMap.values(), deptCache); + + int safety = 0; + while (!cursorMap.isEmpty() && safety++ < Short.MAX_VALUE) { + // 收集本轮需要加载的父部门 ID(避免重复加载) + Set parentIdsToLoad = new HashSet<>(); + for (Long currentId : cursorMap.values()) { + DeptDO current = deptCache.get(currentId); + if (current == null) { + continue; + } + Long parentId = current.getParentId(); + if (parentId != null && !DeptDO.PARENT_ID_ROOT.equals(parentId) && !deptCache.containsKey(parentId)) { + parentIdsToLoad.add(parentId); + } + } + loadDeptBatch(parentIdsToLoad, deptCache); + + // 遍历当前指针,决定是否上卷或结束 + Iterator> iterator = cursorMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + Long originalId = entry.getKey(); + Long currentId = entry.getValue(); + DeptDO current = deptCache.get(currentId); + + if (current == null) { + result.put(originalId, null); + iterator.remove(); + continue; + } + + Long parentId = current.getParentId(); + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + // 已到达 ROOT(顶层) + result.put(originalId, current.getId()); + iterator.remove(); + continue; + } + + DeptDO parent = deptCache.get(parentId); + if (parent == null || !CommonStatusEnum.ENABLE.getStatus().equals(parent.getStatus())) { + // 父部门缺失或禁用,则当前部门视为顶层 + result.put(originalId, current.getId()); + iterator.remove(); + continue; + } + + // 向上继续追溯 + entry.setValue(parentId); + } + } + + return result; } /** - * 确保外部系统字典存在(含字典类型与对应值),若缺失则自动创建 + * 将给定的部门 ID 集合按批次加载到缓存 */ - private void ensureExternalSystemDict(String systemCode) { - String normalizedCode = StrUtil.trimToNull(systemCode); - if (normalizedCode == null) { + private void loadDeptBatch(Collection ids, Map deptCache) { + if (CollUtil.isEmpty(ids)) { + return; + } + List toLoad = ids.stream() + .filter(Objects::nonNull) + .filter(id -> !deptCache.containsKey(id)) + .distinct() + .collect(Collectors.toList()); + if (CollUtil.isEmpty(toLoad)) { return; } - try { - DictTypeDO dictType = dictTypeService.getDictType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM); - if (dictType == null) { - DictTypeSaveReqVO typeReq = new DictTypeSaveReqVO(); - typeReq.setName("部门外部系统标识"); - typeReq.setType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM); - typeReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - typeReq.setRemark("外部组织同步自动创建"); - dictTypeService.createDictType(typeReq); - } else if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) { - DictTypeSaveReqVO updateReq = new DictTypeSaveReqVO(); - updateReq.setId(dictType.getId()); - updateReq.setName(dictType.getName()); - updateReq.setType(dictType.getType()); - updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - updateReq.setRemark(dictType.getRemark()); - dictTypeService.updateDictType(updateReq); - } - if (dictDataService.getDictData(DictTypeConstants.DEPT_EXTERNAL_SYSTEM, normalizedCode) == null) { - DictDataSaveReqVO dataReq = new DictDataSaveReqVO(); - dataReq.setDictType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM); - dataReq.setLabel(normalizedCode); - dataReq.setValue(normalizedCode); - dataReq.setSort(0); - dataReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - dataReq.setRemark("外部组织同步自动创建"); - dictDataService.createDictData(dataReq); + for (int i = 0; i < toLoad.size(); i += BATCH_SIZE) { + int end = Math.min(i + BATCH_SIZE, toLoad.size()); + List batch = toLoad.subList(i, end); + List depts = getDeptList(batch); + if (CollUtil.isEmpty(depts)) { + continue; + } + for (DeptDO dept : depts) { + if (dept != null && dept.getId() != null) { + deptCache.putIfAbsent(dept.getId(), dept); + } } - } catch (Exception ex) { - log.warn("[Dept] Ensure external system dict failed, systemCode={}", normalizedCode, ex); } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java index 796e8cf3..e2c09f09 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java @@ -57,6 +57,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } result.increasePulled(records.size()); List queue = new ArrayList<>(records); + Set readyParentIds = new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -79,6 +80,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } Long deptId = externalId.longValue(); ParentHolder parentHolder = resolveSubcompanyParent(sub.getSupsubcomid()); + if (!isParentReady(parentHolder.parentId(), readyParentIds)) { + continue; + } boolean canceled = isCanceledFlag(sub.getCanceled()); DeptSaveReqVO saveReq = buildSubcompanySaveReq(sub, deptId, parentHolder.parentId(), canceled); try { @@ -87,6 +91,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { canceled, options); applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname()); + if (outcome.deptId() != null) { + readyParentIds.add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 同步分部失败: id={} name={}", sub.getId(), sub.getSubcompanyname(), ex); result.increaseFailed(); @@ -101,8 +108,19 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } if (!queue.isEmpty()) { for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) { - log.warn("[iWork] 分部因父级缺失未同步: id={} name={}", remaining.getId(), remaining.getSubcompanyname()); - result.increaseFailed(); + log.warn("[iWork] 分部父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getSubcompanyname()); + DeptSaveReqVO saveReq = buildSubcompanySaveReq(remaining, + remaining.getId() == null ? null : remaining.getId().longValue(), + resolveSubcompanyParent(remaining.getSupsubcomid()).parentId(), + isCanceledFlag(remaining.getCanceled())); + saveReq.setDelayCodeGeneration(true); + try { + DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); + applyDeptOutcome(result, outcome, "分部", remaining.getSubcompanyname()); + } catch (Exception ex) { + log.error("[iWork] 分部占位插入失败: id={} name={}", remaining.getId(), remaining.getSubcompanyname(), ex); + result.increaseFailed(); + } } } return result; @@ -117,6 +135,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } result.increasePulled(records.size()); List queue = new ArrayList<>(records); + Set readyParentIds = new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -139,6 +158,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } Long deptId = externalId.longValue(); ParentHolder parentHolder = resolveDepartmentParent(dept); + if (!isParentReady(parentHolder.parentId(), readyParentIds)) { + continue; + } boolean canceled = isCanceledFlag(dept.getCanceled()); DeptSaveReqVO saveReq = buildDepartmentSaveReq(dept, deptId, parentHolder.parentId(), canceled); try { @@ -147,6 +169,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { canceled, options); applyDeptOutcome(result, outcome, "部门", dept.getDepartmentname()); + if (outcome.deptId() != null) { + readyParentIds.add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 同步部门失败: id={} name={}", dept.getId(), dept.getDepartmentname(), ex); result.increaseFailed(); @@ -161,8 +186,19 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { } if (!queue.isEmpty()) { for (IWorkHrDepartmentPageRespVO.Department remaining : queue) { - log.warn("[iWork] 部门因父级缺失未同步: id={} name={}", remaining.getId(), remaining.getDepartmentname()); - result.increaseFailed(); + log.warn("[iWork] 部门父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getDepartmentname()); + DeptSaveReqVO saveReq = buildDepartmentSaveReq(remaining, + remaining.getId() == null ? null : remaining.getId().longValue(), + resolveDepartmentParent(remaining).parentId(), + isCanceledFlag(remaining.getCanceled())); + saveReq.setDelayCodeGeneration(true); + try { + DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); + applyDeptOutcome(result, outcome, "部门", remaining.getDepartmentname()); + } catch (Exception ex) { + log.error("[iWork] 部门占位插入失败: id={} name={}", remaining.getId(), remaining.getDepartmentname(), ex); + result.increaseFailed(); + } } } return result; @@ -493,6 +529,16 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { return new ParentHolder(DeptDO.PARENT_ID_ROOT); } + private boolean isParentReady(Long parentId, Set readyParentIds) { + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + return true; + } + if (readyParentIds.contains(parentId)) { + return true; + } + return deptService.getDept(parentId) != null; + } + private PostDO resolvePostByCode(String code) { String key = buildPostCacheKey(code); PostDO cached = postCache.get(key); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java index 967f3161..382a15fd 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java @@ -193,10 +193,14 @@ public interface AdminUserService { * @param status 状态 * @return 用户们 */ - List getUserListByStatus(Integer status, Integer limit); + List getUserListByStatus(Integer status, Integer limit, String keyword); + + default List getUserListByStatus(Integer status, Integer limit) { + return getUserListByStatus(status, limit, null); + } default List getUserListByStatus(Integer status) { - return getUserListByStatus(status, null); + return getUserListByStatus(status, null, null); } /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java index b9126216..93058b6f 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java @@ -664,8 +664,8 @@ public class AdminUserServiceImpl implements AdminUserService { } @Override - public List getUserListByStatus(Integer status, Integer limit) { - List users = userMapper.selectListByStatus(status, limit); + public List getUserListByStatus(Integer status, Integer limit, String keyword) { + List users = userMapper.selectListByStatus(status, limit, keyword); fillUserDeptInfo(users); return users; } diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java index 1a877052..92361e83 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java @@ -3,23 +3,23 @@ package com.zt.plat.module.system.service.dept; import com.zt.plat.framework.common.enums.CommonStatusEnum; import com.zt.plat.framework.common.util.object.ObjectUtils; import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; -import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; import com.zt.plat.module.system.dal.mysql.dept.DeptExternalCodeMapper; import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; -import com.zt.plat.module.system.service.dept.DeptExternalCodeServiceImpl; import com.zt.plat.module.system.dal.redis.RedisKeyConstants; +import com.zt.plat.module.system.enums.dept.DeptSourceEnum; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; -import org.springframework.cache.CacheManager; -import org.springframework.cache.concurrent.ConcurrentMapCacheManager; -import org.springframework.cache.annotation.EnableCaching; import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import java.util.Arrays; import java.util.List; @@ -69,7 +69,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { reqVO.setName(name); reqVO.setSort(sort); reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - reqVO.setDeptSource(1); + reqVO.setDeptSource(DeptSourceEnum.SYNC.getSource()); reqVO.setIsCompany(false); reqVO.setIsGroup(false); return deptService.createDept(reqVO); @@ -83,7 +83,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { o.setParentId(DeptDO.PARENT_ID_ROOT); o.setStatus(randomCommonStatus()); o.setCode(null); - }).setDeptSource(1); + }).setDeptSource(DeptSourceEnum.SYNC.getSource()); // 调用 Long deptId = deptService.createDept(reqVO); @@ -119,7 +119,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { topLevelReq.setName("总部"); topLevelReq.setSort(1); topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - topLevelReq.setDeptSource(1); + topLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); Long topLevelId = deptService.createDept(topLevelReq); DeptDO firstTop = deptMapper.selectById(topLevelId); assertEquals("ZT001", firstTop.getCode()); @@ -129,12 +129,117 @@ public class DeptServiceImplTest extends BaseDbUnitTest { secondTopLevelReq.setName("总部"); secondTopLevelReq.setSort(2); secondTopLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - secondTopLevelReq.setDeptSource(1); + secondTopLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); Long secondTopId = deptService.createDept(secondTopLevelReq); DeptDO secondTop = deptMapper.selectById(secondTopId); assertEquals("ZT002", secondTop.getCode()); } + @Test + public void testCreateDept_externalUsesCuPrefixAndIndependentSequence() { + // 自建 EXTERNAL 顶级生成 CU001,且不受 ZT 序列影响 + DeptSaveReqVO externalTop = new DeptSaveReqVO(); + externalTop.setParentId(DeptDO.PARENT_ID_ROOT); + externalTop.setName("自建总部"); + externalTop.setSort(1); + externalTop.setStatus(CommonStatusEnum.ENABLE.getStatus()); + externalTop.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long cuTopId = deptService.createDept(externalTop); + DeptDO cuTop = deptMapper.selectById(cuTopId); + assertEquals("CU001", cuTop.getCode()); + + // 同时创建同步来源(非 EXTERNAL),仍使用 ZT 序列 + DeptSaveReqVO syncTop = new DeptSaveReqVO(); + syncTop.setParentId(DeptDO.PARENT_ID_ROOT); + syncTop.setName("同步总部"); + syncTop.setSort(2); + syncTop.setStatus(CommonStatusEnum.ENABLE.getStatus()); + syncTop.setDeptSource(DeptSourceEnum.SYNC.getSource()); + Long ztTopId = deptService.createDept(syncTop); + DeptDO ztTop = deptMapper.selectById(ztTopId); + assertEquals("ZT001", ztTop.getCode()); + + // 再创建一个自建顶级,应独立递增为 CU002 + DeptSaveReqVO externalTop2 = new DeptSaveReqVO(); + externalTop2.setParentId(DeptDO.PARENT_ID_ROOT); + externalTop2.setName("自建二部"); + externalTop2.setSort(3); + externalTop2.setStatus(CommonStatusEnum.ENABLE.getStatus()); + externalTop2.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long cuTop2Id = deptService.createDept(externalTop2); + DeptDO cuTop2 = deptMapper.selectById(cuTop2Id); + assertEquals("CU002", cuTop2.getCode()); + } + + @Test + public void testCreateDept_externalChildFollowsCuPrefix() { + DeptSaveReqVO externalTop = new DeptSaveReqVO(); + externalTop.setParentId(DeptDO.PARENT_ID_ROOT); + externalTop.setName("自建根"); + externalTop.setSort(1); + externalTop.setStatus(CommonStatusEnum.ENABLE.getStatus()); + externalTop.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long topId = deptService.createDept(externalTop); + DeptDO top = deptMapper.selectById(topId); + assertEquals("CU001", top.getCode()); + + DeptSaveReqVO childReq = new DeptSaveReqVO(); + childReq.setParentId(topId); + childReq.setName("自建子"); + childReq.setSort(1); + childReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); + childReq.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long childId = deptService.createDept(childReq); + DeptDO child = deptMapper.selectById(childId); + assertEquals("CU001001", child.getCode()); + } + + @Test + public void testCreateDept_externalChildUnderSyncParentUsesCuPrefix() { + // 同步来源父级,使用 ZT 序列 + DeptSaveReqVO syncTop = new DeptSaveReqVO(); + syncTop.setParentId(DeptDO.PARENT_ID_ROOT); + syncTop.setName("同步父"); + syncTop.setSort(1); + syncTop.setStatus(CommonStatusEnum.ENABLE.getStatus()); + syncTop.setDeptSource(DeptSourceEnum.SYNC.getSource()); + Long syncTopId = deptService.createDept(syncTop); + DeptDO syncTopDept = deptMapper.selectById(syncTopId); + assertEquals("ZT001", syncTopDept.getCode()); + + // 在同步父级下新增外部子部门,前缀替换为 CU,序列与 ZT 独立 + DeptSaveReqVO externalChild1 = new DeptSaveReqVO(); + externalChild1.setParentId(syncTopId); + externalChild1.setName("外部子1"); + externalChild1.setSort(1); + externalChild1.setStatus(CommonStatusEnum.ENABLE.getStatus()); + externalChild1.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long child1Id = deptService.createDept(externalChild1); + DeptDO child1 = deptMapper.selectById(child1Id); + assertEquals("CU001001", child1.getCode()); + + DeptSaveReqVO externalChild2 = new DeptSaveReqVO(); + externalChild2.setParentId(syncTopId); + externalChild2.setName("外部子2"); + externalChild2.setSort(2); + externalChild2.setStatus(CommonStatusEnum.ENABLE.getStatus()); + externalChild2.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + Long child2Id = deptService.createDept(externalChild2); + DeptDO child2 = deptMapper.selectById(child2Id); + assertEquals("CU001002", child2.getCode()); + + // 同步子部门仍使用 ZT 序列,不受 CU 序列影响 + DeptSaveReqVO syncChild = new DeptSaveReqVO(); + syncChild.setParentId(syncTopId); + syncChild.setName("同步子"); + syncChild.setSort(3); + syncChild.setStatus(CommonStatusEnum.ENABLE.getStatus()); + syncChild.setDeptSource(DeptSourceEnum.SYNC.getSource()); + Long syncChildId = deptService.createDept(syncChild); + DeptDO syncChildDept = deptMapper.selectById(syncChildId); + assertEquals("ZT001001", syncChildDept.getCode()); + } + @Test public void testCreateDept_topLevelAutoCode_ignoreCustomInput() { String customCode = "ROOT-001"; @@ -143,7 +248,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { topLevelReq.setName("集团"); topLevelReq.setSort(1); topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - topLevelReq.setDeptSource(1); + topLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); topLevelReq.setCode(customCode); Long deptId = deptService.createDept(topLevelReq); @@ -166,7 +271,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { o.setParentId(DeptDO.PARENT_ID_ROOT); o.setId(dbDeptDO.getId()); o.setStatus(randomCommonStatus()); - }).setDeptSource(1); + }).setDeptSource(DeptSourceEnum.SYNC.getSource()); reqVO.setCode(dbDeptDO.getCode()); // 调用 @@ -195,7 +300,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { updateReq.setParentId(parentBId); updateReq.setSort(1); updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - updateReq.setDeptSource(1); + updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); deptService.updateDept(updateReq); DeptDO updatedChild = deptMapper.selectById(childId); @@ -223,7 +328,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { updateReq1.setName("多系统部门"); updateReq1.setSort(1); updateReq1.setStatus(CommonStatusEnum.ENABLE.getStatus()); - updateReq1.setDeptSource(1); + updateReq1.setDeptSource(DeptSourceEnum.SYNC.getSource()); updateReq1.setExternalSystemCode("ERP"); updateReq1.setExternalDeptCode("ERP-100"); deptService.updateDept(updateReq1); @@ -235,7 +340,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { updateReq2.setName("多系统部门"); updateReq2.setSort(1); updateReq2.setStatus(CommonStatusEnum.ENABLE.getStatus()); - updateReq2.setDeptSource(1); + updateReq2.setDeptSource(DeptSourceEnum.SYNC.getSource()); updateReq2.setExternalSystemCode("OA"); updateReq2.setExternalDeptCode("OA-100"); deptService.updateDept(updateReq2); @@ -257,7 +362,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { createA.setName("iWork-A"); createA.setSort(1); createA.setStatus(CommonStatusEnum.ENABLE.getStatus()); - createA.setDeptSource(1); + createA.setDeptSource(DeptSourceEnum.SYNC.getSource()); createA.setExternalSystemCode("IWORK"); createA.setExternalDeptCode("IW-001"); Long deptAId = deptService.createDept(createA); @@ -272,7 +377,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { createB.setName("iWork-B"); createB.setSort(2); createB.setStatus(CommonStatusEnum.ENABLE.getStatus()); - createB.setDeptSource(1); + createB.setDeptSource(DeptSourceEnum.SYNC.getSource()); createB.setExternalSystemCode("IWORK"); createB.setExternalDeptCode("IW-001"); Long deptBId = deptService.createDept(createB); @@ -300,7 +405,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { updateReq.setName("子-更新"); updateReq.setSort(1); updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); - updateReq.setDeptSource(1); + updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); updateReq.setExternalSystemCode("IWORK"); updateReq.setExternalDeptCode("IW-CHILD"); @@ -689,4 +794,48 @@ public class DeptServiceImplTest extends BaseDbUnitTest { assertEquals("ZT001002", updatedChild2.getCode()); } + @Test + public void testCreateDept_delayCodeGeneration_thenGenerateWhenParentReady() { + Long missingParentId = 900L; + + DeptSaveReqVO childReq = new DeptSaveReqVO(); + childReq.setParentId(missingParentId); + childReq.setName("延迟子部门"); + childReq.setSort(1); + childReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); + childReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); + childReq.setDelayCodeGeneration(true); + + Long childId = deptService.createDept(childReq); + DeptDO child = deptMapper.selectById(childId); + assertNotNull(childId); + assertNull(child.getCode()); + + // 后补父级并赋予编码 + DeptDO parent = new DeptDO(); + parent.setId(missingParentId); + parent.setParentId(DeptDO.PARENT_ID_ROOT); + parent.setName("后补父级"); + parent.setCode("ZT900"); + parent.setSort(1); + parent.setStatus(CommonStatusEnum.ENABLE.getStatus()); + parent.setDeptSource(DeptSourceEnum.SYNC.getSource()); + deptMapper.insert(parent); + + // 触发子部门生成编码 + DeptSaveReqVO updateReq = new DeptSaveReqVO(); + updateReq.setId(childId); + updateReq.setParentId(missingParentId); + updateReq.setName("延迟子部门"); + updateReq.setSort(1); + updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); + updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource()); + updateReq.setDelayCodeGeneration(false); + + deptService.updateDept(updateReq); + + DeptDO updatedChild = deptMapper.selectById(childId); + assertEquals(parent.getCode() + "001", updatedChild.getCode()); + } + }