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 c7d59b58..15a3e94a 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 @@ -216,4 +216,12 @@ public class Dept extends TenantEntity { @TableField(updateStrategy = FieldStrategy.NOT_NULL) private String oaSupSubComId; + /** + * OA上次更新时间(OA 的 modified 字段),仅同步比对使用 + */ + @JsonIgnore + @Schema(description = "OA上次更新时间", hidden = true) + @TableField(updateStrategy = FieldStrategy.NOT_NULL) + private String oaModified; + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java index 3b37571b..05ca3571 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java @@ -35,8 +35,12 @@ public enum UserSex { if (name == null || name.isEmpty()) { return null; } + String sexName = name.trim(); + if ("-1".equals(sexName) || "0".equals(sexName) || "未知".equals(sexName)) { + return null; + } return Stream.of(values()) - .filter(e -> e.getName().equals(name)) + .filter(e -> e.getName().equals(sexName) || String.valueOf(e.getCode()).equals(sexName)) .map(UserSex::getCode) .findFirst().orElse(null); } 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 590a8baf..d8390cf5 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 @@ -66,4 +66,7 @@ public class OaOrgSyncPageVO implements Serializable { @Schema(description = "是否已到最后一页") private Boolean finished; + + @Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传") + private String modifiedSince; } 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 b493bdf3..b57811da 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 @@ -63,4 +63,7 @@ public class OaPersonSyncPageVO implements Serializable { @Schema(description = "是否已到最后一页") private Boolean finished; + + @Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传") + private String modifiedSince; } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java index 3700a9e0..398ba356 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java @@ -25,6 +25,7 @@ */ package org.springblade.system.pojo.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -131,6 +132,11 @@ public class User extends TenantEntity implements IPhone { * 是否是oa的账号 */ private Integer isOa; + /** + * OA上次更新时间(OA 的 modified 字段),仅同步比对使用 + */ + @TableField(updateStrategy = FieldStrategy.NOT_NULL) + private String oaModified; /** * 是否推送mk */ 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 67dbf494..39d60f72 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 @@ -93,7 +93,8 @@ public class DeptController extends BladeController { @PreAuth(menu = "dept") @GetMapping("/list") @Parameters({ - @Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")) }) @ApiOperationSupport(order = 2) @@ -108,7 +109,8 @@ public class DeptController extends BladeController { @PreAuth(menu = "dept") @GetMapping("/lazy-list") @Parameters({ - @Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")), @Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string")) }) @ApiOperationSupport(order = 3) @@ -195,8 +197,10 @@ public class DeptController extends BladeController { @Operation(summary = "同步OA公司") public R syncOaCompany( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "20") Integer size) { - return R.data(oaSyncService.syncCompanyPage(current, size)); + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(defaultValue = "false") Boolean incremental, + @RequestParam(required = false) String modifiedSince) { + return R.data(oaSyncService.syncCompanyPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince)); } /** @@ -208,8 +212,10 @@ public class DeptController extends BladeController { @Operation(summary = "同步OA部门") public R syncOaDepartment( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "20") Integer size) { - return R.data(oaSyncService.syncDepartmentPage(current, size)); + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(defaultValue = "false") Boolean incremental, + @RequestParam(required = false) String modifiedSince) { + return R.data(oaSyncService.syncDepartmentPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince)); } /** diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java index 6aab4608..697e5d9d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java @@ -171,8 +171,10 @@ public class UserController { @Operation(summary = "同步OA人员") public R syncIamAccounts( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "20") Integer size) { - return R.data(oaSyncService.syncPersonFromUserList(current, size)); + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(defaultValue = "false") Boolean incremental, + @RequestParam(required = false) String modifiedSince) { + return R.data(oaSyncService.syncPersonFromUserList(current, size, Boolean.TRUE.equals(incremental), modifiedSince)); } /** @@ -186,6 +188,18 @@ public class UserController { return R.status(userService.updateUser(user)); } + /** + * 启用或停用 + */ + @IsAdmin + @PostMapping("/change-status") + @ApiOperationSupport(order = 6) + @Operation(summary = "启用停用", description = "传入id和status") + public R changeStatus(@Parameter(description = "用户主键", required = true) @RequestParam Long id, + @Parameter(description = "状态:1启用,0停用", required = true) @RequestParam Integer status) { + return R.status(userService.changeUserStatus(id, status)); + } + /** * 删除 */ 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 d46b3c82..f7c1829b 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 @@ -47,7 +47,8 @@ public interface DeptConvert { // 父公司id转为父部门编码,要拼接前缀 @Mapping(source = "supsubcomid", target = "parentCode") @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") - @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + // OA modified 单独落库,用于增量比对,避免覆盖本系统 updateTime + @Mapping(source = "modified", target = "oaModified") Dept baseConvert(OACompanyResponse company); @Mapping(source = "departmentname", target = "deptName") @@ -57,7 +58,8 @@ public interface DeptConvert { // 所属子公司id转为所属公司编码,要拼接前缀 @Mapping(source = "subcompanyid1", target = "belongCompanyCode") @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") - @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + // OA modified 单独落库,用于增量比对,避免覆盖本系统 updateTime + @Mapping(source = "modified", target = "oaModified") Dept baseConvert(OADepartmentResponse department); /** diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java index 28f7004b..6decb305 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java @@ -70,6 +70,7 @@ public interface UserConvert { @Mapping(source = "lastname", target = "realName") @Mapping(source = "mobile", target = "phone") @Mapping(source = "email", target = "email") + @Mapping(source = "modified", target = "oaModified") User baseConvert(OAPersonResponse person); @Mapping(source = "subcompanyname", target = "companyName") diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml index a2cb09de..f45626dd 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml @@ -80,6 +80,9 @@ and dept.dept_name like concat(concat('%', #{param3.deptName}),'%') + + and dept.short_name like concat(concat('%', #{param3.shortName}),'%') + and dept.full_name like concat(concat('%', #{param3.fullName}),'%') diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java index 3e11ad4b..3b003897 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java @@ -32,27 +32,33 @@ public interface IOASyncService { /** * 按页从 OA 人员接口同步组织与人员 * - * @param current 当前页,从 1 开始 - * @param size 每页条数 + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA) + * @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值 * @return 本页同步结果 */ - OaPersonSyncPageVO syncPersonFromUserList(int current, int size); + OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince); /** * 按页从 OA 公司接口同步公司 * - * @param current 当前页,从 1 开始 - * @param size 每页条数 + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA) + * @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值 * @return 本页同步结果 */ - OaOrgSyncPageVO syncCompanyPage(int current, int size); + OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince); /** * 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表 * - * @param current 当前页,从 1 开始 - * @param size 每页条数 + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA) + * @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值 * @return 本页同步结果 */ - OaOrgSyncPageVO syncDepartmentPage(int current, int size); + OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index 7c3db61f..fedecc77 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -93,6 +93,15 @@ public interface IUserService extends BaseService { */ boolean updateUser(User user); + /** + * 启用或停用用户(租户守卫校验用户归属,仅更新状态) + * + * @param userId 用户主键 + * @param status 状态:1 启用,0 停用 + * @return 是否成功 + */ + boolean changeUserStatus(Long userId, Integer status); + /** * 修改用户基本信息(仅限当前登录用户自身,忽略角色 / 部门 / 岗位 / 账号等敏感字段) * 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 d94020a4..24f461de 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 @@ -434,6 +434,10 @@ public class DeptServiceImpl extends ServiceImpl implements ID if (dept.getIsPlatformCompany() == null) { dept.setIsPlatformCompany(0); } + // 排序未填时默认 1 + if (dept.getSort() == null) { + dept.setSort(1); + } if (Func.isEmpty(dept.getTenantId())) { throw new ServiceException("租户ID不能为空"); } 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 7bd93f98..3829e7a6 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 @@ -60,6 +60,7 @@ public class OASyncServiceImpl implements IOASyncService { private final IOAClient oaClient; private final DeptConvert deptConvert; private final IDeptService deptService; + private final IUserService userService; private final IRoleService roleService; private final IMKPushService mkPushService; private final IDataSyncRecordService dataSyncRecordService; @@ -124,10 +125,10 @@ public class OASyncServiceImpl implements IOASyncService { @Transactional(rollbackFor = Exception.class) @Override - public OaPersonSyncPageVO syncPersonFromUserList(int current, int size) { + public OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince) { try { ComposeLogUtil.addLog(log); - return this.syncPersonFromOaPage(current, size); + return this.syncPersonFromOaPage(current, size, incremental, modifiedSince); } finally { ComposeLogUtil.removeLastLog(); } @@ -135,10 +136,10 @@ public class OASyncServiceImpl implements IOASyncService { @Transactional(rollbackFor = Exception.class) @Override - public OaOrgSyncPageVO syncCompanyPage(int current, int size) { + public OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince) { try { ComposeLogUtil.addLog(log); - return this.syncCompanyFromOaPage(current, size); + return this.syncCompanyFromOaPage(current, size, incremental, modifiedSince); } finally { ComposeLogUtil.removeLastLog(); } @@ -146,10 +147,10 @@ public class OASyncServiceImpl implements IOASyncService { @Transactional(rollbackFor = Exception.class) @Override - public OaOrgSyncPageVO syncDepartmentPage(int current, int size) { + public OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince) { try { ComposeLogUtil.addLog(log); - return this.syncDepartmentFromOaPage(current, size); + return this.syncDepartmentFromOaPage(current, size, incremental, modifiedSince); } finally { ComposeLogUtil.removeLastLog(); } @@ -280,14 +281,17 @@ public class OASyncServiceImpl implements IOASyncService { /** * 按页从 OA 人员列表同步组织与人员 * - * @param current 当前页 - * @param size 每页条数 + * @param current 当前页 + * @param size 每页条数 + * @param incremental 是否增量同步 + * @param modifiedSince 增量起始时间(后续分页回传) * @return 本页同步结果 */ - private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size) { + 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); - OAPersonSearch personSearch = buildPersonSearch(null); + IncrementalStart incrementalStart = resolvePersonIncrementalStart(incremental, modifiedSince); + OAPersonSearch personSearch = buildPersonSearch(incrementalStart.getStartTime()); personSearch.setCurPage(pageNo); personSearch.setPageSize(pageSize); OAResponse oaResponse = oaClient.queryPersonPage(new OASearch<>(personSearch)); @@ -311,26 +315,31 @@ public class OASyncServiceImpl implements IOASyncService { pageVO.setFetchedCount(oaPersons.size()); pageVO.setSyncedCount(personSyncCount.getSyncedCount()); pageVO.setSkippedCount(personSyncCount.getSkippedCount()); + pageVO.setModifiedSince(incrementalStart.getModifiedSince()); boolean finished = oaPersons.isEmpty() || oaPersons.size() < pageSize || (long) pageNo * pageSize >= totalSize; pageVO.setFinished(finished); - ComposeLogUtil.getLastLog().info("OA人员分页同步完成 {}/{},成功{},跳过{}", - pageNo, totalSize, personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount()); + ComposeLogUtil.getLastLog().info("OA人员分页同步完成 mode={} {}/{},成功{},跳过{}", + incremental ? "incremental" : "full", pageNo, totalSize, + personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount()); return pageVO; } /** * 按页从 OA 公司接口同步公司 * - * @param current 当前页 - * @param size 每页条数 + * @param current 当前页 + * @param size 每页条数 + * @param incremental 是否增量同步 + * @param modifiedSince 增量起始时间(后续分页回传) * @return 本页同步结果 */ - private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size) { + 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); - OACompanySearch companySearch = buildCompanySearch(null); + IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.COMPANY); + OACompanySearch companySearch = buildCompanySearch(incrementalStart.getStartTime()); companySearch.setCurPage(pageNo); companySearch.setPageSize(pageSize); OAResponse oaResponse = oaClient.queryCompanyPage(new OASearch<>(companySearch)); @@ -347,6 +356,7 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); } OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount); + pageVO.setModifiedSince(incrementalStart.getModifiedSince()); if (Boolean.TRUE.equals(pageVO.getFinished())) { // 公司全部分批入库后:重建层级,并删除上级不存在的数据 deptService.rebuildOaHierarchy(); @@ -354,19 +364,22 @@ public class OASyncServiceImpl implements IOASyncService { } CacheUtil.clear(SYS_CACHE); CacheUtil.clear(SYS_CACHE, Boolean.FALSE); - ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}", - pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); + ComposeLogUtil.getLastLog().info("OA公司分页同步完成 mode={} {}/{},成功{},跳过{}", + incremental ? "incremental" : "full", pageNo, totalSize, + syncCount.getSyncedCount(), syncCount.getSkippedCount()); return pageVO; } /** * 按页从 OA 部门接口同步部门 * - * @param current 当前页 - * @param size 每页条数 + * @param current 当前页 + * @param size 每页条数 + * @param incremental 是否增量同步 + * @param modifiedSince 增量起始时间(后续分页回传) * @return 本页同步结果 */ - private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size) { + private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size, boolean incremental, String modifiedSince) { int pageNo = current < 1 ? 1 : current; int pageSize = size < 1 ? 20 : Math.min(size, 200); String subCompanyIds = getOaSubCompanyIds(true); @@ -376,7 +389,8 @@ public class OASyncServiceImpl implements IOASyncService { emptyPageVO.setFinished(true); return emptyPageVO; } - OADepartmentSearch departmentSearch = buildDepartmentSearch(null, subCompanyIds); + IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.DEPT); + OADepartmentSearch departmentSearch = buildDepartmentSearch(incrementalStart.getStartTime(), subCompanyIds); departmentSearch.setCurPage(pageNo); departmentSearch.setPageSize(pageSize); OAResponse oaResponse = oaClient.queryDepartmentPage(new OASearch<>(departmentSearch)); @@ -393,6 +407,7 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); } OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount); + pageVO.setModifiedSince(incrementalStart.getModifiedSince()); if (Boolean.TRUE.equals(pageVO.getFinished())) { // 部门分批同步完成后,重建层级、清理无法挂载数据并刷新祖级列表 deptService.rebuildOaHierarchy(); @@ -401,11 +416,112 @@ public class OASyncServiceImpl implements IOASyncService { } CacheUtil.clear(SYS_CACHE); CacheUtil.clear(SYS_CACHE, Boolean.FALSE); - ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}", - pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); + ComposeLogUtil.getLastLog().info("OA部门分页同步完成 mode={} {}/{},成功{},跳过{}", + incremental ? "incremental" : "full", pageNo, totalSize, + syncCount.getSyncedCount(), syncCount.getSkippedCount()); return pageVO; } + /** + * 解析增量同步起始时间:优先使用前端回传值,保证同阶段分页起点不变 + */ + private IncrementalStart resolveIncrementalStart(boolean incremental, String modifiedSince, DeptCategory deptCategory) { + if (!incremental) { + return IncrementalStart.empty(); + } + if (StringUtils.isNotBlank(modifiedSince)) { + try { + Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME); + return new IncrementalStart(startTime, modifiedSince.trim()); + } catch (Exception exception) { + ComposeLogUtil.getLastLog().warn("解析回传 modifiedSince 失败:{},重新计算", modifiedSince); + } + } + Dept latest = deptService.getOne(Wrappers.lambdaQuery() + .select(Dept::getOaModified) + .eq(Dept::getIsOa, YES) + .eq(Dept::getDeptCategory, deptCategory.getCode()) + .isNotNull(Dept::getOaModified) + .ne(Dept::getOaModified, "") + .orderByDesc(Dept::getOaModified) + .last("limit 1"), false); + if (latest == null || StringUtils.isBlank(latest.getOaModified())) { + ComposeLogUtil.getLastLog().info("本地无 {} oaModified 记录,增量同步按全量拉取后按字段比对跳过", + deptCategory.getName()); + return IncrementalStart.empty(); + } + try { + Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME); + ComposeLogUtil.getLastLog().info("增量同步{} OA modified 起始时间:{}", + deptCategory.getName(), latest.getOaModified()); + return new IncrementalStart(startTime, latest.getOaModified()); + } catch (Exception exception) { + ComposeLogUtil.getLastLog().warn("解析本地最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified()); + return IncrementalStart.empty(); + } + } + + /** + * 人员增量同步起始时间:优先回传值,否则取本地 OA 用户最大 oaModified + */ + private IncrementalStart resolvePersonIncrementalStart(boolean incremental, String modifiedSince) { + if (!incremental) { + return IncrementalStart.empty(); + } + if (StringUtils.isNotBlank(modifiedSince)) { + try { + Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME); + return new IncrementalStart(startTime, modifiedSince.trim()); + } catch (Exception exception) { + ComposeLogUtil.getLastLog().warn("解析人员回传 modifiedSince 失败:{},重新计算", modifiedSince); + } + } + User latest = userService.getOne(Wrappers.lambdaQuery() + .select(User::getOaModified) + .eq(User::getIsOa, YES) + .isNotNull(User::getOaModified) + .ne(User::getOaModified, "") + .orderByDesc(User::getOaModified) + .last("limit 1"), false); + if (latest == null || StringUtils.isBlank(latest.getOaModified())) { + ComposeLogUtil.getLastLog().info("本地无人员 oaModified 记录,增量同步按全量拉取后按字段比对跳过"); + return IncrementalStart.empty(); + } + try { + Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME); + ComposeLogUtil.getLastLog().info("增量同步人员 OA modified 起始时间:{}", latest.getOaModified()); + return new IncrementalStart(startTime, latest.getOaModified()); + } catch (Exception exception) { + ComposeLogUtil.getLastLog().warn("解析本地人员最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified()); + return IncrementalStart.empty(); + } + } + + /** + * 增量同步起点 + */ + private static class IncrementalStart { + private final Date startTime; + private final String modifiedSince; + + private IncrementalStart(Date startTime, String modifiedSince) { + this.startTime = startTime; + this.modifiedSince = modifiedSince; + } + + private static IncrementalStart empty() { + return new IncrementalStart(null, null); + } + + private Date getStartTime() { + return startTime; + } + + private String getModifiedSince() { + return modifiedSince; + } + } + /** * 组装组织分页同步结果 */ @@ -507,14 +623,10 @@ public class OASyncServiceImpl implements IOASyncService { return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList()); } - // 查询数据库的部门,转换成map - Map deptMap = getAllCompanyDeptMap(); - - // 处理部门 - List notHandledList = handleDept(allParam, deptMap, DeptCategory.COMPANY); - int syncedCount = allParam.size() - notHandledList.size(); - int skippedCount = filteredSkipCount + notHandledList.size(); - return new OrgSyncCount(syncedCount, skippedCount, notHandledList); + // 查询已存在公司(含 oaModified),按编码索引 + Map existingDeptMap = loadExistingDeptMap(DeptCategory.COMPANY, null); + Map deptIdByCode = toDeptIdMap(existingDeptMap); + return persistSyncedDepts(allParam, existingDeptMap, deptIdByCode, DeptCategory.COMPANY, filteredSkipCount); } /** @@ -558,39 +670,62 @@ public class OASyncServiceImpl implements IOASyncService { 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() - .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode())) - .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b)); + // 查询数据库已有部门(含 oaModified) + Map existingDeptMap = loadExistingDeptMap(DeptCategory.DEPT, deptCodes); + Map deptIdByCode = toDeptIdMap(existingDeptMap); // 公司编码也放入 map,根部门挂到公司时才能解析父级 - companyDeptMap.forEach(deptMap::putIfAbsent); - // 处理部门 - List notHandledList = this.handleDept(allParam, deptMap, DeptCategory.DEPT); - int syncedCount = allParam.size() - notHandledList.size(); - int skippedCount = filteredSkipCount + notHandledList.size(); - return new OrgSyncCount(syncedCount, skippedCount, notHandledList); + companyDeptMap.forEach(deptIdByCode::putIfAbsent); + return persistSyncedDepts(allParam, existingDeptMap, deptIdByCode, DeptCategory.DEPT, filteredSkipCount); } /** - * 处理部门 - * @param allParam 待处理的部门 - * @param deptMap 数据库所有部门编码和id map - * @return 未处理的数据 + * 加载已存在组织,按部门编码索引 + * + * @param deptCategory 组织类型 + * @param deptCodes 限定编码;为空则按类型全量加载 */ - private List handleDept(List allParam, Map deptMap, DeptCategory deptCategory) { - // 数据库存在的所有部门id - Set existsDeptIds = new HashSet<>(deptMap.values()); + private Map loadExistingDeptMap(DeptCategory deptCategory, Set deptCodes) { + if (deptCodes != null && deptCodes.isEmpty()) { + return new HashMap<>(); + } + List deptList = deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, deptCategory.getCode()) + .in(deptCodes != null, Dept::getDeptCode, deptCodes) + .select(Dept::getId, Dept::getDeptCode, Dept::getOaModified, Dept::getParentId)); + return deptList.stream() + .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode())) + .collect(Collectors.toMap(Dept::getDeptCode, dept -> dept, (a, b) -> b)); + } + + private Map toDeptIdMap(Map existingDeptMap) { + Map deptIdByCode = new HashMap<>(); + existingDeptMap.forEach((code, dept) -> deptIdByCode.put(code, dept.getId())); + return deptIdByCode; + } + + /** + * 持久化同步数据:不存在则新增;存在且 oaModified 不一致才修改 + * + * @param allParam 待处理数据 + * @param existingDeptMap 已存在组织(同类型) + * @param deptIdByCode 编码→id(含上级公司等) + * @param deptCategory 组织类型 + * @param filteredSkipCount 上游过滤跳过数 + * @return 同步统计 + */ + private OrgSyncCount persistSyncedDepts(List allParam, Map existingDeptMap, + Map deptIdByCode, DeptCategory deptCategory, + int filteredSkipCount) { + Set existsDeptIds = existingDeptMap.values().stream() + .map(Dept::getId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); allParam.forEach(dept -> { - if (deptMap.containsKey(dept.getDeptCode())) { - // 根据部门编码获取对应的部门id - dept.setId(deptMap.get(dept.getDeptCode())); + if (deptIdByCode.containsKey(dept.getDeptCode())) { + dept.setId(deptIdByCode.get(dept.getDeptCode())); } else { - // 没有就生成一个id dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); - deptMap.put(dept.getDeptCode(), dept.getId()); + deptIdByCode.put(dept.getDeptCode(), dept.getId()); } }); @@ -600,7 +735,7 @@ public class OASyncServiceImpl implements IOASyncService { if (dept.getParentId() != null) { continue; } - Long parentId = deptMap.get(dept.getParentCode()); + Long parentId = deptIdByCode.get(dept.getParentCode()); if (parentId != null) { dept.setParentId(parentId); continue; @@ -615,11 +750,10 @@ public class OASyncServiceImpl implements IOASyncService { deptCategory.getName(), unresolvedParentCount); } - // 父id不为空才新增;已存在的记录即使父id未解析也更新(空父id不会覆盖原值) + // 父id不为空才新增;已存在的记录即使父id未解析也允许更新(空父id不会覆盖原值) List list = allParam.stream() .filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId())) .toList(); - // 不存在的新增 List addList = list.stream() .filter(dept -> !existsDeptIds.contains(dept.getId())) .toList(); @@ -627,18 +761,43 @@ public class OASyncServiceImpl implements IOASyncService { ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size()); deptService.saveBatch(addList); } - // 存在的修改 + // 已存在且 oaModified 不一致(或不存在可比对值)才修改 List updateList = list.stream() .filter(dept -> existsDeptIds.contains(dept.getId())) + .filter(dept -> !shouldSkipOaUpdate(existingDeptMap.get(dept.getDeptCode()), dept)) .toList(); if (CollectionUtil.isNotEmpty(updateList)) { ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size()); deptService.updateBatchById(updateList); } - // 返回仍未保存的数据 - return allParam.stream() + int unchangedCount = (int) list.stream() + .filter(dept -> existsDeptIds.contains(dept.getId())) + .filter(dept -> shouldSkipOaUpdate(existingDeptMap.get(dept.getDeptCode()), dept)) + .count(); + List notHandledList = allParam.stream() .filter(dept -> dept.getParentId() == null && !existsDeptIds.contains(dept.getId())) .toList(); + int syncedCount = addList.size() + updateList.size(); + int skippedCount = filteredSkipCount + unchangedCount + notHandledList.size(); + if (unchangedCount > 0) { + ComposeLogUtil.getLastLog().info("跳过未变更{}:{}", deptCategory.getName(), unchangedCount); + } + return new OrgSyncCount(syncedCount, skippedCount, notHandledList); + } + + /** + * oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段 + */ + private boolean shouldSkipOaUpdate(Dept existing, Dept incoming) { + if (existing == null) { + return false; + } + String localModified = StringUtils.trimToEmpty(existing.getOaModified()); + String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified()); + if (localModified.isEmpty() || remoteModified.isEmpty()) { + return false; + } + return localModified.equals(remoteModified); } /** 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 6a8a1884..eba6b8cd 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 @@ -249,6 +249,7 @@ public class OaUserListSyncHelper { }); Map userMap = new HashMap<>(); Set existsUserIds = new HashSet<>(); + Set unchangedUserIds = new HashSet<>(); users.forEach(user -> { User existingUser = existingByAccount.get(user.getAccount()); if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) { @@ -258,6 +259,9 @@ public class OaUserListSyncHelper { user.setId(existingUser.getId()); user.setPassword(null); existsUserIds.add(existingUser.getId()); + if (shouldSkipOaUpdate(existingUser, user)) { + unchangedUserIds.add(existingUser.getId()); + } } else { user.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); user.setPostId("-1"); @@ -279,12 +283,18 @@ public class OaUserListSyncHelper { } List updateUsers = users.stream() .filter(user -> existsUserIds.contains(user.getId())) + .filter(user -> !unchangedUserIds.contains(user.getId())) .toList(); if (CollectionUtil.isNotEmpty(updateUsers)) { ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); userService.updateBatchById(updateUsers); } + if (!unchangedUserIds.isEmpty()) { + ComposeLogUtil.getLastLog().info("跳过未变更用户:{}", unchangedUserIds.size()); + } ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount); + int syncedCount = addUsers.size() + updateUsers.size(); + skippedCount += unchangedUserIds.size(); if (userMap.isEmpty()) { return new PersonSyncCount(0, skippedCount); } @@ -295,7 +305,13 @@ public class OaUserListSyncHelper { .collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (first, second) -> second)); Set existsUserDeptIds = new HashSet<>(userDeptMap.values()); List userDeptList = oaPersons.stream() - .filter(oaPerson -> userMap.containsKey(userConvert.resolveAccount(oaPerson))) + .filter(oaPerson -> { + String account = userConvert.resolveAccount(oaPerson); + if (!userMap.containsKey(account)) { + return false; + } + return !unchangedUserIds.contains(userMap.get(account)); + }) .map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex)) .filter(Objects::nonNull) .toList(); @@ -333,6 +349,7 @@ public class OaUserListSyncHelper { .collect(Collectors.toSet()); String defaultRoleId = getDefaultRoleId(); List updateUserParams = list.stream() + .filter(userDeptIds -> !unchangedUserIds.contains(userDeptIds.getUserId())) .filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH) .map(userDeptIds -> { User user = new User(); @@ -347,7 +364,22 @@ public class OaUserListSyncHelper { if (CollectionUtil.isNotEmpty(updateUserParams)) { userService.updateBatchById(updateUserParams); } - return new PersonSyncCount(users.size(), skippedCount); + return new PersonSyncCount(syncedCount, skippedCount); + } + + /** + * oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段 + */ + private boolean shouldSkipOaUpdate(User existing, User incoming) { + if (existing == null) { + return false; + } + String localModified = StringUtils.trimToEmpty(existing.getOaModified()); + String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified()); + if (localModified.isEmpty() || remoteModified.isEmpty()) { + return false; + } + return localModified.equals(remoteModified); } private Dept getOrCreateRootCompany() { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index ec283ec2..3a463c4d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -339,6 +339,23 @@ public class UserServiceImpl extends BaseServiceImpl implement return submitUserInfo(user) && submitUserDept(user) && submitUserScopes(user); } + @Override + @Transactional(rollbackFor = Exception.class) + public boolean changeUserStatus(Long userId, Integer status) { + if (Func.isEmpty(userId)) { + throw new ServiceException("用户主键不能为空"); + } + if (!Objects.equals(status, DataStatusEnum.ENABLE.getCode()) && !Objects.equals(status, 0) + && !Objects.equals(status, DataStatusEnum.DISABLE.getCode())) { + throw new ServiceException("启停状态不正确"); + } + TenantGuard.verify(this, userId, USER); + Integer targetStatus = Objects.equals(status, DataStatusEnum.ENABLE.getCode()) + ? DataStatusEnum.ENABLE.getCode() : 0; + CacheUtil.clear(USER_CACHE); + return changeStatus(Collections.singletonList(userId), targetStatus); + } + @Override public boolean updateUserInfo(User user) { // 用户修改自身信息强制指定当前请求账号的ID @@ -364,7 +381,14 @@ public class UserServiceImpl extends BaseServiceImpl implement throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone())); } user.setPassword(null); - return updateById(user); + Integer originalSex = user.getSex(); + Integer normalizedSex = normalizeUserSex(originalSex); + user.setSex(normalizedSex); + boolean updated = updateById(user); + if (user.getId() != null && originalSex != null && normalizedSex == null) { + this.update(Wrappers.lambdaUpdate().set(User::getSex, null).eq(User::getId, user.getId())); + } + return updated; } private boolean submitUserDept(User user) { @@ -1086,6 +1110,7 @@ public class UserServiceImpl extends BaseServiceImpl implement throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone())); } CacheUtil.clear(USER_CACHE); + user.setSex(normalizeUserSex(user.getSex())); return save(user) && submitUserDept(user) && submitUserScopes(user); } @@ -1096,6 +1121,16 @@ public class UserServiceImpl extends BaseServiceImpl implement if (user.getIncludeNewCustomer() == null) user.setIncludeNewCustomer(0); } + /** + * 性别仅保留男 / 女 / 未知;空值或历史占位 -1 不回填,按空处理。 + */ + private Integer normalizeUserSex(Integer sex) { + if (Objects.equals(sex, 1) || Objects.equals(sex, 2) || Objects.equals(sex, 3)) { + return sex; + } + return null; + } + private boolean submitUserScopes(User user) { userDataScopeMapper.delete(Wrappers.lambdaQuery().eq(UserDataScope::getUserId, user.getId())); userCustomerScopeMapper.delete(Wrappers.lambdaQuery().eq(UserCustomerScope::getUserId, user.getId())); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/UserWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/UserWrapper.java index 7dbfaa32..3630589b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/UserWrapper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/UserWrapper.java @@ -38,6 +38,7 @@ import org.springblade.system.pojo.enums.DictEnum; import org.springblade.system.pojo.vo.UserVO; import java.util.List; +import java.util.Objects; /** * 包装类,返回视图层所需的字段 @@ -81,7 +82,11 @@ public class UserWrapper extends BaseEntityWrapper { userVO.setPostId(StringPool.EMPTY); userVO.setPostName("暂未分配"); } - userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex())); + if (user.getSex() == null || Objects.equals(user.getSex(), -1)) { + userVO.setSexName(""); + } else { + userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex())); + } userVO.setUserTypeName(DictCache.getValue(DictEnum.USER_TYPE, user.getUserType())); return userVO; } 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 a4bbad50..81b9cb44 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 @@ -38,6 +38,7 @@ import org.springblade.core.tenant.annotation.TenantIgnore; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.SysCache; import org.springblade.system.cache.UserCache; import org.springblade.transport.excel.CustomerArchiveExcel; import org.springblade.transport.mapper.CreditRatingStandardMapper; @@ -118,6 +119,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl deptIdList = Func.toLongList(customer.getDeptIds()); - if (Func.isEmpty(deptIdList)) { - throw new ServiceException("所属组织不正确"); - } - customer.setDeptId(deptIdList.get(0)); - } - customer.setDeptName(trimToEmpty(customer.getDeptName())); + applyFixedOwnerDept(customer); customer.setAccessType(Func.isEmpty(customer.getAccessType()) ? ACCESS_TEMPORARY : customer.getAccessType()); customer.setApprovalStatus(Func.isEmpty(customer.getApprovalStatus()) ? APPROVAL_DRAFT : customer.getApprovalStatus()); if (Func.isEmpty(customer.getStatus())) { @@ -1628,6 +1623,25 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl deptIdList = Func.toLongList(deptIds); + if (Func.isEmpty(deptIdList) || deptIdList.get(0) == null) { + throw new ServiceException("所属组织「" + FIXED_OWNER_DEPT_NAME + "」不正确"); + } + Long deptId = deptIdList.get(0); + customer.setDeptId(deptId); + customer.setDeptIds(String.valueOf(deptId)); + } + private String trimToEmpty(String value) { return value == null ? "" : value.trim(); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java index 080cf984..deb2065f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java @@ -739,9 +739,6 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl 0) { - throw new ServiceException("项目应收账款额度不能超过项目资金使用额度"); - } validateDays(projectApply.getReceivableDays(), "应收账款回款期限"); validateDays(projectApply.getPaymentDays(), "回款账期"); validateAmount(projectApply.getProjectScale(), "项目规模", true); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 16619068..64c4837e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -1926,7 +1926,7 @@ public class WaybillServiceImpl extends BaseServiceImpl if (isRoadTransport(waybill.getTransportType())) { if ("承运商".equals(waybill.getCarrierType())) { TransportBusinessSupport.validateRequired(waybill.getCarrierName(), "承运商不能为空"); - } else { + } else if (!isBatchImport(waybill)) { TransportBusinessSupport.validateRequired(waybill.getDriverName(), "司机不能为空"); } } @@ -2079,6 +2079,10 @@ public class WaybillServiceImpl extends BaseServiceImpl return value.contains("公路") || value.contains("道路") || value.contains("road") || "gl".equals(value); } + private boolean isBatchImport(Waybill waybill) { + return waybill != null && TransportBusinessSupport.DATA_SOURCE_BATCH_IMPORT.equals(waybill.getDataSource()); + } + private void fillJsonFromFlatFields(Waybill waybill) { if (Func.isEmpty(waybill.getTaskEntryMode())) { waybill.setTaskEntryMode("simple"); diff --git a/doc/sql/transport/blade_dept_oa_modified_20261010.sql b/doc/sql/transport/blade_dept_oa_modified_20261010.sql new file mode 100644 index 00000000..97960aff --- /dev/null +++ b/doc/sql/transport/blade_dept_oa_modified_20261010.sql @@ -0,0 +1,4 @@ +-- 机构表:记录 OA 的 modified(上次更新时间),用于增量同步比对 + +ALTER TABLE `blade_dept` + ADD COLUMN `oa_modified` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上次更新时间(modified)' AFTER `oa_sup_sub_com_id`; diff --git a/doc/sql/transport/blade_user_oa_modified_20261010.sql b/doc/sql/transport/blade_user_oa_modified_20261010.sql new file mode 100644 index 00000000..1b92853f --- /dev/null +++ b/doc/sql/transport/blade_user_oa_modified_20261010.sql @@ -0,0 +1,4 @@ +-- 用户表:记录 OA 的 modified(上次更新时间),用于增量同步比对 + +ALTER TABLE `blade_user` + ADD COLUMN `oa_modified` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上次更新时间(modified)' AFTER `is_oa`;