mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 09:11:52 +08:00
优化 BpmParallelMultiInstanceBehavior 逻辑,实现会签、或签的任务分配
This commit is contained in:
parent
39e89bd378
commit
692daf900b
@ -9,8 +9,15 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flowable 相关的工具方法
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
public class FlowableUtils {
|
public class FlowableUtils {
|
||||||
|
|
||||||
|
// ========== User 相关的工具方法 ==========
|
||||||
|
|
||||||
public static void setAuthenticatedUserId(Long userId) {
|
public static void setAuthenticatedUserId(Long userId) {
|
||||||
Authentication.setAuthenticatedUserId(String.valueOf(userId));
|
Authentication.setAuthenticatedUserId(String.valueOf(userId));
|
||||||
}
|
}
|
||||||
@ -19,6 +26,8 @@ public class FlowableUtils {
|
|||||||
Authentication.setAuthenticatedUserId(null);
|
Authentication.setAuthenticatedUserId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== BPMN 相关的工具方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得 BPMN 流程中,指定的元素们
|
* 获得 BPMN 流程中,指定的元素们
|
||||||
*
|
*
|
||||||
@ -59,4 +68,15 @@ public class FlowableUtils {
|
|||||||
BpmnXMLConverter converter = new BpmnXMLConverter();
|
BpmnXMLConverter converter = new BpmnXMLConverter();
|
||||||
return converter.convertToXML(model);
|
return converter.convertToXML(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Execution 相关的工具方法 ==========
|
||||||
|
|
||||||
|
public static String formatCollectionVariable(String activityId) {
|
||||||
|
return activityId + "_assignees";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatCollectionElementVariable(String activityId) {
|
||||||
|
return activityId + "_assignee";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.controller.admin.task;
|
|
@ -3,12 +3,8 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
|
|||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@ApiModel("管理后台 - 通过流程任务的 Request VO")
|
@ApiModel("管理后台 - 通过流程任务的 Request VO")
|
||||||
@Data
|
@Data
|
||||||
@ -20,6 +16,6 @@ public class BpmTaskApproveReqVO {
|
|||||||
|
|
||||||
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
||||||
@NotEmpty(message = "审批意见不能为空")
|
@NotEmpty(message = "审批意见不能为空")
|
||||||
private String comment;
|
private String reason;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,5 +39,5 @@ public class BpmTaskBackReqVO {
|
|||||||
|
|
||||||
@ApiModelProperty(value = "审批结果", required = true, example = "任务驳回")
|
@ApiModelProperty(value = "审批结果", required = true, example = "任务驳回")
|
||||||
@NotNull(message = "审批结果")
|
@NotNull(message = "审批结果")
|
||||||
private String comment;
|
private String reason;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,6 @@ public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
|
|||||||
@ApiModelProperty(value = "任务结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
|
@ApiModelProperty(value = "任务结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
|
||||||
private Integer result;
|
private Integer result;
|
||||||
@ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
|
@ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
|
||||||
private String comment;
|
private String reason;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,6 @@ public class BpmTaskRejectReqVO {
|
|||||||
|
|
||||||
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
||||||
@NotEmpty(message = "审批意见不能为空")
|
@NotEmpty(message = "审批意见不能为空")
|
||||||
private String comment;
|
private String reason;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,10 @@ import org.flowable.task.api.Task;
|
|||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import org.mapstruct.Mappings;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程实例 Convert
|
* 流程实例 Convert
|
||||||
@ -105,11 +103,11 @@ public interface BpmProcessInstanceConvert {
|
|||||||
.setProcessInstanceName(instance.getName());
|
.setProcessInstanceName(instance.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert2RejectReq(ProcessInstance instance, String comment) {
|
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert2RejectReq(ProcessInstance instance, String reason) {
|
||||||
return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
|
return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
|
||||||
.setProcessInstanceName(instance.getName())
|
.setProcessInstanceName(instance.getName())
|
||||||
.setProcessInstanceId(instance.getId())
|
.setProcessInstanceId(instance.getId())
|
||||||
.setComment(comment)
|
.setReason(reason)
|
||||||
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
|
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class BpmTaskExtDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 审批建议
|
* 审批建议
|
||||||
*/
|
*/
|
||||||
private String comment;
|
private String reason;
|
||||||
/**
|
/**
|
||||||
* 任务的结束时间
|
* 任务的结束时间
|
||||||
*
|
*
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
|
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
|
||||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
@ -21,28 +20,8 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
|
|||||||
return selectList(BpmTaskExtDO::getTaskId, taskIds);
|
return selectList(BpmTaskExtDO::getTaskId, taskIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询全部任务
|
|
||||||
*
|
|
||||||
* @return 返回任务
|
|
||||||
*/
|
|
||||||
@TenantIgnore
|
|
||||||
default List<BpmTaskExtDO> listAll() {
|
|
||||||
return selectList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询任务
|
|
||||||
*
|
|
||||||
* @param procInstId 流程id
|
|
||||||
*
|
|
||||||
* @return 返回任务列表
|
|
||||||
*/
|
|
||||||
@TenantIgnore
|
|
||||||
List<BpmTaskExtDO> listByProcInstId(@Param("procInstId") String procInstId);
|
|
||||||
|
|
||||||
default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
|
default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
|
||||||
return selectList("process_instance_id", processInstanceId);
|
return selectList(BpmTaskExtDO::getProcessInstanceId, processInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,11 +35,11 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
|
|||||||
* 任务驳回
|
* 任务驳回
|
||||||
*
|
*
|
||||||
* @param taskId 任务列表
|
* @param taskId 任务列表
|
||||||
* @param comment 驳回理由
|
* @param reason 驳回理由
|
||||||
*
|
*
|
||||||
* @return 返回驳回结果,是否成功
|
* @return 返回驳回结果,是否成功
|
||||||
*/
|
*/
|
||||||
Boolean backByTaskId(@Param("taskId") String taskId, @Param("comment") String comment);
|
Boolean backByTaskId(@Param("taskId") String taskId, @Param("reason") String reason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 逻辑删除任务
|
* 逻辑删除任务
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
|
||||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
|
||||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -10,26 +8,20 @@ import org.flowable.bpmn.model.Activity;
|
|||||||
import org.flowable.engine.delegate.DelegateExecution;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
||||||
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
|
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
|
||||||
import org.flowable.engine.impl.util.CommandContextUtil;
|
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
|
||||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
|
||||||
|
* 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
|
||||||
|
* 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
|
||||||
|
*
|
||||||
* @author kemengkai
|
* @author kemengkai
|
||||||
* @create 2022-04-21 16:57
|
* @date 2022-04-21 16:57
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
|
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
|
||||||
|
|
||||||
/**
|
|
||||||
* EL表达式集合模板
|
|
||||||
*/
|
|
||||||
private final static String EXPRESSION_TEXT_TEMPLATE = "${coll_userList}";
|
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private BpmTaskAssignRuleService bpmTaskRuleService;
|
private BpmTaskAssignRuleService bpmTaskRuleService;
|
||||||
|
|
||||||
@ -39,78 +31,28 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建并行任务
|
* 重写该方法,主要实现两个功能:
|
||||||
|
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
|
||||||
|
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
|
||||||
*
|
*
|
||||||
* @param multiInstanceRootExecution 并行任务入参
|
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
|
||||||
*
|
*
|
||||||
* @return 返回结果
|
* @param execution 执行任务
|
||||||
|
* @return 数量
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected int createInstances(DelegateExecution multiInstanceRootExecution) {
|
protected int resolveNrOfInstances(DelegateExecution execution) {
|
||||||
// 查找任务信息
|
// 第一步,设置 collectionVariable 和 CollectionVariable
|
||||||
// BpmTaskAssignRuleDO taskRule = getTaskRule(multiInstanceRootExecution);
|
// 从 execution.getVariable() 读取所有任务处理人的 key
|
||||||
BpmTaskAssignRuleDO taskRule = null;
|
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
|
||||||
// 获取任务用户
|
super.collectionVariable = FlowableUtils.formatCollectionVariable(execution.getCurrentActivityId());
|
||||||
Set<Long> assigneeUserIds = calculateTaskCandidateUsers(multiInstanceRootExecution, taskRule);
|
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
|
||||||
// 设置任务集合变量
|
super.collectionElementVariable = FlowableUtils.formatCollectionElementVariable(execution.getCurrentActivityId());
|
||||||
String expressionText = String.format("%s_userList", taskRule.getTaskDefinitionKey());
|
|
||||||
// 设置任务集合变量与任务关系
|
|
||||||
multiInstanceRootExecution.setVariable(expressionText, assigneeUserIds);
|
|
||||||
// 设置任务集合EL表达式
|
|
||||||
this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
|
|
||||||
.createExpression(String.format("${%s}", expressionText));
|
|
||||||
// 根据会签,或签类型,设置会签,或签条件
|
|
||||||
// if (BpmTaskAssignRuleTypeEnum.USER_SIGN.getType().equals(taskRule.getType())) {
|
|
||||||
// // 会签
|
|
||||||
// this.completionCondition = "${ nrOfInstances == nrOfCompletedInstances }";
|
|
||||||
// } else {
|
|
||||||
// // 或签
|
|
||||||
// this.completionCondition = "${ nrOfCompletedInstances == 1 }";
|
|
||||||
// }
|
|
||||||
// 设置取出集合变量
|
|
||||||
this.collectionElementVariable = "user";
|
|
||||||
return super.createInstances(multiInstanceRootExecution);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// 第二步,获取任务的所有处理人
|
||||||
protected Object resolveCollection(DelegateExecution execution) {
|
Set<Long> assigneeUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(execution);
|
||||||
Object collection = null;
|
execution.setVariable(super.collectionVariable, assigneeUserIds);
|
||||||
if (EXPRESSION_TEXT_TEMPLATE.equals(this.collectionExpression.getExpressionText())) {
|
return assigneeUserIds.size();
|
||||||
// 查找任务信息
|
|
||||||
// BpmTaskAssignRuleDO taskRule = getTaskRule(execution);
|
|
||||||
BpmTaskAssignRuleDO taskRule = null;
|
|
||||||
// 设置任务集合变量
|
|
||||||
String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
|
|
||||||
// 获取任务用户
|
|
||||||
Set<Long> assigneeUserIds = calculateTaskCandidateUsers(execution, taskRule);
|
|
||||||
// 设置任务集合变量与任务关系
|
|
||||||
execution.setVariable(expressionText, assigneeUserIds);
|
|
||||||
// 设置任务集合EL表达式
|
|
||||||
this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
|
|
||||||
.createExpression(String.format("${%s}", expressionText));
|
|
||||||
}
|
|
||||||
if (this.collectionExpression != null) {
|
|
||||||
collection = this.collectionExpression.getValue(execution);
|
|
||||||
} else if (this.collectionVariable != null) {
|
|
||||||
collection = execution.getVariable(this.collectionVariable);
|
|
||||||
} else if (this.collectionString != null) {
|
|
||||||
collection = this.collectionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Long> calculateTaskCandidateUsers(DelegateExecution task, BpmTaskAssignRuleDO rule) {
|
|
||||||
Set<Long> assigneeUserIds = SetUtils.asSet(1L, 104L);
|
|
||||||
|
|
||||||
// 移除被禁用的用户
|
|
||||||
// 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
|
|
||||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
|
||||||
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
|
|
||||||
task.getProcessDefinitionId(), task.getCurrentActivityId(), toJsonString(rule));
|
|
||||||
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
|
|
||||||
}
|
|
||||||
return assigneeUserIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
|
||||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -16,14 +16,12 @@ import org.flowable.task.service.TaskService;
|
|||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义的流程任务的 assignee 负责人的分配
|
* 自定义的【单个】流程任务的 assignee 负责人的分配
|
||||||
* 第一步,获得对应的分配规则;
|
* 第一步,基于分配规则,计算出分配任务的【单个】候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
|
||||||
* 第二步,根据分配规则,计算出分配任务的候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
|
* 第二步,随机选择一个候选人,则选择作为 assignee 负责人。
|
||||||
* 第三步,随机选择一个候选人,则选择作为 assignee 负责人。
|
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@ -38,30 +36,28 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
|
||||||
protected void handleAssignments(TaskService taskService, String assignee, String owner,
|
protected void handleAssignments(TaskService taskService, String assignee, String owner,
|
||||||
List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
|
List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
|
||||||
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
|
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
|
||||||
// 第一步,获得任务的候选用户们
|
// 第一步,获得任务的候选用户
|
||||||
Set<Long> candidateUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(task);
|
Long assigneeUserId = calculateTaskCandidateUsers(execution);
|
||||||
// 第二步,选择一个作为候选人
|
Assert.notNull(assigneeUserId, "任务处理人不能为空");
|
||||||
Long assigneeUserId = chooseTaskAssignee(execution, candidateUserIds);
|
// 第二步,设置作为负责人
|
||||||
// 第三步,设置作为负责人
|
|
||||||
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
|
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long chooseTaskAssignee(DelegateExecution execution, Set<Long> candidateUserIds) {
|
private Long calculateTaskCandidateUsers(DelegateExecution execution) {
|
||||||
// 获取任务变量
|
// 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。它的任务处理人在 BpmParallelMultiInstanceBehavior 中已经被分配了
|
||||||
Map<String, Object> variables = execution.getVariables();
|
if (super.multiInstanceActivityBehavior != null) {
|
||||||
// 设置任务集合变量key
|
return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), Long.class);
|
||||||
String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
|
|
||||||
// 判断当前任务是否为并行任务, 是的话获取任务变量
|
|
||||||
if (variables.containsKey(expressionText)) {
|
|
||||||
String user = variables.get("user").toString();
|
|
||||||
return Long.valueOf(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 芋艿:未来可以优化下,改成轮询的策略
|
// 情况二,如果非多实例的任务,则计算任务处理人
|
||||||
|
// 第一步,先计算可处理该任务的处理人们
|
||||||
|
Set<Long> candidateUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(execution);
|
||||||
|
// 第二步,后随机选择一个任务的处理人
|
||||||
|
// 疑问:为什么一定要选择一个任务处理人?
|
||||||
|
// 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。
|
||||||
int index = RandomUtil.randomInt(candidateUserIds.size());
|
int index = RandomUtil.randomInt(candidateUserIds.size());
|
||||||
return CollUtil.get(candidateUserIds, index);
|
return CollUtil.get(candidateUserIds, index);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script;
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
// TODO @芋艿:迁移到 bpm 的 core 下
|
||||||
/**
|
/**
|
||||||
* Bpm 任务分配的自定义 Script 脚本
|
* Bpm 任务分配的自定义 Script 脚本
|
||||||
* 使用场景:
|
* 使用场景:
|
||||||
@ -17,12 +18,12 @@ import java.util.Set;
|
|||||||
public interface BpmTaskAssignScript {
|
public interface BpmTaskAssignScript {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于流程任务,获得任务的候选用户们
|
* 基于执行任务,获得任务的候选用户们
|
||||||
*
|
*
|
||||||
* @param task 任务
|
* @param execution 执行任务
|
||||||
* @return 候选人用户的编号数组
|
* @return 候选人用户的编号数组
|
||||||
*/
|
*/
|
||||||
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
|
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得枚举值
|
* 获得枚举值
|
||||||
|
@ -7,9 +7,8 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
|||||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -35,10 +34,10 @@ public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssign
|
|||||||
@Lazy // 解决循环依赖
|
@Lazy // 解决循环依赖
|
||||||
private BpmProcessInstanceService bpmProcessInstanceService;
|
private BpmProcessInstanceService bpmProcessInstanceService;
|
||||||
|
|
||||||
protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
|
protected Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, int level) {
|
||||||
Assert.isTrue(level > 0, "level 必须大于 0");
|
Assert.isTrue(level > 0, "level 必须大于 0");
|
||||||
// 获得发起人
|
// 获得发起人
|
||||||
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
|
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||||
// 获得对应 leve 的部门
|
// 获得对应 leve 的部门
|
||||||
DeptRespDTO dept = null;
|
DeptRespDTO dept = null;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
|
||||||
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -16,9 +15,8 @@ import java.util.Set;
|
|||||||
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
|
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
|
||||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
return calculateTaskCandidateUsers(execution, 1);
|
||||||
return calculateTaskCandidateUsers(task, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
|
||||||
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -16,9 +15,8 @@ import java.util.Set;
|
|||||||
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
|
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
|
||||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
return calculateTaskCandidateUsers(execution, 2);
|
||||||
return calculateTaskCandidateUsers(task, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl
|
|||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||||
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -27,8 +26,8 @@ public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
|
|||||||
private BpmProcessInstanceService bpmProcessInstanceService;
|
private BpmProcessInstanceService bpmProcessInstanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
|
||||||
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
|
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||||
return SetUtils.asSet(startUserId);
|
return SetUtils.asSet(startUserId);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
* 1. Controller URL:以 /bpm/ 开头,避免和其它 Module 冲突
|
* 1. Controller URL:以 /bpm/ 开头,避免和其它 Module 冲突
|
||||||
* 2. DataObject 表名:以 bpm_ 开头,方便在数据库中区分
|
* 2. DataObject 表名:以 bpm_ 开头,方便在数据库中区分
|
||||||
*
|
*
|
||||||
* 注意,由于 Bpm 模块下,容易和其它模块重名,所以类名都加载 Pay 的前缀~
|
* 注意,由于 Bpm 模块下,容易和其它模块重名,所以类名都加载 Bpm 的前缀~
|
||||||
*/
|
*/
|
||||||
package cn.iocoder.yudao.module.bpm;
|
package cn.iocoder.yudao.module.bpm;
|
||||||
|
@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAs
|
|||||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
@ -86,6 +86,12 @@ public interface BpmTaskAssignRuleService {
|
|||||||
*/
|
*/
|
||||||
void checkTaskAssignRuleAllConfig(String id);
|
void checkTaskAssignRuleAllConfig(String id);
|
||||||
|
|
||||||
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
|
/**
|
||||||
|
* 计算当前执行任务的处理人
|
||||||
|
*
|
||||||
|
* @param execution 执行任务
|
||||||
|
* @return 处理人的编号数组
|
||||||
|
*/
|
||||||
|
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.flowable.bpmn.model.BpmnModel;
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
import org.flowable.bpmn.model.UserTask;
|
import org.flowable.bpmn.model.UserTask;
|
||||||
import org.flowable.common.engine.api.FlowableException;
|
import org.flowable.common.engine.api.FlowableException;
|
||||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@ -40,6 +40,7 @@ import javax.annotation.Resource;
|
|||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static cn.hutool.core.text.CharSequenceUtil.format;
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
@ -226,95 +227,93 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
|
|||||||
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
|
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
|
||||||
CollectionUtils.convertSet(options, String::valueOf));
|
CollectionUtils.convertSet(options, String::valueOf));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
|
throw new IllegalArgumentException(format("未知的规则类型({})", type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题
|
@DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题
|
||||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
|
||||||
BpmTaskAssignRuleDO rule = getTaskRule(task);
|
BpmTaskAssignRuleDO rule = getTaskRule(execution);
|
||||||
return calculateTaskCandidateUsers(task, rule);
|
return calculateTaskCandidateUsers(execution, rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
|
BpmTaskAssignRuleDO getTaskRule(DelegateExecution execution) {
|
||||||
List<BpmTaskAssignRuleDO> taskRules = getTaskAssignRuleListByProcessDefinitionId(
|
List<BpmTaskAssignRuleDO> taskRules = getTaskAssignRuleListByProcessDefinitionId(
|
||||||
task.getProcessDefinitionId(), task.getTaskDefinitionKey());
|
execution.getProcessDefinitionId(), execution.getCurrentActivityId());
|
||||||
if (CollUtil.isEmpty(taskRules)) {
|
if (CollUtil.isEmpty(taskRules)) {
|
||||||
throw new FlowableException(
|
throw new FlowableException(format("流程任务({}/{}/{}) 找不到符合的任务规则",
|
||||||
StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则", task.getId(), task.getProcessDefinitionId(),
|
execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
|
||||||
task.getTaskDefinitionKey()));
|
|
||||||
}
|
}
|
||||||
if (taskRules.size() > 1) {
|
if (taskRules.size() > 1) {
|
||||||
throw new FlowableException(
|
throw new FlowableException(format("流程任务({}/{}/{}) 找到过多任务规则({})",
|
||||||
StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})", task.getId(), task.getProcessDefinitionId(),
|
execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
|
||||||
task.getTaskDefinitionKey(), taskRules.size()));
|
|
||||||
}
|
}
|
||||||
return taskRules.get(0);
|
return taskRules.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
|
||||||
Set<Long> assigneeUserIds = null;
|
Set<Long> assigneeUserIds = null;
|
||||||
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
|
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByRole(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByPost(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByUser(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(rule);
|
||||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
|
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
|
||||||
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
|
assigneeUserIds = calculateTaskCandidateUsersByScript(execution, rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除被禁用的用户
|
// 移除被禁用的用户
|
||||||
removeDisableUsers(assigneeUserIds);
|
removeDisableUsers(assigneeUserIds);
|
||||||
// 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
|
// 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
|
||||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
if (CollUtil.isEmpty(assigneeUserIds)) {
|
||||||
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
|
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", execution.getId(),
|
||||||
task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
|
execution.getProcessDefinitionId(), execution.getCurrentActivityId(), toJsonString(rule));
|
||||||
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
|
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
|
||||||
}
|
}
|
||||||
return assigneeUserIds;
|
return assigneeUserIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {
|
||||||
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
|
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByDeptMember(BpmTaskAssignRuleDO rule) {
|
||||||
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
|
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
|
||||||
return convertSet(users, AdminUserRespDTO::getId);
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByDeptLeader(BpmTaskAssignRuleDO rule) {
|
||||||
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
|
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
|
||||||
return convertSet(depts, DeptRespDTO::getLeaderUserId);
|
return convertSet(depts, DeptRespDTO::getLeaderUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
|
||||||
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
|
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
|
||||||
return convertSet(users, AdminUserRespDTO::getId);
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByUser(BpmTaskAssignRuleDO rule) {
|
||||||
return rule.getOptions();
|
return rule.getOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByUserGroup(BpmTaskAssignRuleDO rule) {
|
||||||
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
|
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
|
||||||
Set<Long> userIds = new HashSet<>();
|
Set<Long> userIds = new HashSet<>();
|
||||||
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
|
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
|
||||||
return userIds;
|
return userIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
private Set<Long> calculateTaskCandidateUsersByScript(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
|
||||||
// 获得对应的脚本
|
// 获得对应的脚本
|
||||||
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
|
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
|
||||||
rule.getOptions().forEach(id -> {
|
rule.getOptions().forEach(id -> {
|
||||||
@ -326,7 +325,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
|
|||||||
});
|
});
|
||||||
// 逐个计算任务
|
// 逐个计算任务
|
||||||
Set<Long> userIds = new HashSet<>();
|
Set<Long> userIds = new HashSet<>();
|
||||||
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
|
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution)));
|
||||||
return userIds;
|
return userIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
|
|||||||
public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
|
public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
|
||||||
Map<String, Object> templateParams = new HashMap<>();
|
Map<String, Object> templateParams = new HashMap<>();
|
||||||
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
|
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
|
||||||
templateParams.put("comment", reqDTO.getComment());
|
templateParams.put("reason", reqDTO.getReason());
|
||||||
templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
|
templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
|
||||||
smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
|
smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
|
||||||
BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));
|
BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));
|
||||||
|
@ -28,6 +28,6 @@ public class BpmMessageSendWhenProcessInstanceRejectReqDTO {
|
|||||||
* 不通过理由
|
* 不通过理由
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "不通过理由不能为空")
|
@NotEmpty(message = "不通过理由不能为空")
|
||||||
private String comment;
|
private String reason;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|||||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
|
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
|
||||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
|
|
||||||
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
|
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
|
||||||
import org.flowable.engine.history.HistoricProcessInstance;
|
import org.flowable.engine.history.HistoricProcessInstance;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
@ -141,9 +140,9 @@ public interface BpmProcessInstanceService {
|
|||||||
* 更新 ProcessInstance 拓展记录为不通过
|
* 更新 ProcessInstance 拓展记录为不通过
|
||||||
*
|
*
|
||||||
* @param id 流程编号
|
* @param id 流程编号
|
||||||
* @param comment 理由。例如说,审批不通过时,需要传递该值
|
* @param reason 理由。例如说,审批不通过时,需要传递该值
|
||||||
*/
|
*/
|
||||||
void updateProcessInstanceExtReject(String id, String comment);
|
void updateProcessInstanceExtReject(String id, String reason);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -251,11 +251,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updateProcessInstanceExtReject(String id, String comment) {
|
public void updateProcessInstanceExtReject(String id, String reason) {
|
||||||
// 需要主动查询,因为 instance 只有 id 属性
|
// 需要主动查询,因为 instance 只有 id 属性
|
||||||
ProcessInstance processInstance = getProcessInstance(id);
|
ProcessInstance processInstance = getProcessInstance(id);
|
||||||
// 删除流程实例,以实现驳回任务时,取消整个审批流程
|
// 删除流程实例,以实现驳回任务时,取消整个审批流程
|
||||||
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(comment)));
|
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason)));
|
||||||
|
|
||||||
// 更新 status + result
|
// 更新 status + result
|
||||||
// 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法,
|
// 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法,
|
||||||
@ -266,7 +266,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
|
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
|
||||||
|
|
||||||
// 发送流程被不通过的消息
|
// 发送流程被不通过的消息
|
||||||
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, comment));
|
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason));
|
||||||
|
|
||||||
// 发送流程实例的状态事件
|
// 发送流程实例的状态事件
|
||||||
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
|
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
|
||||||
|
@ -186,7 +186,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));*/
|
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));*/
|
||||||
|
|
||||||
// 拼接数据
|
// 拼接数据
|
||||||
List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.listByProcInstId(processInstanceId);
|
// List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.listByProcInstId(processInstanceId);
|
||||||
|
List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.selectListByProcessInstanceId(processInstanceId);
|
||||||
|
tmpBpmTaskExtDOList.sort(Comparator.comparing(BpmTaskExtDO::getCreateTime));
|
||||||
return hiTaskInstService.taskGetComment(tmpBpmTaskExtDOList, "");
|
return hiTaskInstService.taskGetComment(tmpBpmTaskExtDOList, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,13 +209,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
// 更新任务拓展表为通过
|
// 更新任务拓展表为通过
|
||||||
taskExtMapper.updateByTaskId(
|
taskExtMapper.updateByTaskId(
|
||||||
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
|
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
|
||||||
.setComment(reqVO.getComment()));
|
.setReason(reqVO.getReason()));
|
||||||
// 判断任务是否为或签,或签时删除其余不用审批的任务
|
// 判断任务是否为或签,或签时删除其余不用审批的任务
|
||||||
List<BpmTaskAssignRuleDO> bpmTaskAssignRuleList =
|
List<BpmTaskAssignRuleDO> bpmTaskAssignRuleList =
|
||||||
taskAssignRuleMapper.selectListByProcessDefinitionId(task.getProcessDefinitionId(),
|
taskAssignRuleMapper.selectListByProcessDefinitionId(task.getProcessDefinitionId(),
|
||||||
task.getTaskDefinitionKey());
|
task.getTaskDefinitionKey());
|
||||||
if (CollUtil.isNotEmpty(bpmTaskAssignRuleList) && bpmTaskAssignRuleList.size() > 0) {
|
if (CollUtil.isNotEmpty(bpmTaskAssignRuleList) && bpmTaskAssignRuleList.size() > 0) {
|
||||||
// edit by 芋艿
|
// edit by 芋艿 TODO
|
||||||
// if (BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType().equals(bpmTaskAssignRuleList.get(0).getType())) {
|
// if (BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType().equals(bpmTaskAssignRuleList.get(0).getType())) {
|
||||||
// taskExtMapper.delTaskByProcInstIdAndTaskIdAndTaskDefKey(
|
// taskExtMapper.delTaskByProcInstIdAndTaskIdAndTaskDefKey(
|
||||||
// new BpmTaskExtDO().setTaskId(task.getId()).setTaskDefKey(task.getTaskDefinitionKey())
|
// new BpmTaskExtDO().setTaskId(task.getId()).setTaskDefKey(task.getTaskDefinitionKey())
|
||||||
@ -233,12 +235,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新流程实例为不通过
|
// 更新流程实例为不通过
|
||||||
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getComment());
|
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getReason());
|
||||||
|
|
||||||
// 更新任务拓展表为不通过
|
// 更新任务拓展表为不通过
|
||||||
taskExtMapper.updateByTaskId(
|
taskExtMapper.updateByTaskId(
|
||||||
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
|
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
|
||||||
.setComment(reqVO.getComment()));
|
.setReason(reqVO.getReason()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -269,7 +271,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
// 逻辑删除hiTaskInst表任务
|
// 逻辑删除hiTaskInst表任务
|
||||||
Boolean delHiTaskInstResult = bpmActivityMapper.delHiTaskInstByTaskId(taskIdList);
|
Boolean delHiTaskInstResult = bpmActivityMapper.delHiTaskInstByTaskId(taskIdList);
|
||||||
// 更新任务拓展表
|
// 更新任务拓展表
|
||||||
Boolean backResult = taskExtMapper.backByTaskId(reqVO.getTaskId(), reqVO.getComment());
|
Boolean backResult = taskExtMapper.backByTaskId(reqVO.getTaskId(), reqVO.getReason());
|
||||||
Boolean delTaskResult = taskExtMapper.delByTaskIds(taskIdList);
|
Boolean delTaskResult = taskExtMapper.delByTaskIds(taskIdList);
|
||||||
if (!delHiActInstResult && !delHiTaskInstResult && !backResult && !delTaskResult) {
|
if (!delHiActInstResult && !delHiTaskInstResult && !backResult && !delTaskResult) {
|
||||||
throw new RuntimeException("任务驳回失败!!!");
|
throw new RuntimeException("任务驳回失败!!!");
|
||||||
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.business.hi.task.inst.service;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
|
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
|
||||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
|
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
|
||||||
@ -67,6 +68,7 @@ public class HiTaskInstService {
|
|||||||
* @return 返回流程信息
|
* @return 返回流程信息
|
||||||
*/
|
*/
|
||||||
@TenantIgnore
|
@TenantIgnore
|
||||||
|
@DataPermission(enable = false) // TODO 芋艿:先临时去掉
|
||||||
public List<BpmTaskRespVO> taskGetComment(List<BpmTaskExtDO> taskList, Object approved) {
|
public List<BpmTaskRespVO> taskGetComment(List<BpmTaskExtDO> taskList, Object approved) {
|
||||||
BpmTaskExtDO task = taskList.get(taskList.size() - 1);
|
BpmTaskExtDO task = taskList.get(taskList.size() - 1);
|
||||||
Map<String, BpmTaskExtDO> bpmTaskMap =
|
Map<String, BpmTaskExtDO> bpmTaskMap =
|
||||||
@ -262,7 +264,7 @@ public class HiTaskInstService {
|
|||||||
DeptDO deptDO = deptMap.get(adminUserDO.getDeptId());
|
DeptDO deptDO = deptMap.get(adminUserDO.getDeptId());
|
||||||
bpmTaskRespVO.setAssigneeUser(setUser(adminUserDO));
|
bpmTaskRespVO.setAssigneeUser(setUser(adminUserDO));
|
||||||
bpmTaskRespVO.getAssigneeUser().setDeptName(deptDO.getName());
|
bpmTaskRespVO.getAssigneeUser().setDeptName(deptDO.getName());
|
||||||
// edit by 芋艿
|
// edit by 芋艿 TODO
|
||||||
// if (!bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType())
|
// if (!bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType())
|
||||||
// && !bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_SIGN.getType())) {
|
// && !bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_SIGN.getType())) {
|
||||||
// break;
|
// break;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<update id="backByTaskId">
|
<update id="backByTaskId">
|
||||||
UPDATE bpm_task_ext
|
UPDATE bpm_task_ext
|
||||||
SET result=2,
|
SET result=2,
|
||||||
`comment`=#{comment}
|
`reason`=#{reason}
|
||||||
WHERE task_id = #{taskId}
|
WHERE task_id = #{taskId}
|
||||||
</update>
|
</update>
|
||||||
<update id="delByTaskIds">
|
<update id="delByTaskIds">
|
||||||
@ -26,12 +26,5 @@
|
|||||||
</foreach>
|
</foreach>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="listByProcInstId" resultType="cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO">
|
|
||||||
SELECT *
|
|
||||||
FROM bpm_task_ext
|
|
||||||
WHERE `process_instance_id` = #{procInstId}
|
|
||||||
AND `deleted` = FALSE
|
|
||||||
ORDER BY create_time
|
|
||||||
LIMIT 500
|
|
||||||
</select>
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
Loading…
Reference in New Issue
Block a user