✨ 组织同步收敛为双顶级与二级公司白名单
集团顶级改为 id=1/广西现代物流集团有限公司,仅同步 OA 公司 2、135 及其子公司与部门,并同步客商固定所属组织名称。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
@@ -69,4 +69,7 @@ public class OaOrgSyncPageVO implements Serializable {
|
||||
|
||||
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||
private String modifiedSince;
|
||||
|
||||
@Schema(description = "全量同步结束后原始数据层级 JSON 文件路径")
|
||||
private String dumpFilePath;
|
||||
}
|
||||
|
||||
+3
@@ -66,4 +66,7 @@ public class OaPersonSyncPageVO implements Serializable {
|
||||
|
||||
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||
private String modifiedSince;
|
||||
|
||||
@Schema(description = "全量同步结束后原始数据层级 JSON 文件路径")
|
||||
private String dumpFilePath;
|
||||
}
|
||||
|
||||
+3
-3
@@ -149,16 +149,16 @@ public interface IDeptService extends IService<Dept> {
|
||||
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();
|
||||
|
||||
|
||||
+69
-5
@@ -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<DeptMapper, Dept> implements ID
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int clearNonTopDepts() {
|
||||
// 同步前清理:保留集团锚点 id=135,以及「外部组织」顶级;其余删除
|
||||
// 同步前清理:保留集团锚点 id=1,以及「外部组织」顶级;其余删除
|
||||
Dept groupRoot = this.ensureOaSyncRootDept();
|
||||
Dept externalRoot = this.ensureExternalOrgTopDept();
|
||||
Set<Long> keepIds = new HashSet<>();
|
||||
@@ -416,6 +417,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> 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<DeptMapper, Dept> 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<DeptMapper, Dept> 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<DeptMapper, Dept> 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<DeptMapper, Dept> 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.<Dept>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<Dept> externalTops = this.list(Wrappers.<Dept>lambdaQuery()
|
||||
@@ -540,11 +603,12 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理历史伪顶级(OACROOT);同名「桂物物流集团」仅清理非 id=135 的顶级
|
||||
* 清理历史伪顶级(OACROOT);同名旧集团仅清理非 id=1 的顶级
|
||||
*/
|
||||
private void removeLegacyOaSyncRoots() {
|
||||
List<Dept> legacyRoots = this.list(Wrappers.<Dept>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
|
||||
|
||||
+147
-5
@@ -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<OAPersonResponse> 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<OACompanyResponse> 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<OADepartmentResponse> 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<String> companyNames = syncAll ? Collections.emptySet() : getCompanyNames();
|
||||
List<Dept> 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<String, Dept> companyByOaId = loadCompanyByOaId();
|
||||
Set<String> allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId);
|
||||
String rootCompanyId = getRootCompanyId();
|
||||
if (!syncAll && rootCompanyId == null) {
|
||||
return new OrgSyncCount(0, oaDepts.size(), Collections.emptyList());
|
||||
}
|
||||
Set<String> rootCompanyDeptNames = syncAll ? Collections.emptySet() : getRootCompanyDeptNames();
|
||||
List<Dept> 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<String> resolveAllowedCompanyOaIds(Map<String, Dept> companyByOaId) {
|
||||
Set<String> 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<Dept> oaCompanyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||
.isNotNull(Dept::getOaId)
|
||||
.ne(Dept::getOaId, ""));
|
||||
Map<String, Dept> companyByOaId = new HashMap<>();
|
||||
for (Dept company : oaCompanyList) {
|
||||
if (StringUtils.isNotBlank(company.getOaId())) {
|
||||
companyByOaId.put(company.getOaId(), company);
|
||||
}
|
||||
}
|
||||
Set<String> allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId);
|
||||
Set<Long> deleteIds = new HashSet<>();
|
||||
for (Dept company : oaCompanyList) {
|
||||
if (!allowedCompanyOaIds.contains(company.getOaId())) {
|
||||
deleteIds.add(company.getId());
|
||||
}
|
||||
}
|
||||
Map<String, String> companyCodeToOaId = new HashMap<>();
|
||||
for (Dept company : oaCompanyList) {
|
||||
if (StringUtils.isNotBlank(company.getDeptCode()) && StringUtils.isNotBlank(company.getOaId())) {
|
||||
companyCodeToOaId.put(company.getDeptCode(), company.getOaId());
|
||||
}
|
||||
}
|
||||
List<Dept> oaDeptList = deptService.list(Wrappers.<Dept>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<Dept> 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<String, Dept> companyByOaId = new HashMap<>();
|
||||
for (Dept company : companyList) {
|
||||
String oaId = this.resolveOaCompanyId(company);
|
||||
if (StringUtils.isNotBlank(oaId)) {
|
||||
companyByOaId.put(oaId, company);
|
||||
}
|
||||
}
|
||||
Set<String> allowedCompanyOaIds = resolveAllowedCompanyOaIds(companyByOaId);
|
||||
return companyList.stream()
|
||||
.map(this::resolveOaCompanyId)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.filter(allowedCompanyOaIds::contains)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<OAPersonResponse> collectingPersons = Collections.synchronizedList(new ArrayList<>());
|
||||
private volatile boolean collecting;
|
||||
|
||||
/**
|
||||
* 机构全量同步会话收集的公司/部门原始数据
|
||||
*/
|
||||
private final List<OACompanyResponse> collectingCompanies = Collections.synchronizedList(new ArrayList<>());
|
||||
private final List<OADepartmentResponse> 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<OAPersonResponse> oaPersons) {
|
||||
if (!collecting || oaPersons == null || oaPersons.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
collectingPersons.addAll(oaPersons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集本页公司原始数据
|
||||
*/
|
||||
public void collectCompanies(List<OACompanyResponse> oaCompanies) {
|
||||
if (!collectingOrg || oaCompanies == null || oaCompanies.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
collectingCompanies.addAll(oaCompanies);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集本页部门原始数据
|
||||
*/
|
||||
public void collectDepartments(List<OADepartmentResponse> oaDepartments) {
|
||||
if (!collectingOrg || oaDepartments == null || oaDepartments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
collectingDepartments.addAll(oaDepartments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量同步结束:先写入人员层级 JSON,再异步补齐公司/部门接口原始层级数据
|
||||
*
|
||||
* @return 人员原始层级 JSON 文件路径,失败时返回 null
|
||||
*/
|
||||
public synchronized String finishFullDump() {
|
||||
if (!collecting) {
|
||||
return null;
|
||||
}
|
||||
List<OAPersonResponse> persons = new ArrayList<>(collectingPersons);
|
||||
collecting = false;
|
||||
collectingPersons.clear();
|
||||
try {
|
||||
String stamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
|
||||
Map<String, Object> 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<OACompanyResponse> companies = new ArrayList<>(collectingCompanies);
|
||||
List<OADepartmentResponse> 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<OACompanyResponse> companies = fetchAllCompanies();
|
||||
List<OADepartmentResponse> 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<OACompanyResponse> companies,
|
||||
List<OADepartmentResponse> departments) throws IOException {
|
||||
Map<String, Object> 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<OACompanyResponse> fetchAllCompanies() {
|
||||
List<OACompanyResponse> 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<OADepartmentResponse> fetchAllDepartments(List<OACompanyResponse> companies) {
|
||||
List<OADepartmentResponse> 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<Map<String, Object>> buildCompanyTree(List<OACompanyResponse> companies) {
|
||||
Map<String, Map<String, Object>> nodeMap = new LinkedHashMap<>();
|
||||
for (OACompanyResponse company : companies) {
|
||||
if (company == null || StringUtils.isBlank(company.getId())) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>>());
|
||||
nodeMap.put(company.getId(), node);
|
||||
}
|
||||
return linkChildren(nodeMap);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildDepartmentTree(List<OADepartmentResponse> departments) {
|
||||
Map<String, Map<String, Object>> nodeMap = new LinkedHashMap<>();
|
||||
for (OADepartmentResponse department : departments) {
|
||||
if (department == null || StringUtils.isBlank(department.getId())) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>>());
|
||||
nodeMap.put(department.getId(), node);
|
||||
}
|
||||
return linkChildren(nodeMap);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildPersonTree(List<OAPersonResponse> persons) {
|
||||
Map<String, Map<String, Object>> companyMap = new LinkedHashMap<>();
|
||||
Map<String, Map<String, Object>> 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<String, Object> companyNode = companyMap.computeIfAbsent(companyId, key -> {
|
||||
Map<String, Object> node = new LinkedHashMap<>();
|
||||
node.put("id", key);
|
||||
node.put("parentId", "0");
|
||||
node.put("type", "company");
|
||||
node.put("name", StringUtils.defaultIfBlank(person.getSubcompanyname(), key));
|
||||
Map<String, Object> 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<Map<String, Object>>());
|
||||
return node;
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> companyChildren = (List<Map<String, Object>>) companyNode.get("children");
|
||||
Map<String, Object> deptNode = deptMap.computeIfAbsent(deptId, key -> {
|
||||
Map<String, Object> node = new LinkedHashMap<>();
|
||||
node.put("id", key);
|
||||
node.put("parentId", companyId);
|
||||
node.put("type", "department");
|
||||
node.put("name", StringUtils.defaultIfBlank(person.getDepartmentname(), key));
|
||||
Map<String, Object> 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<Map<String, Object>>());
|
||||
companyChildren.add(node);
|
||||
return node;
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> deptChildren = (List<Map<String, Object>>) deptNode.get("children");
|
||||
Map<String, Object> 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<Map<String, Object>> linkChildren(Map<String, Map<String, Object>> nodeMap) {
|
||||
List<Map<String, Object>> roots = new ArrayList<>();
|
||||
for (Map<String, Object> node : nodeMap.values()) {
|
||||
String parentId = Objects.toString(node.get("parentId"), "0");
|
||||
Map<String, Object> parent = nodeMap.get(parentId);
|
||||
if (parent != null && !Objects.equals(parent.get("id"), node.get("id"))) {
|
||||
((List<Map<String, Object>>) 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<String, Object> 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();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -86,7 +86,7 @@ public class OaUserListSyncHelper {
|
||||
private final IRoleService roleService;
|
||||
|
||||
/**
|
||||
* 从人员数据提取二级公司、三级部门,并挂到集团顶级(id=135/GWWL) 下
|
||||
* 从人员数据提取二级公司、三级部门,并挂到集团顶级(id=1/GWWL) 下
|
||||
*
|
||||
* @param oaPersons OA人员
|
||||
* @return 组织索引
|
||||
|
||||
+2
-2
@@ -166,7 +166,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
*/
|
||||
private static final String APPROVAL_REJECTED = "rejected";
|
||||
/** 客商所属组织固定为顶层组织 */
|
||||
private static final String FIXED_OWNER_DEPT_NAME = "桂物物流集团";
|
||||
private static final String FIXED_OWNER_DEPT_NAME = "广西现代物流集团有限公司";
|
||||
|
||||
/**
|
||||
* 金蝶协同状态:未推送(新增客商默认值,审批通过后参与推送)
|
||||
@@ -2162,7 +2162,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商所属组织固定为顶层「桂物物流集团」,忽略前端传入的其他组织。
|
||||
* 客商所属组织固定为顶层「广西现代物流集团有限公司」,忽略前端传入的其他组织。
|
||||
*/
|
||||
private void applyFixedOwnerDept(CustomerArchiveVO customer) {
|
||||
customer.setDeptName(FIXED_OWNER_DEPT_NAME);
|
||||
|
||||
+2
-2
@@ -110,7 +110,7 @@ class CertAuditServiceImplTest {
|
||||
ArgumentCaptor<TransportVehicle> 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\"}";
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
+4
-4
@@ -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<String, Object> 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"), "区间要同时带出起止,否则用户得照图再敲一遍");
|
||||
}
|
||||
|
||||
+14
-4
@@ -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<String> OA_SYNC_KEEP_SECOND_LEVEL_COMPANY_IDS = Set.of("2", "135");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user