From 427e4b0c842ccaac061373ad66f395d75d6e7193 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Mon, 21 Sep 2026 16:03:28 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=84=E7=BB=87=E5=90=8C=E6=AD=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springblade/system/pojo/entity/Dept.java | 19 ++ .../system/controller/DeptController.java | 14 + .../system/convert/DeptConvert.java | 10 + .../system/service/IDeptService.java | 12 + .../system/service/impl/DeptServiceImpl.java | 240 +++++++++++++++--- .../service/impl/OASyncServiceImpl.java | 66 +++-- doc/sql/bladex/bladex.mysql.all.create.sql | 5 +- .../transport/blade_dept_oa_id_20260921.sql | 6 + 8 files changed, 324 insertions(+), 48 deletions(-) create mode 100644 doc/sql/transport/blade_dept_oa_id_20260921.sql diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java index 2e075ec..932e41a 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java @@ -25,10 +25,13 @@ */ package org.springblade.system.pojo.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.v3.oas.annotations.media.Schema; @@ -185,4 +188,20 @@ public class Dept extends TenantEntity { @Schema(description = "是否平台公司:0否,1是") private Integer isPlatformCompany; + /** + * OA主键,仅同步记录,不对外展示 + */ + @JsonIgnore + @Schema(description = "OA主键", hidden = true) + @TableField(updateStrategy = FieldStrategy.NOT_NULL) + private String oaId; + + /** + * OA上级主键,仅同步记录,不对外展示 + */ + @JsonIgnore + @Schema(description = "OA上级主键", hidden = true) + @TableField(updateStrategy = FieldStrategy.NOT_NULL) + private String oaSupSubComId; + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java index b2f2280..180dc55 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java @@ -173,6 +173,20 @@ public class DeptController extends BladeController { return R.data(deptService.syncIamOrganizations()); } + /** + * 清除非顶级组织,供自动同步前确认后调用 + */ + @IsAdmin + @PostMapping("/clear-non-top") + @ApiOperationSupport(order = 8) + @Operation(summary = "清除非顶级组织") + public R clearNonTopDepts() { + int clearedCount = deptService.clearNonTopDepts(); + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + return R.data(clearedCount); + } + /** * 从OA按页同步公司 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java index 3199b7e..d46b3c8 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java @@ -80,6 +80,9 @@ public interface DeptConvert { } // 公司所属公司编码就是他自己的部门编码 dept.setBelongCompanyCode(dept.getDeptCode()); + // OA主键与上级OA主键,分批同步结束后用于重建层级 + dept.setOaId(company.getId()); + dept.setOaSupSubComId(company.getSupsubcomid()); // 显示顺序处理 dept.setSort(OAUtils.parseInt(company.getShoworder())); // 部门类型,公司 @@ -100,6 +103,13 @@ public interface DeptConvert { Dept dept = this.baseConvert(oaDept); // 部门编码添加前缀 dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId()); + // OA主键;上级为根部门时记录所属公司OA主键,否则记录上级部门OA主键 + dept.setOaId(oaDept.getId()); + if (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid())) { + dept.setOaSupSubComId(oaDept.getSubcompanyid1()); + } else { + dept.setOaSupSubComId(oaDept.getSupdepid()); + } // 部门所属公司编码就是他所属公司编码加前缀 dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode()); String parentCode = dept.getParentCode(); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java index 9aef013..744ec5d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java @@ -146,6 +146,13 @@ public interface IDeptService extends IService { */ boolean removeDept(String ids); + /** + * 删除非顶级组织,顶级组织(父级为 0)保留 + * + * @return 删除数量 + */ + int clearNonTopDepts(); + /** * 新增或修改部门 *

@@ -187,4 +194,9 @@ public interface IDeptService extends IService { */ void updateAncestors(Date startTime); + /** + * 按 OA 主键与上级 OA 主键重建组织、部门父子关系,并刷新祖级列表 + */ + void rebuildOaHierarchy(); + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index bf43f44..a4b6455 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -46,6 +46,7 @@ import org.springblade.system.cache.SysCache; import org.springblade.system.mapper.DeptMapper; import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.enums.DeptCategory; import org.springblade.system.pojo.vo.DeptVO; import org.springblade.system.pojo.vo.UserVO; import org.springblade.system.props.IamSyncProperties; @@ -53,6 +54,7 @@ import org.springblade.system.service.IDeptService; import org.springblade.system.service.IUserService; import org.springblade.system.wrapper.DeptWrapper; import org.springblade.system.wrapper.UserWrapper; +import org.springblade.thirdparty.oa.constant.OAConstant; import org.springblade.thirdparty.oa.constant.OAConvertConstant; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -232,6 +234,21 @@ public class DeptServiceImpl extends ServiceImpl implements ID return removeByIds(idList); } + @Override + @Transactional(rollbackFor = Exception.class) + public int clearNonTopDepts() { + Long nonTopCount = this.count(Wrappers.lambdaQuery() + .isNotNull(Dept::getParentId) + .ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID)); + if (nonTopCount == null || nonTopCount == 0L) { + return 0; + } + this.remove(Wrappers.lambdaQuery() + .isNotNull(Dept::getParentId) + .ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID)); + return nonTopCount.intValue(); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean submit(Dept dept) { @@ -451,8 +468,12 @@ public class DeptServiceImpl extends ServiceImpl implements ID } private void validateDeptCategory(Dept dept, Dept parent) { + // 顶级组织不要求选择上级组织 if (parent == null) { - throw new ServiceException("请选择上级组织"); + if (Integer.valueOf(6).equals(dept.getDeptCategory()) && Func.isEmpty(dept.getCarrierCustomerId())) { + throw new ServiceException("请选择承运商"); + } + return; } List allowedCategories; if (BladeConstant.TOP_PARENT_ID.equals(parent.getParentId())) { @@ -483,13 +504,24 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (deptCode.length() > 30) { throw new ServiceException("组织编码不能超过30个字符"); } - if (parent == null || StringUtil.isBlank(parent.getDeptCode())) { + // 顶级组织不依赖上级编码 + if (parent == null) { + this.ensureDeptCodeUnique(dept, deptCode); + dept.setDeptCode(deptCode); + return; + } + if (StringUtil.isBlank(parent.getDeptCode())) { throw new ServiceException("请选择已配置组织编码的上级组织"); } String pattern = Pattern.quote(parent.getDeptCode()) + "-\\d+"; if (!deptCode.matches(pattern)) { throw new ServiceException("组织编码格式应为:上级编码-分段数字"); } + this.ensureDeptCodeUnique(dept, deptCode); + dept.setDeptCode(deptCode); + } + + private void ensureDeptCodeUnique(Dept dept, String deptCode) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(Dept::getDeptCode, deptCode); if (Func.isNotEmpty(dept.getId())) { queryWrapper.ne(Dept::getId, dept.getId()); @@ -497,7 +529,6 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (baseMapper.selectCount(queryWrapper) > 0) { throw new ServiceException("组织编码已存在"); } - dept.setDeptCode(deptCode); } @Override @@ -540,24 +571,170 @@ public class DeptServiceImpl extends ServiceImpl implements ID return UserWrapper.build().listVO(userList); } - /** - * 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。 - */ - private void applyTenantScope(QueryWrapper queryWrapper) { - if (!AuthUtil.isAdministrator()) { - queryWrapper.lambda().eq(Dept::getTenantId, AuthUtil.getTenantId()); - } - } - - @Transactional(rollbackFor = Exception.class) @Override public void updateAncestors(Date startTime) { - // 1. 查询需要更新祖级列表的部门 - List list = this.list(Wrappers.lambdaQuery() - // 祖级列表为空 + this.refreshAncestors(false, startTime); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void rebuildOaHierarchy() { + List oaDeptList = this.list(Wrappers.lambdaQuery() + .isNotNull(Dept::getOaId) + .ne(Dept::getOaId, "")); + if (CollectionUtil.isEmpty(oaDeptList)) { + log.info("没有记录OA主键的组织,跳过层级重建"); + return; + } + Map companyByOaId = this.indexByOaId(oaDeptList, DeptCategory.COMPANY.getCode()); + Map departmentByOaId = this.indexByOaId(oaDeptList, DeptCategory.DEPT.getCode()); + Map desiredParentIdMap = new LinkedHashMap<>(); + Map desiredParentCodeMap = new HashMap<>(); + for (Dept dept : oaDeptList) { + OaParentLink parentLink = this.resolveOaParentLink(dept, companyByOaId, departmentByOaId); + if (parentLink == null) { + continue; + } + desiredParentIdMap.put(dept.getId(), parentLink.parentId()); + desiredParentCodeMap.put(dept.getId(), parentLink.parentCode()); + } + Set cyclicIds = this.findCyclicDeptIds(desiredParentIdMap); + if (CollectionUtil.isNotEmpty(cyclicIds)) { + log.warn("检测到OA组织循环引用,跳过层级重建 ids={}", cyclicIds); + cyclicIds.forEach(cyclicId -> { + desiredParentIdMap.remove(cyclicId); + desiredParentCodeMap.remove(cyclicId); + }); + } + List updateList = new ArrayList<>(); + for (Dept dept : oaDeptList) { + Long parentId = desiredParentIdMap.get(dept.getId()); + if (parentId == null) { + continue; + } + String parentCode = desiredParentCodeMap.get(dept.getId()); + if (Objects.equals(parentId, dept.getParentId()) && StringUtil.equals(parentCode, dept.getParentCode())) { + continue; + } + Dept updateParam = new Dept(); + updateParam.setId(dept.getId()); + updateParam.setParentId(parentId); + updateParam.setParentCode(parentCode); + updateList.add(updateParam); + } + if (CollectionUtil.isNotEmpty(updateList)) { + this.updateBatchById(updateList); + log.info("按OA主键重建组织层级,更新父子关系{}条", updateList.size()); + } else { + log.info("OA组织层级已是最新,无需调整父子关系"); + } + this.refreshAncestors(true, null); + } + + /** + * 按机构类型建立 OA 主键索引 + */ + private Map indexByOaId(List oaDeptList, Integer deptCategory) { + Map oaIdMap = new HashMap<>(); + for (Dept dept : oaDeptList) { + if (!Objects.equals(deptCategory, dept.getDeptCategory()) || StringUtil.isBlank(dept.getOaId())) { + continue; + } + Dept previous = oaIdMap.put(dept.getOaId(), dept); + if (previous != null) { + log.warn("OA主键重复,层级重建使用后一条 category={} oaId={} id={} previousId={}", + dept.getDeptCategory(), dept.getOaId(), dept.getId(), previous.getId()); + } + } + return oaIdMap; + } + + /** + * 根据 OA 上级主键解析本系统父级 + */ + private OaParentLink resolveOaParentLink(Dept dept, Map companyByOaId, Map departmentByOaId) { + String parentOaId = dept.getOaSupSubComId(); + boolean rootParent = StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId); + if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) { + if (rootParent) { + return new OaParentLink(OAConvertConstant.ROOT_PARENT_ID, OAConstant.ROOT_COMPANY_ID); + } + Dept parent = companyByOaId.get(parentOaId); + if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) { + log.warn("未找到上级公司,跳过层级重建 oaId={} oaSupSubComId={} deptId={}", + dept.getOaId(), parentOaId, dept.getId()); + return null; + } + return new OaParentLink(parent.getId(), this.resolveParentCode(parent, OAConvertConstant.COMPANY_OA_PREFIX, parentOaId)); + } + if (!DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())) { + return null; + } + if (rootParent) { + log.warn("部门缺少上级OA主键,跳过层级重建 oaId={} deptId={}", dept.getOaId(), dept.getId()); + return null; + } + boolean parentIsDepartment = StringUtil.isNotBlank(dept.getParentCode()) + && dept.getParentCode().startsWith(OAConvertConstant.DEPARTMENT_OA_PREFIX); + boolean parentIsCompany = StringUtil.isNotBlank(dept.getParentCode()) + && dept.getParentCode().startsWith(OAConvertConstant.COMPANY_OA_PREFIX); + Dept parent; + if (parentIsDepartment) { + parent = departmentByOaId.get(parentOaId); + } else if (parentIsCompany) { + parent = companyByOaId.get(parentOaId); + } else { + parent = departmentByOaId.get(parentOaId); + if (parent == null) { + parent = companyByOaId.get(parentOaId); + } + } + if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) { + log.warn("未找到上级组织,跳过层级重建 oaId={} oaSupSubComId={} deptId={}", + dept.getOaId(), parentOaId, dept.getId()); + return null; + } + String prefix = DeptCategory.DEPT.getCode().equals(parent.getDeptCategory()) + ? OAConvertConstant.DEPARTMENT_OA_PREFIX : OAConvertConstant.COMPANY_OA_PREFIX; + return new OaParentLink(parent.getId(), this.resolveParentCode(parent, prefix, parentOaId)); + } + + private String resolveParentCode(Dept parent, String prefix, String parentOaId) { + if (StringUtil.isNotBlank(parent.getDeptCode())) { + return parent.getDeptCode(); + } + return prefix + parentOaId; + } + + /** + * 找出父子关系成环的部门 + */ + private Set findCyclicDeptIds(Map parentIdMap) { + Set cyclicIds = new HashSet<>(); + for (Long deptId : parentIdMap.keySet()) { + Set visiting = new HashSet<>(); + Long currentId = deptId; + while (currentId != null && !OAConvertConstant.ROOT_PARENT_ID.equals(currentId)) { + if (!visiting.add(currentId)) { + cyclicIds.addAll(visiting); + break; + } + currentId = parentIdMap.get(currentId); + } + } + return cyclicIds; + } + + /** + * 刷新祖级列表 + * + * @param refreshAll 是否刷新全部部门 + * @param startTime 增量起始时间,仅 refreshAll 为 false 时生效 + */ + private void refreshAncestors(boolean refreshAll, Date startTime) { + List list = refreshAll ? this.list() : this.list(Wrappers.lambdaQuery() .isNull(Dept::getAncestors) - // 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据 .or(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime)) .or(startTime != null, wrapper -> wrapper.ge(Dept::getUpdateTime, startTime)) ); @@ -565,29 +742,19 @@ public class DeptServiceImpl extends ServiceImpl implements ID log.info("需要更新祖级列表的部门为空"); return; } - // 所有公司部门列表 - List allList = this.list().stream() + List allList = (refreshAll ? list : this.list()).stream() .map(dept -> BeanUtil.copyProperties(dept, DeptVO.class)) .toList(); - // id对应的部门map Map idDeptMap = allList.stream() .collect(Collectors.toMap(DeptVO::getId, Function.identity(), (a, b) -> b)); - // 获取根节点列表 List rootList = getRootList(allList, idDeptMap); - // 计算祖级列表 setAncestors(rootList); - - // 3. 更新祖级列表 List updateParams = list.stream() .filter(dept -> { DeptVO vo = idDeptMap.get(dept.getId()); - if (vo == null) { + if (vo == null || vo.getAncestors() == null) { return false; } - if (vo.getAncestors() == null) { - return false; - } - // 祖级列表和数据库的不一样的才需要更新 return !StringUtil.equals(vo.getAncestors(), dept.getAncestors()); }) .map(dept -> { @@ -602,6 +769,21 @@ public class DeptServiceImpl extends ServiceImpl implements ID } } + /** + * OA 上级链接 + */ + private record OaParentLink(Long parentId, String parentCode) { + } + + /** + * 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。 + */ + private void applyTenantScope(QueryWrapper queryWrapper) { + if (!AuthUtil.isAdministrator()) { + queryWrapper.lambda().eq(Dept::getTenantId, AuthUtil.getTenantId()); + } + } + /** * 获取根节点列表 * @param allList diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java index 0792dc2..5c2e883 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java @@ -76,8 +76,12 @@ public class OASyncServiceImpl implements IOASyncService { this.syncCompany(queryStartTime); // 2. 同步部门 this.syncDept(queryStartTime); - // 2. 更新部门祖级列表 + // 3. 分批写入完成后,按 OA 主键重建组织、部门层级 + deptService.rebuildOaHierarchy(); + // 4. 更新部门祖级列表 deptService.updateAncestors(queryStartTime); + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); }, syncAll); // 2. 推送mk mkPushService.pushOrgAndRecord(startTime); @@ -338,9 +342,12 @@ public class OASyncServiceImpl implements IOASyncService { if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); } + OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount); + if (Boolean.TRUE.equals(pageVO.getFinished())) { + deptService.rebuildOaHierarchy(); + } CacheUtil.clear(SYS_CACHE); CacheUtil.clear(SYS_CACHE, Boolean.FALSE); - OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount); ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}", pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); return pageVO; @@ -379,13 +386,14 @@ public class OASyncServiceImpl implements IOASyncService { if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); } - CacheUtil.clear(SYS_CACHE); - CacheUtil.clear(SYS_CACHE, Boolean.FALSE); OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount); if (Boolean.TRUE.equals(pageVO.getFinished())) { - // 部门同步完成后更新祖级列表 + // 部门分批同步完成后,按 OA 主键重建层级并刷新祖级列表 + deptService.rebuildOaHierarchy(); deptService.updateAncestors(null); } + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}", pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); return pageVO; @@ -527,11 +535,17 @@ public class OASyncServiceImpl implements IOASyncService { if (CollectionUtil.isEmpty(allParam)) { return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList()); } - Set deptCodes = allParam.stream() - .map(Dept::getDeptCode) - .collect(Collectors.toSet()); - // 查询数据库的部门,转换成map - Map deptMap = deptService.list(Wrappers.lambdaQuery() + Set deptCodes = new HashSet<>(); + allParam.forEach(dept -> { + if (StringUtils.isNotEmpty(dept.getDeptCode())) { + deptCodes.add(dept.getDeptCode()); + } + if (StringUtils.isNotEmpty(dept.getParentCode())) { + deptCodes.add(dept.getParentCode()); + } + }); + // 查询数据库的部门,转换成map。包含本页及上级编码,便于命中已入库的上级 + Map deptMap = deptCodes.isEmpty() ? new HashMap<>() : deptService.list(Wrappers.lambdaQuery() .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) .in(Dept::getDeptCode, deptCodes) ).stream() @@ -564,14 +578,30 @@ public class OASyncServiceImpl implements IOASyncService { } }); - // 重新把没有父id的数据设置一下父id - allParam.stream() - .filter(dept -> dept.getParentId() == null && deptMap.containsKey(dept.getDeptCode())) - .forEach(dept -> dept.setParentId(deptMap.get(dept.getParentCode()))); + // 重新把没有父id的数据设置一下父id;上级尚未入库时,新增记录先挂到根节点,分批结束后再重建 + int unresolvedParentCount = 0; + for (Dept dept : allParam) { + if (dept.getParentId() != null) { + continue; + } + Long parentId = deptMap.get(dept.getParentCode()); + if (parentId != null) { + dept.setParentId(parentId); + continue; + } + unresolvedParentCount++; + if (!existsDeptIds.contains(dept.getId())) { + dept.setParentId(OAConvertConstant.ROOT_PARENT_ID); + } + } + if (unresolvedParentCount > 0) { + ComposeLogUtil.getLastLog().warn("同步{}时有{}条上级尚未入库,已先保存,分批结束后按OA主键重建层级", + deptCategory.getName(), unresolvedParentCount); + } - // 父id不为空,保存数据 + // 父id不为空才新增;已存在的记录即使父id未解析也更新(空父id不会覆盖原值) List list = allParam.stream() - .filter(dept -> dept.getParentId() != null) + .filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId())) .toList(); // 不存在的新增 List addList = list.stream() @@ -589,9 +619,9 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size()); deptService.updateBatchById(updateList); } - // 返回未处理的 + // 返回仍未保存的数据 return allParam.stream() - .filter(dept -> dept.getParentId() == null) + .filter(dept -> dept.getParentId() == null && !existsDeptIds.contains(dept.getId())) .toList(); } diff --git a/doc/sql/bladex/bladex.mysql.all.create.sql b/doc/sql/bladex/bladex.mysql.all.create.sql index b805412..a251ed8 100644 --- a/doc/sql/bladex/bladex.mysql.all.create.sql +++ b/doc/sql/bladex/bladex.mysql.all.create.sql @@ -308,12 +308,15 @@ CREATE TABLE `blade_dept` ( `mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码', `carrier_customer_id` bigint NULL DEFAULT NULL COMMENT '承运商客商档案主键', `is_platform_company` tinyint NOT NULL DEFAULT 0 COMMENT '是否平台公司:0否,1是', + `oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键', + `oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键', `sort` int NULL DEFAULT NULL COMMENT '排序', `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', `status` int NULL DEFAULT 1 COMMENT '状态', `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', PRIMARY KEY (`id`) USING BTREE, - UNIQUE KEY `uk_blade_dept_dept_code` (`dept_code`) + UNIQUE KEY `uk_blade_dept_dept_code` (`dept_code`), + INDEX `idx_blade_dept_oa_id` (`oa_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '机构表'; -- ---------------------------- diff --git a/doc/sql/transport/blade_dept_oa_id_20260921.sql b/doc/sql/transport/blade_dept_oa_id_20260921.sql new file mode 100644 index 0000000..3a79d1e --- /dev/null +++ b/doc/sql/transport/blade_dept_oa_id_20260921.sql @@ -0,0 +1,6 @@ +-- 机构表:记录 OA 主键与上级 OA 主键,仅用于同步后重建组织层级,不用于前端展示 + +ALTER TABLE `blade_dept` + ADD COLUMN `oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键' AFTER `is_platform_company`, + ADD COLUMN `oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键' AFTER `oa_id`, + ADD INDEX `idx_blade_dept_oa_id` (`oa_id`);