diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java index d8390cf5..bd1bd7a4 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java @@ -69,4 +69,7 @@ public class OaOrgSyncPageVO implements Serializable { @Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传") private String modifiedSince; + + @Schema(description = "全量同步结束后原始数据层级 JSON 文件路径") + private String dumpFilePath; } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java index b57811da..d92f413b 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java @@ -66,4 +66,7 @@ public class OaPersonSyncPageVO implements Serializable { @Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传") private String modifiedSince; + + @Schema(description = "全量同步结束后原始数据层级 JSON 文件路径") + private String dumpFilePath; } 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 b2afe5f0..d9094bf0 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 @@ -149,16 +149,16 @@ public interface IDeptService extends IService { boolean removeDept(String ids); /** - * 清除非保留顶级外的组织;保留 id=135 集团锚点,以及名称为「外部组织」的顶级 + * 清除非保留顶级外的组织;保留 id=1 集团锚点,以及名称为「外部组织」的顶级 * * @return 删除数量 */ int clearNonTopDepts(); /** - * 确保双顶级:id=135 集团(编码 GWWL)+ 独立「外部组织」顶级 + * 确保双顶级:id=1 集团(编码 GWWL)+ 独立「外部组织」顶级 * - * @return 集团顶级组织(id=135) + * @return 集团顶级组织(id=1) */ Dept ensureOaSyncRootDept(); 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 c0a7c966..d55f05e6 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 @@ -34,6 +34,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.CommonConstant; import org.springblade.common.constant.DataStatusEnum; import org.springblade.core.cache.utils.CacheUtil; import org.springblade.core.log.exception.ServiceException; @@ -390,7 +391,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID @Override @Transactional(rollbackFor = Exception.class) public int clearNonTopDepts() { - // 同步前清理:保留集团锚点 id=135,以及「外部组织」顶级;其余删除 + // 同步前清理:保留集团锚点 id=1,以及「外部组织」顶级;其余删除 Dept groupRoot = this.ensureOaSyncRootDept(); Dept externalRoot = this.ensureExternalOrgTopDept(); Set keepIds = new HashSet<>(); @@ -416,6 +417,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID public Dept ensureOaSyncRootDept() { this.migrateLegacyOaDeptCategory(); this.removeLegacyOaSyncRoots(); + this.migrateLegacyGroupRootToIdOne(); Dept rootDept = this.getById(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID); String tenantId = AuthUtil.getTenantId(); if (StringUtil.isBlank(tenantId)) { @@ -447,14 +449,17 @@ public class DeptServiceImpl extends ServiceImpl implements ID OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE); } else { boolean wronglyNamedAsExternal = OAConvertConstant.EXTERNAL_ORG_NAME.equals(rootDept.getDeptName()); - boolean needUpdate = wronglyNamedAsExternal + boolean legacyNamed = OAConvertConstant.LEGACY_ROOT_DEPT_NAME.equals(rootDept.getDeptName()); + boolean needRename = wronglyNamedAsExternal || legacyNamed || StringUtil.isBlank(rootDept.getDeptName()) + || !OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME.equals(rootDept.getDeptName()); + boolean needUpdate = needRename || !OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE.equals(rootDept.getDeptCode()) || !DeptCategory.GROUP.getCode().equals(rootDept.getDeptCategory()) || !Objects.equals(BladeConstant.TOP_PARENT_ID, rootDept.getParentId()); if (needUpdate) { Dept updateParam = new Dept(); updateParam.setId(rootDept.getId()); - if (wronglyNamedAsExternal || StringUtil.isBlank(rootDept.getDeptName())) { + if (needRename) { updateParam.setDeptName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); updateParam.setFullName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); updateParam.setShortName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); @@ -468,6 +473,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID updateParam.setParentId(BladeConstant.TOP_PARENT_ID); updateParam.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID)); updateParam.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID)); + updateParam.setIsOa(0); this.updateById(updateParam); rootDept.setDeptCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE); rootDept.setBelongCompanyCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE); @@ -475,6 +481,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID rootDept.setParentId(BladeConstant.TOP_PARENT_ID); rootDept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID)); rootDept.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID)); + rootDept.setIsOa(0); log.info("已校正 OA 同步集团顶级 id={} name={} code={} category={}", rootDept.getId(), rootDept.getDeptName(), rootDept.getDeptCode(), rootDept.getDeptCategory()); } @@ -484,7 +491,63 @@ public class DeptServiceImpl extends ServiceImpl implements ID } /** - * 确保存在独立的「外部组织」顶级(与 id=135 集团并存,仅保留一个同名顶级) + * 将历史集团伪顶级 id=135 迁移到 id=1(子节点改挂,再删除旧锚点) + */ + private void migrateLegacyGroupRootToIdOne() { + Long legacyRootId = OAConvertConstant.LEGACY_OA_SYNC_ROOT_DEPT_ID; + Long newRootId = OAConvertConstant.OA_SYNC_ROOT_DEPT_ID; + if (Objects.equals(legacyRootId, newRootId)) { + return; + } + Dept legacyRoot = this.getById(legacyRootId); + if (legacyRoot == null) { + return; + } + // 仅迁移历史伪顶级;若 id=135 已是真实 OA 公司记录则跳过删除 + boolean isOaCompanyRecord = StringUtil.isNotBlank(legacyRoot.getOaId()) + && DeptCategory.COMPANY.getCode().equals(legacyRoot.getDeptCategory()) + && (Objects.equals(legacyRoot.getIsOa(), CommonConstant.YES) || Objects.equals(legacyRoot.getIsOa(), 1)); + if (isOaCompanyRecord) { + log.warn("历史 id={} 已是 OA 同步公司(oaId={}),跳过伪顶级迁移", legacyRootId, legacyRoot.getOaId()); + return; + } + Dept newRoot = this.getById(newRootId); + String tenantId = AuthUtil.getTenantId(); + if (StringUtil.isBlank(tenantId)) { + tenantId = BladeConstant.ADMIN_TENANT_ID; + } + if (newRoot == null) { + newRoot = new Dept(); + newRoot.setId(newRootId); + newRoot.setTenantId(StringUtil.isNotBlank(legacyRoot.getTenantId()) ? legacyRoot.getTenantId() : tenantId); + newRoot.setParentId(BladeConstant.TOP_PARENT_ID); + newRoot.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID)); + newRoot.setDeptName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); + newRoot.setFullName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); + newRoot.setShortName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME); + newRoot.setDeptCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE); + newRoot.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID)); + newRoot.setBelongCompanyCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE); + newRoot.setDeptCategory(DeptCategory.GROUP.getCode()); + newRoot.setSort(0); + newRoot.setStatus(DataStatusEnum.ENABLE.getCode()); + newRoot.setIsDeleted(BladeConstant.DB_NOT_DELETED); + newRoot.setIsOa(0); + newRoot.setIsPlatformCompany(0); + newRoot.setSyncTime(new Date()); + this.save(newRoot); + log.info("迁移创建新集团顶级 id={}(来自历史 id={})", newRootId, legacyRootId); + } + this.update(Wrappers.lambdaUpdate() + .set(Dept::getParentId, newRootId) + .set(Dept::getParentCode, OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE) + .eq(Dept::getParentId, legacyRootId)); + this.removeById(legacyRootId); + log.info("已将历史集团顶级 id={} 迁移到 id={},并删除旧锚点", legacyRootId, newRootId); + } + + /** + * 确保存在独立的「外部组织」顶级(与 id=1 集团并存,仅保留一个同名顶级) */ private Dept ensureExternalOrgTopDept() { List externalTops = this.list(Wrappers.lambdaQuery() @@ -540,11 +603,12 @@ public class DeptServiceImpl extends ServiceImpl implements ID } /** - * 清理历史伪顶级(OACROOT);同名「桂物物流集团」仅清理非 id=135 的顶级 + * 清理历史伪顶级(OACROOT);同名旧集团仅清理非 id=1 的顶级 */ private void removeLegacyOaSyncRoots() { List legacyRoots = this.list(Wrappers.lambdaQuery() .ne(Dept::getId, OAConvertConstant.OA_SYNC_ROOT_DEPT_ID) + .ne(Dept::getId, OAConvertConstant.LEGACY_OA_SYNC_ROOT_DEPT_ID) .and(wrapper -> wrapper .eq(Dept::getDeptCode, OAConvertConstant.LEGACY_ROOT_DEPT_CODE) .or(nameWrapper -> nameWrapper 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 2156b11f..4cb3487f 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 @@ -66,6 +66,7 @@ public class OASyncServiceImpl implements IOASyncService { private final IMKPushService mkPushService; private final IDataSyncRecordService dataSyncRecordService; private final OaUserListSyncHelper oaUserListSyncHelper; + private final OaSyncRawDumpHelper oaSyncRawDumpHelper; @Transactional(rollbackFor = Exception.class) @Override @@ -74,16 +75,18 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.addLog(log); // 1. 同步公司和部门并记录 Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAOrgFetch, queryStartTime -> { - // 0. 确保双顶级:id=135 集团(GWWL) + 独立「外部组织」 + // 0. 确保双顶级:id=1 集团(GWWL) + 独立「外部组织」 deptService.ensureOaSyncRootDept(); // 1. 同步公司 this.syncCompany(queryStartTime); - // 公司分批入库后,重建层级并清理无法挂载的公司 + // 公司分批入库后:清理白名单外公司、重建层级并清理无法挂载的公司 + this.removeOutsideWhitelistOrgs(); deptService.rebuildOaHierarchy(); deptService.removeOaOrphansWithoutParent(); // 2. 同步部门 this.syncDept(queryStartTime); // 3. 分批写入完成后,按 OA 主键重建组织、部门层级 + this.removeOutsideWhitelistOrgs(); deptService.rebuildOaHierarchy(); deptService.removeOaOrphansWithoutParent(); // 4. 更新部门祖级列表 @@ -293,6 +296,10 @@ public class OASyncServiceImpl implements IOASyncService { private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size, boolean incremental, String modifiedSince) { int pageNo = current < 1 ? 1 : current; int pageSize = size < 1 ? 20 : Math.min(size, 200); + // 全量同步首页开启原始数据收集,结束后按父级 id 写入 JSON + if (!incremental && pageNo == 1) { + oaSyncRawDumpHelper.beginFullDump(); + } IncrementalStart incrementalStart = resolvePersonIncrementalStart(incremental, modifiedSince); OAPersonSearch personSearch = buildPersonSearch(incrementalStart.getStartTime()); personSearch.setCurPage(pageNo); @@ -306,6 +313,9 @@ public class OASyncServiceImpl implements IOASyncService { List oaPersons = responseData.getDataList() == null ? Collections.emptyList() : responseData.getDataList(); long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + if (!incremental) { + oaSyncRawDumpHelper.collectPersons(oaPersons); + } OaUserListSyncHelper.OaOrgIndex orgIndex = oaUserListSyncHelper.syncOrgsFromPersons(oaPersons); OaUserListSyncHelper.PersonSyncCount personSyncCount = oaUserListSyncHelper.handlePerson(oaPersons, orgIndex); CacheUtil.clear(USER_CACHE); @@ -323,6 +333,9 @@ public class OASyncServiceImpl implements IOASyncService { || oaPersons.size() < pageSize || (long) pageNo * pageSize >= totalSize; pageVO.setFinished(finished); + if (!incremental && finished) { + pageVO.setDumpFilePath(oaSyncRawDumpHelper.finishFullDump()); + } ComposeLogUtil.getLastLog().info("OA人员分页同步完成 mode={} {}/{},成功{},跳过{}", incremental ? "incremental" : "full", pageNo, totalSize, personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount()); @@ -341,9 +354,12 @@ public class OASyncServiceImpl implements IOASyncService { private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size, boolean incremental, String modifiedSince) { int pageNo = current < 1 ? 1 : current; int pageSize = size < 1 ? 20 : Math.min(size, 200); - // 首屏确保双顶级:id=135 集团(GWWL) + 独立「外部组织」 + // 首屏确保双顶级:id=1 集团(GWWL) + 独立「外部组织」 if (pageNo == 1) { deptService.ensureOaSyncRootDept(); + if (!incremental) { + oaSyncRawDumpHelper.beginOrgFullDump(); + } } IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.COMPANY); OACompanySearch companySearch = buildCompanySearch(incrementalStart.getStartTime()); @@ -358,6 +374,9 @@ public class OASyncServiceImpl implements IOASyncService { List oaCompanies = responseData.getDataList() == null ? Collections.emptyList() : responseData.getDataList(); long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + if (!incremental) { + oaSyncRawDumpHelper.collectCompanies(oaCompanies); + } OrgSyncCount syncCount = handleCompany(oaCompanies, true); if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); @@ -365,7 +384,8 @@ public class OASyncServiceImpl implements IOASyncService { OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount); pageVO.setModifiedSince(incrementalStart.getModifiedSince()); if (Boolean.TRUE.equals(pageVO.getFinished())) { - // 公司全部分批入库后:重建层级,并删除上级不存在的数据 + // 公司全部分批入库后:清理白名单外公司、重建层级,并删除上级不存在的数据 + this.removeOutsideWhitelistOrgs(); deptService.rebuildOaHierarchy(); deptService.removeOaOrphansWithoutParent(); } @@ -394,6 +414,9 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.getLastLog().warn("未查询到已同步的OA公司,跳过部门同步"); OaOrgSyncPageVO emptyPageVO = buildOrgSyncPageVO("department", pageNo, pageSize, 0L, 0, OrgSyncCount.empty()); emptyPageVO.setFinished(true); + if (!incremental) { + emptyPageVO.setDumpFilePath(oaSyncRawDumpHelper.finishOrgFullDump()); + } return emptyPageVO; } IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.DEPT); @@ -409,6 +432,9 @@ public class OASyncServiceImpl implements IOASyncService { List oaDepartments = responseData.getDataList() == null ? Collections.emptyList() : responseData.getDataList(); long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + if (!incremental) { + oaSyncRawDumpHelper.collectDepartments(oaDepartments); + } OrgSyncCount syncCount = handleDept(oaDepartments, true); if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); @@ -416,10 +442,14 @@ public class OASyncServiceImpl implements IOASyncService { OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount); pageVO.setModifiedSince(incrementalStart.getModifiedSince()); if (Boolean.TRUE.equals(pageVO.getFinished())) { - // 部门分批同步完成后,重建层级、清理无法挂载数据并刷新祖级列表 + // 部门分批同步完成后,清理白名单外组织、重建层级、清理无法挂载数据并刷新祖级列表 + this.removeOutsideWhitelistOrgs(); deptService.rebuildOaHierarchy(); deptService.removeOaOrphansWithoutParent(); deptService.updateAncestors(null); + if (!incremental) { + pageVO.setDumpFilePath(oaSyncRawDumpHelper.finishOrgFullDump()); + } } CacheUtil.clear(SYS_CACHE); CacheUtil.clear(SYS_CACHE, Boolean.FALSE); @@ -623,6 +653,7 @@ public class OASyncServiceImpl implements IOASyncService { } Set companyNames = syncAll ? Collections.emptySet() : getCompanyNames(); List allParam = oaCompanies.stream() + .filter(this::isAllowedOaCompany) .filter(company -> syncAll || companyNames.contains(company.getSubcompanyname())) .map(deptConvert::company2dept) .toList(); @@ -679,12 +710,14 @@ public class OASyncServiceImpl implements IOASyncService { return OrgSyncCount.empty(); } Map companyByOaId = loadCompanyByOaId(); + Set allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId); String rootCompanyId = getRootCompanyId(); if (!syncAll && rootCompanyId == null) { return new OrgSyncCount(0, oaDepts.size(), Collections.emptyList()); } Set rootCompanyDeptNames = syncAll ? Collections.emptySet() : getRootCompanyDeptNames(); List allParam = oaDepts.stream() + .filter(oaDept -> allowedCompanyOaIds.contains(oaDept.getSubcompanyid1())) .filter(oaDept -> syncAll || rootCompanyId == null || !rootCompanyId.equals(oaDept.getSubcompanyid1()) @@ -1011,6 +1044,106 @@ public class OASyncServiceImpl implements IOASyncService { } } + /** + * 是否允许同步的 OA 公司:二级白名单,或其上级为二级白名单(三级子公司) + */ + private boolean isAllowedOaCompany(OACompanyResponse company) { + if (company == null || StringUtils.isBlank(company.getId())) { + return false; + } + if (OAConvertConstant.OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS.contains(company.getId())) { + return true; + } + return OAConvertConstant.OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS.contains(company.getSupsubcomid()); + } + + /** + * 解析允许同步部门的所属公司 OA 主键:二级白名单 ∪ 已同步的三级子公司 + */ + private Set resolveAllowedCompanyOaIds(Map companyByOaId) { + Set allowed = new HashSet<>(OAConvertConstant.OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS); + if (companyByOaId == null || companyByOaId.isEmpty()) { + return allowed; + } + for (Dept company : companyByOaId.values()) { + if (company == null || StringUtils.isBlank(company.getOaId())) { + continue; + } + if (OAConvertConstant.OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS.contains(company.getOaId()) + || OAConvertConstant.OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS.contains(company.getOaSupSubComId())) { + allowed.add(company.getOaId()); + } + } + return allowed; + } + + /** + * 清理不在白名单树内的 OA 公司及其子树(含部门) + */ + private int removeOutsideWhitelistOrgs() { + List oaCompanyList = deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode()) + .isNotNull(Dept::getOaId) + .ne(Dept::getOaId, "")); + Map companyByOaId = new HashMap<>(); + for (Dept company : oaCompanyList) { + if (StringUtils.isNotBlank(company.getOaId())) { + companyByOaId.put(company.getOaId(), company); + } + } + Set allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId); + Set deleteIds = new HashSet<>(); + for (Dept company : oaCompanyList) { + if (!allowedCompanyOaIds.contains(company.getOaId())) { + deleteIds.add(company.getId()); + } + } + Map companyCodeToOaId = new HashMap<>(); + for (Dept company : oaCompanyList) { + if (StringUtils.isNotBlank(company.getDeptCode()) && StringUtils.isNotBlank(company.getOaId())) { + companyCodeToOaId.put(company.getDeptCode(), company.getOaId()); + } + } + List oaDeptList = deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) + .isNotNull(Dept::getOaId) + .ne(Dept::getOaId, "")); + for (Dept dept : oaDeptList) { + String belongOaId = null; + if (StringUtils.isNotBlank(dept.getBelongCompanyCode())) { + belongOaId = companyCodeToOaId.get(dept.getBelongCompanyCode()); + } + if (StringUtils.isBlank(belongOaId) && StringUtils.isNotBlank(dept.getOaSupSubComId()) + && companyByOaId.containsKey(dept.getOaSupSubComId())) { + // 根部门上级可能直接是公司 OA 主键 + belongOaId = dept.getOaSupSubComId(); + } + if (StringUtils.isBlank(belongOaId) || !allowedCompanyOaIds.contains(belongOaId)) { + deleteIds.add(dept.getId()); + } + } + if (CollectionUtil.isEmpty(deleteIds)) { + return 0; + } + List allDeptList = deptService.list(); + boolean cascadeChanged = true; + while (cascadeChanged) { + cascadeChanged = false; + for (Dept dept : allDeptList) { + if (deleteIds.contains(dept.getId())) { + continue; + } + if (dept.getParentId() != null && deleteIds.contains(dept.getParentId())) { + deleteIds.add(dept.getId()); + cascadeChanged = true; + } + } + } + deptService.removeByIds(deleteIds); + ComposeLogUtil.getLastLog().info("清理白名单外 OA 组织{}条", deleteIds.size()); + return deleteIds.size(); + } + /** * 获取 OA 部门查询使用的子公司 id * @@ -1037,9 +1170,18 @@ public class OASyncServiceImpl implements IOASyncService { .ne(Dept::getOaId, "") ); } + Map companyByOaId = new HashMap<>(); + for (Dept company : companyList) { + String oaId = this.resolveOaCompanyId(company); + if (StringUtils.isNotBlank(oaId)) { + companyByOaId.put(oaId, company); + } + } + Set allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId); return companyList.stream() .map(this::resolveOaCompanyId) .filter(StringUtils::isNotBlank) + .filter(allowedCompanyOaIds::contains) .distinct() .collect(Collectors.joining(",")); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaSyncRawDumpHelper.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaSyncRawDumpHelper.java new file mode 100644 index 00000000..0674f00f --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaSyncRawDumpHelper.java @@ -0,0 +1,379 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.system.log.ComposeLogUtil; +import org.springblade.thirdparty.oa.feign.IOAClient; +import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse; +import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse; +import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springblade.thirdparty.oa.pojo.search.OACompanySearch; +import org.springblade.thirdparty.oa.pojo.search.OADepartmentSearch; +import org.springblade.thirdparty.oa.pojo.search.OASearch; +import org.springblade.thirdparty.oa.util.OAUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * OA 全量同步原始数据落盘:按父级 id 组装层级 JSON + * + * @author Chill + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class OaSyncRawDumpHelper { + + private static final String ORPHAN_COMPANY_ID = "_orphan_company"; + private static final String ORPHAN_DEPT_ID = "_orphan_dept"; + + private final IOAClient oaClient; + + @Value("${third-party.oa.sync-dump-dir:./data/oa-sync}") + private String syncDumpDir; + + /** + * 当前全量同步会话收集到的人员原始数据 + */ + private final List collectingPersons = Collections.synchronizedList(new ArrayList<>()); + private volatile boolean collecting; + + /** + * 机构全量同步会话收集的公司/部门原始数据 + */ + private final List collectingCompanies = Collections.synchronizedList(new ArrayList<>()); + private final List collectingDepartments = Collections.synchronizedList(new ArrayList<>()); + private volatile boolean collectingOrg; + + /** + * 全量同步首页启动收集 + */ + public synchronized void beginFullDump() { + collectingPersons.clear(); + collecting = true; + ComposeLogUtil.getLastLog().info("已开启 OA 全量同步原始数据收集,目录={}", resolveDumpDir()); + } + + /** + * 机构全量同步首页启动收集公司/部门原始数据 + */ + public synchronized void beginOrgFullDump() { + collectingCompanies.clear(); + collectingDepartments.clear(); + collectingOrg = true; + ComposeLogUtil.getLastLog().info("已开启 OA 机构全量同步原始数据收集,目录={}", resolveDumpDir()); + } + + /** + * 收集本页人员原始数据 + */ + public void collectPersons(List oaPersons) { + if (!collecting || oaPersons == null || oaPersons.isEmpty()) { + return; + } + collectingPersons.addAll(oaPersons); + } + + /** + * 收集本页公司原始数据 + */ + public void collectCompanies(List oaCompanies) { + if (!collectingOrg || oaCompanies == null || oaCompanies.isEmpty()) { + return; + } + collectingCompanies.addAll(oaCompanies); + } + + /** + * 收集本页部门原始数据 + */ + public void collectDepartments(List oaDepartments) { + if (!collectingOrg || oaDepartments == null || oaDepartments.isEmpty()) { + return; + } + collectingDepartments.addAll(oaDepartments); + } + + /** + * 全量同步结束:先写入人员层级 JSON,再异步补齐公司/部门接口原始层级数据 + * + * @return 人员原始层级 JSON 文件路径,失败时返回 null + */ + public synchronized String finishFullDump() { + if (!collecting) { + return null; + } + List persons = new ArrayList<>(collectingPersons); + collecting = false; + collectingPersons.clear(); + try { + String stamp = DateUtil.format(new Date(), "yyyyMMddHHmmss"); + Map personPayload = new LinkedHashMap<>(); + personPayload.put("exportTime", DateUtil.format(new Date(), DateUtil.PATTERN_DATETIME)); + personPayload.put("personCount", persons.size()); + personPayload.put("personTree", buildPersonTree(persons)); + personPayload.put("personsRaw", persons); + Path personFile = writeDumpFile("oa-full-sync-person-raw-" + stamp + ".json", personPayload); + ComposeLogUtil.getLastLog().info("OA 人员原始层级数据已写入 {}", personFile.toAbsolutePath()); + CompletableFuture.runAsync(() -> dumpOrgRawAsync(stamp)); + return personFile.toAbsolutePath().toString(); + } catch (Exception exception) { + log.error("写入 OA 全量同步人员原始 JSON 失败", exception); + ComposeLogUtil.getLastLog().error("写入 OA 全量同步人员原始 JSON 失败:{}", exception.getMessage()); + return null; + } + } + + /** + * 机构全量同步结束:写入公司/部门接口原始数据(按父级 id 层级) + * + * @return JSON 文件路径,失败时返回 null + */ + public synchronized String finishOrgFullDump() { + if (!collectingOrg) { + return null; + } + List companies = new ArrayList<>(collectingCompanies); + List departments = new ArrayList<>(collectingDepartments); + collectingOrg = false; + collectingCompanies.clear(); + collectingDepartments.clear(); + try { + String stamp = DateUtil.format(new Date(), "yyyyMMddHHmmss"); + Path orgFile = writeOrgDumpFile(stamp, companies, departments); + ComposeLogUtil.getLastLog().info("OA 机构原始层级数据已写入 {}", orgFile.toAbsolutePath()); + return orgFile.toAbsolutePath().toString(); + } catch (Exception exception) { + log.error("写入 OA 机构全量同步原始 JSON 失败", exception); + ComposeLogUtil.getLastLog().error("写入 OA 机构全量同步原始 JSON 失败:{}", exception.getMessage()); + return null; + } + } + + private void dumpOrgRawAsync(String stamp) { + try { + List companies = fetchAllCompanies(); + List departments = fetchAllDepartments(companies); + Path orgFile = writeOrgDumpFile(stamp, companies, departments); + log.info("OA 公司/部门原始层级数据已写入 {}", orgFile.toAbsolutePath()); + } catch (Exception exception) { + log.error("异步写入 OA 公司/部门原始 JSON 失败", exception); + } + } + + private Path writeOrgDumpFile(String stamp, List companies, + List departments) throws IOException { + Map orgPayload = new LinkedHashMap<>(); + orgPayload.put("exportTime", DateUtil.format(new Date(), DateUtil.PATTERN_DATETIME)); + orgPayload.put("companyCount", companies.size()); + orgPayload.put("departmentCount", departments.size()); + orgPayload.put("companyTree", buildCompanyTree(companies)); + orgPayload.put("departmentTree", buildDepartmentTree(departments)); + orgPayload.put("companiesRaw", companies); + orgPayload.put("departmentsRaw", departments); + return writeDumpFile("oa-full-sync-org-raw-" + stamp + ".json", orgPayload); + } + + private List fetchAllCompanies() { + List companies = new ArrayList<>(); + OACompanySearch companySearch = new OACompanySearch(); + companySearch.setCurPage(1); + companySearch.setPageSize(100); + OAUtils.pageSyncHandler(companySearch, param -> oaClient.queryCompanyPage(new OASearch<>(param)), response -> { + log.error("导出公司原始数据失败 {}", JSON.toJSONString(response)); + return new ServiceException("导出公司原始数据失败"); + }, 10000, log::info).accept(companies::addAll); + return companies; + } + + private List fetchAllDepartments(List companies) { + List departments = new ArrayList<>(); + String subCompanyIds = companies.stream() + .map(OACompanyResponse::getId) + .filter(StringUtils::isNotBlank) + .distinct() + .reduce((left, right) -> left + "," + right) + .orElse(""); + OADepartmentSearch departmentSearch = new OADepartmentSearch(); + departmentSearch.setCurPage(1); + departmentSearch.setPageSize(100); + departmentSearch.setSubcompanyid1(subCompanyIds); + OAUtils.pageSyncHandler(departmentSearch, param -> oaClient.queryDepartmentPage(new OASearch<>(param)), response -> { + log.error("导出部门原始数据失败 {}", JSON.toJSONString(response)); + return new ServiceException("导出部门原始数据失败"); + }, 10000, log::info).accept(departments::addAll); + return departments; + } + + private List> buildCompanyTree(List companies) { + Map> nodeMap = new LinkedHashMap<>(); + for (OACompanyResponse company : companies) { + if (company == null || StringUtils.isBlank(company.getId())) { + continue; + } + Map node = new LinkedHashMap<>(); + node.put("id", company.getId()); + node.put("parentId", blankToRoot(company.getSupsubcomid())); + node.put("type", "company"); + node.put("name", company.getSubcompanyname()); + node.put("raw", company); + node.put("children", new ArrayList>()); + nodeMap.put(company.getId(), node); + } + return linkChildren(nodeMap); + } + + private List> buildDepartmentTree(List departments) { + Map> nodeMap = new LinkedHashMap<>(); + for (OADepartmentResponse department : departments) { + if (department == null || StringUtils.isBlank(department.getId())) { + continue; + } + Map node = new LinkedHashMap<>(); + node.put("id", department.getId()); + node.put("parentId", blankToRoot(department.getSupdepid())); + node.put("companyId", department.getSubcompanyid1()); + node.put("type", "department"); + node.put("name", department.getDepartmentname()); + node.put("raw", department); + node.put("children", new ArrayList>()); + nodeMap.put(department.getId(), node); + } + return linkChildren(nodeMap); + } + + private List> buildPersonTree(List persons) { + Map> companyMap = new LinkedHashMap<>(); + Map> deptMap = new LinkedHashMap<>(); + for (OAPersonResponse person : persons) { + if (person == null) { + continue; + } + String companyId = StringUtils.defaultIfBlank(person.getSubcompanyid1(), ORPHAN_COMPANY_ID); + String deptId = StringUtils.defaultIfBlank(person.getDepartmentid(), ORPHAN_DEPT_ID + "@" + companyId); + Map companyNode = companyMap.computeIfAbsent(companyId, key -> { + Map node = new LinkedHashMap<>(); + node.put("id", key); + node.put("parentId", "0"); + node.put("type", "company"); + node.put("name", StringUtils.defaultIfBlank(person.getSubcompanyname(), key)); + Map raw = new LinkedHashMap<>(); + raw.put("subcompanyid1", person.getSubcompanyid1()); + raw.put("subcompanyname", person.getSubcompanyname()); + raw.put("subcompanycode", person.getSubcompanycode()); + node.put("raw", raw); + node.put("children", new ArrayList>()); + return node; + }); + @SuppressWarnings("unchecked") + List> companyChildren = (List>) companyNode.get("children"); + Map deptNode = deptMap.computeIfAbsent(deptId, key -> { + Map node = new LinkedHashMap<>(); + node.put("id", key); + node.put("parentId", companyId); + node.put("type", "department"); + node.put("name", StringUtils.defaultIfBlank(person.getDepartmentname(), key)); + Map raw = new LinkedHashMap<>(); + raw.put("departmentid", person.getDepartmentid()); + raw.put("departmentname", person.getDepartmentname()); + raw.put("departmentcode", person.getDepartmentcode()); + raw.put("subcompanyid1", person.getSubcompanyid1()); + node.put("raw", raw); + node.put("children", new ArrayList>()); + companyChildren.add(node); + return node; + }); + @SuppressWarnings("unchecked") + List> deptChildren = (List>) deptNode.get("children"); + Map personNode = new LinkedHashMap<>(); + personNode.put("id", StringUtils.defaultIfBlank(person.getId(), person.getLoginid())); + personNode.put("parentId", deptId); + personNode.put("type", "person"); + personNode.put("name", person.getLastname()); + personNode.put("raw", person); + personNode.put("children", Collections.emptyList()); + deptChildren.add(personNode); + } + return new ArrayList<>(companyMap.values()); + } + + @SuppressWarnings("unchecked") + private List> linkChildren(Map> nodeMap) { + List> roots = new ArrayList<>(); + for (Map node : nodeMap.values()) { + String parentId = Objects.toString(node.get("parentId"), "0"); + Map parent = nodeMap.get(parentId); + if (parent != null && !Objects.equals(parent.get("id"), node.get("id"))) { + ((List>) parent.get("children")).add(node); + } else { + roots.add(node); + } + } + return roots; + } + + private String blankToRoot(String parentId) { + if (StringUtils.isBlank(parentId) || "0".equals(parentId) || "-1".equals(parentId)) { + return "0"; + } + return parentId; + } + + private Path writeDumpFile(String fileName, Map payload) throws IOException { + Path dir = resolveDumpDir(); + Files.createDirectories(dir); + Path dumpFile = dir.resolve(fileName); + String json = JSON.toJSONString(payload, JSONWriter.Feature.PrettyFormat, JSONWriter.Feature.WriteMapNullValue); + Files.writeString(dumpFile, json, StandardCharsets.UTF_8); + return dumpFile; + } + + private Path resolveDumpDir() { + return Paths.get(StringUtils.defaultIfBlank(syncDumpDir, "./data/oa-sync")).toAbsolutePath().normalize(); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java index 9a6da9c0..77ba09d0 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java @@ -86,7 +86,7 @@ public class OaUserListSyncHelper { private final IRoleService roleService; /** - * 从人员数据提取二级公司、三级部门,并挂到集团顶级(id=135/GWWL) 下 + * 从人员数据提取二级公司、三级部门,并挂到集团顶级(id=1/GWWL) 下 * * @param oaPersons OA人员 * @return 组织索引 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index f3545757..6a305b09 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -166,7 +166,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl captor = ArgumentCaptor.forClass(TransportVehicle.class); verify(transportVehicleService).updateById(captor.capture()); TransportVehicle patched = captor.getValue(); - assertEquals("桂物物流集团", patched.getOwnerName(), "所有人要跟着生效值更新"); + assertEquals("广西现代物流集团有限公司", patched.getOwnerName(), "所有人要跟着生效值更新"); assertEquals("管理挂靠", patched.getBusinessRelation()); assertEquals(18000, patched.getApprovedLoadKg()); assertEquals(RECORD_ID, patched.getEffectiveCertRecordId(), "内容与指针必须是同一次 UPDATE"); @@ -441,7 +441,7 @@ class CertAuditServiceImplTest { */ static String completeVehicleSnapshot() { return "{\"drivingLicenseNo\":\"450100123456\",\"approvedLoadKg\":18000," - + "\"ownerName\":\"桂物物流集团\",\"businessRelation\":\"管理挂靠\"," + + "\"ownerName\":\"广西现代物流集团有限公司\",\"businessRelation\":\"管理挂靠\"," + "\"vehicleType\":\"重型货车\",\"driveLicenseImage\":\"http://oss/xsz.jpg\"}"; } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java index 505f57a1..44d58724 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java @@ -464,13 +464,13 @@ class DriverCertServiceImplTest { assertEquals("重型货车", vehicleCaptor.getValue().getVehicleType()); assertNull(vehicleCaptor.getValue().getEffectiveCertRecordId(), "新建的车不得自带生效快照——是否已认证只能由审核通过决定"); - assertEquals("桂物物流集团", vehicleCaptor.getValue().getOrganizationName(), + assertEquals("广西现代物流集团有限公司", vehicleCaptor.getValue().getOrganizationName(), "所属组织必须从司机档案带出:该列 NOT NULL 且无默认值,空着这条 INSERT 直接报错"); assertEquals("管理挂靠", vehicleCaptor.getValue().getBusinessRelation(), "业务关系由司机在表单里选,服务端照实落库、不替用户猜"); assertNotNull(vehicleCaptor.getValue().getRegistrationNo(), "机动车登记编号该列 NOT NULL:司机端选填,缺值要写空串而不是 null"); - assertEquals("桂物物流集团", vehicleCaptor.getValue().getOwnerName(), + assertEquals("广西现代物流集团有限公司", vehicleCaptor.getValue().getOwnerName(), "所有人是行驶证上的登记主体,与所属组织是两个字段"); // 绑定:司机的 driving_vehicle 被追加该车牌 @@ -663,7 +663,7 @@ class DriverCertServiceImplTest { VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); request.setPlateNo(plateNo); request.setVehicleType("重型货车"); - request.setOwnerName("桂物物流集团"); + request.setOwnerName("广西现代物流集团有限公司"); request.setPlateColor("黄牌"); request.setEnergyType("柴油"); request.setBusinessRelation("管理挂靠"); @@ -691,7 +691,7 @@ class DriverCertServiceImplTest { driver.setDriverName("张三"); // 车辆表的 organization_name 是 NOT NULL 且无默认值:司机缺组织时建车必然失败, // 故建车链路要求它非空,fixture 也必须给 - driver.setOrganizationName("桂物物流集团"); + driver.setOrganizationName("广西现代物流集团有限公司"); driver.setDrivingVehicle(drivingVehicle); return driver; } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrFieldSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrFieldSupportTest.java index 52530a56..f20619d8 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrFieldSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrFieldSupportTest.java @@ -32,7 +32,7 @@ class OcrFieldSupportTest { "words_result": { "号牌号码": {"words": "桂A32523"}, "车辆类型": {"words": "重型厢式货车"}, - "所有人": {"words": "桂物物流集团"}, + "所有人": {"words": "广西现代物流集团有限公司"}, "车辆识别代码": {"words": "LGAX2A130J1001234"}, "档案编号": {"words": "450100123456"}, "核定载质量": {"words": "18000kg"}, @@ -48,7 +48,7 @@ class OcrFieldSupportTest { assertEquals("桂A32523", fields.get("plateNo")); assertEquals("重型厢式货车", fields.get("vehicleType")); - assertEquals("桂物物流集团", fields.get("ownerName")); + assertEquals("广西现代物流集团有限公司", fields.get("ownerName")); assertEquals("LGAX2A130J1001234", fields.get("vin")); assertEquals("450100123456", fields.get("drivingLicenseNo")); assertEquals(18000, fields.get("approvedLoadKg"), "带 kg 单位也要能取到数字"); @@ -160,7 +160,7 @@ class OcrFieldSupportTest { { "words_result": { "道路运输证号": {"words": "桂交运管桂字450100123456号"}, - "业户名称": {"words": "桂物物流集团"}, + "业户名称": {"words": "广西现代物流集团有限公司"}, "有效期": {"words": "2020-01-01 至 2030-01-01"} } } @@ -169,7 +169,7 @@ class OcrFieldSupportTest { Map fields = OcrFieldSupport.extract(BaiduOcrType.ROAD_TRANSPORT_CERTIFICATE, null, result); assertEquals("桂交运管桂字450100123456号", fields.get("roadTransportCertNo")); - assertEquals("桂物物流集团", fields.get("ownerName")); + assertEquals("广西现代物流集团有限公司", fields.get("ownerName")); assertEquals("2030-01-01", fields.get("roadTransportCertEndDate"), "止期字段取区间的最后一段,取第一段会把有效期起填成止"); assertEquals("2020-01-01", fields.get("roadTransportCertStartDate"), "区间要同时带出起止,否则用户得照图再敲一遍"); } diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java index e157641b..10144bb0 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java @@ -1,5 +1,7 @@ package org.springblade.thirdparty.oa.constant; +import java.util.Set; + /** * OA转换常量类 * @author bfhuange @@ -33,11 +35,15 @@ public class OAConvertConstant { /** * OA 同步挂载的本地顶级组织主键(集团) */ - public static final Long OA_SYNC_ROOT_DEPT_ID = 135L; + public static final Long OA_SYNC_ROOT_DEPT_ID = 1L; /** - * OA 同步挂载的本地顶级组织默认名称(集团;已存在时不覆盖原名称,除非误写成外部组织) + * 历史集团顶级主键(迁移前为 id=135) */ - public static final String OA_SYNC_ROOT_DEPT_NAME = "桂物物流集团"; + public static final Long LEGACY_OA_SYNC_ROOT_DEPT_ID = 135L; + /** + * OA 同步挂载的本地顶级组织默认名称(集团) + */ + public static final String OA_SYNC_ROOT_DEPT_NAME = "广西现代物流集团有限公司"; /** * OA 同步挂载的本地顶级组织编码(集团) */ @@ -55,11 +61,15 @@ public class OAConvertConstant { */ public static final String LEGACY_ROOT_DEPT_CODE = "OACROOT"; /** - * 历史人员同步创建的伪顶级名称(与集团同名时仅清理非 id=135 的记录) + * 历史集团顶级名称(迁移/清理旧名时使用) */ public static final String LEGACY_ROOT_DEPT_NAME = "桂物物流集团"; /** * 同步人员时挂载的顶级组织名称(兼容旧引用) */ public static final String ROOT_COMPANY_NAME = OA_SYNC_ROOT_DEPT_NAME; + /** + * OA 同步保留的二级公司主键(OA 侧 id) + */ + public static final Set OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS = Set.of("2", "135"); }