feat(流程管理): 新增流程相关

1.流程设计器新增可动态指定任务处理人。
2.可执行跳过第一个节点。
3.获取自定义配置验证是否需要指定动态人员、组。
approve-sys
tony 4 years ago
parent 1162897ba1
commit 0ed995e0c0

@ -0,0 +1,27 @@
package com.ruoyi.common.constant;
/**
*
* @author Xuan xuan
* @date 2021/4/17 22:46
*/
public class ProcessConstants {
/** 动态数据 */
public static final String DATA_TYPE = "dynamic";
/** 单个审批人 */
public static final String USER_TYPE_ASSIGNEE = "assignee";
/** 候选人 */
public static final String USER_TYPE_USERS = "candidateUsers";
/** 审批组 */
public static final String USER_TYPE_ROUPS = "candidateGroups";
/** 单个审批人 */
public static final String PROCESS_APPROVAL = "approval";
}

@ -0,0 +1,22 @@
package com.ruoyi.flowable.domain.dto;
import com.ruoyi.common.core.domain.entity.SysUser;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
*
* @author Xuan xuan
* @date 2021/4/17 22:59
*/
@Data
public class FlowNextDto implements Serializable {
private String type;
private String vars;
private List<SysUser> userList;
}

@ -1,6 +1,7 @@
package com.ruoyi.flowable.domain.dto; package com.ruoyi.flowable.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.domain.entity.SysUser;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Getter; import lombok.Getter;

@ -151,8 +151,11 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl
@Override @Override
public AjaxResult startProcessInstanceById(String procDefId, Map<String, Object> variables) { public AjaxResult startProcessInstanceById(String procDefId, Map<String, Object> variables) {
try { try {
// 设置流程发起人Id到流程中
Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
identityService.setAuthenticatedUserId(userId.toString()); identityService.setAuthenticatedUserId(userId.toString());
variables.put("initiator",userId);
variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true);
runtimeService.startProcessInstanceById(procDefId, variables); runtimeService.startProcessInstanceById(procDefId, variables);
return AjaxResult.success("流程启动成功"); return AjaxResult.success("流程启动成功");
} catch (Exception e) { } catch (Exception e) {

@ -5,12 +5,14 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.ruoyi.common.constant.ProcessConstants;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.CustomException; import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.flowable.domain.dto.FlowCommentDto; import com.ruoyi.flowable.domain.dto.FlowCommentDto;
import com.ruoyi.flowable.domain.dto.FlowNextDto;
import com.ruoyi.flowable.domain.dto.FlowTaskDto; import com.ruoyi.flowable.domain.dto.FlowTaskDto;
import com.ruoyi.flowable.domain.vo.FlowTaskVo; import com.ruoyi.flowable.domain.vo.FlowTaskVo;
import com.ruoyi.flowable.factory.FlowServiceFactory; import com.ruoyi.flowable.factory.FlowServiceFactory;
@ -23,6 +25,7 @@ import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
@ -86,34 +89,23 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
taskService.addComment(task.getTaskId(), task.getInstanceId(), "1", task.getComment()); taskService.addComment(task.getTaskId(), task.getInstanceId(), "1", task.getComment());
} }
Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
// 变量信息 // 读取变量 动态设置下一环节审批人
if (task.getValues() != null && task.getValues().size() > 0) { // Map<String, Object> taskValues = task.getValues();
Map<String, Object> map = (Map<String, Object>) task.getValues().get("mainTable"); // if (taskValues != null && taskValues.size() > 0) {
map.put("FormOrigData" + task.getTaskId(), JSON.toJSONString(task.getValues())); // if(StringUtils.isNotBlank((taskValues.get("assignee").toString()))){
// List<TaskDto> list = getTaskFormList(task.getTaskId()); // taskService.ad
// // 节点上挂载的表单ID
// if (!CollectionUtils.isEmpty(list)) {
// List<Object> formList = new ArrayList<>();
// for (TaskDto taskDto : list) {
// formList.addAll(taskDto.getFormList());
// } // }
// if(CollectionUtils.isNotEmpty((List<String>) taskValues.get("candidateUsers"))){
//
// }
// if(CollectionUtils.isNotEmpty((List<String>) taskValues.get("candidateGroups"))){
// //
// List<String> strs = new ArrayList<>();
// for (int k = 0; k < formList.size(); k++) {
// Map<String, Object> map1 = (Map<String, Object>) formList.get(k);
// strs.add(map1.get("id").toString());
// } // }
// } // }
taskService.setVariables(task.getTaskId(), map);
}
// 设置任务审批人员 // 设置任务审批人员
taskService.setAssignee(task.getTaskId(), userId.toString()); taskService.setAssignee(task.getTaskId(), userId.toString());
// todo : 这种方式无法动态设置任务接收人
// if (StringUtils.isNotBlank(task.getAssignee())) {
// taskService.addCandidateUser(task.getTaskId(), task.getAssignee());
// }
// 提交任务 // 提交任务
taskService.complete(task.getTaskId()); taskService.complete(task.getTaskId(),task.getValues());
} }
@ -617,6 +609,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
.processInstanceId(task.getProcessInstanceId()) .processInstanceId(task.getProcessInstanceId())
.singleResult(); .singleResult();
SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
// SysUser startUser = sysUserService.selectUserById(Long.parseLong(task.getAssignee()));
flowTask.setStartUserId(startUser.getNickName()); flowTask.setStartUserId(startUser.getNickName());
flowTask.setStartUserName(startUser.getNickName()); flowTask.setStartUserName(startUser.getNickName());
flowTask.setStartDeptName(startUser.getDept().getDeptName()); flowTask.setStartDeptName(startUser.getDept().getDeptName());
@ -757,6 +750,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
map.put("finished", false); map.put("finished", false);
} }
} }
// 第一次申请获取初始化表单
if (StringUtils.isNotBlank(deployId)) { if (StringUtils.isNotBlank(deployId)) {
SysForm sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); SysForm sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId);
map.put("formData", JSONObject.parseObject(sysForm.getFormContent())); map.put("formData", JSONObject.parseObject(sysForm.getFormContent()));
@ -852,7 +846,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
@Override @Override
public AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo) { public AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo) {
Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult();
List<FlowTaskDto> nextNodeList = new ArrayList<>(); FlowNextDto flowNextDto = new FlowNextDto();
if (Objects.nonNull(task)) { if (Objects.nonNull(task)) {
ExecutionEntity ee = (ExecutionEntity) runtimeService.createExecutionQuery() ExecutionEntity ee = (ExecutionEntity) runtimeService.createExecutionQuery()
.executionId(task.getExecutionId()).singleResult(); .executionId(task.getExecutionId()).singleResult();
@ -862,26 +856,34 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(crruentActivityId); FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(crruentActivityId);
// 输出连线 // 输出连线
List<SequenceFlow> outFlows = flowNode.getOutgoingFlows(); List<SequenceFlow> outFlows = flowNode.getOutgoingFlows();
for (SequenceFlow sequenceFlow : outFlows) { for (SequenceFlow sequenceFlow : outFlows) {
// 下一个审userTask // 下一个审userTask
FlowElement targetFlow = sequenceFlow.getTargetFlowElement(); FlowElement targetFlow = sequenceFlow.getTargetFlowElement();
if (targetFlow instanceof UserTask) { if (targetFlow instanceof UserTask) {
FlowTaskDto flowTaskDto = new FlowTaskDto();
// 当流程设计时未指定任务接受人员/组时 判定为用户动态选择下一任务审批人 // 当流程设计时未指定任务接受人员/组时 判定为用户动态选择下一任务审批人
// todo 1. 读取自定义节点属性来验证是否动态选择审批人 // todo 1. 读取自定义节点属性来验证是否动态选择审批人
// 2. 验证表达式 // 2. 验证表达式
if (StringUtils.isBlank(((UserTask) targetFlow).getAssignee()) && String dataType = targetFlow.getAttributeValue("http://flowable.org/bpmn", "dataType");
CollectionUtils.isEmpty(((UserTask) targetFlow).getCandidateGroups()) && String userType = targetFlow.getAttributeValue("http://flowable.org/bpmn", "userType");
CollectionUtils.isEmpty(((UserTask) targetFlow).getCandidateUsers())) { // Map<String, List<ExtensionAttribute>> attributes = targetFlow.getAttributes();
flowTaskDto.setTaskDefKey(targetFlow.getId()); // List<ExtensionAttribute> extensionAttributes = attributes.get("dataType");
flowTaskDto.setTaskName(targetFlow.getName()); // for (ExtensionAttribute attribute : extensionAttributes) {
nextNodeList.add(flowTaskDto); // String value = attribute.getValue();
// }
if (ProcessConstants.DATA_TYPE.equals(dataType)){
if (ProcessConstants.USER_TYPE_ASSIGNEE.equals(userType)){
List<SysUser> list = sysUserService.selectUserList(new SysUser());
flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL);
flowNextDto.setType(ProcessConstants.USER_TYPE_ASSIGNEE);
flowNextDto.setUserList(list);
}
} }
} }
} }
} }
return AjaxResult.success(nextNodeList); return AjaxResult.success(flowNextDto);
} }
/** /**

@ -68,6 +68,10 @@ export default {
{ label: '候选人员', value: 'candidateUsers' }, { label: '候选人员', value: 'candidateUsers' },
{ label: '候选组', value: 'candidateGroups' } { label: '候选组', value: 'candidateGroups' }
], ],
dataTypeOption: [
{ label: '固定', value: 'fixed' },
{ label: '动态', value: 'dynamic' }
],
dialogName: '', dialogName: '',
executionListenerLength: 0, executionListenerLength: 0,
taskListenerLength: 0, taskListenerLength: 0,
@ -116,6 +120,31 @@ export default {
dic: _this.userTypeOption, dic: _this.userTypeOption,
show: !!_this.showConfig.userType show: !!_this.showConfig.userType
}, },
{
xType: 'radio',
name: 'dataType',
label: '指定方式',
dic: _this.dataTypeOption,
show: !!_this.showConfig.dataType
},
// {
// xType: 'input',
// name: 'assigneeFixed',
// label: '()',
// show: !!_this.showConfig.assigneeFixed && _this.formData.userType === 'assignee' && _this.formData.dataType === 'fixed'
// },
// {
// xType: 'input',
// name: 'candidateUsersFixed',
// label: '()',
// show: !!_this.showConfig.candidateUsersFixed && _this.formData.userType === 'candidateUsers' && _this.formData.dataType === 'fixed'
// },
// {
// xType: 'input',
// name: 'candidateGroupsFixed',
// label: '()',
// show: !!_this.showConfig.candidateGroupsFixed && _this.formData.userType === 'candidateGroups' && _this.formData.dataType === 'fixed'
// },
{ {
xType: 'select', xType: 'select',
name: 'assignee', name: 'assignee',
@ -253,6 +282,15 @@ export default {
}) })
} }
}, },
//
'formData.dataType': function(val) {
const that = this
this.updateProperties({'flowable:dataType': val})
if (val === 'dynamic') {
debugger
this.updateProperties({'flowable:userType': that.formData.userType})
}
},
'formData.assignee': function(val) { 'formData.assignee': function(val) {
if (this.formData.userType !== 'assignee') { if (this.formData.userType !== 'assignee') {
delete this.element.businessObject.$attrs[`flowable:assignee`] delete this.element.businessObject.$attrs[`flowable:assignee`]

@ -6,9 +6,13 @@ export default {
}, },
'bpmn:UserTask': { 'bpmn:UserTask': {
userType: true, userType: true,
dataType: true,
assignee: true, assignee: true,
candidateUsers: true, candidateUsers: true,
candidateGroups: true, candidateGroups: true,
// assigneeFixed: true,
// candidateUsersFixed: true,
// candidateGroupsFixed: true,
async: true, async: true,
priority: true, priority: true,
formKey: true, formKey: true,

@ -29,6 +29,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<div> <div>
<el-button size="mini" icon="el-icon-download" @click="showXML()">xml</el-button>
<el-button size="mini" icon="el-icon-download" @click="saveXML(true)">xml</el-button> <el-button size="mini" icon="el-icon-download" @click="saveXML(true)">xml</el-button>
<el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button> <el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button>
<el-button size="mini" type="primary" @click="save"></el-button> <el-button size="mini" type="primary" @click="save"></el-button>
@ -44,7 +45,9 @@
</el-aside> </el-aside>
</el-container> </el-container>
</el-container> </el-container>
<el-dialog :title="xmlTitle" :visible.sync="xmlOpen" width="50%" append-to-body>
<div v-html="xmlContent" />
</el-dialog>
</div> </div>
</template> </template>
@ -67,6 +70,9 @@ export default {
type: String, type: String,
default: '' default: ''
}, },
xmlTitle: "",
xmlOpen: false,
xmlContent: "",
users: { users: {
type: Array, type: Array,
default: () => [] default: () => []
@ -287,6 +293,17 @@ export default {
console.log(err) console.log(err)
} }
}, },
async showXML() {
try {
const { xml } = await this.modeler.saveXML({ format: true })
this.xmlOpen = true;
this.xmlTitle = 'xml查看';
debugger
// this.xmlContent = this.parseXml(xml);
} catch (err) {
console.log(err)
}
},
async saveImg(type = 'svg', download = false) { async saveImg(type = 'svg', download = false) {
try { try {
const { svg } = await this.modeler.saveSVG({ format: true }) const { svg } = await this.modeler.saveSVG({ format: true })
@ -321,6 +338,50 @@ export default {
a.download = filename a.download = filename
a.click() a.click()
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
},
parseXml(content) {
let xml_doc = null
try {
xml_doc = (new DOMParser()).parseFromString(content.replace(/[\n\r\s]/g, ''), 'text/xml')
} catch (e) {
return false
}
let flag = 0
function build_xml(index, list, element) {
let t = []
for (let i = 0; i < flag; i++) {
t.push('&nbsp;&nbsp;&nbsp;&nbsp;')
}
t = t.join('')
list.push(t + '&lt;<span class="code-key">' + element.nodeName + '</span>&gt;<br/>')
for (let i = 0; i < element.childNodes.length; i++) {
const nodeName = element.childNodes[i].nodeName
if (element.childNodes[i].childNodes.length === 0) {
const value_txt = ''
const item = t + '&nbsp;&nbsp;&nbsp;&nbsp;&lt;<span class="code-key">' + nodeName +
'</span>&gt;' + value_txt + '&lt;/<span class="code-key">' + nodeName + '</span>&gt;<br/>'
list.push(item)
} else if ((element.childNodes[i].childNodes.length === 1 && element.childNodes[i].childNodes[0].nodeValue != null)) {
const value = element.childNodes[i].childNodes[0].nodeValue
const value_color = !isNaN(Number(value)) ? 'code-number' : 'code-string'
const value_txt = '<span class="' + value_color + '">' + value + '</span>'
const item = t + '&nbsp;&nbsp;&nbsp;&nbsp;&lt;<span class="code-key">' + nodeName +
'</span>&gt;' + value_txt + '&lt;/<span class="code-key">' + nodeName + '</span>&gt;<br/>'
list.push(item)
} else {
flag++
build_xml(++index, list, element.childNodes[i])
flag--
}
}
list.push(t + '&lt;/<span class="code-key">' + element.nodeName + '</span>&gt;<BR/>')
}
const list = []
build_xml(0, list, xml_doc.documentElement)
return list.join('')
} }
} }
} }
@ -374,6 +435,12 @@ export default {
min-height: 650px; min-height: 650px;
} }
.code-string{color:#993300;}
.code-number{color:#cc00cc;}
.code-boolean{color:#000033;}
.code-null{color:magenta;}
.code-key{color:#003377;font-weight:bold;}
// .highlight.djs-shape .djs-visual > :nth-child(1) { // .highlight.djs-shape .djs-visual > :nth-child(1) {
// fill: green !important; // fill: green !important;
// stroke: green !important; // stroke: green !important;

@ -404,8 +404,6 @@ export default {
}, },
/** 挂载表单 */ /** 挂载表单 */
submitFormDeploy(){ submitFormDeploy(){
debugger
console.log(this.formDeployParam)
addDeployForm(this.formDeployParam).then(res =>{ addDeployForm(this.formDeployParam).then(res =>{
this.msgSuccess(res.msg); this.msgSuccess(res.msg);
this.formDeployOpen = false; this.formDeployOpen = false;

@ -14,7 +14,6 @@
<script> <script>
import { readXml, saveXml, userList } from "@/api/flowable/definition"; import { readXml, saveXml, userList } from "@/api/flowable/definition";
// import bpmnModeler from "workflow-bpmn-modeler";
import bpmnModeler from '@/components/Process/index' import bpmnModeler from '@/components/Process/index'
export default { export default {
@ -26,7 +25,8 @@ export default {
return { return {
xml: "", // xml xml: "", // xml
users: [ users: [
// { name: "", id: "1" }, { nickName: "#{initiator}", userId: "#{initiator}" },
{nickName: "#{approval}", userId: "#{approval}"}
// { name: "", id: "2" }, // { name: "", id: "2" },
// { name: "", id: "100" }, // { name: "", id: "100" },
], ],
@ -78,7 +78,13 @@ export default {
// //
// } // }
userList().then(res =>{ userList().then(res =>{
this.users = res.data res.data.forEach(val =>{
let obj = {};
obj.userId = val.userId;
obj.nickName = val.nickName;
this.users.push(obj)
})
debugger
}) })
}, },
}, },

@ -239,9 +239,6 @@ export default {
this.total = response.data.total; this.total = response.data.total;
this.loading = false; this.loading = false;
}); });
// readXml("fc8504de-9063-11eb-b009-00e04c1b045a").then(res =>{
// this.xmlData = res.data
// });
}, },
// //
cancel() { cancel() {

@ -140,7 +140,8 @@ export default {
deployId: "", // deployId: "", //
taskId: "" ,// taskId: "" ,//
procDefId: "", // procDefId: "", //
assignee: null assignee: null,
vars: "",
}, },
userList:[], // userList:[], //
formConf: {}, // formConf: {}, //
@ -234,11 +235,9 @@ export default {
taskId: taskId taskId: taskId
} }
getNextFlowNode(params).then(res => { getNextFlowNode(params).then(res => {
if (res.data){ if (res.data.userList){
userList().then(res =>{ this.userList = res.data.userList;
this.userList = res.data;
this.taskForm.noUserShow = true; this.taskForm.noUserShow = true;
})
} }
}) })
} }
@ -247,6 +246,10 @@ export default {
handleComplete() { handleComplete() {
this.$refs["taskForm"].validate(valid => { this.$refs["taskForm"].validate(valid => {
if (valid) { if (valid) {
let values = {
"approval": this.taskForm.assignee
}
this.taskForm.values =values
complete(this.taskForm).then(response => { complete(this.taskForm).then(response => {
this.msgSuccess(response.msg); this.msgSuccess(response.msg);
this.goBack(); this.goBack();

Loading…
Cancel
Save