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:
@@ -23,7 +23,7 @@ public final class ApprovalConstants {
|
|||||||
public static final String STATUS_APPROVED = "APPROVED";
|
public static final String STATUS_APPROVED = "APPROVED";
|
||||||
public static final String STATUS_ARCHIVED = "ARCHIVED";
|
public static final String STATUS_ARCHIVED = "ARCHIVED";
|
||||||
|
|
||||||
/** 发起人可编辑、可提交的状态。 */
|
/** 报送人可编辑、可提交的状态。 */
|
||||||
public static final Set<String> EDITABLE_STATUSES =
|
public static final Set<String> EDITABLE_STATUSES =
|
||||||
Collections.unmodifiableSet(new java.util.HashSet<>(Arrays.asList(
|
Collections.unmodifiableSet(new java.util.HashSet<>(Arrays.asList(
|
||||||
STATUS_DRAFT, STATUS_REJECTED, STATUS_WITHDRAWN)));
|
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_USER = "user";
|
||||||
public static final String ROLE_GUEST = "guest";
|
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 long MAX_ATTACHMENT_SIZE = 20L * 1024 * 1024;
|
||||||
public static final int MAX_ATTACHMENT_COUNT = 9;
|
public static final int MAX_ATTACHMENT_COUNT = 9;
|
||||||
@@ -141,11 +149,16 @@ public final class ApprovalConstants {
|
|||||||
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
|
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
|
||||||
+ "]";
|
+ "]";
|
||||||
|
|
||||||
/** 内部协调单流程定义(version=1,表单与派单由后续迭代启用)。 */
|
/**
|
||||||
|
* 内部协调单流程定义(version=2)。
|
||||||
|
*
|
||||||
|
* <p>version=1 的派单节点按受理部门全员生成任务,导致普通成员也能派单且部门无成员时卡单;
|
||||||
|
* v2 改为受理部门负责人(或签)。版本不可变:已发布实例仍引用其定义版本。</p>
|
||||||
|
*/
|
||||||
public static final String COORDINATION_NODES_JSON = "["
|
public static final String COORDINATION_NODES_JSON = "["
|
||||||
+ "{\"key\":\"reportLeader\",\"type\":\"APPROVE\",\"resolver\":\"reporterOrgLeader\",\"name\":\"报送部门负责人\"},"
|
+ "{\"key\":\"reportLeader\",\"type\":\"APPROVE\",\"resolver\":\"reporterOrgLeader\",\"name\":\"报送部门负责人\"},"
|
||||||
+ "{\"key\":\"acceptLeader\",\"type\":\"APPROVE\",\"resolver\":\"acceptOrgLeader\",\"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\":\"confirm\",\"type\":\"CONFIRM\",\"resolver\":\"reporterAndDispatchTarget\",\"name\":\"存档\"},"
|
||||||
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
|
+ "{\"key\":\"archive\",\"type\":\"ARCHIVE\",\"resolver\":\"archivist\",\"name\":\"归档\"}"
|
||||||
+ "]";
|
+ "]";
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ public interface ApprovalExportMapper {
|
|||||||
*/
|
*/
|
||||||
List<ApprovalExportRow> selectExportRows(@Param("param") ApprovalQueryParam param,
|
List<ApprovalExportRow> selectExportRows(@Param("param") ApprovalQueryParam param,
|
||||||
@Param("userId") Integer userId,
|
@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,
|
int countVisible(@Param("id") Long id,
|
||||||
@Param("userId") Integer userId,
|
@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,
|
List<ApprovalItemRow> selectInstancePage(@Param("page") IPage<ApprovalItemRow> page,
|
||||||
@Param("param") ApprovalQueryParam param,
|
@Param("param") ApprovalQueryParam param,
|
||||||
@Param("userId") Integer userId,
|
@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("userId") Integer userId,
|
||||||
@Param("taskStatus") String taskStatus);
|
@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}
|
i.reporter_id = #{userId}
|
||||||
OR (i.status != 'DRAFT' AND (
|
OR (i.status != 'DRAFT' AND (
|
||||||
#{allVisible} = 1
|
#{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_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 EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
|
||||||
OR i.dispatch_target_id = #{userId}
|
OR i.dispatch_target_id = #{userId}
|
||||||
@@ -54,11 +55,11 @@
|
|||||||
<if test="param.status != null and param.status != ''">
|
<if test="param.status != null and param.status != ''">
|
||||||
AND i.status = #{param.status}
|
AND i.status = #{param.status}
|
||||||
</if>
|
</if>
|
||||||
<if test="param.keyword != null and param.keyword != ''">
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
AND (i.biz_no LIKE CONCAT('%', #{param.keyword}, '%')
|
AND (i.biz_no LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR f.name LIKE CONCAT('%', #{param.keyword}, '%')
|
OR f.name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR co.title LIKE CONCAT('%', #{param.keyword}, '%')
|
OR co.title LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR f.implement_org LIKE CONCAT('%', #{param.keyword}, '%'))
|
OR f.implement_org LIKE CONCAT('%', #{param.keywords}, '%'))
|
||||||
</if>
|
</if>
|
||||||
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
|
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
|
||||||
AND i.submit_time >= #{param.submitTimeStart}
|
AND i.submit_time >= #{param.submitTimeStart}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
i.reporter_id = #{userId}
|
i.reporter_id = #{userId}
|
||||||
OR (i.status != 'DRAFT' AND (
|
OR (i.status != 'DRAFT' AND (
|
||||||
#{allVisible} = 1
|
#{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_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 EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
|
||||||
OR i.dispatch_target_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">
|
<!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">
|
<mapper namespace="com.gxwebsoft.approval.mapper.ApprovalQueryMapper">
|
||||||
|
|
||||||
<!-- PC 单据查询:数据权限条件与详情保持一致(草稿仅发起人可见) -->
|
<!-- PC 单据查询:数据权限条件与详情保持一致(草稿仅报送人可见) -->
|
||||||
<select id="selectInstancePage" resultType="com.gxwebsoft.approval.support.ApprovalItemRow">
|
<select id="selectInstancePage" resultType="com.gxwebsoft.approval.support.ApprovalItemRow">
|
||||||
SELECT i.id,
|
SELECT i.id,
|
||||||
i.biz_no,
|
i.biz_no,
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
i.reporter_id = #{userId}
|
i.reporter_id = #{userId}
|
||||||
OR (i.status != 'DRAFT' AND (
|
OR (i.status != 'DRAFT' AND (
|
||||||
#{allVisible} = 1
|
#{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_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 EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
|
||||||
OR i.dispatch_target_id = #{userId}
|
OR i.dispatch_target_id = #{userId}
|
||||||
@@ -47,11 +48,11 @@
|
|||||||
<if test="param.status != null and param.status != ''">
|
<if test="param.status != null and param.status != ''">
|
||||||
AND i.status = #{param.status}
|
AND i.status = #{param.status}
|
||||||
</if>
|
</if>
|
||||||
<if test="param.keyword != null and param.keyword != ''">
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
AND (i.biz_no LIKE CONCAT('%', #{param.keyword}, '%')
|
AND (i.biz_no LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR f.name LIKE CONCAT('%', #{param.keyword}, '%')
|
OR f.name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR co.title LIKE CONCAT('%', #{param.keyword}, '%')
|
OR co.title LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR f.implement_org LIKE CONCAT('%', #{param.keyword}, '%'))
|
OR f.implement_org LIKE CONCAT('%', #{param.keywords}, '%'))
|
||||||
</if>
|
</if>
|
||||||
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
|
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
|
||||||
AND i.submit_time >= #{param.submitTimeStart}
|
AND i.submit_time >= #{param.submitTimeStart}
|
||||||
|
|||||||
@@ -40,6 +40,42 @@
|
|||||||
ORDER BY (i.submit_time IS NULL) ASC, i.submit_time DESC, i.id DESC
|
ORDER BY (i.submit_time IS NULL) ASC, i.submit_time DESC, i.id DESC
|
||||||
</select>
|
</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 id="countPendingInstances" resultType="java.lang.Integer">
|
||||||
SELECT COUNT(DISTINCT instance_id)
|
SELECT COUNT(DISTINCT instance_id)
|
||||||
FROM approval_task
|
FROM approval_task
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.io.Serializable;
|
|||||||
/**
|
/**
|
||||||
* 内部协调单草稿参数;id 为审批实例id,更新草稿时传入。
|
* 内部协调单草稿参数;id 为审批实例id,更新草稿时传入。
|
||||||
*
|
*
|
||||||
* <p>报送部门与经办人由服务端按登录态带出,不在参数中接收。</p>
|
* <p>报送部门与报送人由服务端按登录态带出,不在参数中接收。</p>
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class ApprovalCoordinationParam implements Serializable {
|
public class ApprovalCoordinationParam implements Serializable {
|
||||||
|
|||||||
@@ -21,16 +21,13 @@ public class ApprovalQueryParam extends BaseParam {
|
|||||||
@ApiModelProperty("单号")
|
@ApiModelProperty("单号")
|
||||||
private String bizNo;
|
private String bizNo;
|
||||||
|
|
||||||
@ApiModelProperty("关键词:单号、名称或标题、实施单位")
|
|
||||||
private String keyword;
|
|
||||||
|
|
||||||
@ApiModelProperty("报送部门id")
|
@ApiModelProperty("报送部门id")
|
||||||
private Integer reportOrgId;
|
private Integer reportOrgId;
|
||||||
|
|
||||||
@ApiModelProperty("受理部门id")
|
@ApiModelProperty("受理部门id")
|
||||||
private Integer acceptOrgId;
|
private Integer acceptOrgId;
|
||||||
|
|
||||||
@ApiModelProperty("发起人id")
|
@ApiModelProperty("报送人id")
|
||||||
private Integer reporterId;
|
private Integer reporterId;
|
||||||
|
|
||||||
@ApiModelProperty("单据状态")
|
@ApiModelProperty("单据状态")
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ public class ApprovalTaskListParam extends BaseParam {
|
|||||||
|
|
||||||
@ApiModelProperty("单据类型")
|
@ApiModelProperty("单据类型")
|
||||||
private String docType;
|
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) {
|
public ApprovalAttachmentVO upload(Long instanceId, MultipartFile file, String category, User user) {
|
||||||
ApprovalInstance instance = instanceMapper.selectById(instanceId);
|
ApprovalInstance instance = instanceMapper.selectById(instanceId);
|
||||||
|
|||||||
@@ -95,11 +95,6 @@ public class ApprovalDirectoryService {
|
|||||||
USER_MAPPER, tenantId, tenantId, tenantId, roleCode, tenantId);
|
USER_MAPPER, tenantId, tenantId, tenantId, roleCode, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 部门负责人角色是否已配置至少一名启用成员。 */
|
|
||||||
public boolean hasDepartmentLeader(Integer organizationId) {
|
|
||||||
return organizationId != null && !listRoleMembers("dept_leader_" + organizationId).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 启用用户搜索,最多 50 条。 */
|
/** 启用用户搜索,最多 50 条。 */
|
||||||
public List<ApprovalUserVO> searchUsers(String keywords, Integer organizationId) {
|
public List<ApprovalUserVO> searchUsers(String keywords, Integer organizationId) {
|
||||||
Integer tenantId = ApprovalTenantContext.currentTenantId();
|
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.config.ConfigProperties;
|
||||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||||
import com.gxwebsoft.common.system.entity.FileRecord;
|
import com.gxwebsoft.common.system.entity.FileRecord;
|
||||||
|
import com.gxwebsoft.common.system.entity.Menu;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import org.apache.poi.xssf.streaming.SXSSFRow;
|
import org.apache.poi.xssf.streaming.SXSSFRow;
|
||||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||||
@@ -75,7 +76,9 @@ public class ApprovalExportService {
|
|||||||
public ApprovalExportResult export(ApprovalQueryParam param, User user) throws IOException {
|
public ApprovalExportResult export(ApprovalQueryParam param, User user) throws IOException {
|
||||||
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
||||||
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
|
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) {
|
if (rows.size() > MAX_EXPORT_ROWS) {
|
||||||
throw new BusinessException("请缩小筛选范围");
|
throw new BusinessException("请缩小筛选范围");
|
||||||
}
|
}
|
||||||
@@ -105,6 +108,15 @@ public class ApprovalExportService {
|
|||||||
return new ApprovalExportResult(record, rows.size());
|
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 {
|
private void writeWorkbook(List<ApprovalExportRow> rows, File target) throws IOException {
|
||||||
SXSSFWorkbook workbook = new SXSSFWorkbook(100);
|
SXSSFWorkbook workbook = new SXSSFWorkbook(100);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -69,17 +69,6 @@ public class ApprovalFileService {
|
|||||||
return fileRecord;
|
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) {
|
public List<FileRecord> listByIds(Collection<Integer> ids) {
|
||||||
if (ids == null || ids.isEmpty()) {
|
if (ids == null || ids.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|||||||
@@ -14,16 +14,30 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程定义服务:按租户幂等发布 version=1,并解析节点 JSON。
|
* 流程定义服务:按租户幂等发布各流程的最新版本,并解析节点 JSON。
|
||||||
|
*
|
||||||
|
* <p>版本不可变:流程调整只新增版本行,已创建实例继续引用其 definition_id;
|
||||||
|
* {@link #getOrCreate(String)} 始终返回最新版本。</p>
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class ApprovalFlowService {
|
public class ApprovalFlowService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ApprovalFlowService.class);
|
private static final Logger log = LoggerFactory.getLogger(ApprovalFlowService.class);
|
||||||
private static final int INITIAL_VERSION = 1;
|
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
|
@Resource
|
||||||
private ApprovalFlowDefinitionMapper flowDefinitionMapper;
|
private ApprovalFlowDefinitionMapper flowDefinitionMapper;
|
||||||
@@ -31,18 +45,19 @@ public class ApprovalFlowService {
|
|||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取租户下的流程定义;不存在时创建 version=1。
|
* 获取租户下流程定义的最新版本;尚无该版本时发布新版。
|
||||||
*
|
*
|
||||||
* <p>并发首次创建依赖唯一键 uk_flow_ver 兜底,冲突后重新查询。</p>
|
* <p>并发首次创建依赖唯一键 uk_flow_ver 兜底,冲突后重新读取最新版本。</p>
|
||||||
*/
|
*/
|
||||||
public ApprovalFlowDefinition getOrCreate(String flowCode) {
|
public ApprovalFlowDefinition getOrCreate(String flowCode) {
|
||||||
ApprovalFlowDefinition definition = find(flowCode);
|
int latestVersion = latestVersion(flowCode);
|
||||||
if (definition != null) {
|
ApprovalFlowDefinition definition = findLatest(flowCode);
|
||||||
|
if (definition != null && definition.getVersion() >= latestVersion) {
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
ApprovalFlowDefinition created = new ApprovalFlowDefinition();
|
ApprovalFlowDefinition created = new ApprovalFlowDefinition();
|
||||||
created.setFlowCode(flowCode);
|
created.setFlowCode(flowCode);
|
||||||
created.setVersion(INITIAL_VERSION);
|
created.setVersion(latestVersion);
|
||||||
created.setName(flowName(flowCode));
|
created.setName(flowName(flowCode));
|
||||||
created.setNodesJson(flowNodesJson(flowCode));
|
created.setNodesJson(flowNodesJson(flowCode));
|
||||||
try {
|
try {
|
||||||
@@ -50,8 +65,8 @@ public class ApprovalFlowService {
|
|||||||
return created;
|
return created;
|
||||||
} catch (DuplicateKeyException e) {
|
} catch (DuplicateKeyException e) {
|
||||||
log.info("审批流程定义已被并发创建,重新读取: {}", flowCode);
|
log.info("审批流程定义已被并发创建,重新读取: {}", flowCode);
|
||||||
definition = find(flowCode);
|
definition = findLatest(flowCode);
|
||||||
if (definition == null) {
|
if (definition == null || definition.getVersion() < latestVersion) {
|
||||||
throw new BusinessException("审批流程定义初始化失败");
|
throw new BusinessException("审批流程定义初始化失败");
|
||||||
}
|
}
|
||||||
return definition;
|
return definition;
|
||||||
@@ -92,13 +107,18 @@ public class ApprovalFlowService {
|
|||||||
return node == null ? null : node.getName();
|
return node == null ? null : node.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApprovalFlowDefinition find(String flowCode) {
|
private ApprovalFlowDefinition findLatest(String flowCode) {
|
||||||
return flowDefinitionMapper.selectOne(new LambdaQueryWrapper<ApprovalFlowDefinition>()
|
return flowDefinitionMapper.selectOne(new LambdaQueryWrapper<ApprovalFlowDefinition>()
|
||||||
.eq(ApprovalFlowDefinition::getFlowCode, flowCode)
|
.eq(ApprovalFlowDefinition::getFlowCode, flowCode)
|
||||||
.eq(ApprovalFlowDefinition::getVersion, INITIAL_VERSION)
|
.orderByDesc(ApprovalFlowDefinition::getVersion)
|
||||||
.last("LIMIT 1"));
|
.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) {
|
private String flowName(String flowCode) {
|
||||||
if (ApprovalConstants.FLOW_COMPLETION.equals(flowCode)) {
|
if (ApprovalConstants.FLOW_COMPLETION.equals(flowCode)) {
|
||||||
return "业务完成确认单";
|
return "业务完成确认单";
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.gxwebsoft.approval.vo.ApprovalInstanceVO;
|
|||||||
import com.gxwebsoft.approval.vo.ApprovalItemVO;
|
import com.gxwebsoft.approval.vo.ApprovalItemVO;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalMyTaskVO;
|
import com.gxwebsoft.approval.vo.ApprovalMyTaskVO;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalOrganizationVO;
|
import com.gxwebsoft.approval.vo.ApprovalOrganizationVO;
|
||||||
|
import com.gxwebsoft.approval.vo.ApprovalPendingTaskVO;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalPermissionsVO;
|
import com.gxwebsoft.approval.vo.ApprovalPermissionsVO;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalRecordVO;
|
import com.gxwebsoft.approval.vo.ApprovalRecordVO;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalUserVO;
|
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.core.web.PageResult;
|
||||||
import com.gxwebsoft.common.system.entity.Menu;
|
import com.gxwebsoft.common.system.entity.Menu;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
@@ -59,7 +61,9 @@ import java.util.stream.Collectors;
|
|||||||
public class ApprovalInstanceService {
|
public class ApprovalInstanceService {
|
||||||
|
|
||||||
private static final BigDecimal MAX_FEE = new BigDecimal("99999999.99");
|
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
|
@Resource
|
||||||
private ApprovalInstanceMapper instanceMapper;
|
private ApprovalInstanceMapper instanceMapper;
|
||||||
@@ -103,7 +107,7 @@ public class ApprovalInstanceService {
|
|||||||
/**
|
/**
|
||||||
* 新建或更新内部协调单草稿,返回实例id。
|
* 新建或更新内部协调单草稿,返回实例id。
|
||||||
*
|
*
|
||||||
* <p>报送部门与经办人取登录态;受理部门必须是启用组织,保存时即校验。</p>
|
* <p>报送部门与报送人取登录态;受理部门必须是启用组织,保存时即校验。</p>
|
||||||
*/
|
*/
|
||||||
public Long saveCoordination(ApprovalCoordinationParam param, User user) {
|
public Long saveCoordination(ApprovalCoordinationParam param, User user) {
|
||||||
validateCoordination(param);
|
validateCoordination(param);
|
||||||
@@ -233,7 +237,7 @@ public class ApprovalInstanceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 详情;草稿仅发起人可见,其余按任务/记录/派单目标或 allVisible 角色判断。
|
* 详情;草稿仅报送人可见,其余按任务/记录/派单目标或 allVisible 角色判断。
|
||||||
*/
|
*/
|
||||||
public ApprovalInstanceVO detail(Long instanceId, User user) {
|
public ApprovalInstanceVO detail(Long instanceId, User user) {
|
||||||
ApprovalInstance instance = instanceMapper.selectById(instanceId);
|
ApprovalInstance instance = instanceMapper.selectById(instanceId);
|
||||||
@@ -242,7 +246,9 @@ public class ApprovalInstanceService {
|
|||||||
}
|
}
|
||||||
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
||||||
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
|
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("无权查看该单据");
|
throw new BusinessException("无权查看该单据");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +273,7 @@ public class ApprovalInstanceService {
|
|||||||
vo.setConfirms(confirms(instance));
|
vo.setConfirms(confirms(instance));
|
||||||
ApprovalMyTaskVO myTask = myTask(instance, user.getUserId());
|
ApprovalMyTaskVO myTask = myTask(instance, user.getUserId());
|
||||||
vo.setMyTask(myTask);
|
vo.setMyTask(myTask);
|
||||||
|
vo.setPendingTasks(pendingTasks(instance));
|
||||||
vo.setPermissions(permissions(instance, user, myTask));
|
vo.setPermissions(permissions(instance, user, myTask));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -284,8 +291,17 @@ public class ApprovalInstanceService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 我的待办:本人 PENDING 任务对应的实例,按实例去重。
|
* 我的待办:本人 PENDING 任务对应的实例,按实例去重。
|
||||||
|
*
|
||||||
|
* <p>PC 传 {@code scope=all} 时返回全租户待办,仅 assign 权限可用;
|
||||||
|
* 员工端不传 scope,行为不变。</p>
|
||||||
*/
|
*/
|
||||||
public PageResult<ApprovalItemVO> todo(ApprovalTaskListParam param, User user) {
|
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);
|
return taskPage(param, user, ApprovalConstants.TASK_PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,6 +312,17 @@ public class ApprovalInstanceService {
|
|||||||
return taskPage(param, user, ApprovalConstants.TASK_DONE);
|
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) {
|
private PageResult<ApprovalItemVO> taskPage(ApprovalTaskListParam param, User user, String taskStatus) {
|
||||||
long current = param.getPage() == null ? 1 : Math.max(1, param.getPage());
|
long current = param.getPage() == null ? 1 : Math.max(1, param.getPage());
|
||||||
long size = param.getLimit() == null ? 10 : Math.max(1, param.getLimit());
|
long size = param.getLimit() == null ? 10 : Math.max(1, param.getLimit());
|
||||||
@@ -552,6 +579,28 @@ public class ApprovalInstanceService {
|
|||||||
return vo;
|
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) {
|
private ApprovalPermissionsVO permissions(ApprovalInstance instance, User user, ApprovalMyTaskVO myTask) {
|
||||||
ApprovalPermissionsVO permissions = new ApprovalPermissionsVO();
|
ApprovalPermissionsVO permissions = new ApprovalPermissionsVO();
|
||||||
boolean reporter = instance.getReporterId().equals(user.getUserId());
|
boolean reporter = instance.getReporterId().equals(user.getUserId());
|
||||||
@@ -565,6 +614,8 @@ public class ApprovalInstanceService {
|
|||||||
.eq(ApprovalTask::getStatus, ApprovalConstants.TASK_DONE));
|
.eq(ApprovalTask::getStatus, ApprovalConstants.TASK_DONE));
|
||||||
permissions.setCanWithdraw(done == null || done == 0);
|
permissions.setCanWithdraw(done == null || done == 0);
|
||||||
}
|
}
|
||||||
|
permissions.setCanAssign(hasAuthority(user, PERMISSION_ASSIGN)
|
||||||
|
&& ApprovalConstants.STATUS_PENDING.equals(instance.getStatus()));
|
||||||
if (myTask != null) {
|
if (myTask != null) {
|
||||||
boolean approveNode = ApprovalConstants.NODE_APPROVE.equals(myTask.getNodeType());
|
boolean approveNode = ApprovalConstants.NODE_APPROVE.equals(myTask.getNodeType());
|
||||||
boolean rejectableNode = approveNode || ApprovalConstants.NODE_DISPATCH.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.setCanDispatch(ApprovalConstants.NODE_DISPATCH.equals(myTask.getNodeType()));
|
||||||
permissions.setCanConfirm(ApprovalConstants.NODE_CONFIRM.equals(myTask.getNodeType()));
|
permissions.setCanConfirm(ApprovalConstants.NODE_CONFIRM.equals(myTask.getNodeType()));
|
||||||
permissions.setCanArchive(ApprovalConstants.NODE_ARCHIVE.equals(myTask.getNodeType()));
|
permissions.setCanArchive(ApprovalConstants.NODE_ARCHIVE.equals(myTask.getNodeType()));
|
||||||
permissions.setCanAssign(hasAuthority(user, PERMISSION_ASSIGN));
|
|
||||||
}
|
}
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.gxwebsoft.approval.support.ApprovalItemRow;
|
|||||||
import com.gxwebsoft.approval.support.ApprovalNode;
|
import com.gxwebsoft.approval.support.ApprovalNode;
|
||||||
import com.gxwebsoft.approval.vo.ApprovalItemVO;
|
import com.gxwebsoft.approval.vo.ApprovalItemVO;
|
||||||
import com.gxwebsoft.common.core.web.PageResult;
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.Menu;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -44,8 +45,10 @@ public class ApprovalQueryService {
|
|||||||
long size = param.getLimit() == null ? DEFAULT_LIMIT : Math.max(1, param.getLimit());
|
long size = param.getLimit() == null ? DEFAULT_LIMIT : Math.max(1, param.getLimit());
|
||||||
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
int allVisible = directoryService.hasAnyRole(user.getUserId(),
|
||||||
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
|
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);
|
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<>();
|
Map<Long, List<ApprovalNode>> nodeCache = new HashMap<>();
|
||||||
List<ApprovalItemVO> list = new ArrayList<>(rows.size());
|
List<ApprovalItemVO> list = new ArrayList<>(rows.size());
|
||||||
for (ApprovalItemRow row : rows) {
|
for (ApprovalItemRow row : rows) {
|
||||||
@@ -73,6 +76,15 @@ public class ApprovalQueryService {
|
|||||||
return item;
|
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) {
|
private String nodeName(Map<Long, List<ApprovalNode>> cache, Long definitionId, String nodeKey) {
|
||||||
if (definitionId == null || nodeKey == null) {
|
if (definitionId == null || nodeKey == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -88,5 +88,8 @@ public class ApprovalInstanceVO implements Serializable {
|
|||||||
|
|
||||||
private ApprovalMyTaskVO myTask;
|
private ApprovalMyTaskVO myTask;
|
||||||
|
|
||||||
|
/** 实例当前全部待办任务,供有 assign 权限的 PC 用户按任务改派。 */
|
||||||
|
private List<ApprovalPendingTaskVO> pendingTasks;
|
||||||
|
|
||||||
private ApprovalPermissionsVO permissions;
|
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;
|
||||||
|
}
|
||||||
@@ -120,17 +120,22 @@ class ApprovalCoordinationIT {
|
|||||||
.andExpect(jsonPath("$.data.list[0].acceptOrgName").isNotEmpty())
|
.andExpect(jsonPath("$.data.list[0].acceptOrgName").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.data.list[0].statusName").value("未提交"));
|
.andExpect(jsonPath("$.data.list[0].statusName").value("未提交"));
|
||||||
|
|
||||||
// 流程定义按租户幂等,节点顺序固定;一期不产生外部协调流程
|
// 流程定义按租户幂等发布最新版本,节点顺序固定;v1 保留不改,v2 派单归受理部门负责人
|
||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_flow_definition "
|
assertThat(data.queryMaster("SELECT MAX(version) FROM approval_flow_definition "
|
||||||
+ "WHERE tenant_id = ? AND flow_code = 'coordination_internal' AND version = 1", TENANT)).isEqualTo(1);
|
+ "WHERE tenant_id = ? AND flow_code = 'coordination_internal'", Integer.class, TENANT)).isEqualTo(2);
|
||||||
String nodes = data.queryMaster("SELECT nodes_json FROM approval_flow_definition "
|
String nodes = data.queryMaster("SELECT nodes_json FROM approval_flow_definition "
|
||||||
+ "WHERE tenant_id = ? AND flow_code = 'coordination_internal' AND version = 1", String.class, TENANT);
|
+ "WHERE tenant_id = ? AND flow_code = 'coordination_internal' "
|
||||||
|
+ "ORDER BY version DESC LIMIT 1", String.class, TENANT);
|
||||||
assertThat(nodes).contains("reportLeader").contains("acceptLeader").contains("dispatch")
|
assertThat(nodes).contains("reportLeader").contains("acceptLeader").contains("dispatch")
|
||||||
.contains("confirm").contains("archive");
|
.contains("confirm").contains("archive");
|
||||||
|
assertThat(nodes).contains("\"type\": \"DISPATCH\", \"resolver\": \"acceptOrgLeader\"");
|
||||||
|
// 实例固定引用创建时的定义版本(v2)
|
||||||
|
assertThat(data.queryMaster("SELECT definition_version FROM approval_instance WHERE id = ?",
|
||||||
|
Integer.class, instanceId)).isEqualTo(2);
|
||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_flow_definition "
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_flow_definition "
|
||||||
+ "WHERE tenant_id = ? AND flow_code = 'coordination_external'", TENANT)).isZero();
|
+ "WHERE tenant_id = ? AND flow_code = 'coordination_external'", TENANT)).isZero();
|
||||||
|
|
||||||
// 报送部门与经办人只取登录态,客户端伪造字段被忽略
|
// 报送部门与报送人只取登录态,客户端伪造字段被忽略
|
||||||
ApprovalTestData.TestUser other = data.createUser("他人" + data.uniqueSuffix(), acceptOrgId);
|
ApprovalTestData.TestUser other = data.createUser("他人" + data.uniqueSuffix(), acceptOrgId);
|
||||||
String forged = "{\"acceptOrgId\":" + acceptOrgId + ",\"title\":\"伪造部门\",\"content\":\"内容\","
|
String forged = "{\"acceptOrgId\":" + acceptOrgId + ",\"title\":\"伪造部门\",\"content\":\"内容\","
|
||||||
+ "\"reporterId\":" + other.userId + ",\"reportOrgId\":" + acceptOrgId + "}";
|
+ "\"reporterId\":" + other.userId + ",\"reportOrgId\":" + acceptOrgId + "}";
|
||||||
@@ -350,7 +355,10 @@ class ApprovalCoordinationIT {
|
|||||||
String.class, instanceId)).isEqualTo("dispatch");
|
String.class, instanceId)).isEqualTo("dispatch");
|
||||||
assertThat(data.queryMaster("SELECT current_node_type FROM approval_instance WHERE id = ?",
|
assertThat(data.queryMaster("SELECT current_node_type FROM approval_instance WHERE id = ?",
|
||||||
String.class, instanceId)).isEqualTo("DISPATCH");
|
String.class, instanceId)).isEqualTo("DISPATCH");
|
||||||
assertThat(pendingTaskIds(instanceId)).contains(acceptMember.userId);
|
// 派单节点只分配给受理部门负责人,普通部门成员无派单待办
|
||||||
|
assertThat(pendingTaskIds(instanceId)).containsExactly(acceptLeader.userId);
|
||||||
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
|
+ "AND assignee_id = ? AND node_type = 'DISPATCH'", instanceId, acceptMember.userId)).isZero();
|
||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_record WHERE instance_id = ? "
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_record WHERE instance_id = ? "
|
||||||
+ "AND action = 'APPROVE'", instanceId)).isEqualTo(2);
|
+ "AND action = 'APPROVE'", instanceId)).isEqualTo(2);
|
||||||
assertThat(data.queryMaster("SELECT opinion FROM approval_task WHERE id = ?", String.class, reportTask))
|
assertThat(data.queryMaster("SELECT opinion FROM approval_task WHERE id = ?", String.class, reportTask))
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ class ApprovalDispatchIT {
|
|||||||
long instanceId = reachDispatch("部门候选池");
|
long instanceId = reachDispatch("部门候选池");
|
||||||
long dispatchTask = pendingTaskId(instanceId, acceptLeader.userId);
|
long dispatchTask = pendingTaskId(instanceId, acceptLeader.userId);
|
||||||
|
|
||||||
|
// 派单节点只落在受理部门负责人(或签),普通部门成员不产生派单待办
|
||||||
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
|
+ "AND node_type = 'DISPATCH'", instanceId)).isEqualTo(1);
|
||||||
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
|
+ "AND assignee_id = ? AND node_type = 'DISPATCH'", instanceId, acceptMember.userId)).isZero();
|
||||||
|
|
||||||
// 部门内停用成员不进入候选池
|
// 部门内停用成员不进入候选池
|
||||||
ApprovalTestData.TestUser disabled =
|
ApprovalTestData.TestUser disabled =
|
||||||
data.createUser("派单停用成员" + data.uniqueSuffix(), acceptOrgId, TENANT);
|
data.createUser("派单停用成员" + data.uniqueSuffix(), acceptOrgId, TENANT);
|
||||||
@@ -158,6 +164,11 @@ class ApprovalDispatchIT {
|
|||||||
// 受理部门成员全部调离后部门为空,派单待办仍在负责人手中
|
// 受理部门成员全部调离后部门为空,派单待办仍在负责人手中
|
||||||
data.setUserOrganization(acceptLeader.userId, reportOrgId);
|
data.setUserOrganization(acceptLeader.userId, reportOrgId);
|
||||||
data.setUserOrganization(acceptMember.userId, reportOrgId);
|
data.setUserOrganization(acceptMember.userId, reportOrgId);
|
||||||
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
|
+ "AND assignee_id = ? AND node_type = 'DISPATCH' AND status = 'PENDING'",
|
||||||
|
instanceId, acceptLeader.userId)).isEqualTo(1);
|
||||||
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
|
+ "AND assignee_id = ? AND node_type = 'DISPATCH'", instanceId, acceptMember.userId)).isZero();
|
||||||
|
|
||||||
String response = mockMvc.perform(authed(get("/api/approval/task/" + dispatchTask + "/candidates"),
|
String response = mockMvc.perform(authed(get("/api/approval/task/" + dispatchTask + "/candidates"),
|
||||||
acceptLeader, reportOrgId, APPROVE))
|
acceptLeader, reportOrgId, APPROVE))
|
||||||
@@ -179,7 +190,6 @@ class ApprovalDispatchIT {
|
|||||||
void dispatchesToSelectedCandidateAndCreatesBothConfirmTasks() throws Exception {
|
void dispatchesToSelectedCandidateAndCreatesBothConfirmTasks() throws Exception {
|
||||||
long instanceId = reachDispatch("派单存档");
|
long instanceId = reachDispatch("派单存档");
|
||||||
long dispatchTask = pendingTaskId(instanceId, acceptLeader.userId);
|
long dispatchTask = pendingTaskId(instanceId, acceptLeader.userId);
|
||||||
long otherDispatchTask = pendingTaskId(instanceId, acceptMember.userId);
|
|
||||||
|
|
||||||
mockMvc.perform(authed(post("/api/approval/task/" + dispatchTask + "/dispatch"),
|
mockMvc.perform(authed(post("/api/approval/task/" + dispatchTask + "/dispatch"),
|
||||||
acceptLeader, acceptOrgId, APPROVE)
|
acceptLeader, acceptOrgId, APPROVE)
|
||||||
@@ -193,13 +203,13 @@ class ApprovalDispatchIT {
|
|||||||
.andExpect(jsonPath("$.data.dispatchTargetName").value(acceptMember.realName))
|
.andExpect(jsonPath("$.data.dispatchTargetName").value(acceptMember.realName))
|
||||||
.andExpect(jsonPath("$.data.myTask").doesNotExist());
|
.andExpect(jsonPath("$.data.myTask").doesNotExist());
|
||||||
|
|
||||||
// 派单任务完成并快照派单对象,同节点其余派单待办取消
|
// 派单任务完成并快照派单对象;派单节点只有负责人的一条或签待办
|
||||||
assertThat(data.queryMaster("SELECT status FROM approval_task WHERE id = ?", String.class, dispatchTask))
|
assertThat(data.queryMaster("SELECT status FROM approval_task WHERE id = ?", String.class, dispatchTask))
|
||||||
.isEqualTo("DONE");
|
.isEqualTo("DONE");
|
||||||
assertThat(data.queryMaster("SELECT dispatch_target_id FROM approval_task WHERE id = ?",
|
assertThat(data.queryMaster("SELECT dispatch_target_id FROM approval_task WHERE id = ?",
|
||||||
Integer.class, dispatchTask)).isEqualTo(acceptMember.userId);
|
Integer.class, dispatchTask)).isEqualTo(acceptMember.userId);
|
||||||
assertThat(data.queryMaster("SELECT status FROM approval_task WHERE id = ?", String.class, otherDispatchTask))
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
.isEqualTo("CANCELLED");
|
+ "AND node_type = 'DISPATCH'", instanceId)).isEqualTo(1);
|
||||||
assertThat(data.queryMaster("SELECT dispatch_target_id FROM approval_instance WHERE id = ?",
|
assertThat(data.queryMaster("SELECT dispatch_target_id FROM approval_instance WHERE id = ?",
|
||||||
Integer.class, instanceId)).isEqualTo(acceptMember.userId);
|
Integer.class, instanceId)).isEqualTo(acceptMember.userId);
|
||||||
assertThat(data.queryMaster("SELECT dispatch_target_name FROM approval_instance WHERE id = ?",
|
assertThat(data.queryMaster("SELECT dispatch_target_name FROM approval_instance WHERE id = ?",
|
||||||
@@ -273,7 +283,6 @@ class ApprovalDispatchIT {
|
|||||||
void concurrentDispatchOnlySucceedsOnce() throws Exception {
|
void concurrentDispatchOnlySucceedsOnce() throws Exception {
|
||||||
long instanceId = reachDispatch("并发派单");
|
long instanceId = reachDispatch("并发派单");
|
||||||
long leaderTask = pendingTaskId(instanceId, acceptLeader.userId);
|
long leaderTask = pendingTaskId(instanceId, acceptLeader.userId);
|
||||||
long memberTask = pendingTaskId(instanceId, acceptMember.userId);
|
|
||||||
|
|
||||||
CountDownLatch start = new CountDownLatch(1);
|
CountDownLatch start = new CountDownLatch(1);
|
||||||
CountDownLatch done = new CountDownLatch(2);
|
CountDownLatch done = new CountDownLatch(2);
|
||||||
@@ -283,7 +292,7 @@ class ApprovalDispatchIT {
|
|||||||
try {
|
try {
|
||||||
dispatchAsync(executor, start, done, leaderTask, acceptLeader, acceptOrgId,
|
dispatchAsync(executor, start, done, leaderTask, acceptLeader, acceptOrgId,
|
||||||
acceptMember.userId, success, conflict);
|
acceptMember.userId, success, conflict);
|
||||||
dispatchAsync(executor, start, done, memberTask, acceptMember, acceptOrgId,
|
dispatchAsync(executor, start, done, leaderTask, acceptLeader, acceptOrgId,
|
||||||
acceptLeader.userId, success, conflict);
|
acceptLeader.userId, success, conflict);
|
||||||
start.countDown();
|
start.countDown();
|
||||||
assertThat(done.await(30, TimeUnit.SECONDS)).isTrue();
|
assertThat(done.await(30, TimeUnit.SECONDS)).isTrue();
|
||||||
@@ -298,7 +307,7 @@ class ApprovalDispatchIT {
|
|||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
+ "AND node_type = 'DISPATCH' AND status = 'DONE'", instanceId)).isEqualTo(1);
|
+ "AND node_type = 'DISPATCH' AND status = 'DONE'", instanceId)).isEqualTo(1);
|
||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
+ "AND node_type = 'DISPATCH' AND status = 'CANCELLED'", instanceId)).isEqualTo(1);
|
+ "AND node_type = 'DISPATCH'", instanceId)).isEqualTo(1);
|
||||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||||
+ "AND node_type = 'CONFIRM' AND status = 'PENDING'", instanceId)).isEqualTo(2);
|
+ "AND node_type = 'CONFIRM' AND status = 'PENDING'", instanceId)).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class ApprovalExportIT {
|
|||||||
|
|
||||||
mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("{\"keyword\":" + quote(mark) + "}"))
|
.content("{\"keywords\":" + quote(mark) + "}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(1))
|
.andExpect(jsonPath("$.code").value(1))
|
||||||
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("请缩小筛选范围")));
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("请缩小筛选范围")));
|
||||||
@@ -164,7 +164,7 @@ class ApprovalExportIT {
|
|||||||
|
|
||||||
String response = mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
String response = mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("{\"keyword\":" + quote(mark) + "}"))
|
.content("{\"keywords\":" + quote(mark) + "}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(0))
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
@@ -244,7 +244,7 @@ class ApprovalExportIT {
|
|||||||
|
|
||||||
mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
mockMvc.perform(authed(post(URL), reporter, LIST, EXPORT)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("{\"keyword\":" + quote(mark) + "}"))
|
.content("{\"keywords\":" + quote(mark) + "}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(0))
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("共2条")));
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("共2条")));
|
||||||
@@ -281,10 +281,10 @@ class ApprovalExportIT {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> exportBizNos(ApprovalTestData.TestUser user, String keyword) throws Exception {
|
private List<String> exportBizNos(ApprovalTestData.TestUser user, String keywords) throws Exception {
|
||||||
String response = mockMvc.perform(authed(post(URL), user, LIST, EXPORT)
|
String response = mockMvc.perform(authed(post(URL), user, LIST, EXPORT)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("{\"keyword\":" + quote(keyword) + "}"))
|
.content("{\"keywords\":" + quote(keywords) + "}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(0))
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
|||||||
@@ -142,6 +142,54 @@ class ApprovalPcTodoIT {
|
|||||||
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("无权处理该任务")));
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("无权处理该任务")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listsAllTenantPendingTasksForAssignUserAndReassignsOthers() throws Exception {
|
||||||
|
String title = "全租户待办" + data.uniqueSuffix();
|
||||||
|
long instanceId = createAndSubmit(title);
|
||||||
|
long taskId = pendingTaskId(instanceId, leader.userId);
|
||||||
|
|
||||||
|
// 有 assign 权限的 PC 用户通过 scope=all 看到他人待办
|
||||||
|
mockMvc.perform(authed(get("/api/approval/todo/page").param("scope", "all")
|
||||||
|
.param("keywords", title), admin, organizationId, LIST, ASSIGN))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
|
.andExpect(jsonPath("$.data.count").value(1))
|
||||||
|
.andExpect(jsonPath("$.data.list[0].id").value((int) instanceId));
|
||||||
|
|
||||||
|
// 非处理人也能打开详情,并按任务 id 拿到待办任务与改派权限
|
||||||
|
mockMvc.perform(authed(get("/api/approval/instance/" + instanceId), admin, organizationId, LIST, ASSIGN))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
|
.andExpect(jsonPath("$.data.permissions.canAssign").value(true))
|
||||||
|
.andExpect(jsonPath("$.data.myTask").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.data.pendingTasks.length()").value(1))
|
||||||
|
.andExpect(jsonPath("$.data.pendingTasks[0].id").value((int) taskId))
|
||||||
|
.andExpect(jsonPath("$.data.pendingTasks[0].assigneeId").value(leader.userId))
|
||||||
|
.andExpect(jsonPath("$.data.pendingTasks[0].nodeName").value("报送部门负责人"));
|
||||||
|
|
||||||
|
// 按待办任务 id 改派成功,待办转移到新处理人
|
||||||
|
ApprovalTestData.TestUser target = data.createUser("全租户改派目标" + data.uniqueSuffix(), organizationId);
|
||||||
|
mockMvc.perform(assign(taskId, admin, ASSIGN, target.userId, "全租户改派" + data.uniqueSuffix()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
|
.andExpect(jsonPath("$.data.assigneeId").value(target.userId));
|
||||||
|
mockMvc.perform(authed(get("/api/approval/todo/page"), leader, organizationId, LIST))
|
||||||
|
.andExpect(jsonPath("$.data.count").value(0));
|
||||||
|
mockMvc.perform(authed(get("/api/approval/todo/page"), target, organizationId, LIST))
|
||||||
|
.andExpect(jsonPath("$.data.count").value(1))
|
||||||
|
.andExpect(jsonPath("$.data.list[0].id").value((int) instanceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsScopeAllWithoutAssignAuthority() throws Exception {
|
||||||
|
long instanceId = createAndSubmit("全租户越权" + data.uniqueSuffix());
|
||||||
|
assertThat(instanceId).isPositive();
|
||||||
|
|
||||||
|
mockMvc.perform(authed(get("/api/approval/todo/page").param("scope", "all"), admin, organizationId, LIST))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(403));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsAssignToUnknownOrInactiveUser() throws Exception {
|
void rejectsAssignToUnknownOrInactiveUser() throws Exception {
|
||||||
long instanceId = createAndSubmit("改派无效目标");
|
long instanceId = createAndSubmit("改派无效目标");
|
||||||
|
|||||||
@@ -81,25 +81,25 @@ class ApprovalQueryIT {
|
|||||||
long draft = createCompletionDraft("场馆维护草稿", "广西体育中心保障部");
|
long draft = createCompletionDraft("场馆维护草稿", "广西体育中心保障部");
|
||||||
|
|
||||||
// 单据类型 + 关键词命中实施单位
|
// 单据类型 + 关键词命中实施单位
|
||||||
assertThat(queryIds("?docType=completion&keyword=实施部")).containsExactly(submitted);
|
assertThat(queryIds("?docType=completion&keywords=实施部")).containsExactly(submitted);
|
||||||
// 关键词命中协调单标题
|
// 关键词命中协调单标题
|
||||||
assertThat(queryIds("?docType=coordination&keyword=维修协调")).containsExactly(coordination);
|
assertThat(queryIds("?docType=coordination&keywords=维修协调")).containsExactly(coordination);
|
||||||
// 关键词命中单号
|
// 关键词命中单号
|
||||||
String bizNo = data.queryMaster("SELECT biz_no FROM approval_instance WHERE id = ?", String.class, submitted);
|
String bizNo = data.queryMaster("SELECT biz_no FROM approval_instance WHERE id = ?", String.class, submitted);
|
||||||
assertThat(queryIds("?keyword=" + bizNo)).containsExactly(submitted);
|
assertThat(queryIds("?keywords=" + bizNo)).containsExactly(submitted);
|
||||||
// 报送部门:两类单据都按报送部门过滤,其它部门不命中
|
// 报送部门:两类单据都按报送部门过滤,其它部门不命中
|
||||||
assertThat(queryIds("?reportOrgId=" + reportOrgId))
|
assertThat(queryIds("?reportOrgId=" + reportOrgId))
|
||||||
.containsExactlyInAnyOrder(submitted, coordination, draft);
|
.containsExactlyInAnyOrder(submitted, coordination, draft);
|
||||||
assertThat(queryIds("?reportOrgId=" + acceptOrgId)).isEmpty();
|
assertThat(queryIds("?reportOrgId=" + acceptOrgId)).isEmpty();
|
||||||
// 受理部门
|
// 受理部门
|
||||||
assertThat(queryIds("?acceptOrgId=" + acceptOrgId)).containsExactly(coordination);
|
assertThat(queryIds("?acceptOrgId=" + acceptOrgId)).containsExactly(coordination);
|
||||||
// 发起人
|
// 报送人
|
||||||
assertThat(queryIds("?reporterId=" + reporter.userId)).containsExactlyInAnyOrder(submitted, coordination, draft);
|
assertThat(queryIds("?reporterId=" + reporter.userId)).containsExactlyInAnyOrder(submitted, coordination, draft);
|
||||||
// 状态
|
// 状态
|
||||||
assertThat(queryIds("?status=PENDING")).containsExactlyInAnyOrder(submitted, coordination);
|
assertThat(queryIds("?status=PENDING")).containsExactlyInAnyOrder(submitted, coordination);
|
||||||
assertThat(queryIds("?status=DRAFT")).containsExactly(draft);
|
assertThat(queryIds("?status=DRAFT")).containsExactly(draft);
|
||||||
// 总数与分页总数一致
|
// 总数与分页总数一致
|
||||||
assertThat(queryCount("?keyword=实施部")).isEqualTo(1);
|
assertThat(queryCount("?keywords=实施部")).isEqualTo(1);
|
||||||
// 单号精确筛选
|
// 单号精确筛选
|
||||||
assertThat(queryIds("?bizNo=" + bizNo)).containsExactly(submitted);
|
assertThat(queryIds("?bizNo=" + bizNo)).containsExactly(submitted);
|
||||||
// 提交时间范围
|
// 提交时间范围
|
||||||
@@ -142,7 +142,7 @@ class ApprovalQueryIT {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void limitsListByDataPermission() throws Exception {
|
void limitsListByDataPermission() throws Exception {
|
||||||
long draft = createCompletionDraft("仅发起人可见草稿");
|
long draft = createCompletionDraft("仅报送人可见草稿");
|
||||||
long pending = seedCoordination("待处理协调单", "PENDING", LocalDateTime.now().minusHours(1), null);
|
long pending = seedCoordination("待处理协调单", "PENDING", LocalDateTime.now().minusHours(1), null);
|
||||||
|
|
||||||
ApprovalTestData.TestUser outsider = data.createUser("无关人员" + data.uniqueSuffix(), acceptOrgId);
|
ApprovalTestData.TestUser outsider = data.createUser("无关人员" + data.uniqueSuffix(), acceptOrgId);
|
||||||
@@ -161,7 +161,7 @@ class ApprovalQueryIT {
|
|||||||
ApprovalTestData.TestRole archivistRole = data.createRole(ApprovalConstants.ROLE_ARCHIVIST, "收文员");
|
ApprovalTestData.TestRole archivistRole = data.createRole(ApprovalConstants.ROLE_ARCHIVIST, "收文员");
|
||||||
data.bind(archivist.userId, archivistRole.roleId);
|
data.bind(archivist.userId, archivistRole.roleId);
|
||||||
|
|
||||||
// 发起人可见自己的草稿与在途单据
|
// 报送人可见自己的草稿与在途单据
|
||||||
assertThat(queryIdsAs(reporter, "")).containsExactlyInAnyOrder(draft, pending);
|
assertThat(queryIdsAs(reporter, "")).containsExactlyInAnyOrder(draft, pending);
|
||||||
// 参与人:有任务、有处理记录、被派单
|
// 参与人:有任务、有处理记录、被派单
|
||||||
assertThat(queryIdsAs(assignee, "")).containsExactly(pending);
|
assertThat(queryIdsAs(assignee, "")).containsExactly(pending);
|
||||||
@@ -170,7 +170,7 @@ class ApprovalQueryIT {
|
|||||||
// 无关人员看不到他人单据
|
// 无关人员看不到他人单据
|
||||||
assertThat(queryIdsAs(outsider, "")).isEmpty();
|
assertThat(queryIdsAs(outsider, "")).isEmpty();
|
||||||
assertThat(queryIdsAs(outsider, "?status=PENDING")).isEmpty();
|
assertThat(queryIdsAs(outsider, "?status=PENDING")).isEmpty();
|
||||||
// 收文员看全租户,但草稿仍只属于发起人
|
// 收文员看全租户,但草稿仍只属于报送人
|
||||||
assertThat(queryIdsAs(archivist, "")).contains(pending).doesNotContain(draft);
|
assertThat(queryIdsAs(archivist, "")).contains(pending).doesNotContain(draft);
|
||||||
assertThat(queryIdsAs(archivist, "?status=DRAFT")).isEmpty();
|
assertThat(queryIdsAs(archivist, "?status=DRAFT")).isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user