⚡ 优化角色数据权限、用户组织范围与流程完结处理人展示

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-09 22:55:11 +08:00
co-authored by Cursor
parent 2ab7b893fd
commit 719d09fde2
13 changed files with 280 additions and 96 deletions
@@ -415,7 +415,8 @@ public class BusinessProcessServiceImpl extends ServiceImpl<BusinessProcessMappe
businessProcess.setCompleteTime(new Date());
businessProcess.setCurrentNodeIds(null);
businessProcess.setCurrentNodeNames(null);
businessProcess.setCurrentHandlers(null);
// 流程完结后当前处理人统一展示「流程结束」,不再保留最后处理人
businessProcess.setCurrentHandlers("流程结束");
boolean updated = this.updateById(businessProcess);
log.info("标记流程完结 processInstanceId={} approved={} approveStatus={} result={}",
processInstanceId, approved, approveStatus, updated);
@@ -1079,13 +1080,13 @@ public class BusinessProcessServiceImpl extends ServiceImpl<BusinessProcessMappe
if (CollectionUtil.isEmpty(currentNodes)) {
log.info("修改业务流程 当前节点处理人为空");
if (businessProcessId != null) {
// 清空当前节点,当前处理人,接收时间
log.info("修改业务流程 流程结束清空当前节点,当前处理人,接收时间 业务流程id:{} 流程实例id:{}", businessProcessId, processInstanceId);
// 清空当前节点;完结时当前处理人改为「流程结束」,未完结则清空
log.info("修改业务流程 流程结束清空当前节点,更新当前处理人 业务流程id:{} 流程实例id:{} complete={}", businessProcessId, processInstanceId, complete);
this.lambdaUpdate()
.eq(BusinessProcess::getId, businessProcessId)
.set(BusinessProcess::getCurrentNodeIds, null)
.set(BusinessProcess::getCurrentNodeNames, null)
.set(BusinessProcess::getCurrentHandlers, null)
.set(BusinessProcess::getCurrentHandlers, complete ? "流程结束" : null)
.set(complete, BusinessProcess::getIsCompleted, true)
.set(complete, BusinessProcess::getCompleteTime, new Date())
.set(BusinessProcess::getUpdateTime, new Date())
@@ -31,6 +31,7 @@ import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import org.springblade.system.pojo.context.IPhone;
import java.io.Serial;
import java.io.Serializable;
@@ -44,7 +45,7 @@ import java.io.Serializable;
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class UserExcel implements Serializable {
public class UserExcel implements Serializable, IPhone {
@Serial
private static final long serialVersionUID = 1L;
@@ -222,43 +222,94 @@
) menu ORDER BY sort
</select>
<!-- 授权树与侧边栏一致:仅返回系统可见菜单(is_display=1),并保留其下按钮权限节点 -->
<select id="grantTree" resultMap="treeNodeResultMap">
select id, parent_id, name as title, id as "value", id as "key" from blade_menu where is_deleted = 0 order by sort
select id, parent_id, name as title, id as "value", id as "key"
from blade_menu
where is_deleted = 0
and (
(category = 1 and ifnull(is_display, 1) = 1)
or (
category &lt;&gt; 1
and ifnull(is_display, 1) = 1
and parent_id in (
select id from blade_menu
where is_deleted = 0 and category = 1 and ifnull(is_display, 1) = 1
)
)
)
order by sort
</select>
<select id="grantTreeByRole" resultMap="treeNodeResultMap">
select id, parent_id, name as title, id as "value", id as "key" from blade_menu where is_deleted = 0
and id in ( select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach> )
or id in (
select parent_id from blade_menu where is_deleted = 0
and id in ( select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach> )
select id, parent_id, name as title, id as "value", id as "key"
from blade_menu
where is_deleted = 0
and (
(category = 1 and ifnull(is_display, 1) = 1)
or (
category &lt;&gt; 1
and ifnull(is_display, 1) = 1
and parent_id in (
select id from blade_menu
where is_deleted = 0 and category = 1 and ifnull(is_display, 1) = 1
)
)
)
and (
id in (
select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
)
or id in (
select parent_id from blade_menu
where is_deleted = 0
and ifnull(is_display, 1) = 1
and id in (
select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
)
)
)
order by sort
</select>
<select id="grantTopTree" resultMap="treeNodeResultMap">
select id, parent_id, name as title, id as "value", id as "key" from blade_menu where category = 1 and is_deleted = 0 order by sort
select id, parent_id, name as title, id as "value", id as "key"
from blade_menu
where category = 1 and is_deleted = 0 and ifnull(is_display, 1) = 1
order by sort
</select>
<select id="grantTopTreeByRole" resultMap="treeNodeResultMap">
select id, parent_id, name as title, id as "value", id as "key" from blade_menu where category = 1 and is_deleted = 0
and id in ( select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach> )
or id in (
select parent_id from blade_menu where is_deleted = 0
and id in ( select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach> )
)
select id, parent_id, name as title, id as "value", id as "key"
from blade_menu
where category = 1
and is_deleted = 0
and ifnull(is_display, 1) = 1
and (
id in (
select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
)
or id in (
select parent_id from blade_menu
where is_deleted = 0
and ifnull(is_display, 1) = 1
and id in (
select menu_id from blade_role_menu where role_id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
)
)
)
order by sort
</select>
@@ -41,7 +41,8 @@ import java.util.List;
public interface IRoleMenuDataScopeService extends BaseService<RoleMenuDataScope> {
/**
* 查询角色数据权限配置树
* 查询角色数据权限配置树。
* 菜单范围与该角色已保存的菜单权限一致,已配置的数据权限不会被清除。
*
* @param roleId 角色主键
* @return 菜单树与已配置权限
@@ -57,11 +58,12 @@ public interface IRoleMenuDataScopeService extends BaseService<RoleMenuDataScope
boolean submit(RoleMenuDataScopeDTO dto);
/**
* 按菜单编码合并解析角色数据权限
* 按菜单编码合并解析角色数据权限。
* 全部组织、本人创建直接按角色权限返回;所在部门及下级的组织范围由调用方按用户数据权限组织补齐。
*
* @param roleIds 角色ID集合
* @param menuCode 菜单编码
* @param userDeptIds 用户所属组织
* @param userDeptIds 用户所属组织(所在部门及下级不再据此展开)
* @return 解析结果
*/
RoleDataScopeVO resolveByMenuCode(List<Long> roleIds, String menuCode, List<Long> userDeptIds);
@@ -401,4 +401,13 @@ public interface IUserService extends BaseService<User> {
* @param user 用户对象
*/
void encryptPhone(IPhone user);
/**
* 首次同步时初始化数据管理组织;已有配置则跳过更新。
*
* @param userId 用户主键
* @param deptIds 所属组织 ID 集合
* @return 是否成功
*/
boolean initManageDeptScopeIfAbsent(Long userId, List<Long> deptIds);
}
@@ -49,6 +49,7 @@ import org.springblade.system.mapper.DeptMapper;
import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.DeptCategory;
import org.springblade.system.pojo.enums.RoleDataScopeType;
import org.springblade.system.pojo.vo.DeptVO;
import org.springblade.system.pojo.vo.RoleDataScopeVO;
import org.springblade.system.pojo.vo.UserVO;
@@ -271,6 +272,15 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
allowedDeptIds.add(deptId);
}
}
} else if (RoleDataScopeType.DEPT_AND_CHILD.matches(scope.getScopeType())) {
// 所在部门及下级按用户数据权限组织,不按所属组织展开
if (Func.isNotEmpty(scope.getDeptIds())) {
allowedDeptIds.addAll(scope.getDeptIds());
}
List<Long> manageDeptIds = userService.resolveManageDeptIds(userId);
if (Func.isNotEmpty(manageDeptIds)) {
allowedDeptIds.addAll(manageDeptIds);
}
} else if (Func.isNotEmpty(scope.getDeptIds())) {
allowedDeptIds.addAll(scope.getDeptIds());
}
@@ -34,6 +34,7 @@ import org.springblade.common.constant.CommonConstant;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.ParamCache;
import org.springblade.system.convert.UserConvert;
import org.springblade.system.log.ComposeLogUtil;
@@ -790,6 +791,21 @@ public class OaUserListSyncHelper {
if (CollectionUtil.isNotEmpty(updateUserParams)) {
userService.updateBatchById(updateUserParams);
}
// 首次同步:数据管理组织=所属组织;已有数据管理组织的用户下次同步跳过
Set<Long> newUserIds = addUsers.stream()
.map(User::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (UserDeptIdsVO userDeptIds : list) {
if (userDeptIds.getUserId() == null || !newUserIds.contains(userDeptIds.getUserId())) {
continue;
}
List<Long> deptIdList = Func.toLongList(userDeptIds.getDeptIds());
if (Func.isEmpty(deptIdList)) {
continue;
}
userService.initManageDeptScopeIfAbsent(userDeptIds.getUserId(), deptIdList);
}
return new PersonSyncCount(syncedCount, skippedCount, removedCount);
}
@@ -34,13 +34,14 @@ import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.node.ForestNodeMerger;
import org.springblade.core.tool.node.TreeNode;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.SysCache;
import org.springblade.system.mapper.MenuMapper;
import org.springblade.system.mapper.RoleMenuDataScopeDeptMapper;
import org.springblade.system.mapper.RoleMenuDataScopeMapper;
import org.springblade.system.mapper.RoleMenuMapper;
import org.springblade.system.pojo.dto.RoleMenuDataScopeDTO;
import org.springblade.system.pojo.entity.Menu;
import org.springblade.system.pojo.entity.Role;
import org.springblade.system.pojo.entity.RoleMenu;
import org.springblade.system.pojo.entity.RoleMenuDataScope;
import org.springblade.system.pojo.entity.RoleMenuDataScopeDept;
import org.springblade.system.pojo.enums.RoleDataScopeType;
@@ -58,6 +59,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
@@ -74,6 +76,7 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
private final RoleMenuDataScopeDeptMapper roleMenuDataScopeDeptMapper;
private final MenuMapper menuMapper;
private final RoleMenuMapper roleMenuMapper;
private final IRoleService roleService;
@Override
@@ -87,7 +90,8 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
}
RoleMenuDataScopeTreeVO treeVO = new RoleMenuDataScopeTreeVO();
treeVO.setRoleName(role.getRoleName());
List<TreeNode> menuNodes = menuMapper.grantTree();
// 数据权限菜单与已保存的菜单权限保持一致;这里只收窄展示范围,不删除已有数据权限配置
List<TreeNode> menuNodes = filterGrantedMenus(menuMapper.grantTree(), roleId);
treeVO.setMenuTree(ForestNodeMerger.merge(menuNodes));
List<RoleMenuDataScope> scopeList = list(Wrappers.<RoleMenuDataScope>lambdaQuery()
@@ -189,7 +193,7 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
return RoleDataScopeVO.all();
}
List<RoleMenuDataScope> scopeList = listScopes(roleIds, menu.getId());
return mergeScopes(scopeList, userDeptIds);
return mergeScopes(scopeList);
}
@Override
@@ -205,7 +209,45 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
if (Func.isEmpty(scopeList)) {
return RoleDataScopeVO.none();
}
return mergeScopes(scopeList, userDeptIds);
return mergeScopes(scopeList);
}
/**
* 按角色已分配菜单过滤数据权限树,并补齐上级节点以保持层级。
* 未勾选的菜单不再展示;已配置的数据权限记录保持不变,菜单重新勾选后配置仍在。
*
* @param menuNodes 全部菜单节点
* @param roleId 角色主键
* @return 与菜单权限一致的节点
*/
private List<TreeNode> filterGrantedMenus(List<TreeNode> menuNodes, Long roleId) {
if (Func.isEmpty(menuNodes)) {
return Collections.emptyList();
}
Set<Long> grantedMenuIds = roleMenuMapper.selectList(Wrappers.<RoleMenu>lambdaQuery()
.eq(RoleMenu::getRoleId, roleId))
.stream()
.map(RoleMenu::getMenuId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (grantedMenuIds.isEmpty()) {
return Collections.emptyList();
}
Map<Long, Long> parentMap = menuNodes.stream()
.filter(node -> node.getId() != null)
.collect(Collectors.toMap(TreeNode::getId, TreeNode::getParentId, (left, right) -> left));
Set<Long> visibleMenuIds = new LinkedHashSet<>(grantedMenuIds);
for (Long menuId : grantedMenuIds) {
Long parentId = parentMap.get(menuId);
int guard = 0;
while (parentId != null && parentId > 0 && guard++ < 32) {
visibleMenuIds.add(parentId);
parentId = parentMap.get(parentId);
}
}
return menuNodes.stream()
.filter(node -> node.getId() != null && visibleMenuIds.contains(node.getId()))
.toList();
}
private Menu findMenuByCode(String menuCode) {
@@ -230,30 +272,33 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
List<RoleMenuDataScope> scopeList = list(Wrappers.<RoleMenuDataScope>lambdaQuery()
.in(RoleMenuDataScope::getRoleId, roleIds)
.eq(RoleMenuDataScope::getIsDeleted, 0));
return mergeScopes(scopeList, userDeptIds);
return mergeScopes(scopeList);
}
private RoleDataScopeVO mergeScopes(List<RoleMenuDataScope> scopeList, List<Long> userDeptIds) {
/**
* 合并多条角色菜单数据权限。
* 全部组织、本人创建优先于组织范围;所在部门及下级不在此处展开所属组织,
* 由调用方替换为用户的数据权限组织。
*/
private RoleDataScopeVO mergeScopes(List<RoleMenuDataScope> scopeList) {
if (Func.isEmpty(scopeList)) {
return RoleDataScopeVO.all();
}
boolean hasAll = false;
boolean hasDeptScope = false;
boolean hasDeptAndChild = false;
boolean hasCustom = false;
boolean hasOwn = false;
LinkedHashSet<Long> allowedDeptIds = new LinkedHashSet<>();
LinkedHashSet<Long> customDeptIds = new LinkedHashSet<>();
Map<Long, List<Long>> customDeptMap = loadCustomDeptMap(scopeList);
for (RoleMenuDataScope scope : scopeList) {
RoleDataScopeType type = RoleDataScopeType.of(scope.getScopeType());
switch (type) {
case ALL -> hasAll = true;
case DEPT_AND_CHILD -> {
hasDeptScope = true;
allowedDeptIds.addAll(expandDeptIds(userDeptIds, true));
}
case DEPT_AND_CHILD -> hasDeptAndChild = true;
case CUSTOM -> {
hasDeptScope = true;
allowedDeptIds.addAll(customDeptMap.getOrDefault(scope.getId(), Collections.emptyList()));
hasCustom = true;
customDeptIds.addAll(customDeptMap.getOrDefault(scope.getId(), Collections.emptyList()));
}
case OWN_CREATE -> hasOwn = true;
default -> {
@@ -263,8 +308,11 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
if (hasAll) {
return RoleDataScopeVO.all();
}
if (hasDeptScope) {
return RoleDataScopeVO.ofDepts(RoleDataScopeType.CUSTOM.getCode(), new ArrayList<>(allowedDeptIds));
if (hasDeptAndChild) {
return RoleDataScopeVO.ofDepts(RoleDataScopeType.DEPT_AND_CHILD.getCode(), new ArrayList<>(customDeptIds));
}
if (hasCustom) {
return RoleDataScopeVO.ofDepts(RoleDataScopeType.CUSTOM.getCode(), new ArrayList<>(customDeptIds));
}
if (hasOwn) {
return RoleDataScopeVO.ownCreate();
@@ -287,26 +335,4 @@ public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl<RoleMenuDataSc
Collectors.mapping(RoleMenuDataScopeDept::getDeptId, Collectors.toList())));
}
private List<Long> expandDeptIds(List<Long> baseDeptIds, boolean includeChildren) {
if (Func.isEmpty(baseDeptIds)) {
return Collections.emptyList();
}
LinkedHashSet<Long> result = new LinkedHashSet<>();
for (Long deptId : baseDeptIds) {
if (Func.isEmpty(deptId) || deptId <= 0) {
continue;
}
if (includeChildren) {
List<Long> childIds = SysCache.getDeptChildIds(deptId);
if (Func.isNotEmpty(childIds)) {
result.addAll(childIds);
} else {
result.add(deptId);
}
} else {
result.add(deptId);
}
}
return new ArrayList<>(result);
}
}
@@ -256,6 +256,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
user.setStatus(status);
user.setIsOa(1);
user.setSyncTime(new Date());
// 首次同步:数据管理组织默认等于所属组织
user.setDataScopeDeptIds(Func.toLongList(user.getDeptId()));
applyUserDefaults(user);
return saveUser(user);
}
@@ -326,6 +328,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
if (userCount > 0L) {
throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", user.getAccount()));
}
encryptPhone(user);
Long phoneCount = baseMapper.selectCount(
Wrappers.<User>query().lambda()
.eq(User::getTenantId, tenantId)
@@ -373,6 +376,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
}
private boolean submitUserInfo(User user) {
encryptPhone(user);
Long phoneCount = baseMapper.selectCount(
Wrappers.<User>query().lambda()
.eq(User::getTenantId, AuthUtil.getTenantId())
@@ -736,6 +740,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
public List<UserExcel> exportUser(Wrapper<User> queryWrapper) {
List<UserExcel> userList = baseMapper.exportUser(queryWrapper);
userList.forEach(user -> {
decryptPhone(user);
user.setUserTypeName(DictCache.getValue(DictEnum.USER_TYPE, user.getUserType()));
user.setRoleName(StringUtil.join(SysCache.getRoleNames(user.getRoleId())));
user.setDeptName(StringUtil.join(SysCache.getDeptNames(user.getDeptId())));
@@ -1105,6 +1110,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
}
validatePlainPassword(user.getPassword());
user.setPassword(DigestUtil.encrypt(user.getPassword()));
encryptPhone(user);
Long userCount = baseMapper.selectCount(Wrappers.<User>query().lambda().eq(User::getTenantId, tenantId).eq(User::getAccount, user.getAccount()));
if (userCount > 0L && Func.isEmpty(user.getId())) {
throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", user.getAccount()));
@@ -1301,9 +1307,45 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
if (user == null) {
return;
}
if (StringUtil.isNotBlank(user.getPhone())) {
user.setPhone(Hex.toHexString(SM2Util.encrypt(user.getPhone(), properties.getPublicKey())));
String phone = user.getPhone();
if (StringUtil.isBlank(phone)) {
return;
}
// 已是 SM2 密文则跳过,避免重复加密
if (phone.length() > 20 && isSm2HexCipher(phone)) {
return;
}
user.setPhone(Hex.toHexString(SM2Util.encrypt(phone, properties.getPublicKey())));
}
/**
* 首次同步初始化数据管理组织;已有配置时跳过,避免后续同步覆盖手工调整。
*
* @param userId 用户主键
* @param deptIds 所属组织 ID 集合
* @return 是否成功
*/
@Override
public boolean initManageDeptScopeIfAbsent(Long userId, List<Long> deptIds) {
if (Func.isEmpty(userId) || userId <= 0 || Func.isEmpty(deptIds)) {
return true;
}
Long existsCount = userDataScopeMapper.selectCount(Wrappers.<UserDataScope>lambdaQuery()
.eq(UserDataScope::getUserId, userId));
if (existsCount != null && existsCount > 0L) {
return true;
}
User user = new User();
user.setId(userId);
user.setDataScopeDeptIds(deptIds.stream()
.filter(Objects::nonNull)
.filter(deptId -> deptId > 0)
.distinct()
.toList());
if (Func.isEmpty(user.getDataScopeDeptIds())) {
return true;
}
return submitUserScopes(user);
}
private void validatePlainPassword(String password) {
@@ -28,6 +28,7 @@ package org.springblade.system.wrapper;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.cache.DictCache;
@@ -36,6 +37,7 @@ import org.springblade.system.pojo.entity.Tenant;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.DictEnum;
import org.springblade.system.pojo.vo.UserVO;
import org.springblade.system.service.IUserService;
import java.util.List;
import java.util.Objects;
@@ -57,6 +59,8 @@ public class UserWrapper extends BaseEntityWrapper<User, UserVO> {
if (userVO == null) {
return null;
}
// 列表/详情展示解密后的明文手机号,避免 SM2 密文被当成手机号脱敏成乱码
SpringUtil.getBean(IUserService.class).decryptPhone(userVO);
Tenant tenant = SysCache.getTenant(user.getTenantId());
if (tenant != null) {
userVO.setTenantName(tenant.getTenantName());
@@ -128,7 +128,7 @@ public final class MkProcessBizHandlers {
entity.setApprovalStatus(change ? "change_approved" : "approved");
entity.setEffectiveType("formal");
entity.setCurrentNode("审核通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
entity.setApprovedTime(LocalDateTime.now());
// HCS 推送:审批通过置待同步状态,更新成功后即时推送,扫描任务保留兜底(ADR-0001 修订,前端接入设计 §6)
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
@@ -149,7 +149,7 @@ public final class MkProcessBizHandlers {
boolean change = "change_reviewing".equals(entity.getApprovalStatus());
entity.setApprovalStatus(change ? "change_rejected" : "rejected");
entity.setCurrentNode("审核不通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
return service.updateById(entity);
}
}
@@ -201,7 +201,7 @@ public final class MkProcessBizHandlers {
Long projectId = service.approve(id);
entity = service.getById(id);
if (entity != null) {
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(entity);
}
if (projectId != null) {
@@ -219,7 +219,7 @@ public final class MkProcessBizHandlers {
boolean rejected = service.reject(id);
entity = service.getById(id);
if (entity != null) {
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(entity);
}
return rejected;
@@ -273,7 +273,7 @@ public final class MkProcessBizHandlers {
Long contractId = service.approve(id);
entity = service.getById(id);
if (entity != null) {
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(entity);
}
if (contractId != null) {
@@ -291,7 +291,7 @@ public final class MkProcessBizHandlers {
boolean rejected = service.reject(id);
entity = service.getById(id);
if (entity != null) {
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(entity);
}
return rejected;
@@ -348,7 +348,7 @@ public final class MkProcessBizHandlers {
boolean change = "change_reviewing".equals(entity.getApprovalStatus());
entity.setApprovalStatus(change ? "change_approved" : "approved");
entity.setCurrentNode("审核通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
// HCS 推送:审批通过置待同步状态,更新成功后即时推送,扫描任务保留兜底(ADR-0001 修订,前端接入设计 §6)
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
boolean updated = service.updateById(entity);
@@ -368,7 +368,7 @@ public final class MkProcessBizHandlers {
boolean change = "change_reviewing".equals(entity.getApprovalStatus());
entity.setApprovalStatus(change ? "change_rejected" : "rejected");
entity.setCurrentNode("审核不通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
return service.updateById(entity);
}
}
@@ -418,7 +418,7 @@ public final class MkProcessBizHandlers {
}
entity.setApprovalStatus("approved");
entity.setCurrentNode("审核通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
entity.setApprovedTime(LocalDateTime.now());
return service.updateById(entity);
}
@@ -431,7 +431,7 @@ public final class MkProcessBizHandlers {
}
entity.setApprovalStatus("returned");
entity.setCurrentNode("审核不通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
return service.updateById(entity);
}
}
@@ -498,7 +498,7 @@ public final class MkProcessBizHandlers {
FormalSettlement updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -516,7 +516,7 @@ public final class MkProcessBizHandlers {
}
entity.setApprovalStatus("returned");
entity.setCurrentNode("审核不通过");
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
entity.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
return service.updateById(entity);
}
}
@@ -573,7 +573,7 @@ public final class MkProcessBizHandlers {
PaymentApplication updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
// HCS 推送零埋点:MK 审批回调仅置待同步状态,与 /approve 入口收敛于同一权威状态(ADR-0001)
updated.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
service.updateById(updated);
@@ -600,7 +600,7 @@ public final class MkProcessBizHandlers {
}
updated.setApprovalStatus("returned");
updated.setCurrentNode("审核不通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
return service.updateById(updated);
}
}
@@ -654,7 +654,7 @@ public final class MkProcessBizHandlers {
InvoiceApplication updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -673,7 +673,7 @@ public final class MkProcessBizHandlers {
InvoiceApplication updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核不通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -729,7 +729,7 @@ public final class MkProcessBizHandlers {
InvoiceReceipt updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -748,7 +748,7 @@ public final class MkProcessBizHandlers {
InvoiceReceipt updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核不通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -804,7 +804,7 @@ public final class MkProcessBizHandlers {
BillPayment updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -823,7 +823,7 @@ public final class MkProcessBizHandlers {
BillPayment updated = service.getById(id);
if (updated != null) {
updated.setCurrentNode("审核不通过");
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
updated.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
service.updateById(updated);
}
return true;
@@ -20,6 +20,11 @@ public final class MkProcessNodeHelper {
*/
private static final String DRAFT_NODE_NUMBER = "N2";
/**
* 流程完结后业务单据「当前处理人」统一展示文案。
*/
public static final String PROCESS_FINISHED = "流程结束";
private static final List<String> CURRENT_NODE_KEYS = List.of("handlerInfos", "drafterInfos", "adminInfos");
private MkProcessNodeHelper() {
@@ -39,9 +44,20 @@ public final class MkProcessNodeHelper {
}
public static String currentProcessor(Object processInfo) {
// 流程已完结时不再回写最后处理人,统一展示「流程结束」
if (isProcessFinished(processInfo)) {
return PROCESS_FINISHED;
}
return joinDistinct(extractTexts(asMap(processInfo).get("currentHandlers"), "fdName", "name"), "、");
}
/**
* MK 流程是否已完结(fdProcessStatus=30)。
*/
public static boolean isProcessFinished(Object processInfo) {
return "30".equals(text(asMap(processInfo).get("fdProcessStatus")));
}
public static String currentNode(Object processInfo) {
Map<String, Object> taskInfo = asMap(asMap(processInfo).get("fdTaskInfo"));
List<String> nodeNames = new ArrayList<>();
@@ -280,9 +280,15 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
public boolean submit(CustomerArchiveVO customer) {
CustomerArchiveVO beforeDetail = Func.isEmpty(customer.getId()) ? null : detail(customer.getId());
prepare(customer);
validateBase(customer);
boolean created = Func.isEmpty(customer.getId());
CustomerArchive before = created ? null : ensureCustomerAccessible(customer.getId());
if (!created) {
// 编辑时客商编号、供应商编码、统一社会信用代码不允许修改
customer.setCustomerCode(before.getCustomerCode());
customer.setSupplierCode(before.getSupplierCode());
customer.setUnifiedCreditCode(before.getUnifiedCreditCode());
}
validateBase(customer);
if (created) {
// 新增时金蝶已有客商,前端回填金蝶客商编码,建档直接沿用;未回填则自动生成
customer.setCustomerCode(Func.isEmpty(customer.getCustomerCode()) ? this.nextCustomerCode() : customer.getCustomerCode());
@@ -432,7 +438,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
after.setAccessType(ACCESS_FORMAL);
after.setApprovalStatus(APPROVAL_APPROVED);
after.setCurrentNode("审核通过");
after.setCurrentProcessor(Func.isEmpty(processorName) ? "系统" : processorName.trim());
after.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
after.setApprovedTime(LocalDateTime.now());
// 审核通过后按当前未删除评分刷新生效等级/额度(替换旧评分场景在此切换生效)
this.applyCreditFromActiveScore(after);
@@ -462,7 +468,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
CustomerArchive after = copyCustomer(before);
after.setApprovalStatus(APPROVAL_REJECTED);
after.setCurrentNode("审核不通过");
after.setCurrentProcessor(Func.isEmpty(processorName) ? "系统" : processorName.trim());
after.setCurrentProcessor(MkProcessNodeHelper.PROCESS_FINISHED);
addChangeRecord(id, "客商准入审核不通过", before, after);
boolean updated = updateById(after);
log.info("流程审核驳回 id={} processor={} result={}", id, after.getCurrentProcessor(), updated);