fix(approval): 派单归属受理部门负责人、PC 全租户待办改派与 keywords 统一

- 协调单流程定义升至 v2:dispatch 节点 resolver 改为 acceptOrgLeader,v1 行不可变保留;getOrCreate 取最新版本
- 详情新增 pendingTasks,canAssign 在持有 assign 权限且实例 PENDING 时可用
- /todo/page 支持 scope=all(仅 assign 权限),详情/列表/导出可见性 SQL 与 countVisible 一致
- 查询参数 keyword 统一为 BaseParam.keywords;术语统一为报送人
- 删除 ApprovalDirectoryService.hasDepartmentLeader 与 ApprovalFileService.getById 死代码
This commit is contained in:
2026-10-30 22:28:05 +08:00
parent b8ee1bb3b0
commit 252e99421e
26 changed files with 317 additions and 82 deletions
@@ -23,7 +23,7 @@ public final class ApprovalConstants {
public static final String STATUS_APPROVED = "APPROVED";
public static final String STATUS_ARCHIVED = "ARCHIVED";
/** 发起人可编辑、可提交的状态。 */
/** 报送人可编辑、可提交的状态。 */
public static final Set<String> EDITABLE_STATUSES =
Collections.unmodifiableSet(new java.util.HashSet<>(Arrays.asList(
STATUS_DRAFT, STATUS_REJECTED, STATUS_WITHDRAWN)));
@@ -72,6 +72,14 @@ public final class ApprovalConstants {
public static final String ROLE_USER = "user";
public static final String ROLE_GUEST = "guest";
// 权限串(与 spec 约定一致;菜单/按钮共用)
public static final String PERMISSION_LIST = "approval:instance:list";
public static final String PERMISSION_SUBMIT = "approval:instance:submit";
public static final String PERMISSION_APPROVE = "approval:instance:approve";
public static final String PERMISSION_ARCHIVE = "approval:instance:archive";
public static final String PERMISSION_ASSIGN = "approval:instance:assign";
public static final String PERMISSION_EXPORT = "approval:instance:export";
// 附件规则
public static final long MAX_ATTACHMENT_SIZE = 20L * 1024 * 1024;
public static final int MAX_ATTACHMENT_COUNT = 9;
@@ -141,11 +149,16 @@ public final class ApprovalConstants {
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
+ "]";
/** 内部协调单流程定义(version=1,表单与派单由后续迭代启用)。 */
/**
* 内部协调单流程定义(version=2)。
*
* <p>version=1 的派单节点按受理部门全员生成任务,导致普通成员也能派单且部门无成员时卡单;
* v2 改为受理部门负责人(或签)。版本不可变:已发布实例仍引用其定义版本。</p>
*/
public static final String COORDINATION_NODES_JSON = "["
+ "{\"key\":\"reportLeader\",\"type\":\"APPROVE\",\"resolver\":\"reporterOrgLeader\",\"name\":\"报送部门负责人\"},"
+ "{\"key\":\"acceptLeader\",\"type\":\"APPROVE\",\"resolver\":\"acceptOrgLeader\",\"name\":\"受理部门负责人\"},"
+ "{\"key\":\"dispatch\",\"type\":\"DISPATCH\",\"resolver\":\"acceptOrgMembers\",\"name\":\"派单\"},"
+ "{\"key\":\"dispatch\",\"type\":\"DISPATCH\",\"resolver\":\"acceptOrgLeader\",\"name\":\"派单\"},"
+ "{\"key\":\"confirm\",\"type\":\"CONFIRM\",\"resolver\":\"reporterAndDispatchTarget\",\"name\":\"存档\"},"
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
+ "]";
@@ -18,5 +18,6 @@ public interface ApprovalExportMapper {
*/
List<ApprovalExportRow> selectExportRows(@Param("param") ApprovalQueryParam param,
@Param("userId") Integer userId,
@Param("allVisible") Integer allVisible);
@Param("allVisible") Integer allVisible,
@Param("assignVisible") Integer assignVisible);
}
@@ -24,7 +24,8 @@ public interface ApprovalInstanceMapper extends BaseMapper<ApprovalInstance> {
*/
int countVisible(@Param("id") Long id,
@Param("userId") Integer userId,
@Param("allVisible") Integer allVisible);
@Param("allVisible") Integer allVisible,
@Param("assignVisible") Integer assignVisible);
/**
* 我发起的列表。
@@ -18,5 +18,6 @@ public interface ApprovalQueryMapper {
List<ApprovalItemRow> selectInstancePage(@Param("page") IPage<ApprovalItemRow> page,
@Param("param") ApprovalQueryParam param,
@Param("userId") Integer userId,
@Param("allVisible") Integer allVisible);
@Param("allVisible") Integer allVisible,
@Param("assignVisible") Integer assignVisible);
}
@@ -22,6 +22,12 @@ public interface ApprovalTaskMapper extends BaseMapper<ApprovalTask> {
@Param("userId") Integer userId,
@Param("taskStatus") String taskStatus);
/**
* 全租户待办列表:任一 PENDING 任务对应的实例,按实例去重。
*/
List<ApprovalItemRow> selectPendingInstancePage(@Param("page") IPage<ApprovalItemRow> page,
@Param("param") ApprovalTaskListParam param);
/**
* 本人待办实例数量(同一实例多任务只算一条)。
*/
@@ -31,6 +31,7 @@
i.reporter_id = #{userId}
OR (i.status != 'DRAFT' AND (
#{allVisible} = 1
OR (#{assignVisible} = 1 AND i.status = 'PENDING')
OR EXISTS (SELECT 1 FROM approval_task t WHERE t.instance_id = i.id AND t.assignee_id = #{userId})
OR EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
OR i.dispatch_target_id = #{userId}
@@ -54,11 +55,11 @@
<if test="param.status != null and param.status != ''">
AND i.status = #{param.status}
</if>
<if test="param.keyword != null and param.keyword != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keyword}, '%')
OR f.name LIKE CONCAT('%', #{param.keyword}, '%')
OR co.title LIKE CONCAT('%', #{param.keyword}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keyword}, '%'))
<if test="param.keywords != null and param.keywords != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keywords}, '%')
OR f.name LIKE CONCAT('%', #{param.keywords}, '%')
OR co.title LIKE CONCAT('%', #{param.keywords}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keywords}, '%'))
</if>
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
AND i.submit_time &gt;= #{param.submitTimeStart}
@@ -17,6 +17,7 @@
i.reporter_id = #{userId}
OR (i.status != 'DRAFT' AND (
#{allVisible} = 1
OR (#{assignVisible} = 1 AND i.status = 'PENDING')
OR EXISTS (SELECT 1 FROM approval_task t WHERE t.instance_id = i.id AND t.assignee_id = #{userId})
OR EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
OR i.dispatch_target_id = #{userId}
@@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.approval.mapper.ApprovalQueryMapper">
<!-- PC 单据查询:数据权限条件与详情保持一致(草稿仅发起人可见) -->
<!-- PC 单据查询:数据权限条件与详情保持一致(草稿仅报送人可见) -->
<select id="selectInstancePage" resultType="com.gxwebsoft.approval.support.ApprovalItemRow">
SELECT i.id,
i.biz_no,
@@ -24,6 +24,7 @@
i.reporter_id = #{userId}
OR (i.status != 'DRAFT' AND (
#{allVisible} = 1
OR (#{assignVisible} = 1 AND i.status = 'PENDING')
OR EXISTS (SELECT 1 FROM approval_task t WHERE t.instance_id = i.id AND t.assignee_id = #{userId})
OR EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
OR i.dispatch_target_id = #{userId}
@@ -47,11 +48,11 @@
<if test="param.status != null and param.status != ''">
AND i.status = #{param.status}
</if>
<if test="param.keyword != null and param.keyword != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keyword}, '%')
OR f.name LIKE CONCAT('%', #{param.keyword}, '%')
OR co.title LIKE CONCAT('%', #{param.keyword}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keyword}, '%'))
<if test="param.keywords != null and param.keywords != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keywords}, '%')
OR f.name LIKE CONCAT('%', #{param.keywords}, '%')
OR co.title LIKE CONCAT('%', #{param.keywords}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keywords}, '%'))
</if>
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
AND i.submit_time &gt;= #{param.submitTimeStart}
@@ -40,6 +40,42 @@
ORDER BY (i.submit_time IS NULL) ASC, i.submit_time DESC, i.id DESC
</select>
<select id="selectPendingInstancePage" resultType="com.gxwebsoft.approval.support.ApprovalItemRow">
SELECT i.id,
i.biz_no,
i.doc_type,
i.status,
i.reporter_id,
i.reporter_name,
i.report_org_name,
i.accept_org_name,
i.archive_time,
i.current_node_key,
i.definition_id,
i.submit_time,
COALESCE(f.name, co.title) AS title
FROM approval_instance i
LEFT JOIN approval_completion_form f ON f.instance_id = i.id
LEFT JOIN approval_coordination_form co ON co.instance_id = i.id
WHERE EXISTS (
SELECT 1 FROM approval_task t
WHERE t.instance_id = i.id AND t.status = 'PENDING'
)
<if test="param.status != null and param.status != ''">
AND i.status = #{param.status}
</if>
<if test="param.docType != null and param.docType != ''">
AND i.doc_type = #{param.docType}
</if>
<if test="param.keywords != null and param.keywords != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keywords}, '%')
OR f.name LIKE CONCAT('%', #{param.keywords}, '%')
OR co.title LIKE CONCAT('%', #{param.keywords}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keywords}, '%'))
</if>
ORDER BY (i.submit_time IS NULL) ASC, i.submit_time DESC, i.id DESC
</select>
<select id="countPendingInstances" resultType="java.lang.Integer">
SELECT COUNT(DISTINCT instance_id)
FROM approval_task
@@ -8,7 +8,7 @@ import java.io.Serializable;
/**
* 内部协调单草稿参数;id 为审批实例id,更新草稿时传入。
*
* <p>报送部门与经办人由服务端按登录态带出,不在参数中接收。</p>
* <p>报送部门与报送人由服务端按登录态带出,不在参数中接收。</p>
*/
@Data
public class ApprovalCoordinationParam implements Serializable {
@@ -21,16 +21,13 @@ public class ApprovalQueryParam extends BaseParam {
@ApiModelProperty("单号")
private String bizNo;
@ApiModelProperty("关键词:单号、名称或标题、实施单位")
private String keyword;
@ApiModelProperty("报送部门id")
private Integer reportOrgId;
@ApiModelProperty("受理部门id")
private Integer acceptOrgId;
@ApiModelProperty("发起人id")
@ApiModelProperty("报送人id")
private Integer reporterId;
@ApiModelProperty("单据状态")
@@ -17,4 +17,8 @@ public class ApprovalTaskListParam extends BaseParam {
@ApiModelProperty("单据类型")
private String docType;
/** 查询范围:mine(默认,本人待办/已办)或 all(全租户待办,需 assign 权限)。 */
@ApiModelProperty("查询范围 mine/all")
private String scope;
}
@@ -87,7 +87,7 @@ public class ApprovalAttachmentService {
}
/**
* 上传附件:仅发起人且实例可编辑时可上传。
* 上传附件:仅报送人且实例可编辑时可上传。
*/
public ApprovalAttachmentVO upload(Long instanceId, MultipartFile file, String category, User user) {
ApprovalInstance instance = instanceMapper.selectById(instanceId);
@@ -95,11 +95,6 @@ public class ApprovalDirectoryService {
USER_MAPPER, tenantId, tenantId, tenantId, roleCode, tenantId);
}
/** 部门负责人角色是否已配置至少一名启用成员。 */
public boolean hasDepartmentLeader(Integer organizationId) {
return organizationId != null && !listRoleMembers("dept_leader_" + organizationId).isEmpty();
}
/** 启用用户搜索,最多 50 条。 */
public List<ApprovalUserVO> searchUsers(String keywords, Integer organizationId) {
Integer tenantId = ApprovalTenantContext.currentTenantId();
@@ -9,6 +9,7 @@ import com.gxwebsoft.approval.support.ApprovalNode;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.system.entity.FileRecord;
import com.gxwebsoft.common.system.entity.Menu;
import com.gxwebsoft.common.system.entity.User;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
@@ -75,7 +76,9 @@ public class ApprovalExportService {
public ApprovalExportResult export(ApprovalQueryParam param, User user) throws IOException {
int allVisible = directoryService.hasAnyRole(user.getUserId(),
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
List<ApprovalExportRow> rows = exportMapper.selectExportRows(param, user.getUserId(), allVisible);
int assignVisible = hasAuthority(user, ApprovalConstants.PERMISSION_ASSIGN) ? 1 : 0;
List<ApprovalExportRow> rows = exportMapper.selectExportRows(param, user.getUserId(), allVisible,
assignVisible);
if (rows.size() > MAX_EXPORT_ROWS) {
throw new BusinessException("请缩小筛选范围");
}
@@ -105,6 +108,15 @@ public class ApprovalExportService {
return new ApprovalExportResult(record, rows.size());
}
private boolean hasAuthority(User user, String authority) {
if (user.getAuthorities() == null) {
return false;
}
return user.getAuthorities().stream()
.map(Menu::getAuthority)
.anyMatch(authority::equals);
}
private void writeWorkbook(List<ApprovalExportRow> rows, File target) throws IOException {
SXSSFWorkbook workbook = new SXSSFWorkbook(100);
try {
@@ -69,17 +69,6 @@ public class ApprovalFileService {
return fileRecord;
}
public FileRecord getById(Integer id) {
if (id == null) {
return null;
}
List<FileRecord> records = jdbcTemplate.query(
"SELECT id, name, path, length, content_type, create_user_id, deleted FROM sys_file_record "
+ "WHERE id = ? AND tenant_id = ? AND deleted = 0",
FILE_MAPPER, id, ApprovalTenantContext.currentTenantId());
return records.isEmpty() ? null : records.get(0);
}
public List<FileRecord> listByIds(Collection<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
@@ -14,16 +14,30 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 流程定义服务:按租户幂等发布 version=1,并解析节点 JSON。
* 流程定义服务:按租户幂等发布各流程的最新版本,并解析节点 JSON。
*
* <p>版本不可变:流程调整只新增版本行,已创建实例继续引用其 definition_id;
* {@link #getOrCreate(String)} 始终返回最新版本。</p>
*/
@Service
public class ApprovalFlowService {
private static final Logger log = LoggerFactory.getLogger(ApprovalFlowService.class);
private static final int INITIAL_VERSION = 1;
/** 各流程当前发布版本;调整节点时必须递增并保留旧版本行。 */
private static final Map<String, Integer> LATEST_VERSIONS;
static {
Map<String, Integer> versions = new LinkedHashMap<>();
versions.put(ApprovalConstants.FLOW_COMPLETION, 1);
versions.put(ApprovalConstants.FLOW_COORDINATION_INTERNAL, 2);
LATEST_VERSIONS = Collections.unmodifiableMap(versions);
}
@Resource
private ApprovalFlowDefinitionMapper flowDefinitionMapper;
@@ -31,18 +45,19 @@ public class ApprovalFlowService {
private ObjectMapper objectMapper;
/**
* 获取租户下的流程定义;不存在时创建 version=1。
* 获取租户下流程定义的最新版本;尚无该版本时发布新版。
*
* <p>并发首次创建依赖唯一键 uk_flow_ver 兜底,冲突后重新查询。</p>
* <p>并发首次创建依赖唯一键 uk_flow_ver 兜底,冲突后重新读取最新版本。</p>
*/
public ApprovalFlowDefinition getOrCreate(String flowCode) {
ApprovalFlowDefinition definition = find(flowCode);
if (definition != null) {
int latestVersion = latestVersion(flowCode);
ApprovalFlowDefinition definition = findLatest(flowCode);
if (definition != null && definition.getVersion() >= latestVersion) {
return definition;
}
ApprovalFlowDefinition created = new ApprovalFlowDefinition();
created.setFlowCode(flowCode);
created.setVersion(INITIAL_VERSION);
created.setVersion(latestVersion);
created.setName(flowName(flowCode));
created.setNodesJson(flowNodesJson(flowCode));
try {
@@ -50,8 +65,8 @@ public class ApprovalFlowService {
return created;
} catch (DuplicateKeyException e) {
log.info("审批流程定义已被并发创建,重新读取: {}", flowCode);
definition = find(flowCode);
if (definition == null) {
definition = findLatest(flowCode);
if (definition == null || definition.getVersion() < latestVersion) {
throw new BusinessException("审批流程定义初始化失败");
}
return definition;
@@ -92,13 +107,18 @@ public class ApprovalFlowService {
return node == null ? null : node.getName();
}
private ApprovalFlowDefinition find(String flowCode) {
private ApprovalFlowDefinition findLatest(String flowCode) {
return flowDefinitionMapper.selectOne(new LambdaQueryWrapper<ApprovalFlowDefinition>()
.eq(ApprovalFlowDefinition::getFlowCode, flowCode)
.eq(ApprovalFlowDefinition::getVersion, INITIAL_VERSION)
.orderByDesc(ApprovalFlowDefinition::getVersion)
.last("LIMIT 1"));
}
private int latestVersion(String flowCode) {
Integer version = LATEST_VERSIONS.get(flowCode);
return version == null ? INITIAL_VERSION : version;
}
private String flowName(String flowCode) {
if (ApprovalConstants.FLOW_COMPLETION.equals(flowCode)) {
return "业务完成确认单";
@@ -27,6 +27,7 @@ import com.gxwebsoft.approval.vo.ApprovalInstanceVO;
import com.gxwebsoft.approval.vo.ApprovalItemVO;
import com.gxwebsoft.approval.vo.ApprovalMyTaskVO;
import com.gxwebsoft.approval.vo.ApprovalOrganizationVO;
import com.gxwebsoft.approval.vo.ApprovalPendingTaskVO;
import com.gxwebsoft.approval.vo.ApprovalPermissionsVO;
import com.gxwebsoft.approval.vo.ApprovalRecordVO;
import com.gxwebsoft.approval.vo.ApprovalUserVO;
@@ -34,6 +35,7 @@ import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.Menu;
import com.gxwebsoft.common.system.entity.User;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
@@ -59,7 +61,9 @@ import java.util.stream.Collectors;
public class ApprovalInstanceService {
private static final BigDecimal MAX_FEE = new BigDecimal("99999999.99");
private static final String PERMISSION_ASSIGN = "approval:instance:assign";
private static final String PERMISSION_ASSIGN = ApprovalConstants.PERMISSION_ASSIGN;
/** 待办范围:全租户待办(仅 assign 权限可用)。 */
private static final String SCOPE_ALL = "all";
@Resource
private ApprovalInstanceMapper instanceMapper;
@@ -103,7 +107,7 @@ public class ApprovalInstanceService {
/**
* 新建或更新内部协调单草稿,返回实例id。
*
* <p>报送部门与经办人取登录态;受理部门必须是启用组织,保存时即校验。</p>
* <p>报送部门与报送人取登录态;受理部门必须是启用组织,保存时即校验。</p>
*/
public Long saveCoordination(ApprovalCoordinationParam param, User user) {
validateCoordination(param);
@@ -233,7 +237,7 @@ public class ApprovalInstanceService {
}
/**
* 详情;草稿仅发起人可见,其余按任务/记录/派单目标或 allVisible 角色判断。
* 详情;草稿仅报送人可见,其余按任务/记录/派单目标或 allVisible 角色判断。
*/
public ApprovalInstanceVO detail(Long instanceId, User user) {
ApprovalInstance instance = instanceMapper.selectById(instanceId);
@@ -242,7 +246,9 @@ public class ApprovalInstanceService {
}
int allVisible = directoryService.hasAnyRole(user.getUserId(),
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
if (instanceMapper.countVisible(instanceId, user.getUserId(), allVisible) == 0) {
int assignVisible = hasAuthority(user, PERMISSION_ASSIGN)
&& ApprovalConstants.STATUS_PENDING.equals(instance.getStatus()) ? 1 : 0;
if (instanceMapper.countVisible(instanceId, user.getUserId(), allVisible, assignVisible) == 0) {
throw new BusinessException("无权查看该单据");
}
@@ -267,6 +273,7 @@ public class ApprovalInstanceService {
vo.setConfirms(confirms(instance));
ApprovalMyTaskVO myTask = myTask(instance, user.getUserId());
vo.setMyTask(myTask);
vo.setPendingTasks(pendingTasks(instance));
vo.setPermissions(permissions(instance, user, myTask));
return vo;
}
@@ -284,8 +291,17 @@ public class ApprovalInstanceService {
/**
* 我的待办:本人 PENDING 任务对应的实例,按实例去重。
*
* <p>PC 传 {@code scope=all} 时返回全租户待办,仅 assign 权限可用;
* 员工端不传 scope,行为不变。</p>
*/
public PageResult<ApprovalItemVO> todo(ApprovalTaskListParam param, User user) {
if (SCOPE_ALL.equals(param.getScope())) {
if (!hasAuthority(user, PERMISSION_ASSIGN)) {
throw new AccessDeniedException("无权限查看全部待办");
}
return pendingPage(param);
}
return taskPage(param, user, ApprovalConstants.TASK_PENDING);
}
@@ -296,6 +312,17 @@ public class ApprovalInstanceService {
return taskPage(param, user, ApprovalConstants.TASK_DONE);
}
/**
* 全租户待办:任一 PENDING 任务对应的实例,按实例去重。
*/
private PageResult<ApprovalItemVO> pendingPage(ApprovalTaskListParam param) {
long current = param.getPage() == null ? 1 : Math.max(1, param.getPage());
long size = param.getLimit() == null ? 10 : Math.max(1, param.getLimit());
Page<ApprovalItemRow> page = new Page<>(current, size);
List<ApprovalItemRow> rows = taskMapper.selectPendingInstancePage(page, param);
return new PageResult<>(toItems(rows), page.getTotal());
}
private PageResult<ApprovalItemVO> taskPage(ApprovalTaskListParam param, User user, String taskStatus) {
long current = param.getPage() == null ? 1 : Math.max(1, param.getPage());
long size = param.getLimit() == null ? 10 : Math.max(1, param.getLimit());
@@ -552,6 +579,28 @@ public class ApprovalInstanceService {
return vo;
}
private List<ApprovalPendingTaskVO> pendingTasks(ApprovalInstance instance) {
if (!ApprovalConstants.STATUS_PENDING.equals(instance.getStatus())) {
return new ArrayList<>();
}
List<ApprovalTask> tasks = taskMapper.selectList(new LambdaQueryWrapper<ApprovalTask>()
.eq(ApprovalTask::getInstanceId, instance.getId())
.eq(ApprovalTask::getStatus, ApprovalConstants.TASK_PENDING)
.orderByAsc(ApprovalTask::getId));
List<ApprovalPendingTaskVO> result = new ArrayList<>(tasks.size());
for (ApprovalTask task : tasks) {
ApprovalPendingTaskVO vo = new ApprovalPendingTaskVO();
vo.setId(task.getId());
vo.setNodeKey(task.getNodeKey());
vo.setNodeName(flowService.nodeName(instance.getDefinitionId(), task.getNodeKey()));
vo.setNodeType(task.getNodeType());
vo.setAssigneeId(task.getAssigneeId());
vo.setAssigneeName(task.getAssigneeName());
result.add(vo);
}
return result;
}
private ApprovalPermissionsVO permissions(ApprovalInstance instance, User user, ApprovalMyTaskVO myTask) {
ApprovalPermissionsVO permissions = new ApprovalPermissionsVO();
boolean reporter = instance.getReporterId().equals(user.getUserId());
@@ -565,6 +614,8 @@ public class ApprovalInstanceService {
.eq(ApprovalTask::getStatus, ApprovalConstants.TASK_DONE));
permissions.setCanWithdraw(done == null || done == 0);
}
permissions.setCanAssign(hasAuthority(user, PERMISSION_ASSIGN)
&& ApprovalConstants.STATUS_PENDING.equals(instance.getStatus()));
if (myTask != null) {
boolean approveNode = ApprovalConstants.NODE_APPROVE.equals(myTask.getNodeType());
boolean rejectableNode = approveNode || ApprovalConstants.NODE_DISPATCH.equals(myTask.getNodeType());
@@ -573,7 +624,6 @@ public class ApprovalInstanceService {
permissions.setCanDispatch(ApprovalConstants.NODE_DISPATCH.equals(myTask.getNodeType()));
permissions.setCanConfirm(ApprovalConstants.NODE_CONFIRM.equals(myTask.getNodeType()));
permissions.setCanArchive(ApprovalConstants.NODE_ARCHIVE.equals(myTask.getNodeType()));
permissions.setCanAssign(hasAuthority(user, PERMISSION_ASSIGN));
}
return permissions;
}
@@ -8,6 +8,7 @@ import com.gxwebsoft.approval.support.ApprovalItemRow;
import com.gxwebsoft.approval.support.ApprovalNode;
import com.gxwebsoft.approval.vo.ApprovalItemVO;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.Menu;
import com.gxwebsoft.common.system.entity.User;
import org.springframework.stereotype.Service;
@@ -44,8 +45,10 @@ public class ApprovalQueryService {
long size = param.getLimit() == null ? DEFAULT_LIMIT : Math.max(1, param.getLimit());
int allVisible = directoryService.hasAnyRole(user.getUserId(),
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
int assignVisible = hasAuthority(user, ApprovalConstants.PERMISSION_ASSIGN) ? 1 : 0;
Page<ApprovalItemRow> page = new Page<>(current, size);
List<ApprovalItemRow> rows = queryMapper.selectInstancePage(page, param, user.getUserId(), allVisible);
List<ApprovalItemRow> rows = queryMapper.selectInstancePage(page, param, user.getUserId(), allVisible,
assignVisible);
Map<Long, List<ApprovalNode>> nodeCache = new HashMap<>();
List<ApprovalItemVO> list = new ArrayList<>(rows.size());
for (ApprovalItemRow row : rows) {
@@ -73,6 +76,15 @@ public class ApprovalQueryService {
return item;
}
private boolean hasAuthority(User user, String authority) {
if (user.getAuthorities() == null) {
return false;
}
return user.getAuthorities().stream()
.map(Menu::getAuthority)
.anyMatch(authority::equals);
}
private String nodeName(Map<Long, List<ApprovalNode>> cache, Long definitionId, String nodeKey) {
if (definitionId == null || nodeKey == null) {
return null;
@@ -88,5 +88,8 @@ public class ApprovalInstanceVO implements Serializable {
private ApprovalMyTaskVO myTask;
/** 实例当前全部待办任务,供有 assign 权限的 PC 用户按任务改派。 */
private List<ApprovalPendingTaskVO> pendingTasks;
private ApprovalPermissionsVO permissions;
}
@@ -0,0 +1,27 @@
package com.gxwebsoft.approval.vo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.io.Serializable;
/**
* 实例上的待办任务(PC 改派按任务 id 操作)。
*/
@Data
@ApiModel(description = "实例待办任务")
public class ApprovalPendingTaskVO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String nodeKey;
private String nodeName;
private String nodeType;
private Integer assigneeId;
private String assigneeName;
}