Files
flow-center-service/src/main/java/com/yunhai/flowcenter/service/FlowMapAdminService.java
T

289 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.service;
import com.yunhai.flowcenter.entity.FlwAssigneeMap;
import com.yunhai.flowcenter.repository.FlwAssigneeMapRepository;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.platform.openapi.base.OpenApiGroupClient;
import com.yunhai.platform.openapi.base.OpenApiUserClient;
import com.yunhai.platform.utils.model.BoaGroup;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 办理人映射管理(概览 §4.7/§六):DEPT_KEY → 办理人显式映射,成员检索经
* UserBase /oapi/user/byId 补全姓名快照(检索按 id 精确查;按名检索二期接 UserBase 模糊端点)。
* <p>
* 2026-09-16 新增「一键拉取教师组」:按 {@code dept-group-type}(默认 7=教师组,与 svc-hall
* {@code svchall.item.dept-group-type} 同口径)从 UserBase 学校组子树取科室容器,其子组即科室
* (容器无子组时容器本身即科室),deptKey=UserBase 组 id;成员=组内用户(不含子组)。
* 批量落 flw_assignee_map,替代管理端逐人录入。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FlowMapAdminService {
private final FlwAssigneeMapRepository mapRepository;
private final OpenApiUserClient userClient;
private final OpenApiGroupClient groupClient;
/** 科室"容器组"类型(UserBase GroupTypeEnum,默认 7=教师组;口径承袭 svc-hall) */
@Value("${flow-center.map.dept-group-type:7}")
private int deptGroupType = 7;
public List<FlwAssigneeMap> listByDept(Long schId, String deptKey) {
if (deptKey == null || deptKey.trim().isEmpty()) {
return mapRepository.findEnabledBySchId(schId);
}
return mapRepository.findBySchIdAndDeptKeyOrderByUserIdAsc(schId, deptKey.trim());
}
/** 新增/启用映射(uk: sch+dept+user;已存在则刷新姓名/状态) */
@Transactional
public FlwAssigneeMap save(Long schId, String deptKey, String deptName,
Integer userId, String userName) {
if (deptKey == null || deptKey.trim().isEmpty() || userId == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "deptKey/userId 必填");
}
if (userName == null || userName.trim().isEmpty()) {
userName = fetchName(userId);
}
FlwAssigneeMap map = mapRepository.findBySchIdAndDeptKeyAndUserId(schId, deptKey.trim(), userId);
if (map == null) {
map = new FlwAssigneeMap();
map.setSchId(schId);
map.setDeptKey(deptKey.trim());
map.setUserId(userId);
}
map.setDeptName(deptName);
map.setUserName(userName);
map.setStatus(0);
return mapRepository.save(map);
}
/** 停用映射(保留历史,DEPT_KEY 解析只取 status=0) */
@Transactional
public void disable(Long schId, Long id) {
FlwAssigneeMap map = mapRepository.findById(id)
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "映射不存在:" + id));
if (!map.getSchId().equals(schId)) {
throw new BizException(ErrorCode.FORBIDDEN, "映射不属于该学校");
}
map.setStatus(1);
mapRepository.save(map);
}
/** 成员检索:按用户 id 从 UserBase 取姓名快照(管理端录入校验) */
public BoaUser lookupUser(Integer userId) {
try {
return userClient.getUserById(String.valueOf(userId));
} catch (Exception e) {
throw new BizException(ErrorCode.PARAM_ERROR, "UserBase 用户检索失败(服务不可达)");
}
}
// ==================== 选人组件数据源 ====================
/** 选人组件树节点:组=isParent(true)+children;用户=叶子(isParent 缺省 false) */
@Getter
@Setter
public static class SelectUserNodeVo {
private Integer id;
private String name;
private Boolean isParent;
private List<SelectUserNodeVo> children = new ArrayList<>();
}
/**
* 学校组树(组→组内用户叶子),供管理端「选择用户」弹窗(多选指定用户/单选映射办理人)。
* 与 {@link #previewGroupDepts(Long)} 同源:UserBase getGroupById(schId, withSub) 子树 + 逐组挂成员。
*/
public List<SelectUserNodeVo> userTree(Long schId) {
BoaGroup subtree;
try {
subtree = groupClient.getGroupById(schId.intValue(), true);
} catch (Exception e) {
throw new BizException(ErrorCode.PARAM_ERROR, "UserBase 组树不可达:" + e.getMessage());
}
if (subtree == null || subtree.getId() == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "学校组不存在(schId=" + schId + ")");
}
return Collections.singletonList(groupNode(subtree));
}
/** 组节点:先挂用户叶子再递归子组;isParent=UserBase 已宣告子组,防"空组"被当作用户叶子 */
private SelectUserNodeVo groupNode(BoaGroup group) {
SelectUserNodeVo vo = new SelectUserNodeVo();
vo.setId(group.getId());
vo.setName(group.getName());
boolean declaredSub = group.getChildren() != null && !group.getChildren().isEmpty();
try {
List<BoaUser> members = groupClient.getGroupUsers(group.getId(), false, null);
if (members != null) {
for (BoaUser u : members) {
if (u != null && u.getId() != null) {
SelectUserNodeVo leaf = new SelectUserNodeVo();
leaf.setId(u.getId());
leaf.setName(u.getName() == null ? "" : u.getName());
vo.getChildren().add(leaf);
}
}
}
} catch (Exception e) {
log.warn("[flow-center] 组成员拉取失败(groupId={}, name={}):{}", group.getId(), group.getName(), e.getMessage());
}
for (BoaGroup child : group.getChildren() == null ? Collections.<BoaGroup>emptyList() : group.getChildren()) {
if (child != null && child.getId() != null) {
vo.getChildren().add(groupNode(child));
}
}
vo.setIsParent(declaredSub || !vo.getChildren().isEmpty());
return vo;
}
// ==================== 一键拉取教师组 ====================
/** 科室预览/导入项(deptKey=UserBase 组 id) */
@Getter
@Setter
public static class GroupDeptVo {
private String deptKey;
private String deptName;
private List<MemberVo> users = new ArrayList<>();
}
@Getter
@Setter
public static class MemberVo {
private Integer userId;
private String userName;
public MemberVo() {
}
public MemberVo(Integer userId, String userName) {
this.userId = userId;
this.userName = userName;
}
}
/** 导入结果计数 */
@Getter
@Setter
public static class GroupImportResultVo {
private int depts;
private int users;
private int skippedExisting;
}
/**
* 预览:学校子树内教师组容器 → 科室列表(含组内成员)。
* UserBase 不可达/学校组不存在时抛业务异常(预览即校验,导入前先给前端看结果)。
*/
public List<GroupDeptVo> previewGroupDepts(Long schId) {
BoaGroup subtree;
try {
subtree = groupClient.getGroupById(schId.intValue(), true);
} catch (Exception e) {
throw new BizException(ErrorCode.PARAM_ERROR, "UserBase 组树不可达:" + e.getMessage());
}
if (subtree == null || subtree.getId() == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "学校组不存在(schId=" + schId + ")");
}
List<BoaGroup> depts = new ArrayList<>();
collectDeptGroups(subtree, depts);
List<GroupDeptVo> out = new ArrayList<>();
for (BoaGroup dept : depts) {
GroupDeptVo vo = new GroupDeptVo();
vo.setDeptKey(String.valueOf(dept.getId()));
vo.setDeptName(dept.getName());
try {
List<BoaUser> members = groupClient.getGroupUsers(dept.getId(), false, null);
if (members != null) {
for (BoaUser u : members) {
if (u != null && u.getId() != null) {
vo.getUsers().add(new MemberVo(u.getId(),
u.getName() == null ? "" : u.getName()));
}
}
}
} catch (Exception e) {
log.warn("[flow-center] 组成员拉取失败(groupId={}, name={}):{}", dept.getId(), dept.getName(), e.getMessage());
}
out.add(vo);
}
return out;
}
/**
* 一键导入:按 {@link #previewGroupDepts} 的口径批量落映射;已存在(sch+dept+user 且启用中)
* 跳过不重复刷写。空成员的科室保留为无映射科室(流程编辑器可见提示,成员后续手工补)。
*/
@Transactional
public GroupImportResultVo importGroups(Long schId) {
List<GroupDeptVo> depts = previewGroupDepts(schId);
GroupImportResultVo result = new GroupImportResultVo();
for (GroupDeptVo dept : depts) {
for (MemberVo m : dept.getUsers()) {
FlwAssigneeMap existing = mapRepository.findBySchIdAndDeptKeyAndUserId(
schId, dept.getDeptKey(), m.getUserId());
if (existing != null && existing.getStatus() == 0) {
result.setSkippedExisting(result.getSkippedExisting() + 1);
continue;
}
save(schId, dept.getDeptKey(), dept.getDeptName(), m.getUserId(), m.getUserName());
result.setUsers(result.getUsers() + 1);
}
result.setDepts(result.getDepts() + 1);
}
return result;
}
/**
* 收集科室组:子树内 type=容器组类型的节点,其**子组**为科室(容器无子组时容器自身即科室)。
* 语义与 svc-hall ServiceItemService.buildDeptTree 逐字对齐(同一份学校组树两种消费)。
*/
private void collectDeptGroups(BoaGroup node, List<BoaGroup> out) {
if (node == null) {
return;
}
if (node.getType() != null && node.getType() == deptGroupType) {
if (node.getChildren() == null || node.getChildren().isEmpty()) {
out.add(node);
} else {
for (BoaGroup child : node.getChildren()) {
if (child != null && child.getId() != null) {
out.add(child);
}
}
}
return;
}
if (node.getChildren() != null) {
for (BoaGroup child : node.getChildren()) {
collectDeptGroups(child, out);
}
}
}
private String fetchName(Integer userId) {
try {
BoaUser user = userClient.getUserById(String.valueOf(userId));
return user == null ? "" : (user.getName() == null ? "" : user.getName());
} catch (Exception e) {
return "";
}
}
}