diff --git a/pom.xml b/pom.xml index 1ce5af976..ba9ced2d7 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,9 @@ 3.11.0 3.1.2 1.3.0 + + + 7.0.0 @@ -111,6 +114,14 @@ import + + org.flowable + flowable-bom + ${flowable.version} + pom + import + + me.zhyd.oauth @@ -353,6 +364,13 @@ ${revision} + + + org.dromara + ruoyi-workflow + ${revision} + + diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 26cd02333..cbf03ce36 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -75,6 +75,12 @@ ruoyi-demo + + + org.dromara + ruoyi-workflow + + de.codecentric spring-boot-admin-starter-client diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 847e4c2dd..5589e5864 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -43,7 +43,7 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: root # 从库数据源 @@ -51,7 +51,7 @@ spring: lazy: true type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: password: # oracle: diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index c8817aae6..b4f4040bd 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -46,7 +46,7 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: root # 从库数据源 @@ -54,7 +54,7 @@ spring: lazy: true type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: password: # oracle: diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 0d334a2d4..a3ea740be 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -266,3 +266,21 @@ websocket: path: /resource/websocket # 设置访问源地址 allowedOrigins: '*' + +--- #flowable配置 +flowable: + async-executor-activate: false #关闭定时任务JOB + # 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。 + database-schema-update: true + activity-font-name: 宋体 + label-font-name: 宋体 + annotation-font-name: 宋体 + # 关闭各个模块生成表,目前只使用工作流基础表 + idm: + enabled: false + cmmn: + enabled: false + dmn: + enabled: false + app: + enabled: false diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 4044916fb..daff497a0 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -14,6 +14,7 @@ ruoyi-generator ruoyi-job ruoyi-system + ruoyi-workflow ruoyi-modules diff --git a/ruoyi-modules/ruoyi-workflow/pom.xml b/ruoyi-modules/ruoyi-workflow/pom.xml new file mode 100644 index 000000000..f0391e358 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/pom.xml @@ -0,0 +1,93 @@ + + + + org.dromara + ruoyi-modules + ${revision} + ../pom.xml + + 4.0.0 + jar + ruoyi-workflow + + + 工作流模块 + + + + + + + org.flowable + flowable-spring-boot-autoconfigure + + + org.flowable + flowable-spring-security + + + + + + org.flowable + flowable-spring-configurator + + + + org.flowable + flowable-spring-boot-starter-actuator + + + + + org.flowable + flowable-image-generator + + + + + org.flowable + flowable-json-converter + 6.8.0 + + + + + org.apache.xmlgraphics + batik-all + 1.10 + + + xalan + xalan + + + + + + + org.dromara + ruoyi-system + + + + org.dromara + ruoyi-common-websocket + + + + org.dromara + ruoyi-common-mail + + + + org.dromara + ruoyi-common-sms + + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java new file mode 100644 index 000000000..5ea262d17 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java @@ -0,0 +1,27 @@ +package org.dromara.workflow.annotation; + + +import java.lang.annotation.*; + +/** + * 流程任务监听注解 + * + * @author may + * @date 2023-12-27 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface FlowListenerAnnotation { + + /** + * 流程定义key + */ + String processDefinitionKey(); + + /** + * 节点id + */ + String taskDefId() default ""; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java new file mode 100644 index 000000000..cc8aaddd8 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java @@ -0,0 +1,28 @@ +package org.dromara.workflow.common; + + +import lombok.Data; + +/** + * 分页参数 + * + * @author may + */ +@Data +public class PageEntity { + + /** + * 当前页码 + */ + private Integer pageNum = 0; + + /** + * 页容量 + */ + private Integer pageSize = 10; + + public Integer getPageNum() { + return (pageNum - 1) * pageSize; + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java new file mode 100644 index 000000000..614f9fffa --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java @@ -0,0 +1,92 @@ +package org.dromara.workflow.common.constant; + + +/** + * 工作流常量 + * + * @author may + */ +public interface FlowConstant { + + String MESSAGE_CURRENT_TASK_IS_NULL = "当前任务不存在或你不是任务办理人!"; + + String MESSAGE_SUSPENDED = "当前任务已挂起不可审批!"; + + /** + * 连线 + */ + String SEQUENCE_FLOW = "sequenceFlow"; + + /** + * 并行网关 + */ + String PARALLEL_GATEWAY = "parallelGateway"; + + /** + * 排它网关 + */ + String EXCLUSIVE_GATEWAY = "exclusiveGateway"; + + /** + * 包含网关 + */ + String INCLUSIVE_GATEWAY = "inclusiveGateway"; + + /** + * 结束节点 + */ + String END_EVENT = "endEvent"; + + + /** + * 流程委派标识 + */ + String PENDING = "PENDING"; + + /** + * 候选人标识 + */ + String CANDIDATE = "candidate"; + + /** + * 会签任务总数 + */ + String NUMBER_OF_INSTANCES = "nrOfInstances"; + + /** + * 正在执行的会签总数 + */ + String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances"; + + /** + * 已完成的会签任务总数 + */ + String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances"; + + /** + * 循环的索引值,可以使用elementIndexVariable属性修改loopCounter的变量名 + */ + String LOOP_COUNTER = "loopCounter"; + + String ZIP = "ZIP"; + + /** + * 流程实例对象 + */ + String PROCESS_INSTANCE_VO = "processInstanceVo"; + + /** + * 流程发起人 + */ + String INITIATOR = "initiator"; + + /** + * 开启跳过表达式变量 + */ + String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; + + /** + * 模型标识key命名规范正则表达式 + */ + String MODEL_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9_]{0,254}$"; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java new file mode 100644 index 000000000..43204f542 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java @@ -0,0 +1,93 @@ +package org.dromara.workflow.common.enums; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; + +import java.util.Arrays; + +/** + * 业务状态枚举 + * + * @author may + */ +@Getter +@AllArgsConstructor +public enum BusinessStatusEnum { + /** + * 已撤销 + */ + CANCEL("cancel", "已撤销"), + /** + * 草稿 + */ + DRAFT("draft", "草稿"), + /** + * 待审核 + */ + WAITING("waiting", "待审核"), + /** + * 已完成 + */ + FINISH("finish", "已完成"), + /** + * 已作废 + */ + INVALID("invalid", "已作废"), + /** + * 已退回 + */ + BACK("back", "已退回"), + /** + * 已终止 + */ + TERMINATION("termination", "已终止"); + + /** + * 状态 + */ + private final String status; + + /** + * 描述 + */ + private final String desc; + + /** + * 获取业务状态 + * + * @param status 状态 + */ + public static String findByStatus(String status) { + if (StringUtils.isBlank(status)) { + return StrUtil.EMPTY; + } + return Arrays.stream(BusinessStatusEnum.values()) + .filter(statusEnum -> statusEnum.getStatus().equals(status)) + .findFirst() + .map(BusinessStatusEnum::getDesc) + .orElse(StrUtil.EMPTY); + } + + /** + * 启动流程校验 + * + * @param status 状态 + */ + public static void checkStartStatus(String status) { + if (WAITING.getStatus().equals(status)) { + throw new ServiceException("该单据已提交过申请,正在审批中!"); + } else if (FINISH.getStatus().equals(status)) { + throw new ServiceException("该单据已完成申请!"); + } else if (INVALID.getStatus().equals(status)) { + throw new ServiceException("该单据已作废!"); + } else if (TERMINATION.getStatus().equals(status)) { + throw new ServiceException("该单据已终止!"); + } else if (StringUtils.isBlank(status)) { + throw new ServiceException("流程状态为空!"); + } + } +} + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java new file mode 100644 index 000000000..d7ba1bf01 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java @@ -0,0 +1,31 @@ +package org.dromara.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 消息类型枚举 + * + * @author may + */ +@Getter +@AllArgsConstructor +public enum MessageTypeEnum { + /** + * 站内信 + */ + SYSTEM_MESSAGE("1", "站内信"), + /** + * 邮箱 + */ + EMAIL_MESSAGE("2", "邮箱"), + /** + * 短信 + */ + SMS_MESSAGE("3", "短信"); + + private final String code; + + private final String desc; +} + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java new file mode 100644 index 000000000..03be8dc53 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java @@ -0,0 +1,90 @@ +package org.dromara.workflow.common.enums; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +/** + * 任务状态枚举 + * + * @author may + */ +@Getter +@AllArgsConstructor +public enum TaskStatusEnum { + /** + * 撤销 + */ + CANCEL("cancel", "撤销"), + /** + * 通过 + */ + PASS("pass", "通过"), + /** + * 待审核 + */ + WAITING("waiting", "待审核"), + /** + * 作废 + */ + INVALID("invalid", "作废"), + /** + * 退回 + */ + BACK("back", "退回"), + /** + * 终止 + */ + TERMINATION("termination", "终止"), + /** + * 转办 + */ + TRANSFER("transfer", "转办"), + /** + * 委托 + */ + PENDING("pending", "委托"), + /** + * 抄送 + */ + COPY("copy", "抄送"), + /** + * 加签 + */ + SIGN("sign", "加签"), + /** + * 减签 + */ + SIGN_OFF("sign_off", "减签"); + + /** + * 状态 + */ + private final String status; + + /** + * 描述 + */ + private final String desc; + + /** + * 任务业务状态 + * + * @param status 状态 + */ + public static String findByStatus(String status) { + if (StringUtils.isBlank(status)) { + return StrUtil.EMPTY; + } + + return Arrays.stream(TaskStatusEnum.values()) + .filter(statusEnum -> statusEnum.getStatus().equals(status)) + .findFirst() + .map(TaskStatusEnum::getDesc) + .orElse(StrUtil.EMPTY); + } +} + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java new file mode 100644 index 000000000..31e31e6eb --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java @@ -0,0 +1,135 @@ +package org.dromara.workflow.controller; + +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.bo.ModelBo; +import org.dromara.workflow.domain.vo.ModelVo; +import org.dromara.workflow.service.IActModelService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Model; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; + +/** + * 模型管理 控制层 + * + * @author may + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/model") +public class ActModelController extends BaseController { + + private final RepositoryService repositoryService; + + private final IActModelService actModelService; + + + /** + * 分页查询模型 + * + * @param modelBo 模型参数 + */ + @GetMapping("/list") + public TableDataInfo page(ModelBo modelBo) { + return actModelService.page(modelBo); + } + + /** + * 新增模型 + * + * @param modelBo 模型请求对象 + */ + @Log(title = "模型管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/save") + public R saveNewModel(@Validated(AddGroup.class) @RequestBody ModelBo modelBo) { + return toAjax(actModelService.saveNewModel(modelBo)); + } + + /** + * 查询模型 + * + * @param id 模型id + */ + @GetMapping("/getInfo/{id}") + public R getInfo(@NotBlank(message = "模型id不能为空") @PathVariable String id) { + return R.ok(actModelService.getInfo(id)); + } + + /** + * 修改模型信息 + * + * @param modelBo 模型数据 + */ + @Log(title = "模型管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping(value = "/update") + public R update(@RequestBody ModelBo modelBo) { + return toAjax(actModelService.update(modelBo)); + } + + /** + * 编辑XMl模型 + * + * @param modelBo 模型数据 + */ + @Log(title = "模型管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping(value = "/editModelXml") + public R editModel(@Validated(EditGroup.class) @RequestBody ModelBo modelBo) { + return toAjax(actModelService.editModelXml(modelBo)); + } + + /** + * 删除流程模型 + * + * @param ids 模型id + */ + @Log(title = "模型管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @DeleteMapping("/{ids}") + @Transactional(rollbackFor = Exception.class) + public R delete(@NotEmpty(message = "主键不能为空") @PathVariable String[] ids) { + Arrays.stream(ids).parallel().forEachOrdered(repositoryService::deleteModel); + return R.ok(); + } + + /** + * 模型部署 + * + * @param id 模型id + */ + @Log(title = "模型管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/modelDeploy/{id}") + public R deploy(@NotBlank(message = "模型id不能为空") @PathVariable("id") String id) { + return toAjax(actModelService.modelDeploy(id)); + } + + /** + * 导出模型zip压缩包 + * + * @param modelId 模型id + * @param response 相应 + */ + @GetMapping("/export/zip/{modelId}") + public void exportZip(@NotEmpty(message = "模型id不能为空") @PathVariable String modelId, + HttpServletResponse response) { + actModelService.exportZip(modelId, response); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java new file mode 100644 index 000000000..a19cba152 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java @@ -0,0 +1,146 @@ +package org.dromara.workflow.controller; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.bo.ProcessDefinitionBo; +import org.dromara.workflow.domain.vo.ProcessDefinitionVo; +import org.dromara.workflow.service.IActProcessDefinitionService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * 流程定义管理 控制层 + * + * @author may + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/processDefinition") +public class ActProcessDefinitionController extends BaseController { + + private final IActProcessDefinitionService actProcessDefinitionService; + + /** + * 分页查询 + * + * @param processDefinitionBo 参数 + */ + @GetMapping("/list") + public TableDataInfo page(ProcessDefinitionBo processDefinitionBo) { + return actProcessDefinitionService.page(processDefinitionBo); + } + + /** + * 查询历史流程定义列表 + * + * @param key 流程定义key + */ + @GetMapping("/getProcessDefinitionListByKey/{key}") + public R> getProcessDefinitionListByKey(@NotEmpty(message = "流程定义key不能为空") @PathVariable String key) { + return R.ok("操作成功", actProcessDefinitionService.getProcessDefinitionListByKey(key)); + } + + /** + * 查看流程定义图片 + * + * @param processDefinitionId 流程定义id + */ + @GetMapping("/processDefinitionImage/{processDefinitionId}") + public R processDefinitionImage(@PathVariable String processDefinitionId) { + return R.ok("操作成功", actProcessDefinitionService.processDefinitionImage(processDefinitionId)); + } + + /** + * 查看流程定义xml文件 + * + * @param processDefinitionId 流程定义id + */ + @GetMapping("/processDefinitionXml/{processDefinitionId}") + public R> getXml(@NotBlank(message = "流程定义id不能为空") @PathVariable String processDefinitionId) { + Map map = new HashMap<>(); + String xmlStr = actProcessDefinitionService.processDefinitionXml(processDefinitionId); + map.put("xml", Arrays.asList(xmlStr.split("\n"))); + map.put("xmlStr", xmlStr); + return R.ok(map); + } + + /** + * 删除流程定义 + * + * @param deploymentId 部署id + * @param processDefinitionId 流程定义id + */ + @Log(title = "流程定义管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @DeleteMapping("/{deploymentId}/{processDefinitionId}") + public R deleteDeployment(@NotBlank(message = "流程部署id不能为空") @PathVariable String deploymentId, + @NotBlank(message = "流程定义id不能为空") @PathVariable String processDefinitionId) { + return toAjax(actProcessDefinitionService.deleteDeployment(deploymentId, processDefinitionId)); + } + + /** + * 激活或者挂起流程定义 + * + * @param processDefinitionId 流程定义id + */ + @Log(title = "流程定义管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/updateProcessDefState/{processDefinitionId}") + public R updateProcDefState(@NotBlank(message = "流程定义id不能为空") @PathVariable String processDefinitionId) { + return toAjax(actProcessDefinitionService.updateProcessDefState(processDefinitionId)); + } + + /** + * 迁移流程定义 + * + * @param currentProcessDefinitionId 当前流程定义id + * @param fromProcessDefinitionId 需要迁移到的流程定义id + */ + @Log(title = "流程定义管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/migrationProcessDefinition/{currentProcessDefinitionId}/{fromProcessDefinitionId}") + public R migrationProcessDefinition(@NotBlank(message = "当前流程定义id") @PathVariable String currentProcessDefinitionId, + @NotBlank(message = "需要迁移到的流程定义id") @PathVariable String fromProcessDefinitionId) { + return toAjax(actProcessDefinitionService.migrationProcessDefinition(currentProcessDefinitionId, fromProcessDefinitionId)); + } + + /** + * 流程定义转换为模型 + * + * @param processDefinitionId 流程定义id + */ + @Log(title = "流程定义管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/convertToModel/{processDefinitionId}") + public R convertToModel(@NotEmpty(message = "流程定义id不能为空") @PathVariable String processDefinitionId) { + return toAjax(actProcessDefinitionService.convertToModel(processDefinitionId)); + } + + /** + * 通过zip或xml部署流程定义 + * + * @param file 文件 + * @param categoryCode 分类 + */ + @Log(title = "流程定义管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/deployByFile") + public R deployByFile(@RequestParam("file") MultipartFile file, @RequestParam("categoryCode") String categoryCode) { + return toAjax(actProcessDefinitionService.deployByFile(file, categoryCode)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java new file mode 100644 index 000000000..cd2c9e2d6 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java @@ -0,0 +1,157 @@ +package org.dromara.workflow.controller; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.bo.ProcessInstanceBo; +import org.dromara.workflow.domain.bo.ProcessInvalidBo; +import org.dromara.workflow.domain.bo.TaskUrgingBo; +import org.dromara.workflow.domain.vo.ProcessInstanceVo; +import org.dromara.workflow.service.IActProcessInstanceService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.Map; + +/** + * 流程实例管理 控制层 + * + * @author may + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/processInstance") +public class ActProcessInstanceController extends BaseController { + + private final IActProcessInstanceService actProcessInstanceService; + + /** + * 分页查询正在运行的流程实例 + * + * @param processInstanceBo 参数 + */ + @GetMapping("/getProcessInstanceRunningByPage") + public TableDataInfo getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo) { + return actProcessInstanceService.getProcessInstanceRunningByPage(processInstanceBo); + } + + /** + * 分页查询已结束的流程实例 + * + * @param processInstanceBo 参数 + */ + @GetMapping("/getProcessInstanceFinishByPage") + public TableDataInfo getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo) { + return actProcessInstanceService.getProcessInstanceFinishByPage(processInstanceBo); + } + + /** + * 通过流程实例id获取历史流程图 + * + * @param processInstanceId 流程实例id + */ + @GetMapping("/getHistoryProcessImage/{processInstanceId}") + public R getHistoryProcessImage(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) { + return R.ok("操作成功", actProcessInstanceService.getHistoryProcessImage(processInstanceId)); + } + + /** + * 通过流程实例id获取历史流程图运行中,历史等节点 + * + * @param processInstanceId 流程实例id + */ + @GetMapping("/getHistoryProcessList/{processInstanceId}") + public R> getHistoryProcessList(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) { + return R.ok("操作成功", actProcessInstanceService.getHistoryProcessList(processInstanceId)); + } + + /** + * 获取审批记录 + * + * @param processInstanceId 流程实例id + */ + @GetMapping("/getHistoryRecord/{processInstanceId}") + public R> getHistoryRecord(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) { + return R.ok(actProcessInstanceService.getHistoryRecord(processInstanceId)); + } + + /** + * 作废流程实例,不会删除历史记录(删除运行中的实例) + * + * @param processInvalidBo 参数 + */ + @Log(title = "流程实例管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @PostMapping("/deleteRuntimeProcessInst") + public R deleteRuntimeProcessInst(@Validated(AddGroup.class) @RequestBody ProcessInvalidBo processInvalidBo) { + return toAjax(actProcessInstanceService.deleteRuntimeProcessInst(processInvalidBo)); + } + + /** + * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + */ + @Log(title = "流程实例管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @DeleteMapping("/deleteRuntimeProcessAndHisInst/{processInstanceIds}") + public R deleteRuntimeProcessAndHisInst(@NotNull(message = "流程实例id不能为空") @PathVariable String[] processInstanceIds) { + return toAjax(actProcessInstanceService.deleteRuntimeProcessAndHisInst(Arrays.asList(processInstanceIds))); + } + + /** + * 已完成的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + */ + @Log(title = "流程实例管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @DeleteMapping("/deleteFinishProcessAndHisInst/{processInstanceIds}") + public R deleteFinishProcessAndHisInst(@NotNull(message = "流程实例id不能为空") @PathVariable String[] processInstanceIds) { + return toAjax(actProcessInstanceService.deleteFinishProcessAndHisInst(Arrays.asList(processInstanceIds))); + } + + /** + * 撤销流程申请 + * + * @param processInstanceId 流程实例id + */ + @Log(title = "流程实例管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/cancelProcessApply/{processInstanceId}") + public R cancelProcessApply(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) { + return toAjax(actProcessInstanceService.cancelProcessApply(processInstanceId)); + } + + /** + * 分页查询当前登录人单据 + * + * @param processInstanceBo 参数 + */ + @GetMapping("/getCurrentSubmitByPage") + public TableDataInfo getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo) { + return actProcessInstanceService.getCurrentSubmitByPage(processInstanceBo); + } + + /** + * 任务催办(给当前任务办理人发送站内信,邮件,短信等) + * + * @param taskUrgingBo 任务催办 + */ + @Log(title = "流程实例管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/taskUrging") + public R taskUrging(@RequestBody TaskUrgingBo taskUrgingBo) { + return toAjax(actProcessInstanceService.taskUrging(taskUrgingBo)); + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java new file mode 100644 index 000000000..7bf61a7d0 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java @@ -0,0 +1,246 @@ +package org.dromara.workflow.controller; + +import cn.hutool.core.convert.Convert; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.bo.*; +import org.dromara.workflow.domain.vo.TaskVo; +import org.dromara.workflow.service.IActTaskService; +import org.dromara.workflow.utils.WorkflowUtils; +import org.flowable.engine.TaskService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 任务管理 控制层 + * + * @author may + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/task") +public class ActTaskController extends BaseController { + + private final IActTaskService actTaskService; + + private final TaskService taskService; + + + /** + * 启动任务 + * + * @param startProcessBo 启动流程参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/startWorkFlow") + public R> startWorkFlow(@RequestBody StartProcessBo startProcessBo) { + Map map = actTaskService.startWorkFlow(startProcessBo); + return R.ok("提交成功", map); + } + + /** + * 办理任务 + * + * @param completeTaskBo 办理任务参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/completeTask") + public R completeTask(@Validated(AddGroup.class) @RequestBody CompleteTaskBo completeTaskBo) { + return toAjax(actTaskService.completeTask(completeTaskBo)); + } + + /** + * 查询当前用户的待办任务 + * + * @param taskBo 参数 + */ + @GetMapping("/getTaskWaitByPage") + public TableDataInfo getTaskWaitByPage(TaskBo taskBo) { + return actTaskService.getTaskWaitByPage(taskBo); + } + + /** + * 查询当前租户所有待办任务 + * + * @param taskBo 参数 + */ + @GetMapping("/getAllTaskWaitByPage") + public TableDataInfo getAllTaskWaitByPage(TaskBo taskBo) { + return actTaskService.getAllTaskWaitByPage(taskBo); + } + + /** + * 查询当前用户的已办任务 + * + * @param taskBo 参数 + */ + @GetMapping("/getTaskFinishByPage") + public TableDataInfo getTaskFinishByPage(TaskBo taskBo) { + return actTaskService.getTaskFinishByPage(taskBo); + } + + /** + * 查询当前用户的抄送 + * + * @param taskBo 参数 + */ + @GetMapping("/getTaskCopyByPage") + public TableDataInfo getTaskCopyByPage(TaskBo taskBo) { + return actTaskService.getTaskCopyByPage(taskBo); + } + + /** + * 查询当前租户所有已办任务 + * + * @param taskBo 参数 + */ + @GetMapping("/getAllTaskFinishByPage") + public TableDataInfo getAllTaskFinishByPage(TaskBo taskBo) { + return actTaskService.getAllTaskFinishByPage(taskBo); + } + + /** + * 签收(拾取)任务 + * + * @param taskId 任务id + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/claim/{taskId}") + public R claimTask(@NotBlank(message = "任务id不能为空") @PathVariable String taskId) { + try { + taskService.claim(taskId, Convert.toStr(LoginHelper.getUserId())); + return R.ok(); + } catch (Exception e) { + e.printStackTrace(); + return R.fail("签收任务失败:" + e.getMessage()); + } + } + + /** + * 归还(拾取的)任务 + * + * @param taskId 任务id + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/returnTask/{taskId}") + public R returnTask(@NotBlank(message = "任务id不能为空") @PathVariable String taskId) { + try { + taskService.setAssignee(taskId, null); + return R.ok(); + } catch (Exception e) { + e.printStackTrace(); + return R.fail("归还任务失败:" + e.getMessage()); + } + } + + /** + * 委派任务 + * + * @param delegateBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/delegateTask") + public R delegateTask(@Validated({AddGroup.class}) @RequestBody DelegateBo delegateBo) { + return toAjax(actTaskService.delegateTask(delegateBo)); + } + + /** + * 终止任务 + * + * @param terminationBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.DELETE) + @RepeatSubmit() + @PostMapping("/terminationTask") + public R terminationTask(@RequestBody TerminationBo terminationBo) { + return toAjax(actTaskService.terminationTask(terminationBo)); + } + + /** + * 转办任务 + * + * @param transmitBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/transferTask") + public R transferTask(@Validated({AddGroup.class}) @RequestBody TransmitBo transmitBo) { + return toAjax(actTaskService.transferTask(transmitBo)); + } + + /** + * 会签任务加签 + * + * @param addMultiBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/addMultiInstanceExecution") + public R addMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody AddMultiBo addMultiBo) { + return toAjax(actTaskService.addMultiInstanceExecution(addMultiBo)); + } + + /** + * 会签任务减签 + * + * @param deleteMultiBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/deleteMultiInstanceExecution") + public R deleteMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody DeleteMultiBo deleteMultiBo) { + return toAjax(actTaskService.deleteMultiInstanceExecution(deleteMultiBo)); + } + + /** + * 驳回审批 + * + * @param backProcessBo 参数 + */ + @Log(title = "任务管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/backProcess") + public R backProcess(@RequestBody BackProcessBo backProcessBo) { + return R.ok(actTaskService.backProcess(backProcessBo)); + } + + /** + * 获取流程状态 + * + * @param taskId 任务id + */ + @GetMapping("/getBusinessStatus/{taskId}") + public R getBusinessStatus(@PathVariable String taskId) { + return R.ok("操作成功", WorkflowUtils.getBusinessStatusByTaskId(taskId)); + } + + + /** + * 修改任务办理人 + * + * @param taskIds 任务id + * @param userId 办理人id + */ + @Log(title = "任务管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/updateAssignee/{taskIds}/{userId}") + public R updateAssignee(@PathVariable String[] taskIds, @PathVariable String userId) { + return toAjax(actTaskService.updateAssignee(taskIds, userId)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java new file mode 100644 index 000000000..cc83c28ff --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java @@ -0,0 +1,107 @@ +package org.dromara.workflow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.TestLeave; +import org.dromara.workflow.domain.bo.TestLeaveBo; +import org.dromara.workflow.domain.vo.TestLeaveVo; +import org.dromara.workflow.service.ITestLeaveService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 请假 + * + * @author may + * @date 2023-07-21 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/leave") +public class TestLeaveController extends BaseController { + + private final ITestLeaveService testLeaveService; + + /** + * 查询请假列表 + */ + @SaCheckPermission("demo:leave:list") + @GetMapping("/list") + public TableDataInfo list(TestLeaveBo bo, PageQuery pageQuery) { + return testLeaveService.queryPageList(bo, pageQuery); + } + + /** + * 导出请假列表 + */ + @SaCheckPermission("demo:leave:export") + @Log(title = "请假", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(TestLeaveBo bo, HttpServletResponse response) { + List list = testLeaveService.queryList(bo); + ExcelUtil.exportExcel(list, "请假", TestLeaveVo.class, response); + } + + /** + * 获取请假详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("demo:leave:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(testLeaveService.queryById(id)); + } + + /** + * 新增请假 + */ + @SaCheckPermission("demo:leave:add") + @Log(title = "请假", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) { + return R.ok(testLeaveService.insertByBo(bo)); + } + + /** + * 修改请假 + */ + @SaCheckPermission("demo:leave:edit") + @Log(title = "请假", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody TestLeaveBo bo) { + return R.ok(testLeaveService.updateByBo(bo)); + } + + /** + * 删除请假 + * + * @param ids 主键串 + */ + @SaCheckPermission("demo:leave:remove") + @Log(title = "请假", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(testLeaveService.deleteWithValidByIds(List.of(ids))); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java new file mode 100644 index 000000000..8dced8929 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java @@ -0,0 +1,106 @@ +package org.dromara.workflow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.domain.bo.WfCategoryBo; +import org.dromara.workflow.domain.vo.WfCategoryVo; +import org.dromara.workflow.service.IWfCategoryService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 流程分类 + * + * @author may + * @date 2023-06-28 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/category") +public class WfCategoryController extends BaseController { + + private final IWfCategoryService wfCategoryService; + + /** + * 查询流程分类列表 + */ + @SaCheckPermission("workflow:category:list") + @GetMapping("/list") + public R> list(WfCategoryBo bo) { + List list = wfCategoryService.queryList(bo); + return R.ok(list); + + } + + /** + * 导出流程分类列表 + */ + @SaCheckPermission("workflow:category:export") + @Log(title = "流程分类", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(WfCategoryBo bo, HttpServletResponse response) { + List list = wfCategoryService.queryList(bo); + ExcelUtil.exportExcel(list, "流程分类", WfCategoryVo.class, response); + } + + /** + * 获取流程分类详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("workflow:category:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(wfCategoryService.queryById(id)); + } + + /** + * 新增流程分类 + */ + @SaCheckPermission("workflow:category:add") + @Log(title = "流程分类", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody WfCategoryBo bo) { + return toAjax(wfCategoryService.insertByBo(bo)); + } + + /** + * 修改流程分类 + */ + @SaCheckPermission("workflow:category:edit") + @Log(title = "流程分类", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody WfCategoryBo bo) { + return toAjax(wfCategoryService.updateByBo(bo)); + } + + /** + * 删除流程分类 + * + * @param ids 主键串 + */ + @SaCheckPermission("workflow:category:remove") + @Log(title = "流程分类", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(wfCategoryService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java new file mode 100644 index 000000000..78f808fca --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java @@ -0,0 +1,72 @@ +package org.dromara.workflow.controller; + +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.system.domain.bo.SysUserBo; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.workflow.domain.bo.SysUserMultiBo; +import org.dromara.workflow.domain.vo.TaskVo; +import org.dromara.workflow.service.IWorkflowUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * 工作流用户选人管理 控制层 + * + * @author may + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/user") +public class WorkflowUserController extends BaseController { + + private final IWorkflowUserService workflowUserService; + + /** + * 分页查询工作流选择加签人员 + * + * @param sysUserMultiBo 参数 + */ + @GetMapping("/getWorkflowAddMultiListByPage") + public TableDataInfo getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo) { + return workflowUserService.getWorkflowAddMultiInstanceByPage(sysUserMultiBo); + } + + /** + * 查询工作流选择减签人员 + * + * @param taskId 任务id + */ + @GetMapping("/getWorkflowDeleteMultiInstanceList/{taskId}") + public R> getWorkflowDeleteMultiInstanceList(@PathVariable String taskId) { + return R.ok(workflowUserService.getWorkflowDeleteMultiInstanceList(taskId)); + } + + /** + * 按照用户id查询用户 + * + * @param userIds 用户id + */ + @GetMapping("/getUserListByIds/{userIds}") + public R> getUserListByIds(@PathVariable List userIds) { + return R.ok(workflowUserService.getUserListByIds(userIds)); + } + + /** + * 分页查询用户 + * + * @param sysUserBo 参数 + * @param pageQuery 分页 + */ + @GetMapping("/getUserListByPage") + public TableDataInfo getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery) { + return workflowUserService.getUserListByPage(sysUserBo, pageQuery); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java new file mode 100644 index 000000000..e87fb9253 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java @@ -0,0 +1,152 @@ +package org.dromara.workflow.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 流程实例对象 act_hi_procinst + * + * @author may + * @date 2023-07-22 + */ +@Data +@TableName("act_hi_procinst") +public class ActHiProcinst implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @TableId(value = "ID_") + private String id; + + /** + * + */ + @TableField(value = "REV_") + private Long rev; + + /** + * + */ + @TableField(value = "PROC_INST_ID_") + private String procInstId; + + /** + * + */ + @TableField(value = "BUSINESS_KEY_") + private String businessKey; + + /** + * + */ + @TableField(value = "PROC_DEF_ID_") + private String procDefId; + + /** + * + */ + @TableField(value = "START_TIME_") + private Date startTime; + + /** + * + */ + @TableField(value = "END_TIME_") + private Date endTime; + + /** + * + */ + @TableField(value = "DURATION_") + private Long duration; + + /** + * + */ + @TableField(value = "START_USER_ID_") + private String startUserId; + + /** + * + */ + @TableField(value = "START_ACT_ID_") + private String startActId; + + /** + * + */ + @TableField(value = "END_ACT_ID_") + private String endActId; + + /** + * + */ + @TableField(value = "SUPER_PROCESS_INSTANCE_ID_") + private String superProcessInstanceId; + + /** + * + */ + @TableField(value = "DELETE_REASON_") + private String deleteReason; + + /** + * + */ + @TableField(value = "TENANT_ID_") + private String tenantId; + + /** + * + */ + @TableField(value = "NAME_") + private String name; + + /** + * + */ + @TableField(value = "CALLBACK_ID_") + private String callbackId; + + /** + * + */ + @TableField(value = "CALLBACK_TYPE_") + private String callbackType; + + /** + * + */ + @TableField(value = "REFERENCE_ID_") + private String referenceId; + + /** + * + */ + @TableField(value = "REFERENCE_TYPE_") + private String referenceType; + + /** + * + */ + @TableField(value = "PROPAGATED_STAGE_INST_ID_") + private String propagatedStageInstId; + + /** + * + */ + @TableField(value = "BUSINESS_STATUS_") + private String businessStatus; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java new file mode 100644 index 000000000..578ea9c87 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java @@ -0,0 +1,193 @@ +package org.dromara.workflow.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +import java.io.Serial; + +/** + * 流程历史任务对象 act_hi_taskinst + * + * @author gssong + * @date 2024-03-02 + */ +@Data +@TableName("act_hi_taskinst") +public class ActHiTaskinst implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @TableId(value = "ID_") + private String id; + + /** + * 版本 + */ + @TableField(value = "REV_") + private Long rev; + + /** + * 流程定义id + */ + @TableField(value = "PROC_DEF_ID_") + private String procDefId; + + /** + * + */ + @TableField(value = "TASK_DEF_ID_") + private String taskDefId; + + /** + * 任务节点id + */ + @TableField(value = "TASK_DEF_KEY_") + private String taskDefKey; + + /** + * 流程实例id + */ + @TableField(value = "PROC_INST_ID_") + private String procInstId; + + /** + * 流程执行id + */ + @TableField(value = "EXECUTION_ID") + private String executionId; + + /** + * + */ + @TableField(value = "SCOPE_ID_") + private String scopeId; + + /** + * + */ + @TableField(value = "SUB_SCOPE_ID_") + private String subScopeId; + + /** + * 先用当前字段标识抄送类型 + */ + @TableField(value = "SCOPE_TYPE_") + private String scopeType; + + /** + * + */ + @TableField(value = "SCOPE_DEFINITION_ID_") + private String scopeDefinitionId; + + /** + * + */ + @TableField(value = "PROPAGATED_STAGE_INST_ID_") + private String propagatedStageInstId; + + /** + * 任务名称 + */ + @TableField(value = "NAME_") + private String name; + + /** + * 父级id + */ + @TableField(value = "PARENT_TASK_ID_") + private String parentTaskId; + + /** + * 描述 + */ + @TableField(value = "DESCRIPTION_") + private String description; + + /** + * 办理人 + */ + @TableField(value = "OWNER_") + private String owner; + + /** + * 办理人 + */ + @TableField(value = "ASSIGNEE_") + private String assignee; + + /** + * 开始事件 + */ + @TableField(value = "START_TIME_") + private Date startTime; + + /** + * 认领时间 + */ + @TableField(value = "CLAIM_TIME_") + private Date claimTime; + + /** + * 结束时间 + */ + @TableField(value = "END_TIME_") + private Date endTime; + + /** + * 持续时间 + */ + @TableField(value = "DURATION_") + private Long duration; + + /** + * 删除原因 + */ + @TableField(value = "DELETE_REASON_") + private String deleteReason; + + /** + * 优先级 + */ + @TableField(value = "PRIORITY_") + private Long priority; + + /** + * 到期时间 + */ + @TableField(value = "DUE_DATE_") + private Date dueDate; + + /** + * + */ + @TableField(value = "FORM_KEY_") + private String formKey; + + /** + * 分类 + */ + @TableField(value = "CATEGORY_") + private String category; + + /** + * 最后修改时间 + */ + @TableField(value = "LAST_UPDATED_TIME_") + private Date lastUpdatedTime; + + /** + * 租户id + */ + @TableField(value = "TENANT_ID_") + private String tenantId; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java new file mode 100644 index 000000000..0e2646701 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java @@ -0,0 +1,58 @@ +package org.dromara.workflow.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.util.Date; + +/** + * 请假对象 test_leave + * + * @author may + * @date 2023-07-21 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("test_leave") +public class TestLeave extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 请假类型 + */ + private String leaveType; + + /** + * 开始时间 + */ + private Date startDate; + + /** + * 结束时间 + */ + private Date endDate; + + /** + * 请假天数 + */ + private Integer leaveDays; + + /** + * 请假原因 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java new file mode 100644 index 000000000..94a7cf500 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java @@ -0,0 +1,52 @@ +package org.dromara.workflow.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 流程分类对象 wf_category + * + * @author may + * @date 2023-06-27 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wf_category") +public class WfCategory extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 分类名称 + */ + private String categoryName; + + /** + * 分类编码 + */ + private String categoryCode; + + /** + * 父级id + */ + private Long parentId; + + /** + * 排序 + */ + private Long sortNum; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java new file mode 100644 index 000000000..320ec64ca --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java @@ -0,0 +1,40 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 加签参数请求 + * + * @author may + */ +@Data +public class AddMultiBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务ID + */ + @NotBlank(message = "任务ID不能为空", groups = AddGroup.class) + private String taskId; + + /** + * 加签人员id + */ + @NotEmpty(message = "加签人员不能为空", groups = AddGroup.class) + private List assignees; + + /** + * 加签人员名称 + */ + @NotEmpty(message = "加签人员不能为空", groups = AddGroup.class) + private List assigneeNames; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java new file mode 100644 index 000000000..7ac6b38e5 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java @@ -0,0 +1,43 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + + +/** + * 驳回参数请求 + * + * @author may + */ +@Data +public class BackProcessBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务ID + */ + @NotBlank(message = "任务ID不能为空", groups = AddGroup.class) + private String taskId; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 驳回的节点id(目前未使用,直接驳回到申请人) + */ + private String targetActivityId; + + /** + * 办理意见 + */ + private String message; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java new file mode 100644 index 000000000..062390551 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java @@ -0,0 +1,65 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.workflow.domain.vo.WfCopy; + +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 办理任务请求对象 + * + * @author may + */ +@Data +public class CompleteTaskBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotBlank(message = "任务id不能为空", groups = {AddGroup.class}) + private String taskId; + + /** + * 附件id + */ + private String fileId; + + /** + * 抄送人员 + */ + private List wfCopyList; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 办理意见 + */ + private String message; + + /** + * 流程变量 + */ + private Map variables; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java new file mode 100644 index 000000000..a6846a6d6 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java @@ -0,0 +1,38 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 委派任务请求对象 + * + * @author may + */ +@Data +public class DelegateBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 委派人id + */ + @NotBlank(message = "委派人id不能为空", groups = {AddGroup.class}) + private String userId; + + /** + * 委派人名称 + */ + @NotBlank(message = "委派人名称不能为空", groups = {AddGroup.class}) + private String nickName; + + /** + * 任务id + */ + @NotBlank(message = "任务id不能为空", groups = {AddGroup.class}) + private String taskId; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java new file mode 100644 index 000000000..e533167d9 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java @@ -0,0 +1,52 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 减签参数请求 + * + * @author may + */ +@Data +public class DeleteMultiBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务ID + */ + @NotBlank(message = "任务ID不能为空", groups = AddGroup.class) + private String taskId; + + /** + * 减签人员 + */ + @NotEmpty(message = "减签人员不能为空", groups = AddGroup.class) + private List taskIds; + + /** + * 执行id + */ + @NotEmpty(message = "执行id不能为空", groups = AddGroup.class) + private List executionIds; + + /** + * 人员id + */ + @NotEmpty(message = "减签人员id不能为空", groups = AddGroup.class) + private List assigneeIds; + + /** + * 人员名称 + */ + @NotEmpty(message = "减签人员不能为空", groups = AddGroup.class) + private List assigneeNames; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java new file mode 100644 index 000000000..12efd87a8 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java @@ -0,0 +1,69 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.workflow.common.PageEntity; +import org.dromara.workflow.common.constant.FlowConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 模型请求对象 + * + * @author may + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ModelBo extends PageEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 模型id + */ + @NotBlank(message = "模型ID不能为空", groups = {EditGroup.class}) + private String id; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = {AddGroup.class}) + private String name; + + /** + * 模型标识key + */ + @NotBlank(message = "模型标识key不能为空", groups = {AddGroup.class}) + @Pattern(regexp = FlowConstant.MODEL_KEY_PATTERN, message = "模型标识key只能字符或者下划线开头", groups = {AddGroup.class}) + private String key; + + /** + * 模型分类 + */ + @NotBlank(message = "模型分类不能为空", groups = {AddGroup.class}) + private String categoryCode; + + /** + * 模型XML + */ + @NotBlank(message = "模型XML不能为空", groups = {AddGroup.class}) + private String xml; + + /** + * 模型SVG图片 + */ + @NotBlank(message = "模型SVG不能为空", groups = {EditGroup.class}) + private String svg; + + /** + * 备注 + */ + private String description; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java new file mode 100644 index 000000000..86e9e0135 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.workflow.common.PageEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 流程定义请求对象 + * + * @author may + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProcessDefinitionBo extends PageEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程定义名称key + */ + private String key; + + /** + * 流程定义名称 + */ + private String name; + + /** + * 模型分类 + */ + private String categoryCode; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java new file mode 100644 index 000000000..ff8af2335 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java @@ -0,0 +1,46 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.workflow.common.PageEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 流程实例请求对象 + * + * @author may + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProcessInstanceBo extends PageEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程名称 + */ + private String name; + + /** + * 流程key + */ + private String key; + + /** + * 任务发起人 + */ + private String startUserId; + + /** + * 业务id + */ + private String businessKey; + + /** + * 模型分类 + */ + private String categoryCode; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java new file mode 100644 index 000000000..35d565255 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java @@ -0,0 +1,31 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 流程实例作废请求对象 + * + * @author may + */ +@Data +public class ProcessInvalidBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程实例id + */ + @NotBlank(message = "流程实例id不能为空", groups = {AddGroup.class}) + private String processInstanceId; + + /** + * 作废原因 + */ + private String deleteReason; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java new file mode 100644 index 000000000..aff3f9745 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java @@ -0,0 +1,45 @@ +package org.dromara.workflow.domain.bo; + + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 启动流程对象 + * + * @author may + */ +@Data +public class StartProcessBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务唯一值id + */ + private String businessKey; + + /** + * 流程执行key + */ + private String processKey; + + /** + * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} + */ + private Map variables; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java new file mode 100644 index 000000000..b54f481ce --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java @@ -0,0 +1,34 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; +import org.dromara.workflow.common.PageEntity; + + +/** + * 用户加签查询 + * + * @author may + */ +@Data + +public class SysUserMultiBo extends PageEntity { + /** + * 人员名称 + */ + private String userName; + + /** + * 人员名称 + */ + private String nickName; + + /** + * 部门id + */ + private String deptId; + + /** + * 任务id + */ + private String taskId; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java new file mode 100644 index 000000000..cd2eeadf5 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java @@ -0,0 +1,36 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.workflow.common.PageEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 任务请求对象 + * + * @author may + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TaskBo extends PageEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务名称 + */ + private String name; + + /** + * 流程定义名称 + */ + private String processDefinitionName; + + /** + * 流程定义key + */ + private String processDefinitionKey; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java new file mode 100644 index 000000000..20856efa9 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java @@ -0,0 +1,34 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 任务催办 + * + * @author may + */ +@Data +public class TaskUrgingBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程实例id + */ + private String processInstanceId; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 催办内容(为空默认系统内置信息) + */ + private String message; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java new file mode 100644 index 000000000..61b761678 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 终止任务请求对象 + * + * @author may + */ +@Data +public class TerminationBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotBlank(message = "任务id为空", groups = AddGroup.class) + private String taskId; + + /** + * 转办人id + */ + @NotBlank(message = "转办人不能为空", groups = AddGroup.class) + private String userId; + + /** + * 审批意见 + */ + private String comment; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java new file mode 100644 index 000000000..e71be591a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java @@ -0,0 +1,75 @@ +package org.dromara.workflow.domain.bo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.workflow.domain.TestLeave; + +import java.util.Date; + +/** + * 请假业务对象 test_leave + * + * @author may + * @date 2023-07-21 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = TestLeave.class, reverseConvertGenerate = false) +public class TestLeaveBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {EditGroup.class}) + private Long id; + + /** + * 请假类型 + */ + @NotBlank(message = "请假类型不能为空", groups = {AddGroup.class, EditGroup.class}) + private String leaveType; + + /** + * 开始时间 + */ + @NotNull(message = "开始时间不能为空", groups = {AddGroup.class, EditGroup.class}) + @JsonFormat(pattern = "yyyy-MM-dd") + private Date startDate; + + /** + * 结束时间 + */ + @NotNull(message = "结束时间不能为空", groups = {AddGroup.class, EditGroup.class}) + @JsonFormat(pattern = "yyyy-MM-dd") + private Date endDate; + + /** + * 请假天数 + */ + @NotNull(message = "请假天数不能为空", groups = {AddGroup.class, EditGroup.class}) + private Integer leaveDays; + + /** + * 开始时间 + */ + private Integer startLeaveDays; + + /** + * 结束时间 + */ + private Integer endLeaveDays; + + /** + * 请假原因 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java new file mode 100644 index 000000000..3eb6609fd --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 终转办务请求对象 + * + * @author may + */ +@Data +public class TransmitBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotBlank(message = "任务id为空", groups = AddGroup.class) + private String taskId; + + /** + * 转办人id + */ + @NotBlank(message = "转办人不能为空", groups = AddGroup.class) + private String userId; + + /** + * 审批意见 + */ + private String comment; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java new file mode 100644 index 000000000..69608fda3 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java @@ -0,0 +1,54 @@ +package org.dromara.workflow.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.workflow.domain.WfCategory; + +/** + * 流程分类业务对象 wf_category + * + * @author may + * @date 2023-06-27 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = WfCategory.class, reverseConvertGenerate = false) +public class WfCategoryBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {EditGroup.class}) + private Long id; + + /** + * 分类名称 + */ + @NotBlank(message = "分类名称不能为空", groups = {AddGroup.class, EditGroup.class}) + private String categoryName; + + /** + * 分类编码 + */ + @NotBlank(message = "分类编码不能为空", groups = {AddGroup.class, EditGroup.class}) + private String categoryCode; + + /** + * 父级id + */ + @NotNull(message = "父级id不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long parentId; + + /** + * 排序 + */ + private Long sortNum; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java new file mode 100644 index 000000000..459b2d0f6 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java @@ -0,0 +1,89 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.constant.TransConstant; +import org.flowable.engine.task.Attachment; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 流程审批记录视图 + * + * @author may + */ +@Data +public class ActHistoryInfoVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 任务id + */ + private String id; + /** + * 节点id + */ + private String taskDefinitionKey; + /** + * 任务名称 + */ + private String name; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 开始时间 + */ + private Date startTime; + /** + * 结束时间 + */ + private Date endTime; + /** + * 运行时长 + */ + private String runDuration; + /** + * 状态 + */ + private String status; + /** + * 状态 + */ + private String statusName; + /** + * 办理人id + */ + private Long assignee; + + /** + * 办理人名称 + */ + @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee") + private String nickName; + + /** + * 办理人id + */ + private String owner; + + /** + * 审批信息id + */ + private String commentId; + + /** + * 审批信息 + */ + private String comment; + + /** + * 审批附件 + */ + private List attachmentList; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java new file mode 100644 index 000000000..763613165 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java @@ -0,0 +1,47 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 节点图形信息 + * + * @author may + */ +@Data +public class GraphicInfoVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * x坐标 + */ + private double x; + + /** + * y坐标 + */ + private double y; + + /** + * 节点高度 + */ + private double height; + + /** + * 节点宽度 + */ + private double width; + + /** + * 节点id + */ + private String nodeId; + + /** + * 节点名称 + */ + private String nodeName; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java new file mode 100644 index 000000000..53e4bfce7 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java @@ -0,0 +1,27 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +/** + * @author Joram Barrez + */ +@Data +public class GroupRepresentation { + + protected String id; + protected String name; + protected String type; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java new file mode 100644 index 000000000..b2ce81108 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java @@ -0,0 +1,48 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 模型视图对象 + * + * @author may + */ +@Data +public class ModelVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 模型id + */ + private String id; + + /** + * 模型名称 + */ + private String name; + + /** + * 模型标识key + */ + private String key; + + /** + * 模型分类 + */ + private String categoryCode; + + /** + * 模型XML + */ + private String xml; + + /** + * 备注 + */ + private String description; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java new file mode 100644 index 000000000..b99839603 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java @@ -0,0 +1,33 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 多实例信息 + * + * @author may + */ +@Data +public class MultiInstanceVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 会签类型(串行,并行) + */ + private Object type; + + /** + * 会签人员KEY + */ + private String assignee; + + /** + * 会签人员集合KEY + */ + private String assigneeList; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java new file mode 100644 index 000000000..c5876f689 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java @@ -0,0 +1,43 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 参与者 + * + * @author may + */ +@Data +public class ParticipantVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 组id(角色id) + */ + private List groupIds; + + /** + * 候选人id(用户id) 当组id不为空时,将组内人员查出放入candidate + */ + private List candidate; + + /** + * 候选人名称(用户名称) 当组id不为空时,将组内人员查出放入candidateName + */ + private List candidateName; + + /** + * 是否认领标识 + * 当为空时默认当前任务不需要认领 + * 当为true时当前任务说明为候选模式并且有人已经认领了任务可以归还, + * 当为false时当前任务说明为候选模式该任务未认领, + */ + private Boolean claim; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java new file mode 100644 index 000000000..52c201d25 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java @@ -0,0 +1,65 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 流程定义视图 + * + * @author may + */ +@Data +public class ProcessDefinitionVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程定义id + */ + private String id; + + /** + * 流程定义名称 + */ + private String name; + + /** + * 流程定义标识key + */ + private String key; + + /** + * 流程定义版本 + */ + private int version; + + /** + * 流程定义挂起或激活 1激活 2挂起 + */ + private int suspensionState; + + /** + * 流程xml名称 + */ + private String resourceName; + + /** + * 流程图片名称 + */ + private String diagramResourceName; + + /** + * 流程部署id + */ + private String deploymentId; + + /** + * 流程部署时间 + */ + private Date deploymentTime; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java new file mode 100644 index 000000000..3917f4f51 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java @@ -0,0 +1,95 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 流程实例视图 + * + * @author may + */ +@Data +public class ProcessInstanceVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程实例id + */ + private String id; + + /** + * 流程定义id + */ + private String processDefinitionId; + + /** + * 流程定义名称 + */ + private String processDefinitionName; + + /** + * 流程定义key + */ + private String processDefinitionKey; + + /** + * 流程定义版本 + */ + private String processDefinitionVersion; + + /** + * 部署id + */ + private String deploymentId; + + /** + * 业务id + */ + private String businessKey; + + /** + * 是否挂起 + */ + private Boolean isSuspended; + + /** + * 租户id + */ + private String tenantId; + + /** + * 启动时间 + */ + private Date startTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 启动人id + */ + private String startUserId; + + /** + * 流程状态 + */ + private String businessStatus; + + /** + * 流程状态 + */ + private String businessStatusName; + + /** + * 待办任务集合 + */ + private List taskVoList; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java new file mode 100644 index 000000000..fad9d903d --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java @@ -0,0 +1,143 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.constant.TransConstant; + +import java.util.Date; + +/** + * 任务视图 + * + * @author may + */ +@Data +public class TaskVo { + + /** + * 任务id + */ + private String id; + + /** + * 任务名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 优先级 + */ + private Integer priority; + + /** + * 负责此任务的人员的用户id + */ + private String owner; + + /** + * 办理人id + */ + private Long assignee; + + /** + * 办理人 + */ + @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee") + private String assigneeName; + + + /** + * 流程实例id + */ + private String processInstanceId; + + /** + * 执行id + */ + private String executionId; + + /** + * 无用 + */ + private String taskDefinitionId; + + /** + * 流程定义id + */ + private String processDefinitionId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 节点id + */ + private String taskDefinitionKey; + + /** + * 任务截止日期 + */ + private Date dueDate; + + /** + * 流程类别 + */ + private String category; + + /** + * 父级任务id + */ + private String parentTaskId; + + /** + * 租户id + */ + private String tenantId; + + /** + * 认领时间 + */ + private Date claimTime; + + /** + * 流程状态 + */ + private String businessStatus; + + /** + * 流程状态 + */ + private String businessStatusName; + + /** + * 流程定义名称 + */ + private String processDefinitionName; + + /** + * 流程定义key + */ + private String processDefinitionKey; + + /** + * 参与者 + */ + private ParticipantVo participantVo; + + /** + * 是否会签 + */ + private Boolean multiInstance; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java new file mode 100644 index 000000000..c62a35612 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java @@ -0,0 +1,70 @@ +package org.dromara.workflow.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.workflow.domain.TestLeave; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 请假视图对象 test_leave + * + * @author may + * @date 2023-07-21 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = TestLeave.class) +public class TestLeaveVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 请假类型 + */ + @ExcelProperty(value = "请假类型") + private String leaveType; + + /** + * 开始时间 + */ + @ExcelProperty(value = "开始时间") + private Date startDate; + + /** + * 结束时间 + */ + @ExcelProperty(value = "结束时间") + private Date endDate; + + /** + * 请假天数 + */ + @ExcelProperty(value = "请假天数") + private Integer leaveDays; + + /** + * 备注 + */ + @ExcelProperty(value = "请假原因") + private String remark; + + /** + * 流程实例对象 + */ + private ProcessInstanceVo processInstanceVo; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java new file mode 100644 index 000000000..362f64625 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java @@ -0,0 +1,58 @@ +package org.dromara.workflow.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.workflow.domain.WfCategory; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 流程分类视图对象 wf_category + * + * @author may + * @date 2023-06-27 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = WfCategory.class) +public class WfCategoryVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 分类名称 + */ + @ExcelProperty(value = "分类名称") + private String categoryName; + + /** + * 分类编码 + */ + @ExcelProperty(value = "分类编码") + private String categoryCode; + + /** + * 父级id + */ + @ExcelProperty(value = "父级id") + private Long parentId; + + /** + * 排序 + */ + @ExcelProperty(value = "排序") + private Long sortNum; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java new file mode 100644 index 000000000..b1d41bbe5 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java @@ -0,0 +1,23 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +/** + * 抄送 + * + * @author may + */ +@Data +public class WfCopy { + + /** + * 用户id + */ + private Long userId; + + /** + * 用户名称 + */ + private String userName; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java new file mode 100644 index 000000000..39fd9d363 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java @@ -0,0 +1,108 @@ +package org.dromara.workflow.flowable; + +import org.flowable.bpmn.model.AssociationDirection; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; + +import java.awt.*; +import java.awt.geom.Line2D; +import java.awt.geom.RoundRectangle2D; + +public class CustomDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas { + //设置高亮线的颜色 这里我设置成绿色 + protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN; + + public CustomDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); + } + + /** + * 画线颜色设置 + */ + public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, + AssociationDirection associationDirection, boolean highLighted, double scaleFactor) { + + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(CONNECTION_COLOR); + if (connectionType.equals("association")) { + g.setStroke(ASSOCIATION_STROKE); + } else if (highLighted) { + //设置线的颜色 + g.setPaint(HIGHLIGHT_SEQUENCEFLOW_COLOR); + g.setStroke(HIGHLIGHT_FLOW_STROKE); + } + + for (int i = 1; i < xPoints.length; i++) { + Integer sourceX = xPoints[i - 1]; + Integer sourceY = yPoints[i - 1]; + Integer targetX = xPoints[i]; + Integer targetY = yPoints[i]; + Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY); + g.draw(line); + } + + if (isDefault) { + Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); + drawDefaultSequenceFlowIndicator(line, scaleFactor); + } + + if (conditional) { + Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); + drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) { + Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]); + drawArrowHead(line, scaleFactor); + } + if (associationDirection == AssociationDirection.BOTH) { + Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]); + drawArrowHead(line, scaleFactor); + } + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * 高亮节点设置 + */ + public void drawHighLight(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + //设置高亮节点的颜色 + g.setPaint(HIGHLIGHT_COLOR); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * @description: 高亮节点红色 + * @param: x + * @param: y + * @param: width + * @param: height + * @return: void + * @author: gssong + * @date: 2022/4/12 + */ + public void drawHighLightRed(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + //设置高亮节点的颜色 + g.setPaint(Color.green); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java new file mode 100644 index 000000000..e4793a26f --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java @@ -0,0 +1,1120 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.workflow.flowable; + +import org.flowable.bpmn.model.Event; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; +import org.flowable.image.ProcessDiagramGenerator; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.util.List; +import java.util.*; + +/** + * Class to generate an image based the diagram interchange information in a BPMN 2.0 process. + * + * @author Joram Barrez + * @author Tijs Rademakers + * @author Zheng Ji + */ +public class CustomDefaultProcessDiagramGenerator implements ProcessDiagramGenerator { + + protected Map, ActivityDrawInstruction> activityDrawInstructions = new HashMap<>(); + protected Map, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<>(); + + public CustomDefaultProcessDiagramGenerator() { + this(1.0); + } + + // The instructions on how to draw a certain construct is + // created statically and stored in a map for performance. + public CustomDefaultProcessDiagramGenerator(final double scaleFactor) { + // start event + activityDrawInstructions.put(StartEvent.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + StartEvent startEvent = (StartEvent) flowNode; + if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) { + EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0); + if (eventDefinition instanceof TimerEventDefinition) { + processDiagramCanvas.drawTimerStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof ErrorEventDefinition) { + processDiagramCanvas.drawErrorStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof EscalationEventDefinition) { + processDiagramCanvas.drawEscalationStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof ConditionalEventDefinition) { + processDiagramCanvas.drawConditionalStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof SignalEventDefinition) { + processDiagramCanvas.drawSignalStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof MessageEventDefinition) { + processDiagramCanvas.drawMessageStartEvent(graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawNoneStartEvent(graphicInfo); + } + } else { + List eventTypeElements = startEvent.getExtensionElements().get("eventType"); + if (eventTypeElements != null && eventTypeElements.size() > 0) { + processDiagramCanvas.drawEventRegistryStartEvent(graphicInfo, scaleFactor); + + } else { + processDiagramCanvas.drawNoneStartEvent(graphicInfo); + } + } + } + }); + + // signal catch + activityDrawInstructions.put(IntermediateCatchEvent.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode; + if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent.getEventDefinitions().isEmpty()) { + + if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) { + processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) { + processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) { + processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof ConditionalEventDefinition) { + processDiagramCanvas.drawCatchingConditionalEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } + } + } + }); + + // signal throw + activityDrawInstructions.put(ThrowEvent.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + ThrowEvent throwEvent = (ThrowEvent) flowNode; + if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) { + if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) { + processDiagramCanvas.drawThrowingSignalEvent(graphicInfo, scaleFactor); + } else if (throwEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) { + processDiagramCanvas.drawThrowingEscalationEvent(graphicInfo, scaleFactor); + } else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) { + processDiagramCanvas.drawThrowingCompensateEvent(graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor); + } + } else { + processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor); + } + } + }); + + // end event + activityDrawInstructions.put(EndEvent.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + EndEvent endEvent = (EndEvent) flowNode; + if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) { + if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) { + processDiagramCanvas.drawErrorEndEvent(flowNode.getName(), graphicInfo, scaleFactor); + } else if (endEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) { + processDiagramCanvas.drawEscalationEndEvent(flowNode.getName(), graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor); + } + } else { + processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor); + } + } + }); + + // task + activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // user task + activityDrawInstructions.put(UserTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawUserTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // script task + activityDrawInstructions.put(ScriptTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawScriptTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // service task + activityDrawInstructions.put(ServiceTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + ServiceTask serviceTask = (ServiceTask) flowNode; + if ("camel".equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawCamelTask(serviceTask.getName(), graphicInfo, scaleFactor); + }else if (ServiceTask.HTTP_TASK.equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawHttpTask(serviceTask.getName(), graphicInfo, scaleFactor); + } else if (ServiceTask.DMN_TASK.equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawDMNTask(serviceTask.getName(), graphicInfo, scaleFactor); + } else if (ServiceTask.SHELL_TASK.equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawShellTask(serviceTask.getName(), graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor); + } + } + }); + + // http service task + activityDrawInstructions.put(HttpServiceTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawHttpTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // receive task + activityDrawInstructions.put(ReceiveTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawReceiveTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // send task + activityDrawInstructions.put(SendTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawSendTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // manual task + activityDrawInstructions.put(ManualTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawManualTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // send event service task + activityDrawInstructions.put(SendEventServiceTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawSendEventServiceTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // external worker service task + activityDrawInstructions.put(ExternalWorkerServiceTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + ServiceTask serviceTask = (ServiceTask) flowNode; + processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor); + } + }); + + // case service task + activityDrawInstructions.put(CaseServiceTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawCaseServiceTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // businessRuleTask task + activityDrawInstructions.put(BusinessRuleTask.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawBusinessRuleTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // exclusive gateway + activityDrawInstructions.put(ExclusiveGateway.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawExclusiveGateway(graphicInfo, scaleFactor); + } + }); + + // inclusive gateway + activityDrawInstructions.put(InclusiveGateway.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawInclusiveGateway(graphicInfo, scaleFactor); + } + }); + + // parallel gateway + activityDrawInstructions.put(ParallelGateway.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawParallelGateway(graphicInfo, scaleFactor); + } + }); + + // event based gateway + activityDrawInstructions.put(EventGateway.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawEventBasedGateway(graphicInfo, scaleFactor); + } + }); + + // Boundary timer + activityDrawInstructions.put(BoundaryEvent.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode; + if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) { + EventDefinition eventDefinition = boundaryEvent.getEventDefinitions().get(0); + if (eventDefinition instanceof TimerEventDefinition) { + processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof ConditionalEventDefinition) { + processDiagramCanvas.drawCatchingConditionalEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof ErrorEventDefinition) { + processDiagramCanvas.drawCatchingErrorEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof EscalationEventDefinition) { + processDiagramCanvas.drawCatchingEscalationEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof SignalEventDefinition) { + processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof MessageEventDefinition) { + processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (eventDefinition instanceof CompensateEventDefinition) { + processDiagramCanvas.drawCatchingCompensateEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + } + + } else { + List eventTypeElements = boundaryEvent.getExtensionElements().get("eventType"); + if (eventTypeElements != null && eventTypeElements.size() > 0) { + processDiagramCanvas.drawCatchingEventRegistryEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + } + } + } + }); + + // subprocess + activityDrawInstructions.put(SubProcess.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } else { + processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } + } + }); + + // transaction + activityDrawInstructions.put(Transaction.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } else { + processDiagramCanvas.drawExpandedTransaction(flowNode.getName(), graphicInfo, scaleFactor); + } + } + }); + + // Event subprocess + activityDrawInstructions.put(EventSubProcess.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor); + } else { + processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor); + } + } + }); + + // Adhoc subprocess + activityDrawInstructions.put(AdhocSubProcess.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } else { + processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } + } + }); + + // call activity + activityDrawInstructions.put(CallActivity.class, new ActivityDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawCollapsedCallActivity(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // text annotation + artifactDrawInstructions.put(TextAnnotation.class, new ArtifactDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId()); + TextAnnotation textAnnotation = (TextAnnotation) artifact; + processDiagramCanvas.drawTextAnnotation(textAnnotation.getText(), graphicInfo, scaleFactor); + } + }); + + // association + artifactDrawInstructions.put(Association.class, new ArtifactDrawInstruction() { + + @Override + public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + Association association = (Association) artifact; + String sourceRef = association.getSourceRef(); + String targetRef = association.getTargetRef(); + + // source and target can be instance of FlowElement or Artifact + BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef); + BaseElement targetElement = bpmnModel.getFlowElement(targetRef); + if (sourceElement == null) { + sourceElement = bpmnModel.getArtifact(sourceRef); + } + if (targetElement == null) { + targetElement = bpmnModel.getArtifact(targetRef); + } + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId()); + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList); + int[] xPoints = new int[graphicInfoList.size()]; + int[] yPoints = new int[graphicInfoList.size()]; + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + } + + AssociationDirection associationDirection = association.getAssociationDirection(); + processDiagramCanvas.drawAssociation(xPoints, yPoints, associationDirection, false, scaleFactor); + } + }); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + + return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateImage(imageType); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, 1.0, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, boolean drawSequenceFlowNameWithNoLabelDI) { + + return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(), + activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + + return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(), + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generatePngDiagram(BpmnModel bpmnModel, boolean drawSequenceFlowNameWithNoLabelDI) { + return generatePngDiagram(bpmnModel, 1.0, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generatePngDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public InputStream generateJpgDiagram(BpmnModel bpmnModel) { + return generateJpgDiagram(bpmnModel, 1.0, false); + } + + @Override + public InputStream generateJpgDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + return generateDiagram(bpmnModel, "jpg", Collections.emptyList(), Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI); + } + + public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + + return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateBufferedImage(imageType); + } + + public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + + return generateImage(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + + @Override + public BufferedImage generatePngImage(BpmnModel bpmnModel, double scaleFactor) { + return generateImage(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, false); + } + + protected CustomDefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) { + + prepareBpmnModel(bpmnModel); + + CustomDefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); + + // Draw pool shape, if process is participant in collaboration + for (Pool pool : bpmnModel.getPools()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); + processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo, scaleFactor); + } + + // Draw lanes + for (Process process : bpmnModel.getProcesses()) { + for (Lane lane : process.getLanes()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId()); + processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo, scaleFactor); + } + } + + // Draw activities and their sequence-flows + for (Process process : bpmnModel.getProcesses()) { + for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) { + if (!isPartOfCollapsedSubProcess(flowNode, bpmnModel)) { + drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + } + } + + // Draw artifacts + for (Process process : bpmnModel.getProcesses()) { + + for (Artifact artifact : process.getArtifacts()) { + drawArtifact(processDiagramCanvas, bpmnModel, artifact); + } + + List subProcesses = process.findFlowElementsOfType(SubProcess.class, true); + if (subProcesses != null) { + for (SubProcess subProcess : subProcesses) { + + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(subProcess.getId()); + if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + continue; + } + + if (!isPartOfCollapsedSubProcess(subProcess, bpmnModel)) { + for (Artifact subProcessArtifact : subProcess.getArtifacts()) { + drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact); + } + } + } + } + } + + return processDiagramCanvas; + } + + protected void prepareBpmnModel(BpmnModel bpmnModel) { + + // Need to make sure all elements have positive x and y. + // Check all graphicInfo and update the elements accordingly + + List allGraphicInfos = new ArrayList<>(); + if (bpmnModel.getLocationMap() != null) { + allGraphicInfos.addAll(bpmnModel.getLocationMap().values()); + } + if (bpmnModel.getLabelLocationMap() != null) { + allGraphicInfos.addAll(bpmnModel.getLabelLocationMap().values()); + } + if (bpmnModel.getFlowLocationMap() != null) { + for (List flowGraphicInfos : bpmnModel.getFlowLocationMap().values()) { + allGraphicInfos.addAll(flowGraphicInfos); + } + } + + if (allGraphicInfos.size() > 0) { + + boolean needsTranslationX = false; + boolean needsTranslationY = false; + + double lowestX = 0.0; + double lowestY = 0.0; + + // Collect lowest x and y + for (GraphicInfo graphicInfo : allGraphicInfos) { + + double x = graphicInfo.getX(); + double y = graphicInfo.getY(); + + if (x < lowestX) { + needsTranslationX = true; + lowestX = x; + } + if (y < lowestY) { + needsTranslationY = true; + lowestY = y; + } + + } + + // Update all graphicInfo objects + if (needsTranslationX || needsTranslationY) { + + double translationX = Math.abs(lowestX); + double translationY = Math.abs(lowestY); + + for (GraphicInfo graphicInfo : allGraphicInfos) { + if (needsTranslationX) { + graphicInfo.setX(graphicInfo.getX() + translationX); + } + if (needsTranslationY) { + graphicInfo.setY(graphicInfo.getY() + translationY); + } + } + } + + } + + } + + protected void drawActivity(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, + FlowNode flowNode, List highLightedActivities, List highLightedFlows, double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) { + + ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass()); + if (drawInstruction != null) { + + drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode); + + // Gather info on the multi instance marker + boolean multiInstanceSequential = false; + boolean multiInstanceParallel = false; + boolean collapsed = false; + if (flowNode instanceof Activity) { + Activity activity = (Activity) flowNode; + MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics(); + if (multiInstanceLoopCharacteristics != null) { + multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential(); + multiInstanceParallel = !multiInstanceSequential; + } + } + + // Gather info on the collapsed marker + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (flowNode instanceof SubProcess) { + collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded(); + } else if (flowNode instanceof CallActivity) { + collapsed = true; + } + + if (scaleFactor == 1.0) { + // Actually draw the markers + processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), + multiInstanceSequential, multiInstanceParallel, collapsed); + } + + // Draw highlighted activities + if (highLightedActivities.contains(flowNode.getId())) { + drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) { + drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + + } else if (flowNode instanceof Task) { + activityDrawInstructions.get(Task.class).draw(processDiagramCanvas, bpmnModel, flowNode); + + if (highLightedActivities.contains(flowNode.getId())) { + drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) { + drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + } + + // Outgoing transitions of activity + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId())); + String defaultFlow = null; + if (flowNode instanceof Activity) { + defaultFlow = ((Activity) flowNode).getDefaultFlow(); + } else if (flowNode instanceof Gateway) { + defaultFlow = ((Gateway) flowNode).getDefaultFlow(); + } + + boolean isDefault = false; + if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) { + isDefault = true; + } + boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && sequenceFlow.getConditionExpression().trim().length() > 0 && !(flowNode instanceof Gateway); + + String sourceRef = sequenceFlow.getSourceRef(); + String targetRef = sequenceFlow.getTargetRef(); + FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef); + FlowElement targetElement = bpmnModel.getFlowElement(targetRef); + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null && graphicInfoList.size() > 0) { + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList); + int[] xPoints = new int[graphicInfoList.size()]; + int[] yPoints = new int[graphicInfoList.size()]; + + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + } + + processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor); + + // Draw sequenceflow label + GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId()); + if (labelGraphicInfo != null) { + processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); + } else { + if (drawSequenceFlowNameWithNoLabelDI) { + GraphicInfo lineCenter = getLineCenter(graphicInfoList); + processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false); + } + + } + } + } + + // Nested elements + if (flowNode instanceof FlowElementsContainer) { + for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) { + if (nestedFlowElement instanceof FlowNode && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) { + drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, + highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + } + } + } + + /** + * This method makes coordinates of connection flow better. + * + * @param processDiagramCanvas + * @param bpmnModel + * @param sourceElement + * @param targetElement + * @param graphicInfoList + * @return + */ + protected static List connectionPerfectionizer(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, BaseElement sourceElement, BaseElement targetElement, List graphicInfoList) { + GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId()); + GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId()); + + CustomDefaultProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement); + CustomDefaultProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement); + + return processDiagramCanvas.connectionPerfectionizer(sourceShapeType, targetShapeType, sourceGraphicInfo, targetGraphicInfo, graphicInfoList); + } + + /** + * This method returns shape type of base element.
+ * Each element can be presented as rectangle, rhombus, or ellipse. + * + * @param baseElement + * @return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE + */ + protected static CustomDefaultProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) { + if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) { + return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rectangle; + } else if (baseElement instanceof Gateway) { + return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rhombus; + } else if (baseElement instanceof Event) { + return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Ellipse; + } else { + // unknown source element, just do not correct coordinates + } + return null; + } + + protected static GraphicInfo getLineCenter(List graphicInfoList) { + GraphicInfo gi = new GraphicInfo(); + + int[] xPoints = new int[graphicInfoList.size()]; + int[] yPoints = new int[graphicInfoList.size()]; + + double length = 0; + double[] lengths = new double[graphicInfoList.size()]; + lengths[0] = 0; + double m; + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + length += Math.sqrt( + Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(), 2) + + Math.pow((int) graphicInfo.getY() - (int) previousGraphicInfo.getY(), 2)); + lengths[i] = length; + } + m = length / 2; + int p1 = 0; + int p2 = 1; + for (int i = 1; i < lengths.length; i++) { + double len = lengths[i]; + p1 = i - 1; + p2 = i; + if (len > m) { + break; + } + } + + GraphicInfo graphicInfo1 = graphicInfoList.get(p1); + GraphicInfo graphicInfo2 = graphicInfoList.get(p2); + + double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX(); + double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY(); + double OB = lengths[p2] - lengths[p1]; + double ob = m - lengths[p1]; + double ab = AB * ob / OB; + double oa = OA * ob / OB; + + double mx = graphicInfo1.getX() + ab; + double my = graphicInfo1.getY() + oa; + + gi.setX(mx); + gi.setY(my); + return gi; + } + + protected void drawArtifact(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + + ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass()); + if (drawInstruction != null) { + drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact); + } + } + + private static void drawHighLight(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + private static void drawHighLightRed(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightRed((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + protected static CustomDefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + + // We need to calculate maximum values to know how big the image will be in its entirety + double minX = Double.MAX_VALUE; + double maxX = 0; + double minY = Double.MAX_VALUE; + double maxY = 0; + + for (Pool pool : bpmnModel.getPools()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); + minX = graphicInfo.getX(); + maxX = graphicInfo.getX() + graphicInfo.getWidth(); + minY = graphicInfo.getY(); + maxY = graphicInfo.getY() + graphicInfo.getHeight(); + } + + List flowNodes = gatherAllFlowNodes(bpmnModel); + for (FlowNode flowNode : flowNodes) { + + GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + + // width + if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) { + maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth(); + } + if (flowNodeGraphicInfo.getX() < minX) { + minX = flowNodeGraphicInfo.getX(); + } + // height + if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) { + maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight(); + } + if (flowNodeGraphicInfo.getY() < minY) { + minY = flowNodeGraphicInfo.getY(); + } + + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null) { + for (GraphicInfo graphicInfo : graphicInfoList) { + // width + if (graphicInfo.getX() > maxX) { + maxX = graphicInfo.getX(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() > maxY) { + maxY = graphicInfo.getY(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + } + } + + List artifacts = gatherAllArtifacts(bpmnModel); + for (Artifact artifact : artifacts) { + + GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId()); + + if (artifactGraphicInfo != null) { + // width + if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) { + maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth(); + } + if (artifactGraphicInfo.getX() < minX) { + minX = artifactGraphicInfo.getX(); + } + // height + if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) { + maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight(); + } + if (artifactGraphicInfo.getY() < minY) { + minY = artifactGraphicInfo.getY(); + } + } + + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId()); + if (graphicInfoList != null) { + for (GraphicInfo graphicInfo : graphicInfoList) { + // width + if (graphicInfo.getX() > maxX) { + maxX = graphicInfo.getX(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() > maxY) { + maxY = graphicInfo.getY(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + } + + int nrOfLanes = 0; + for (Process process : bpmnModel.getProcesses()) { + for (Lane l : process.getLanes()) { + + nrOfLanes++; + + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId()); + // // width + if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) { + maxX = graphicInfo.getX() + graphicInfo.getWidth(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) { + maxY = graphicInfo.getY() + graphicInfo.getHeight(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + + // Special case, see https://activiti.atlassian.net/browse/ACT-1431 + if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) { + // Nothing to show + minX = 0; + minY = 0; + } + + return new CustomDefaultProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, + imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); + } + + protected static List gatherAllArtifacts(BpmnModel bpmnModel) { + List artifacts = new ArrayList<>(); + for (Process process : bpmnModel.getProcesses()) { + artifacts.addAll(process.getArtifacts()); + } + return artifacts; + } + + protected static List gatherAllFlowNodes(BpmnModel bpmnModel) { + List flowNodes = new ArrayList<>(); + for (Process process : bpmnModel.getProcesses()) { + flowNodes.addAll(gatherAllFlowNodes(process)); + } + return flowNodes; + } + + protected static List gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) { + List flowNodes = new ArrayList<>(); + for (FlowElement flowElement : flowElementsContainer.getFlowElements()) { + if (flowElement instanceof FlowNode) { + flowNodes.add((FlowNode) flowElement); + } + if (flowElement instanceof FlowElementsContainer) { + flowNodes.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement)); + } + } + return flowNodes; + } + + protected boolean isPartOfCollapsedSubProcess(FlowElement flowElement, BpmnModel model) { + SubProcess subProcess = flowElement.getSubProcess(); + if (subProcess != null) { + GraphicInfo graphicInfo = model.getGraphicInfo(subProcess.getId()); + if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + return true; + } + + return isPartOfCollapsedSubProcess(subProcess, model); + } + + return false; + } + + public Map, ActivityDrawInstruction> getActivityDrawInstructions() { + return activityDrawInstructions; + } + + public void setActivityDrawInstructions( + Map, ActivityDrawInstruction> activityDrawInstructions) { + this.activityDrawInstructions = activityDrawInstructions; + } + + public Map, ArtifactDrawInstruction> getArtifactDrawInstructions() { + return artifactDrawInstructions; + } + + public void setArtifactDrawInstructions( + Map, ArtifactDrawInstruction> artifactDrawInstructions) { + this.artifactDrawInstructions = artifactDrawInstructions; + } + + protected interface ActivityDrawInstruction { + void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode); + } + + protected interface ArtifactDrawInstruction { + void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java new file mode 100644 index 000000000..3cdfcf415 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java @@ -0,0 +1,61 @@ +package org.dromara.workflow.flowable.cmd; + +import cn.hutool.core.collection.CollUtil; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES; + +/** + * 串行加签 + * + * @author may + */ +public class AddSequenceMultiInstanceCmd implements Command { + + /** + * 执行id + */ + private final String executionId; + + /** + * 会签人员集合KEY + */ + private final String assigneeList; + + /** + * 加签人员 + */ + private final List assignees; + + public AddSequenceMultiInstanceCmd(String executionId, String assigneeList, List assignees) { + this.executionId = executionId; + this.assigneeList = assigneeList; + this.assignees = assignees; + } + + @Override + public Void execute(CommandContext commandContext) { + ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(); + ExecutionEntity entity = executionEntityManager.findById(executionId); + // 多实例任务总数加 assignees.size() + if (entity.getVariable(NUMBER_OF_INSTANCES) instanceof Integer nrOfInstances) { + entity.setVariable(NUMBER_OF_INSTANCES, nrOfInstances + assignees.size()); + } + // 设置流程变量 + if (entity.getVariable(assigneeList) instanceof List userIds) { + CollUtil.addAll(userIds, assignees); + Map variables = new HashMap<>(16); + variables.put(assigneeList, userIds); + entity.setVariables(variables); + } + return null; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java new file mode 100644 index 000000000..fc335048c --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java @@ -0,0 +1,68 @@ +package org.dromara.workflow.flowable.cmd; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.AttachmentEntity; +import org.flowable.engine.impl.persistence.entity.AttachmentEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * 串行加签 + * + * @author 附件上传 + */ +public class AttachmentCmd implements Command { + + private final String fileId; + + private final String taskId; + + private final String processInstanceId; + + public AttachmentCmd(String fileId, String taskId, String processInstanceId) { + this.fileId = fileId; + this.taskId = taskId; + this.processInstanceId = processInstanceId; + } + + @Override + public Boolean execute(CommandContext commandContext) { + try { + if (StringUtils.isNotBlank(fileId)) { + List fileIds = StreamUtils.toList(Arrays.asList(fileId.split(StrUtil.COMMA)), Long::valueOf); + List sysOssVos = SpringUtils.getBean(ISysOssService.class).listByIds(fileIds); + if (CollUtil.isNotEmpty(sysOssVos)) { + for (SysOssVo sysOssVo : sysOssVos) { + AttachmentEntityManager attachmentEntityManager = CommandContextUtil.getAttachmentEntityManager(); + AttachmentEntity attachmentEntity = attachmentEntityManager.create(); + attachmentEntity.setRevision(1); + attachmentEntity.setUserId(LoginHelper.getUserId().toString()); + attachmentEntity.setName(sysOssVo.getOriginalName()); + attachmentEntity.setDescription(sysOssVo.getOriginalName()); + attachmentEntity.setType(sysOssVo.getFileSuffix()); + attachmentEntity.setTaskId(taskId); + attachmentEntity.setProcessInstanceId(processInstanceId); + attachmentEntity.setContentId(sysOssVo.getOssId().toString()); + attachmentEntity.setTime(new Date()); + attachmentEntityManager.insert(attachmentEntity); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return true; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java new file mode 100644 index 000000000..215d31023 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java @@ -0,0 +1,36 @@ +package org.dromara.workflow.flowable.cmd; + +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.io.Serializable; + +/** + * 删除执行数据 + * + * @author may + */ +public class DeleteExecutionCmd implements Command, Serializable { + + /** + * 执行id + */ + private final String executionId; + + public DeleteExecutionCmd(String executionId) { + this.executionId = executionId; + } + + @Override + public Void execute(CommandContext commandContext) { + ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(); + ExecutionEntity entity = executionEntityManager.findById(executionId); + if (entity != null) { + executionEntityManager.deleteExecutionAndRelatedData(entity, "", false, false); + } + return null; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java new file mode 100644 index 000000000..6773eefff --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java @@ -0,0 +1,82 @@ +package org.dromara.workflow.flowable.cmd; + +import cn.hutool.core.util.ObjectUtil; +import lombok.AllArgsConstructor; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.dromara.workflow.common.constant.FlowConstant.LOOP_COUNTER; +import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES; + + +/** + * 串行减签 + * + * @author may + */ +@AllArgsConstructor +public class DeleteSequenceMultiInstanceCmd implements Command { + + /** + * 当前节点审批人员id + */ + private final String currentUserId; + + /** + * 执行id + */ + private final String executionId; + + /** + * 会签人员集合KEY + */ + private final String assigneeList; + + /** + * 减签人员 + */ + private final List assignees; + + + @Override + @SuppressWarnings("unchecked") + public Void execute(CommandContext commandContext) { + ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(); + ExecutionEntity entity = executionEntityManager.findById(executionId); + // 设置流程变量 + List userIds = new ArrayList<>(); + List variable = (List) entity.getVariable(assigneeList); + for (Object o : variable) { + userIds.add(Long.valueOf(o.toString())); + } + List userIdList = new ArrayList<>(); + userIds.forEach(e -> { + Long userId = assignees.stream().filter(id -> ObjectUtil.equals(id, e)).findFirst().orElse(null); + if (userId == null) { + userIdList.add(e); + } + }); + // 当前任务执行位置 + int loopCounterIndex = -1; + for (int i = 0; i < userIdList.size(); i++) { + Long userId = userIdList.get(i); + if (currentUserId.equals(userId.toString())) { + loopCounterIndex = i; + } + } + Map variables = new HashMap<>(16); + variables.put(NUMBER_OF_INSTANCES, userIdList.size()); + variables.put(assigneeList, userIdList); + variables.put(LOOP_COUNTER, loopCounterIndex); + entity.setVariables(variables); + return null; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java new file mode 100644 index 000000000..1f3088bfc --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java @@ -0,0 +1,39 @@ +package org.dromara.workflow.flowable.cmd; + +import org.dromara.common.core.utils.StreamUtils; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.io.Serializable; +import java.util.List; + +/** + * 获取并行网关执行后保留的执行实例数据 + * + * @author may + */ +public class ExecutionChildByExecutionIdCmd implements Command>, Serializable { + + /** + * 当前任务执行实例id + */ + private final String executionId; + + public ExecutionChildByExecutionIdCmd(String executionId) { + this.executionId = executionId; + } + + @Override + public List execute(CommandContext commandContext) { + ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(); + // 获取当前执行数据 + ExecutionEntity executionEntity = executionEntityManager.findById(executionId); + // 通过当前执行数据的父执行,查询所有子执行数据 + List allChildrenExecution = + executionEntityManager.collectChildren(executionEntity.getParent()); + return StreamUtils.filter(allChildrenExecution, e -> !e.isActive()); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java new file mode 100644 index 000000000..3ba120aef --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.flowable.cmd; + +import org.dromara.common.core.exception.ServiceException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntity; +import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +/** + * 修改流程状态 + * + * @author may + */ +public class UpdateBusinessStatusCmd implements Command { + + private final String processInstanceId; + private final String status; + + public UpdateBusinessStatusCmd(String processInstanceId, String status) { + this.processInstanceId = processInstanceId; + this.status = status; + } + + @Override + public Boolean execute(CommandContext commandContext) { + try { + HistoricProcessInstanceEntityManager manager = CommandContextUtil.getHistoricProcessInstanceEntityManager(); + HistoricProcessInstanceEntity processInstance = manager.findById(processInstanceId); + processInstance.setBusinessStatus(status); + manager.update(processInstance); + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java new file mode 100644 index 000000000..42f6d1c78 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java @@ -0,0 +1,51 @@ +package org.dromara.workflow.flowable.cmd; + +import org.dromara.common.core.exception.ServiceException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.task.service.HistoricTaskService; +import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity; + +import java.util.Date; +import java.util.List; + + +/** + * 修改流程历史 + * + * @author may + */ +public class UpdateHiTaskInstCmd implements Command { + + private final List taskIds; + + private final String processDefinitionId; + + private final String processInstanceId; + + public UpdateHiTaskInstCmd(List taskIds, String processDefinitionId, String processInstanceId) { + this.taskIds = taskIds; + this.processDefinitionId = processDefinitionId; + this.processInstanceId = processInstanceId; + } + + @Override + public Boolean execute(CommandContext commandContext) { + try { + HistoricTaskService historicTaskService = CommandContextUtil.getHistoricTaskService(); + for (String taskId : taskIds) { + HistoricTaskInstanceEntity historicTask = historicTaskService.getHistoricTask(taskId); + if (historicTask != null) { + historicTask.setProcessDefinitionId(processDefinitionId); + historicTask.setProcessInstanceId(processInstanceId); + historicTask.setCreateTime(new Date()); + CommandContextUtil.getHistoricTaskService().updateHistoricTask(historicTask, true); + } + } + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java new file mode 100644 index 000000000..95233d1eb --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java @@ -0,0 +1,32 @@ +package org.dromara.workflow.flowable.config; + +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import org.flowable.common.engine.impl.cfg.IdGenerator; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; + + +/** + * flowable配置 + * + * @author may + */ +@Configuration +public class FlowableConfig implements EngineConfigurationConfigurer { + + @Autowired + private GlobalFlowableListener globalFlowableListener; + @Autowired + private IdentifierGenerator identifierGenerator; + + @Override + public void configure(SpringProcessEngineConfiguration processEngineConfiguration) { + processEngineConfiguration.setIdGenerator(() -> identifierGenerator.nextId(null).toString()); + processEngineConfiguration.setEventListeners(Collections.singletonList(globalFlowableListener)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java new file mode 100644 index 000000000..2ff46a987 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java @@ -0,0 +1,91 @@ +package org.dromara.workflow.flowable.config; + +import cn.hutool.core.collection.CollUtil; +import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.common.engine.api.delegate.event.*; +import org.flowable.common.engine.impl.cfg.TransactionState; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.task.Comment; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.List; + + +/** + * 引擎调度监听 + * + * @author may + */ +@Component +public class GlobalFlowableListener implements FlowableEventListener { + + @Autowired + @Lazy + private TaskService taskService; + + @Autowired + @Lazy + private RuntimeService runtimeService; + + @Autowired + @Lazy + private RepositoryService repositoryService; + + @Override + public void onEvent(FlowableEvent flowableEvent) { + if (flowableEvent instanceof FlowableEngineEvent flowableEngineEvent) { + FlowableEngineEventType engineEventType = (FlowableEngineEventType) flowableEvent.getType(); + switch (engineEventType) { + case JOB_EXECUTION_SUCCESS -> jobExecutionSuccess((FlowableEngineEntityEvent) flowableEngineEvent); + } + } + } + + @Override + public boolean isFailOnException() { + return true; + } + + @Override + public boolean isFireOnTransactionLifecycleEvent() { + return false; + } + + @Override + public String getOnTransaction() { + return TransactionState.COMMITTED.name(); + } + + /** + * 处理边界定时事件自动审批记录 + * + * @param event 事件 + */ + protected void jobExecutionSuccess(FlowableEngineEntityEvent event) { + Execution execution = runtimeService.createExecutionQuery().executionId(event.getExecutionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(event.getProcessDefinitionId()); + FlowElement flowElement = bpmnModel.getFlowElement(execution.getActivityId()); + if (flowElement instanceof BoundaryEvent) { + String attachedToRefId = ((BoundaryEvent) flowElement).getAttachedToRefId(); + List list = runtimeService.createExecutionQuery().activityId(attachedToRefId).list(); + for (Execution ex : list) { + Task task = taskService.createTaskQuery().executionId(ex.getId()).singleResult(); + if (task != null) { + List taskComments = taskService.getTaskComments(task.getId()); + if (CollUtil.isEmpty(taskComments)) { + taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), "超时自动审批!"); + } + } + } + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java new file mode 100644 index 000000000..9da577633 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java @@ -0,0 +1,73 @@ +package org.dromara.workflow.flowable.strategy; + +import org.dromara.common.core.utils.StringUtils; +import org.dromara.workflow.annotation.FlowListenerAnnotation; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 流程任务监听策略 + * + * @author may + * @date 2023-12-27 + */ +@Component +public class FlowEventStrategy implements BeanPostProcessor { + + private final Map flowTaskEventHandlers = new HashMap<>(); + private final Map flowProcessEventHandlers = new HashMap<>(); + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof FlowTaskEventHandler) { + FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class); + if (null != annotation) { + if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isNotBlank(annotation.taskDefId())) { + String id = annotation.processDefinitionKey() + "_" + annotation.taskDefId(); + if (!flowTaskEventHandlers.containsKey(id)) { + flowTaskEventHandlers.put(id, (FlowTaskEventHandler) bean); + } + } + } + } + if (bean instanceof FlowProcessEventHandler) { + FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class); + if (null != annotation) { + if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isBlank(annotation.taskDefId())) { + if (!flowProcessEventHandlers.containsKey(annotation.processDefinitionKey())) { + flowProcessEventHandlers.put(annotation.processDefinitionKey(), (FlowProcessEventHandler) bean); + } + } + } + } + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } + + /** + * 获取可执行bean + * + * @param key key + */ + public FlowTaskEventHandler getTaskHandler(String key) { + if (!flowTaskEventHandlers.containsKey(key)) { + return null; + } + return flowTaskEventHandlers.get(key); + } + + /** + * 获取可执行bean + * + * @param key key + */ + public FlowProcessEventHandler getProcessHandler(String key) { + if (!flowProcessEventHandlers.containsKey(key)) { + return null; + } + return flowProcessEventHandlers.get(key); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java new file mode 100644 index 000000000..4af2c4784 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java @@ -0,0 +1,20 @@ +package org.dromara.workflow.flowable.strategy; + + +/** + * 流程监听 + * + * @author may + * @date 2023-12-27 + */ +public interface FlowProcessEventHandler { + + /** + * 执行办理任务监听 + * + * @param businessKey 业务id + * @param status 状态 + * @param submit 当为true时为申请人节点办理 + */ + void handleProcess(String businessKey, String status, boolean submit); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java new file mode 100644 index 000000000..4cf9d8931 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java @@ -0,0 +1,20 @@ +package org.dromara.workflow.flowable.strategy; + +import org.flowable.task.api.Task; + +/** + * 流程任务监听 + * + * @author may + * @date 2023-12-27 + */ +public interface FlowTaskEventHandler { + + /** + * 执行办理任务监听 + * + * @param task 任务 + * @param businessKey 业务id + */ + void handleTask(Task task, String businessKey); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java new file mode 100644 index 000000000..dbb32aef8 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java @@ -0,0 +1,24 @@ +package org.dromara.workflow.listener; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.workflow.annotation.FlowListenerAnnotation; +import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler; +import org.springframework.stereotype.Component; + +/** + * 自定义监听测试 + * + * @author may + * @date 2023-12-27 + */ +@Slf4j +@Component +@FlowListenerAnnotation(processDefinitionKey = "leave1") +public class TestCustomProcessHandler implements FlowProcessEventHandler { + + + @Override + public void handleProcess(String businessKey, String status, boolean submit) { + log.info("业务ID:" + businessKey + ",状态:" + status); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java new file mode 100644 index 000000000..fb04cd01f --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java @@ -0,0 +1,24 @@ +package org.dromara.workflow.listener; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.workflow.annotation.FlowListenerAnnotation; +import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Component; + +/** + * 自定义监听测试 + * + * @author may + * @date 2023-12-27 + */ +@Slf4j +@Component +@FlowListenerAnnotation(processDefinitionKey = "leave1", taskDefId = "Activity_14633hx") +public class TestCustomTaskHandler implements FlowTaskEventHandler { + + @Override + public void handleTask(Task task, String businessKey) { + log.info("任务名称:" + task.getName() + ",业务ID:" + businessKey); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java new file mode 100644 index 000000000..1a75414c0 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java @@ -0,0 +1,29 @@ +package org.dromara.workflow.listener; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.TaskService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Component; + +/** + * 流程实例监听测试 + * + * @author may + * @date 2023-12-12 + */ +@Slf4j +@RequiredArgsConstructor +@Component("testLeaveExecutionListener") +public class TestLeaveExecutionListener implements ExecutionListener { + + private final TaskService taskService; + + @Override + public void notify(DelegateExecution execution) { + Task task = taskService.createTaskQuery().executionId(execution.getId()).singleResult(); + log.info("执行监听【" + task.getName() + "】"); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java new file mode 100644 index 000000000..5c62417a2 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java @@ -0,0 +1,21 @@ +package org.dromara.workflow.listener; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.task.service.delegate.DelegateTask; +import org.flowable.task.service.delegate.TaskListener; +import org.springframework.stereotype.Component; + +/** + * 流程任务监听测试 + * + * @author may + * @date 2023-12-12 + */ +@Slf4j +@Component("testLeaveTaskListener") +public class TestLeaveTaskListener implements TaskListener { + @Override + public void notify(DelegateTask delegateTask) { + log.info("执行监听【" + delegateTask.getName() + "】"); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java new file mode 100644 index 000000000..a3a41c924 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java @@ -0,0 +1,16 @@ +package org.dromara.workflow.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.workflow.domain.ActHiProcinst; + +/** + * 流程实例Mapper接口 + * + * @author may + * @date 2023-07-22 + */ +@InterceptorIgnore(tenantLine = "true") +public interface ActHiProcinstMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java new file mode 100644 index 000000000..f8bb2605c --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java @@ -0,0 +1,16 @@ +package org.dromara.workflow.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import org.dromara.workflow.domain.ActHiTaskinst; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 流程历史任务Mapper接口 + * + * @author gssong + * @date 2024-03-02 + */ +@InterceptorIgnore(tenantLine = "true") +public interface ActHiTaskinstMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java new file mode 100644 index 000000000..e7094f543 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java @@ -0,0 +1,38 @@ +package org.dromara.workflow.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.workflow.domain.vo.TaskVo; + + +/** + * 任务信息Mapper接口 + * + * @author gssong + * @date 2024-03-02 + */ +@InterceptorIgnore(tenantLine = "true") +public interface ActTaskMapper extends BaseMapperPlus { + /** + * 获取待办信息 + * + * @param page 分页 + * @param queryWrapper 条件 + * @return 结果 + */ + Page getTaskWaitByPage(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 查询当前用户的抄送 + * + * @param page 分页 + * @param queryWrapper 条件 + * @return 结果 + */ + Page getTaskCopyByPage(@Param("page") Page page, @Param(Constants.WRAPPER) QueryWrapper queryWrapper); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java new file mode 100644 index 000000000..cd1edba89 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java @@ -0,0 +1,15 @@ +package org.dromara.workflow.mapper; + +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.workflow.domain.TestLeave; +import org.dromara.workflow.domain.vo.TestLeaveVo; + +/** + * 请假Mapper接口 + * + * @author may + * @date 2023-07-21 + */ +public interface TestLeaveMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java new file mode 100644 index 000000000..98aea024e --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java @@ -0,0 +1,15 @@ +package org.dromara.workflow.mapper; + +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.workflow.domain.WfCategory; +import org.dromara.workflow.domain.vo.WfCategoryVo; + +/** + * 流程分类Mapper接口 + * + * @author may + * @date 2023-06-27 + */ +public interface WfCategoryMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java new file mode 100644 index 000000000..e802c6972 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java @@ -0,0 +1,31 @@ +package org.dromara.workflow.service; + + +import org.dromara.workflow.domain.ActHiProcinst; + +import java.util.List; + +/** + * 流程实例Service接口 + * + * @author may + * @date 2023-07-22 + */ +public interface IActHiProcinstService { + + /** + * 按照业务id查询 + * + * @param businessKeys 业务id + * @return 结果 + */ + List selectByBusinessKeyIn(List businessKeys); + + /** + * 按照业务id查询 + * + * @param businessKey 业务id + * @return 结果 + */ + ActHiProcinst selectByBusinessKey(String businessKey); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java new file mode 100644 index 000000000..d71e93f87 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java @@ -0,0 +1,11 @@ +package org.dromara.workflow.service; + + +/** + * 流程历史任务Service接口 + * + * @author gssong + * @date 2024-03-02 + */ +public interface IActHiTaskinstService { +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java new file mode 100644 index 000000000..c7f6c1858 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java @@ -0,0 +1,71 @@ +package org.dromara.workflow.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.bo.ModelBo; +import org.dromara.workflow.domain.vo.ModelVo; +import org.flowable.engine.repository.Model; + + +/** + * 模型管理 服务层 + * + * @author may + */ +public interface IActModelService { + /** + * 分页查询模型 + * + * @param modelBo 模型参数 + * @return 返回分页列表 + */ + TableDataInfo page(ModelBo modelBo); + + /** + * 新增模型 + * + * @param modelBo 模型请求对象 + * @return 结果 + */ + boolean saveNewModel(ModelBo modelBo); + + /** + * 查询模型 + * + * @param modelId 模型id + * @return 模型数据 + */ + ModelVo getInfo(String modelId); + + /** + * 修改模型信息 + * + * @param modelBo 模型数据 + * @return 结果 + */ + boolean update(ModelBo modelBo); + + /** + * 编辑模型XML + * + * @param modelBo 模型数据 + * @return 结果 + */ + boolean editModelXml(ModelBo modelBo); + + /** + * 模型部署 + * + * @param id 模型id + * @return 结果 + */ + boolean modelDeploy(String id); + + /** + * 导出模型zip压缩包 + * + * @param modelId 模型id + * @param response 相应 + */ + void exportZip(String modelId, HttpServletResponse response); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java new file mode 100644 index 000000000..0fd94c807 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java @@ -0,0 +1,90 @@ +package org.dromara.workflow.service; + +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.bo.ProcessDefinitionBo; +import org.dromara.workflow.domain.vo.ProcessDefinitionVo; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 流程定义 服务层 + * + * @author may + */ +public interface IActProcessDefinitionService { + /** + * 分页查询 + * + * @param processDefinitionBo 参数 + * @return 返回分页列表 + */ + TableDataInfo page(ProcessDefinitionBo processDefinitionBo); + + /** + * 查询历史流程定义列表 + * + * @param key 流程定义key + * @return 结果 + */ + List getProcessDefinitionListByKey(String key); + + /** + * 查看流程定义图片 + * + * @param processDefinitionId 流程定义id + * @return 结果 + */ + String processDefinitionImage(String processDefinitionId); + + /** + * 查看流程定义xml文件 + * + * @param processDefinitionId 流程定义id + * @return 结果 + */ + String processDefinitionXml(String processDefinitionId); + + /** + * 删除流程定义 + * + * @param deploymentId 部署id + * @param processDefinitionId 流程定义id + * @return 结果 + */ + boolean deleteDeployment(String deploymentId, String processDefinitionId); + + /** + * 激活或者挂起流程定义 + * + * @param processDefinitionId 流程定义id + * @return 结果 + */ + boolean updateProcessDefState(String processDefinitionId); + + /** + * 迁移流程定义 + * + * @param currentProcessDefinitionId 当前流程定义id + * @param fromProcessDefinitionId 需要迁移到的流程定义id + * @return 结果 + */ + boolean migrationProcessDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId); + + /** + * 流程定义转换为模型 + * + * @param processDefinitionId 流程定义id + * @return 结果 + */ + boolean convertToModel(String processDefinitionId); + + /** + * 通过zip或xml部署流程定义 + * + * @param file 文件 + * @param categoryCode 分类 + * @return 结果 + */ + boolean deployByFile(MultipartFile file, String categoryCode); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java new file mode 100644 index 000000000..c8af5a4c4 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java @@ -0,0 +1,113 @@ +package org.dromara.workflow.service; + +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.bo.ProcessInstanceBo; +import org.dromara.workflow.domain.bo.ProcessInvalidBo; +import org.dromara.workflow.domain.bo.TaskUrgingBo; +import org.dromara.workflow.domain.vo.ProcessInstanceVo; + +import java.util.List; +import java.util.Map; + +/** + * 流程实例 服务层 + * + * @author may + */ +public interface IActProcessInstanceService { + /** + * 通过流程实例id获取历史流程图 + * + * @param processInstanceId 流程实例id + * @return 结果 + */ + String getHistoryProcessImage(String processInstanceId); + + /** + * 通过流程实例id获取历史流程图运行中,历史等节点 + * + * @param processInstanceId 流程实例id + * @return 结果 + */ + Map getHistoryProcessList(String processInstanceId); + + /** + * 分页查询正在运行的流程实例 + * + * @param processInstanceBo 参数 + * @return 结果 + */ + TableDataInfo getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo); + + /** + * 分页查询已结束的流程实例 + * + * @param processInstanceBo 参数 + * @return 结果 + */ + TableDataInfo getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo); + + /** + * 获取审批记录 + * + * @param processInstanceId 流程实例id + * @return 结果 + */ + Map getHistoryRecord(String processInstanceId); + + /** + * 作废流程实例,不会删除历史记录(删除运行中的实例) + * + * @param processInvalidBo 参数 + * @return 结果 + */ + boolean deleteRuntimeProcessInst(ProcessInvalidBo processInvalidBo); + + /** + * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + * @return 结果 + */ + boolean deleteRuntimeProcessAndHisInst(List processInstanceIds); + + /** + * 按照业务id删除 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param businessKeys 业务id + * @return 结果 + */ + boolean deleteRuntimeProcessAndHisInstByBusinessKeys(List businessKeys); + + /** + * 已完成的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + * @return 结果 + */ + boolean deleteFinishProcessAndHisInst(List processInstanceIds); + + /** + * 撤销流程申请 + * + * @param processInstanceId 流程实例id + * @return 结果 + */ + boolean cancelProcessApply(String processInstanceId); + + /** + * 分页查询当前登录人单据 + * + * @param processInstanceBo 参数 + * @return 结果 + */ + TableDataInfo getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo); + + /** + * 任务催办(给当前任务办理人发送站内信,邮件,短信等) + * + * @param taskUrgingBo 任务催办 + * @return 结果 + */ + boolean taskUrging(TaskUrgingBo taskUrgingBo); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java new file mode 100644 index 000000000..b5596a53f --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java @@ -0,0 +1,129 @@ +package org.dromara.workflow.service; + +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.bo.*; +import org.dromara.workflow.domain.vo.TaskVo; + +import java.util.Map; + +/** + * 任务 服务层 + * + * @author may + */ +public interface IActTaskService { + /** + * 启动任务 + * + * @param startProcessBo 启动流程参数 + * @return 结果 + */ + Map startWorkFlow(StartProcessBo startProcessBo); + + + /** + * 办理任务 + * + * @param completeTaskBo 办理任务参数 + * @return 结果 + */ + boolean completeTask(CompleteTaskBo completeTaskBo); + + /** + * 查询当前用户的待办任务 + * + * @param taskBo 参数 + * @return 结果 + */ + TableDataInfo getTaskWaitByPage(TaskBo taskBo); + + /** + * 查询当前租户所有待办任务 + * + * @param taskBo 参数 + * @return 结果 + */ + TableDataInfo getAllTaskWaitByPage(TaskBo taskBo); + + + /** + * 查询当前用户的已办任务 + * + * @param taskBo 参数 + * @return 结果 + */ + TableDataInfo getTaskFinishByPage(TaskBo taskBo); + + /** + * 查询当前用户的抄送 + * + * @param taskBo 参数 + * @return 结果 + */ + TableDataInfo getTaskCopyByPage(TaskBo taskBo); + + /** + * 查询当前租户所有已办任务 + * + * @param taskBo 参数 + * @return 结果 + */ + TableDataInfo getAllTaskFinishByPage(TaskBo taskBo); + + /** + * 委派任务 + * + * @param delegateBo 参数 + * @return 结果 + */ + boolean delegateTask(DelegateBo delegateBo); + + /** + * 终止任务 + * + * @param terminationBo 参数 + * @return 结果 + */ + boolean terminationTask(TerminationBo terminationBo); + + /** + * 转办任务 + * + * @param transmitBo 参数 + * @return 结果 + */ + boolean transferTask(TransmitBo transmitBo); + + /** + * 会签任务加签 + * + * @param addMultiBo 参数 + * @return 结果 + */ + boolean addMultiInstanceExecution(AddMultiBo addMultiBo); + + /** + * 会签任务减签 + * + * @param deleteMultiBo 参数 + * @return 结果 + */ + boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo); + + /** + * 驳回审批 + * + * @param backProcessBo 参数 + * @return 流程实例id + */ + String backProcess(BackProcessBo backProcessBo); + + /** + * 修改任务办理人 + * + * @param taskIds 任务id + * @param userId 办理人id + * @return 结果 + */ + boolean updateAssignee(String[] taskIds, String userId); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java new file mode 100644 index 000000000..606b255ac --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java @@ -0,0 +1,49 @@ +package org.dromara.workflow.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.TestLeave; +import org.dromara.workflow.domain.bo.TestLeaveBo; +import org.dromara.workflow.domain.vo.TestLeaveVo; + +import java.util.Collection; +import java.util.List; + +/** + * 请假Service接口 + * + * @author may + * @date 2023-07-21 + */ +public interface ITestLeaveService { + + /** + * 查询请假 + */ + TestLeaveVo queryById(Long id); + + /** + * 查询请假列表 + */ + TableDataInfo queryPageList(TestLeaveBo bo, PageQuery pageQuery); + + /** + * 查询请假列表 + */ + List queryList(TestLeaveBo bo); + + /** + * 新增请假 + */ + TestLeave insertByBo(TestLeaveBo bo); + + /** + * 修改请假 + */ + TestLeave updateByBo(TestLeaveBo bo); + + /** + * 校验并批量删除请假信息 + */ + Boolean deleteWithValidByIds(Collection ids); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java new file mode 100644 index 000000000..acf0aa2bc --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java @@ -0,0 +1,51 @@ +package org.dromara.workflow.service; + +import org.dromara.workflow.domain.WfCategory; +import org.dromara.workflow.domain.bo.WfCategoryBo; +import org.dromara.workflow.domain.vo.WfCategoryVo; + +import java.util.Collection; +import java.util.List; + +/** + * 流程分类Service接口 + * + * @author may + * @date 2023-06-28 + */ +public interface IWfCategoryService { + + /** + * 查询流程分类 + */ + WfCategoryVo queryById(Long id); + + + /** + * 查询流程分类列表 + */ + List queryList(WfCategoryBo bo); + + /** + * 新增流程分类 + */ + Boolean insertByBo(WfCategoryBo bo); + + /** + * 修改流程分类 + */ + Boolean updateByBo(WfCategoryBo bo); + + /** + * 校验并批量删除流程分类信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 按照类别编码查询 + * + * @param categoryCode 分类比吗 + * @return 结果 + */ + WfCategory queryByCategoryCode(String categoryCode); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java new file mode 100644 index 000000000..3c915727a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java @@ -0,0 +1,60 @@ +package org.dromara.workflow.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.domain.SysUserRole; +import org.dromara.system.domain.bo.SysUserBo; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.workflow.domain.bo.SysUserMultiBo; +import org.dromara.workflow.domain.vo.TaskVo; + +import java.util.List; + +/** + * 工作流用户选人管理 服务层 + * + * @author may + */ +public interface IWorkflowUserService { + + /** + * 分页查询工作流选择加签人员 + * + * @param sysUserMultiBo 参数 + * @return 结果 + */ + TableDataInfo getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo); + + /** + * 查询工作流选择减签人员 + * + * @param taskId 任务id + * @return 结果 + */ + List getWorkflowDeleteMultiInstanceList(String taskId); + + /** + * 按照用户id查询用户 + * + * @param userIds 用户id + * @return 结果 + */ + List getUserListByIds(List userIds); + + /** + * 按照角色id查询关联用户id + * + * @param roleIds 角色id + * @return 结果 + */ + List getUserRoleListByRoleIds(List roleIds); + + /** + * 分页查询用户 + * + * @param sysUserBo 参数 + * @param pageQuery 分页 + * @return 结果 + */ + TableDataInfo getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java new file mode 100644 index 000000000..06d607b4f --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java @@ -0,0 +1,51 @@ +package org.dromara.workflow.service.impl; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.workflow.domain.ActHiProcinst; +import org.dromara.workflow.mapper.ActHiProcinstMapper; +import org.dromara.workflow.service.IActHiProcinstService; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * 流程实例Service业务层处理 + * + * @author may + * @date 2023-07-22 + */ +@RequiredArgsConstructor +@Service +public class ActHiProcinstServiceImpl implements IActHiProcinstService { + + private final ActHiProcinstMapper baseMapper; + + /** + * 按照业务id查询 + * + * @param businessKeys 业务id + */ + @Override + public List selectByBusinessKeyIn(List businessKeys) { + return baseMapper.selectList(new LambdaQueryWrapper() + .in(ActHiProcinst::getBusinessKey, businessKeys) + .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId())); + } + + /** + * 按照业务id查询 + * + * @param businessKey 业务id + */ + @Override + public ActHiProcinst selectByBusinessKey(String businessKey) { + return baseMapper.selectOne(new LambdaQueryWrapper() + .eq(ActHiProcinst::getBusinessKey, businessKey) + .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId())); + + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java new file mode 100644 index 000000000..7faabc2b4 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java @@ -0,0 +1,18 @@ +package org.dromara.workflow.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.dromara.workflow.service.IActHiTaskinstService; + + +/** + * 流程历史任务Service业务层处理 + * + * @author gssong + * @date 2024-03-02 + */ +@RequiredArgsConstructor +@Service +public class ActHiTaskinstServiceImpl implements IActHiTaskinstService { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java new file mode 100644 index 000000000..01bb12e57 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java @@ -0,0 +1,334 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.json.JSONUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.PNGTranscoder; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.domain.bo.ModelBo; +import org.dromara.workflow.domain.vo.ModelVo; +import org.dromara.workflow.service.IActModelService; +import org.dromara.workflow.utils.ModelUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ModelQuery; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.validation.ValidationError; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * 模型管理 服务层实现 + * + * @author may + */ +@RequiredArgsConstructor +@Service +public class ActModelServiceImpl implements IActModelService { + + private final RepositoryService repositoryService; + + /** + * 分页查询模型 + * + * @param modelBo 模型参数 + * @return 返回分页列表 + */ + @Override + public TableDataInfo page(ModelBo modelBo) { + ModelQuery query = repositoryService.createModelQuery(); + query.modelTenantId(TenantHelper.getTenantId()); + if (StringUtils.isNotEmpty(modelBo.getName())) { + query.modelNameLike("%" + modelBo.getName() + "%"); + } + if (StringUtils.isNotEmpty(modelBo.getKey())) { + query.modelKey(modelBo.getKey()); + } + if (StringUtils.isNotEmpty(modelBo.getCategoryCode())) { + query.modelCategory(modelBo.getCategoryCode()); + } + query.orderByLastUpdateTime().desc(); + // 创建时间降序排列 + query.orderByCreateTime().desc(); + // 分页查询 + List modelList = query.listPage(modelBo.getPageNum(), modelBo.getPageSize()); + // 总记录数 + long total = query.count(); + return new TableDataInfo<>(modelList, total); + } + + /** + * 新增模型 + * + * @param modelBo 模型请求对象 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean saveNewModel(ModelBo modelBo) { + try { + int version = 0; + String key = modelBo.getKey(); + String name = modelBo.getName(); + String description = modelBo.getDescription(); + String categoryCode = modelBo.getCategoryCode(); + String xml = modelBo.getXml(); + Model checkModel = repositoryService.createModelQuery().modelKey(key).modelTenantId(TenantHelper.getTenantId()).singleResult(); + if (ObjectUtil.isNotNull(checkModel)) { + throw new ServiceException("模型key已存在!"); + } + //初始空的模型 + Model model = repositoryService.newModel(); + model.setKey(key); + model.setName(name); + model.setVersion(version); + model.setCategory(categoryCode); + model.setMetaInfo(description); + model.setTenantId(TenantHelper.getTenantId()); + //保存初始化的模型基本信息数据 + repositoryService.saveModel(model); + repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml)); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 查询模型 + * + * @param id 模型id + * @return 模型数据 + */ + @Override + public ModelVo getInfo(String id) { + ModelVo modelVo = new ModelVo(); + Model model = repositoryService.getModel(id); + if (model != null) { + try { + byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId()); + modelVo.setXml(StrUtil.utf8Str(modelEditorSource)); + modelVo.setId(model.getId()); + modelVo.setKey(model.getKey()); + modelVo.setName(model.getName()); + modelVo.setCategoryCode(model.getCategory()); + modelVo.setDescription(model.getMetaInfo()); + return modelVo; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + return modelVo; + } + + /** + * 修改模型信息 + * + * @param modelBo 模型数据 + * @return 结果 + */ + @Override + public boolean update(ModelBo modelBo) { + try { + Model model = repositoryService.getModel(modelBo.getId()); + List list = repositoryService.createModelQuery().modelTenantId(TenantHelper.getTenantId()).modelKey(modelBo.getKey()).list(); + list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> { + throw new ServiceException("模型KEY已存在!"); + }); + model.setCategory(modelBo.getCategoryCode()); + model.setMetaInfo(modelBo.getDescription()); + repositoryService.saveModel(model); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + return true; + } + + /** + * 编辑模型XML + * + * @param modelBo 模型数据 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean editModelXml(ModelBo modelBo) { + try { + String xml = modelBo.getXml(); + String svg = modelBo.getSvg(); + String modelId = modelBo.getId(); + String key = modelBo.getKey(); + String name = modelBo.getName(); + BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xml); + ModelUtils.checkBpmnModel(bpmnModel); + Model model = repositoryService.getModel(modelId); + List list = repositoryService.createModelQuery().modelTenantId(TenantHelper.getTenantId()).modelKey(key).list(); + list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> { + throw new ServiceException("模型KEY已存在!"); + }); + // 校验key命名规范 + if (!Validator.isMatchRegex(FlowConstant.MODEL_KEY_PATTERN, key)) { + throw new ServiceException("模型标识KEY只能字符或者下划线开头!"); + } + model.setKey(key); + model.setName(name); + model.setVersion(model.getVersion() + 1); + repositoryService.saveModel(model); + repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml)); + // 转换图片 + InputStream svgStream = new ByteArrayInputStream(StrUtil.utf8Bytes(svg)); + TranscoderInput input = new TranscoderInput(svgStream); + + PNGTranscoder transcoder = new PNGTranscoder(); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + TranscoderOutput output = new TranscoderOutput(outStream); + + transcoder.transcode(input, output); + final byte[] result = outStream.toByteArray(); + repositoryService.addModelEditorSourceExtra(model.getId(), result); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 模型部署 + * + * @param id 模型id + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean modelDeploy(String id) { + try { + // 查询流程定义模型xml + byte[] xmlBytes = repositoryService.getModelEditorSource(id); + if (ArrayUtil.isEmpty(xmlBytes)) { + throw new ServiceException("模型数据为空,请先设计流程定义模型,再进行部署!"); + } + if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString()))) { + byte[] bytes = ModelUtils.bpmnJsonToXmlBytes(xmlBytes); + if (ArrayUtil.isEmpty(bytes)) { + throw new ServiceException("模型不能为空,请至少设计一条主线流程!"); + } + } + BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xmlBytes); + // 校验模型 + ModelUtils.checkBpmnModel(bpmnModel); + List validationErrors = repositoryService.validateProcess(bpmnModel); + if (CollUtil.isNotEmpty(validationErrors)) { + String errorMsg = validationErrors.stream().map(ValidationError::getProblem).distinct().collect(Collectors.joining(",")); + throw new ServiceException(errorMsg); + } + // 查询模型的基本信息 + Model model = repositoryService.getModel(id); + // xml资源的名称 ,对应act_ge_bytearray表中的name_字段 + String processName = model.getName() + ".bpmn20.xml"; + // 调用部署相关的api方法进行部署流程定义 + Deployment deployment = repositoryService.createDeployment() + // 部署名称 + .name(model.getName()) + // 部署标识key + .key(model.getKey()) + // 部署流程分类 + .category(model.getCategory()) + // bpmn20.xml资源 + .addBytes(processName, xmlBytes) + // 租户id + .tenantId(TenantHelper.getTenantId()) + .deploy(); + + // 更新 部署id 到流程定义模型数据表中 + model.setDeploymentId(deployment.getId()); + repositoryService.saveModel(model); + // 更新分类 + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory()); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 导出模型zip压缩包 + * + * @param modelId 模型id + * @param response 相应 + */ + @Override + public void exportZip(String modelId, HttpServletResponse response) { + ZipOutputStream zos = null; + try { + zos = ZipUtil.getZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8); + // 压缩包文件名 + String zipName = "模型不存在"; + // 查询模型基本信息 + Model model = repositoryService.getModel(modelId); + byte[] xmlBytes = repositoryService.getModelEditorSource(modelId); + if (ObjectUtil.isNotNull(model)) { + if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString())) && ArrayUtil.isEmpty(ModelUtils.bpmnJsonToXmlBytes(xmlBytes))) { + zipName = "模型不能为空,请至少设计一条主线流程!"; + zos.putNextEntry(new ZipEntry(zipName + ".txt")); + zos.write(zipName.getBytes(StandardCharsets.UTF_8)); + } else if (ArrayUtil.isEmpty(xmlBytes)) { + zipName = "模型数据为空,请先设计流程定义模型,再进行部署!"; + zos.putNextEntry(new ZipEntry(zipName + ".txt")); + zos.write(zipName.getBytes(StandardCharsets.UTF_8)); + } else { + String fileName = model.getName() + "-" + model.getKey(); + // 压缩包文件名 + zipName = fileName + ".zip"; + // 将xml添加到压缩包中(指定xml文件名:请假流程.bpmn20.xml + zos.putNextEntry(new ZipEntry(fileName + ".bpmn20.xml")); + zos.write(xmlBytes); + } + } + response.setHeader("Content-Disposition", + "attachment; filename=" + URLEncoder.encode(zipName, StandardCharsets.UTF_8) + ".zip"); + // 刷出响应流 + response.flushBuffer(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (zos != null) { + try { + zos.closeEntry(); + zos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java new file mode 100644 index 000000000..fda10d322 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java @@ -0,0 +1,328 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.domain.WfCategory; +import org.dromara.workflow.domain.bo.ProcessDefinitionBo; +import org.dromara.workflow.domain.vo.ProcessDefinitionVo; +import org.dromara.workflow.service.IActProcessDefinitionService; +import org.dromara.workflow.service.IWfCategoryService; +import org.flowable.engine.HistoryService; +import org.flowable.engine.ProcessMigrationService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.impl.bpmn.deployer.ResourceNameUtil; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.repository.ProcessDefinitionQuery; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.zip.ZipInputStream; + +/** + * 流程定义 服务层实现 + * + * @author may + */ +@RequiredArgsConstructor +@Service +public class ActProcessDefinitionServiceImpl implements IActProcessDefinitionService { + + private final RepositoryService repositoryService; + + private final HistoryService historyService; + + private final ProcessMigrationService processMigrationService; + + private final IWfCategoryService wfCategoryService; + + /** + * 分页查询 + * + * @param processDefinitionBo 参数 + * @return 返回分页列表 + */ + @Override + public TableDataInfo page(ProcessDefinitionBo processDefinitionBo) { + ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); + query.processDefinitionTenantId(TenantHelper.getTenantId()); + if (StringUtils.isNotEmpty(processDefinitionBo.getKey())) { + query.processDefinitionKey(processDefinitionBo.getKey()); + } + if (StringUtils.isNotEmpty(processDefinitionBo.getCategoryCode())) { + query.processDefinitionCategory(processDefinitionBo.getCategoryCode()); + } + if (StringUtils.isNotEmpty(processDefinitionBo.getName())) { + query.processDefinitionNameLike("%" + processDefinitionBo.getName() + "%"); + } + query.orderByDeploymentId().desc(); + // 分页查询 + List processDefinitionVoList = new ArrayList<>(); + List definitionList = query.latestVersion().listPage(processDefinitionBo.getPageNum(), processDefinitionBo.getPageSize()); + List deploymentList = null; + if (CollUtil.isNotEmpty(definitionList)) { + List deploymentIds = StreamUtils.toList(definitionList, ProcessDefinition::getDeploymentId); + deploymentList = repositoryService.createDeploymentQuery().deploymentIds(deploymentIds).list(); + } + for (ProcessDefinition processDefinition : definitionList) { + ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class); + if (CollUtil.isNotEmpty(deploymentList)) { + // 部署时间 + deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> { + processDefinitionVo.setDeploymentTime(e.getDeploymentTime()); + }); + } + processDefinitionVoList.add(processDefinitionVo); + } + // 总记录数 + long total = query.count(); + + return new TableDataInfo<>(processDefinitionVoList, total); + } + + /** + * 查询历史流程定义列表 + * + * @param key 流程定义key + */ + @Override + public List getProcessDefinitionListByKey(String key) { + List processDefinitionVoList = new ArrayList<>(); + ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); + List definitionList = query.processDefinitionTenantId(TenantHelper.getTenantId()).processDefinitionKey(key).list(); + List deploymentList = null; + if (CollUtil.isNotEmpty(definitionList)) { + List deploymentIds = definitionList.stream().map(ProcessDefinition::getDeploymentId).collect(Collectors.toList()); + deploymentList = repositoryService.createDeploymentQuery() + .deploymentIds(deploymentIds).list(); + } + for (ProcessDefinition processDefinition : definitionList) { + ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class); + if (CollUtil.isNotEmpty(deploymentList)) { + // 部署时间 + deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> { + processDefinitionVo.setDeploymentTime(e.getDeploymentTime()); + }); + } + processDefinitionVoList.add(processDefinitionVo); + } + return CollectionUtil.reverse(processDefinitionVoList); + } + + /** + * 查看流程定义图片 + * + * @param processDefinitionId 流程定义id + */ + @SneakyThrows + @Override + public String processDefinitionImage(String processDefinitionId) { + InputStream inputStream = repositoryService.getProcessDiagram(processDefinitionId); + return Base64.encode(IOUtils.toByteArray(inputStream)); + } + + /** + * 查看流程定义xml文件 + * + * @param processDefinitionId 流程定义id + */ + @Override + public String processDefinitionXml(String processDefinitionId) { + StringBuilder xml = new StringBuilder(); + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId); + InputStream inputStream; + try { + inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName()); + xml.append(IOUtils.toString(inputStream, StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } + return xml.toString(); + } + + /** + * 删除流程定义 + * + * @param deploymentId 部署id + * @param processDefinitionId 流程定义id + */ + @Override + public boolean deleteDeployment(String deploymentId, String processDefinitionId) { + try { + List taskInstanceList = historyService.createHistoricTaskInstanceQuery() + .processDefinitionId(processDefinitionId).list(); + if (CollectionUtil.isNotEmpty(taskInstanceList)) { + throw new ServiceException("当前流程定义已被使用不可删除!"); + } + //删除流程定义 + repositoryService.deleteDeployment(deploymentId); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 激活或者挂起流程定义 + * + * @param processDefinitionId 流程定义id + */ + @Override + public boolean updateProcessDefState(String processDefinitionId) { + try { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId).processDefinitionTenantId(TenantHelper.getTenantId()).singleResult(); + //将当前为挂起状态更新为激活状态 + //参数说明:参数1:流程定义id,参数2:是否激活(true是否级联对应流程实例,激活了则对应流程实例都可以审批), + //参数3:什么时候激活,如果为null则立即激活,如果为具体时间则到达此时间后激活 + if (processDefinition.isSuspended()) { + repositoryService.activateProcessDefinitionById(processDefinitionId, true, null); + } else { + repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException("操作失败:" + e.getMessage()); + } + } + + /** + * 迁移流程定义 + * + * @param currentProcessDefinitionId 当前流程定义id + * @param fromProcessDefinitionId 需要迁移到的流程定义id + */ + + @Override + public boolean migrationProcessDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId) { + try { + // 迁移验证 + boolean migrationValid = processMigrationService.createProcessInstanceMigrationBuilder() + .migrateToProcessDefinition(currentProcessDefinitionId) + .validateMigrationOfProcessInstances(fromProcessDefinitionId) + .isMigrationValid(); + if (!migrationValid) { + throw new ServiceException("流程定义差异过大无法迁移,请修改流程图"); + } + // 已结束的流程实例不会迁移 + processMigrationService.createProcessInstanceMigrationBuilder() + .migrateToProcessDefinition(currentProcessDefinitionId) + .migrateProcessInstances(fromProcessDefinitionId); + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + + /** + * 流程定义转换为模型 + * + * @param processDefinitionId 流程定义id + */ + @Override + public boolean convertToModel(String processDefinitionId) { + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId).singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), pd.getResourceName()); + Model model = repositoryService.createModelQuery().modelKey(pd.getKey()).modelTenantId(TenantHelper.getTenantId()).singleResult(); + try { + if (ObjectUtil.isNotNull(model)) { + repositoryService.addModelEditorSource(model.getId(), IoUtil.readBytes(inputStream)); + } else { + Model modelData = repositoryService.newModel(); + modelData.setKey(pd.getKey()); + modelData.setName(pd.getName()); + modelData.setTenantId(pd.getTenantId()); + repositoryService.saveModel(modelData); + repositoryService.addModelEditorSource(modelData.getId(), IoUtil.readBytes(inputStream)); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 通过zip或xml部署流程定义 + * + * @param file 文件 + * @param categoryCode 分类 + */ + @Override + public boolean deployByFile(MultipartFile file, String categoryCode) { + try { + WfCategory wfCategory = wfCategoryService.queryByCategoryCode(categoryCode); + if (wfCategory == null) { + throw new ServiceException("流程分类不存在"); + } + // 文件名 = 流程名称-流程key + String filename = file.getOriginalFilename(); + assert filename != null; + String[] splitFilename = filename.substring(0, filename.lastIndexOf(".")).split("-"); + if (splitFilename.length < 2) { + throw new ServiceException("流程分类不能为空(文件名 = 流程名称-流程key)"); + } + //流程名称 + String processName = splitFilename[0]; + //流程key + String processKey = splitFilename[1]; + // 文件后缀名 + String suffix = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase(); + InputStream inputStream = file.getInputStream(); + Deployment deployment; + if (FlowConstant.ZIP.equals(suffix)) { + deployment = repositoryService.createDeployment() + .tenantId(TenantHelper.getTenantId()) + .addZipInputStream(new ZipInputStream(inputStream)).name(processName).key(processKey).category(categoryCode).deploy(); + } else { + String[] list = ResourceNameUtil.BPMN_RESOURCE_SUFFIXES; + boolean flag = false; + for (String str : list) { + if (filename.contains(str)) { + flag = true; + break; + } + } + if (flag) { + deployment = repositoryService.createDeployment() + .tenantId(TenantHelper.getTenantId()) + .addInputStream(filename, inputStream).name(processName).key(processKey).category(categoryCode).deploy(); + } else { + throw new ServiceException("文件类型上传错误!"); + } + } + // 更新分类 + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), categoryCode); + + return true; + } catch (IOException e) { + e.printStackTrace(); + throw new ServiceException("部署失败" + e.getMessage()); + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java new file mode 100644 index 000000000..b195cc79d --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java @@ -0,0 +1,695 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.common.enums.BusinessStatusEnum; +import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.dromara.workflow.domain.ActHiProcinst; +import org.dromara.workflow.domain.bo.ProcessInstanceBo; +import org.dromara.workflow.domain.bo.ProcessInvalidBo; +import org.dromara.workflow.domain.bo.TaskUrgingBo; +import org.dromara.workflow.domain.vo.ActHistoryInfoVo; +import org.dromara.workflow.domain.vo.GraphicInfoVo; +import org.dromara.workflow.domain.vo.ProcessInstanceVo; +import org.dromara.workflow.domain.vo.TaskVo; +import org.dromara.workflow.flowable.CustomDefaultProcessDiagramGenerator; +import org.dromara.workflow.flowable.strategy.FlowEventStrategy; +import org.dromara.workflow.flowable.cmd.DeleteExecutionCmd; +import org.dromara.workflow.flowable.cmd.ExecutionChildByExecutionIdCmd; +import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler; +import org.dromara.workflow.service.IActHiProcinstService; +import org.dromara.workflow.service.IActProcessInstanceService; +import org.dromara.workflow.utils.WorkflowUtils; +import org.flowable.bpmn.model.*; +import org.flowable.engine.*; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.runtime.ProcessInstanceQuery; +import org.flowable.engine.task.Attachment; +import org.flowable.engine.task.Comment; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * 流程实例 服务层实现 + * + * @author may + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class ActProcessInstanceServiceImpl implements IActProcessInstanceService { + + private final RepositoryService repositoryService; + private final RuntimeService runtimeService; + private final HistoryService historyService; + private final TaskService taskService; + private final IActHiProcinstService actHiProcinstService; + private final ManagementService managementService; + private final FlowEventStrategy flowEventStrategy; + + @Value("${flowable.activity-font-name}") + private String activityFontName; + + @Value("${flowable.label-font-name}") + private String labelFontName; + + @Value("${flowable.annotation-font-name}") + private String annotationFontName; + + /** + * 分页查询正在运行的流程实例 + * + * @param processInstanceBo 参数 + */ + @Override + public TableDataInfo getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo) { + List list = new ArrayList<>(); + ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery(); + query.processInstanceTenantId(TenantHelper.getTenantId()); + if (StringUtils.isNotBlank(processInstanceBo.getName())) { + query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%"); + } + if (StringUtils.isNotBlank(processInstanceBo.getKey())) { + query.processDefinitionKey(processInstanceBo.getKey()); + } + if (StringUtils.isNotBlank(processInstanceBo.getStartUserId())) { + query.startedBy(processInstanceBo.getStartUserId()); + } + if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) { + query.processInstanceBusinessKey(processInstanceBo.getBusinessKey()); + } + if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) { + query.processDefinitionCategory(processInstanceBo.getCategoryCode()); + } + query.orderByStartTime().desc(); + List processInstances = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize()); + for (ProcessInstance processInstance : processInstances) { + ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class); + processInstanceVo.setIsSuspended(processInstance.isSuspended()); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus())); + list.add(processInstanceVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 分页查询已结束的流程实例 + * + * @param processInstanceBo 参数 + */ + @Override + public TableDataInfo getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo) { + List list = new ArrayList<>(); + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().finished() + .orderByProcessInstanceEndTime().desc(); + query.processInstanceTenantId(TenantHelper.getTenantId()); + if (StringUtils.isNotEmpty(processInstanceBo.getName())) { + query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%"); + } + if (StringUtils.isNotBlank(processInstanceBo.getKey())) { + query.processDefinitionKey(processInstanceBo.getKey()); + } + if (StringUtils.isNotEmpty(processInstanceBo.getStartUserId())) { + query.startedBy(processInstanceBo.getStartUserId()); + } + if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) { + query.processInstanceBusinessKey(processInstanceBo.getBusinessKey()); + } + if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) { + query.processDefinitionCategory(processInstanceBo.getCategoryCode()); + } + List historicProcessInstances = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize()); + for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) { + ProcessInstanceVo processInstanceVo = BeanUtil.toBean(historicProcessInstance, ProcessInstanceVo.class); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(historicProcessInstance.getBusinessStatus())); + list.add(processInstanceVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 通过流程实例id获取历史流程图 + * + * @param processInstanceId 流程实例id + */ + @SneakyThrows + @Override + public String getHistoryProcessImage(String processInstanceId) { + String processDefinitionId; + // 获取当前的流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + // 如果流程已经结束,则得到结束节点 + if (Objects.isNull(processInstance)) { + HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + processDefinitionId = pi.getProcessDefinitionId(); + } else { + // 根据流程实例ID获得当前处于活动状态的ActivityId合集 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + processDefinitionId = pi.getProcessDefinitionId(); + } + + // 获得活动的节点 + List highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list(); + + List highLightedFlows = new ArrayList<>(); + List highLightedNodes = new ArrayList<>(); + //高亮 + for (HistoricActivityInstance tempActivity : highLightedFlowList) { + if (FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType())) { + //高亮线 + highLightedFlows.add(tempActivity.getActivityId()); + } else { + //高亮节点 + if (tempActivity.getEndTime() == null) { + highLightedNodes.add(Color.RED.toString() + tempActivity.getActivityId()); + } else { + highLightedNodes.add(tempActivity.getActivityId()); + } + } + } + List highLightedNodeList = new ArrayList<>(); + //运行中的节点 + List redNodeCollect = StreamUtils.filter(highLightedNodes, e -> e.contains(Color.RED.toString())); + //排除与运行中相同的节点 + for (String nodeId : highLightedNodes) { + if (!nodeId.contains(Color.RED.toString()) && !redNodeCollect.contains(Color.RED + nodeId)) { + highLightedNodeList.add(nodeId); + } + } + highLightedNodeList.addAll(redNodeCollect); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + CustomDefaultProcessDiagramGenerator diagramGenerator = new CustomDefaultProcessDiagramGenerator(); + InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodeList, highLightedFlows, activityFontName, labelFontName, annotationFontName, null, 1.0, true); + return Base64.encode(IOUtils.toByteArray(inputStream)); + } + + /** + * 通过流程实例id获取历史流程图运行中,历史等节点 + * + * @param processInstanceId 流程实例id + */ + @Override + public Map getHistoryProcessList(String processInstanceId) { + Map map = new HashMap<>(); + List> taskList = new ArrayList<>(); + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + StringBuilder xml = new StringBuilder(); + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId()); + // 获取节点 + List highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list(); + for (HistoricActivityInstance tempActivity : highLightedFlowList) { + Map task = new HashMap<>(); + if (!FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType()) && + !FlowConstant.PARALLEL_GATEWAY.equals(tempActivity.getActivityType()) && + !FlowConstant.EXCLUSIVE_GATEWAY.equals(tempActivity.getActivityType()) && + !FlowConstant.INCLUSIVE_GATEWAY.equals(tempActivity.getActivityType()) + ) { + task.put("key", tempActivity.getActivityId()); + task.put("completed", tempActivity.getEndTime() != null); + task.put("activityType", tempActivity.getActivityType()); + taskList.add(task); + } + } + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (processInstance != null) { + taskList = taskList.stream().filter(e -> !e.get("activityType").equals(FlowConstant.END_EVENT)).collect(Collectors.toList()); + } + //查询出运行中节点 + List> runtimeNodeList = taskList.stream().filter(e -> !(Boolean) e.get("completed")).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(runtimeNodeList)) { + Iterator> iterator = taskList.iterator(); + while (iterator.hasNext()) { + Map next = iterator.next(); + runtimeNodeList.stream().filter(t -> t.get("key").equals(next.get("key")) && (Boolean) next.get("completed")).findFirst().ifPresent(t -> iterator.remove()); + } + } + map.put("taskList", taskList); + List historyTaskList = getHistoryTaskList(processInstanceId); + map.put("historyList", historyTaskList); + InputStream inputStream; + try { + inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName()); + xml.append(IOUtils.toString(inputStream, String.valueOf(StandardCharsets.UTF_8))); + map.put("xml", xml.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + /** + * 获取历史任务节点信息 + * + * @param processInstanceId 流程实例id + */ + private List getHistoryTaskList(String processInstanceId) { + //查询任务办理记录 + List list = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByHistoricTaskInstanceEndTime().desc().list(); + list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed()); + List actHistoryInfoVoList = new ArrayList<>(); + for (HistoricTaskInstance historicTaskInstance : list) { + ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo(); + BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo); + actHistoryInfoVo.setStatus(actHistoryInfoVo.getEndTime() == null ? "待处理" : "已处理"); + if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) { + actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis())); + } + actHistoryInfoVoList.add(actHistoryInfoVo); + } + List historyInfoVoList = new ArrayList<>(); + Map> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey); + for (Map.Entry> entry : groupByKey.entrySet()) { + ActHistoryInfoVo historyInfoVo = new ActHistoryInfoVo(); + BeanUtils.copyProperties(entry.getValue().get(0), historyInfoVo); + actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst() + .ifPresent(e -> { + historyInfoVo.setStatus("待处理"); + historyInfoVo.setStartTime(e.getStartTime()); + historyInfoVo.setEndTime(null); + historyInfoVo.setRunDuration(null); + }); + historyInfoVoList.add(historyInfoVo); + } + return historyInfoVoList; + } + + /** + * 获取审批记录 + * + * @param processInstanceId 流程实例id + */ + @Override + public Map getHistoryRecord(String processInstanceId) { + Map map = new HashMap<>(); + // 查询任务办理记录 + List list = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).orderByHistoricTaskInstanceEndTime().desc().list(); + list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed()); + List actHistoryInfoVoList = new ArrayList<>(); + List processInstanceComments = taskService.getProcessInstanceComments(processInstanceId); + //附件 + List attachmentList = taskService.getProcessInstanceAttachments(processInstanceId); + for (HistoricTaskInstance historicTaskInstance : list) { + ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo(); + BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo); + if (actHistoryInfoVo.getEndTime() == null) { + actHistoryInfoVo.setStatus(TaskStatusEnum.WAITING.getStatus()); + actHistoryInfoVo.setStatusName(TaskStatusEnum.WAITING.getDesc()); + } + if (CollUtil.isNotEmpty(processInstanceComments)) { + processInstanceComments.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).findFirst().ifPresent(e -> { + actHistoryInfoVo.setComment(e.getFullMessage()); + actHistoryInfoVo.setStatus(e.getType()); + actHistoryInfoVo.setStatusName(TaskStatusEnum.findByStatus(e.getType())); + }); + } + if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) { + actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis())); + } + try { + actHistoryInfoVo.setAssignee(StringUtils.isNotBlank(historicTaskInstance.getAssignee()) ? Long.valueOf(historicTaskInstance.getAssignee()) : null); + } catch (NumberFormatException ignored) { + log.warn("当前任务【{}】,办理人转换人员ID【{}】异常!", historicTaskInstance.getName(), historicTaskInstance.getAssignee()); + } + //附件 + if (CollUtil.isNotEmpty(attachmentList)) { + List attachments = attachmentList.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(attachments)) { + actHistoryInfoVo.setAttachmentList(attachments); + } + } + actHistoryInfoVoList.add(actHistoryInfoVo); + } + List collect = new ArrayList<>(); + // 待办理 + List waitingTask = StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() == null); + // 已办理 + List finishTask = StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() != null); + collect.addAll(waitingTask); + collect.addAll(finishTask); + // 审批记录 + map.put("historyRecordList", collect); + List nodeInfoList = new ArrayList<>(); + Map> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey); + for (Map.Entry> entry : groupByKey.entrySet()) { + ActHistoryInfoVo actHistoryInfoVo = BeanUtil.toBean(entry.getValue().get(0), ActHistoryInfoVo.class); + String nickName = entry.getValue().stream().filter(e -> StringUtils.isNotBlank(e.getNickName()) && e.getEndTime() == null).map(ActHistoryInfoVo::getNickName).toList().stream().distinct().collect(Collectors.joining(StringUtils.SEPARATOR)); + if (StringUtils.isNotBlank(nickName)) { + actHistoryInfoVo.setNickName(nickName); + } + actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() != null).findFirst() + .ifPresent(e -> { + actHistoryInfoVo.setStatus("已处理"); + actHistoryInfoVo.setStartTime(e.getStartTime()); + }); + actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst() + .ifPresent(e -> { + actHistoryInfoVo.setStatus("待处理"); + actHistoryInfoVo.setStartTime(e.getStartTime()); + actHistoryInfoVo.setEndTime(null); + actHistoryInfoVo.setRunDuration(null); + }); + nodeInfoList.add(actHistoryInfoVo); + } + // 节点信息 + map.put("nodeListInfo", nodeInfoList); + BpmnModel bpmnModel = repositoryService.getBpmnModel(list.get(0).getProcessDefinitionId()); + List graphicInfoVos = new ArrayList<>(); + Collection flowElements = bpmnModel.getMainProcess().getFlowElements(); + //节点图形信息 + buildGraphicInfo(flowElements, graphicInfoVos, bpmnModel); + map.put("graphicInfoVos", graphicInfoVos); + return map; + } + + /** + * 构建节点图形信息 + * + * @param flowElements 节点 + */ + private static void buildGraphicInfo(Collection flowElements, List graphicInfoVos, BpmnModel bpmnModel) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof SubProcess) { + Collection subFlowElements = ((SubProcess) flowElement).getFlowElements(); + buildGraphicInfo(subFlowElements, graphicInfoVos, bpmnModel); + } else { + if (flowElement instanceof UserTask) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowElement.getId()); + GraphicInfoVo graphicInfoVo = BeanUtil.toBean(graphicInfo, GraphicInfoVo.class); + graphicInfoVo.setNodeId(flowElement.getId()); + graphicInfoVo.setNodeName(flowElement.getName()); + graphicInfoVos.add(graphicInfoVo); + } + } + } + } + + /** + * 任务完成时间处理 + * + * @param time 时间 + */ + private String getDuration(long time) { + + long day = time / (24 * 60 * 60 * 1000); + long hour = (time / (60 * 60 * 1000) - day * 24); + long minute = ((time / (60 * 1000)) - day * 24 * 60 - hour * 60); + long second = (time / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60); + + if (day > 0) { + return day + "天" + hour + "小时" + minute + "分钟"; + } + if (hour > 0) { + return hour + "小时" + minute + "分钟"; + } + if (minute > 0) { + return minute + "分钟"; + } + if (second > 0) { + return second + "秒"; + } else { + return 0 + "秒"; + } + } + + /** + * 作废流程实例,不会删除历史记录(删除运行中的实例) + * + * @param processInvalidBo 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteRuntimeProcessInst(ProcessInvalidBo processInvalidBo) { + try { + List list = taskService.createTaskQuery().processInstanceId(processInvalidBo.getProcessInstanceId()) + .taskTenantId(TenantHelper.getTenantId()).list(); + List subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId())); + if (CollUtil.isNotEmpty(subTasks)) { + subTasks.forEach(e -> taskService.deleteTask(e.getId())); + } + String deleteReason = LoginHelper.getLoginUser().getNickname() + "作废了当前申请!"; + if (StringUtils.isNotBlank(processInvalidBo.getDeleteReason())) { + deleteReason = LoginHelper.getLoginUser().getNickname() + "作废理由:" + processInvalidBo.getDeleteReason(); + } + for (Task task : StreamUtils.filter(list, e -> StringUtils.isBlank(e.getParentTaskId()))) { + taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.INVALID.getStatus(), deleteReason); + } + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInvalidBo.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + if (ObjectUtil.isNotEmpty(historicProcessInstance) && BusinessStatusEnum.FINISH.getStatus().equals(historicProcessInstance.getBusinessStatus())) { + throw new ServiceException("该单据已完成申请!"); + } + runtimeService.updateBusinessStatus(processInvalidBo.getProcessInstanceId(), BusinessStatusEnum.INVALID.getStatus()); + runtimeService.deleteProcessInstance(processInvalidBo.getProcessInstanceId(), deleteReason); + FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey()); + if (processHandler != null) { + processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.INVALID.getStatus(), false); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteRuntimeProcessAndHisInst(List processInstanceIds) { + try { + // 1.删除运行中流程实例 + List list = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds) + .taskTenantId(TenantHelper.getTenantId()).list(); + List subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId())); + if (CollUtil.isNotEmpty(subTasks)) { + subTasks.forEach(e -> taskService.deleteTask(e.getId())); + } + runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "删除了当前流程申请"); + // 2.删除历史记录 + List historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery() + .processInstanceTenantId(TenantHelper.getTenantId()).processInstanceIds(new HashSet<>(processInstanceIds)).list(); + if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) { + historyService.bulkDeleteHistoricProcessInstances(processInstanceIds); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 按照业务id删除 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param businessKeys 业务id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteRuntimeProcessAndHisInstByBusinessKeys(List businessKeys) { + try { + // 1.删除运行中流程实例 + List actHiProcinsts = actHiProcinstService.selectByBusinessKeyIn(businessKeys); + if (CollUtil.isEmpty(actHiProcinsts)) { + log.warn("当前业务ID:{}查询到流程实例为空!", businessKeys); + return false; + } + List processInstanceIds = StreamUtils.toList(actHiProcinsts, ActHiProcinst::getId); + List list = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds) + .taskTenantId(TenantHelper.getTenantId()).list(); + List subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId())); + if (CollUtil.isNotEmpty(subTasks)) { + subTasks.forEach(e -> taskService.deleteTask(e.getId())); + } + runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "删除了当前流程申请"); + // 2.删除历史记录 + List historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery() + .processInstanceTenantId(TenantHelper.getTenantId()).processInstanceIds(new HashSet<>(processInstanceIds)).list(); + if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) { + historyService.bulkDeleteHistoricProcessInstances(processInstanceIds); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 已完成的实例 删除程实例,删除历史记录,删除业务与流程关联信息 + * + * @param processInstanceIds 流程实例id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteFinishProcessAndHisInst(List processInstanceIds) { + try { + historyService.bulkDeleteHistoricProcessInstances(processInstanceIds); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 撤销流程申请 + * + * @param processInstanceId 流程实例id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean cancelProcessApply(String processInstanceId) { + try { + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId).processInstanceTenantId(TenantHelper.getTenantId()).startedBy(String.valueOf(LoginHelper.getUserId())).singleResult(); + if (ObjectUtil.isNull(processInstance)) { + throw new ServiceException("您不是流程发起人,撤销失败!"); + } + if (processInstance.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + if (BusinessStatusEnum.CANCEL.getStatus().equals(processInstance.getBusinessStatus())) { + throw new ServiceException("该单据已撤销!"); + } + List taskList = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(processInstanceId).list(); + for (Task task : taskList) { + taskService.setAssignee(task.getId(), String.valueOf(LoginHelper.getUserId())); + taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.CANCEL.getStatus(), LoginHelper.getLoginUser().getNickname() + ":撤销申请"); + } + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().finished().orderByHistoricTaskInstanceEndTime().asc().list().get(0); + List nodeIds = StreamUtils.toList(taskList, Task::getTaskDefinitionKey); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId(nodeIds, historicTaskInstance.getTaskDefinitionKey()).changeState(); + Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(processInstanceId).list().get(0); + taskService.setAssignee(task.getId(), historicTaskInstance.getAssignee()); + //获取并行网关执行后保留的执行实例数据 + ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId()); + List executionEntities = managementService.executeCommand(childByExecutionIdCmd); + //删除流程实例垃圾数据 + for (ExecutionEntity executionEntity : executionEntities) { + DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId()); + managementService.executeCommand(deleteExecutionCmd); + } + runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.CANCEL.getStatus()); + FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey()); + if (processHandler != null) { + processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.CANCEL.getStatus(), false); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException("撤销失败:" + e.getMessage()); + } + } + + /** + * 分页查询当前登录人单据 + * + * @param processInstanceBo 参数 + */ + @Override + public TableDataInfo getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo) { + List list = new ArrayList<>(); + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); + query.processInstanceTenantId(TenantHelper.getTenantId()); + query.startedBy(processInstanceBo.getStartUserId()); + if (StringUtils.isNotBlank(processInstanceBo.getName())) { + query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%"); + } + if (StringUtils.isNotBlank(processInstanceBo.getKey())) { + query.processDefinitionKey(processInstanceBo.getKey()); + } + if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) { + query.processInstanceBusinessKey(processInstanceBo.getBusinessKey()); + } + if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) { + query.processDefinitionCategory(processInstanceBo.getCategoryCode()); + } + query.orderByProcessInstanceStartTime().desc(); + List historicProcessInstanceList = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize()); + List taskVoList = new ArrayList<>(); + if (CollUtil.isNotEmpty(historicProcessInstanceList)) { + List processInstanceIds = StreamUtils.toList(historicProcessInstanceList, HistoricProcessInstance::getId); + List taskList = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).taskTenantId(TenantHelper.getTenantId()).list(); + for (Task task : taskList) { + taskVoList.add(BeanUtil.toBean(task, TaskVo.class)); + } + } + for (HistoricProcessInstance processInstance : historicProcessInstanceList) { + ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus())); + if (CollUtil.isNotEmpty(taskVoList)) { + List collect = StreamUtils.filter(taskVoList, e -> e.getProcessInstanceId().equals(processInstance.getId())); + processInstanceVo.setTaskVoList(CollUtil.isNotEmpty(collect) ? collect : Collections.emptyList()); + } + list.add(processInstanceVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 任务催办(给当前任务办理人发送站内信,邮件,短信等) + * + * @param taskUrgingBo 任务催办 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean taskUrging(TaskUrgingBo taskUrgingBo) { + try { + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(taskUrgingBo.getProcessInstanceId()) + .processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + if (processInstance == null) { + throw new ServiceException("任务已结束!"); + } + String message = taskUrgingBo.getMessage(); + if (StringUtils.isBlank(message)) { + message = "您的【" + processInstance.getName() + "】单据还未审批,请您及时处理。"; + } + List list = taskService.createTaskQuery().processInstanceId(taskUrgingBo.getProcessInstanceId()).list(); + WorkflowUtils.sendMessage(list, processInstance.getName(), taskUrgingBo.getMessageType(), message); + } catch (ServiceException e) { + throw new ServiceException(e.getMessage()); + } + return true; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java new file mode 100644 index 000000000..653b733b6 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java @@ -0,0 +1,708 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.dto.RoleDTO; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.common.enums.BusinessStatusEnum; +import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.dromara.workflow.domain.bo.*; +import org.dromara.workflow.domain.vo.MultiInstanceVo; +import org.dromara.workflow.domain.vo.TaskVo; +import org.dromara.workflow.domain.vo.WfCopy; +import org.dromara.workflow.flowable.strategy.FlowEventStrategy; +import org.dromara.workflow.flowable.cmd.*; +import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler; +import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler; +import org.dromara.workflow.mapper.ActTaskMapper; +import org.dromara.workflow.service.IActTaskService; +import org.dromara.workflow.utils.WorkflowUtils; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.engine.*; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskQuery; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.dromara.workflow.common.constant.FlowConstant.FLOWABLE_SKIP_EXPRESSION_ENABLED; +import static org.dromara.workflow.common.constant.FlowConstant.INITIATOR; + +/** + * 任务 服务层实现 + * + * @author may + */ +@RequiredArgsConstructor +@Service +public class ActTaskServiceImpl implements IActTaskService { + + private final RuntimeService runtimeService; + private final TaskService taskService; + private final HistoryService historyService; + private final IdentityService identityService; + private final ManagementService managementService; + private final FlowEventStrategy flowEventStrategy; + private final ActTaskMapper actTaskMapper; + + /** + * 启动任务 + * + * @param startProcessBo 启动流程参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Map startWorkFlow(StartProcessBo startProcessBo) { + Map map = new HashMap<>(); + if (StringUtils.isBlank(startProcessBo.getBusinessKey())) { + throw new ServiceException("启动工作流时必须包含业务ID"); + } + // 判断当前业务是否启动过流程 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(startProcessBo.getBusinessKey()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + if (ObjectUtil.isNotEmpty(historicProcessInstance)) { + BusinessStatusEnum.checkStartStatus(historicProcessInstance.getBusinessStatus()); + } + TaskQuery taskQuery = taskService.createTaskQuery(); + List taskResult = taskQuery.processInstanceBusinessKey(startProcessBo.getBusinessKey()).taskTenantId(TenantHelper.getTenantId()).list(); + if (CollUtil.isNotEmpty(taskResult)) { + if (CollUtil.isNotEmpty(startProcessBo.getVariables())) { + taskService.setVariables(taskResult.get(0).getId(), startProcessBo.getVariables()); + } + map.put("processInstanceId", taskResult.get(0).getProcessInstanceId()); + map.put("taskId", taskResult.get(0).getId()); + return map; + } + // 设置启动人 + identityService.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId())); + Authentication.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId())); + // 启动流程实例(提交申请) + Map variables = startProcessBo.getVariables(); + // 启动跳过表达式 + variables.put(FLOWABLE_SKIP_EXPRESSION_ENABLED, true); + // 流程发起人 + variables.put(INITIATOR, (String.valueOf(LoginHelper.getUserId()))); + ProcessInstance pi; + try { + pi = runtimeService.startProcessInstanceByKeyAndTenantId(startProcessBo.getProcessKey(), startProcessBo.getBusinessKey(), variables, TenantHelper.getTenantId()); + } catch (FlowableObjectNotFoundException e) { + throw new ServiceException("找不到当前【" + startProcessBo.getProcessKey() + "】流程定义!"); + } + // 将流程定义名称 作为 流程实例名称 + runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName()); + // 申请人执行流程 + List taskList = taskService.createTaskQuery().processInstanceId(pi.getId()).taskTenantId(TenantHelper.getTenantId()).list(); + if (taskList.size() > 1) { + throw new ServiceException("请检查流程第一个环节是否为申请人!"); + } + + runtimeService.updateBusinessStatus(pi.getProcessInstanceId(), BusinessStatusEnum.DRAFT.getStatus()); + taskService.setAssignee(taskList.get(0).getId(), LoginHelper.getUserId().toString()); + taskService.setVariable(taskList.get(0).getId(), "processInstanceId", pi.getProcessInstanceId()); + map.put("processInstanceId", pi.getProcessInstanceId()); + map.put("taskId", taskList.get(0).getId()); + return map; + } + + /** + * 办理任务 + * + * @param completeTaskBo 办理任务参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean completeTask(CompleteTaskBo completeTaskBo) { + try { + List roles = LoginHelper.getLoginUser().getRoles(); + String userId = String.valueOf(LoginHelper.getUserId()); + TaskQuery taskQuery = taskService.createTaskQuery(); + taskQuery.taskId(completeTaskBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskCandidateOrAssigned(userId); + if (CollUtil.isNotEmpty(roles)) { + List groupIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId())); + taskQuery.taskCandidateGroupIn(groupIds); + } + Task task = taskQuery.singleResult(); + if (task == null) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + //办理委托任务 + if (ObjectUtil.isNotEmpty(task.getDelegationState()) && FlowConstant.PENDING.equals(task.getDelegationState().name())) { + taskService.resolveTask(completeTaskBo.getTaskId()); + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), task.getProcessInstanceId(), completeTaskBo.getMessage()); + taskService.complete(newTask.getId()); + return true; + } + //附件上传 + AttachmentCmd attachmentCmd = new AttachmentCmd(completeTaskBo.getFileId(), task.getId(), task.getProcessInstanceId()); + managementService.executeCommand(attachmentCmd); + FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey()); + String businessStatus = WorkflowUtils.getBusinessStatus(task.getProcessInstanceId()); + if (BusinessStatusEnum.DRAFT.getStatus().equals(businessStatus) || BusinessStatusEnum.BACK.getStatus().equals(businessStatus) || BusinessStatusEnum.CANCEL.getStatus().equals(businessStatus)) { + if (processHandler != null) { + processHandler.handleProcess(processInstance.getBusinessKey(), businessStatus, true); + } + } + runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.WAITING.getStatus()); + String key = processInstance.getProcessDefinitionKey() + "_" + task.getTaskDefinitionKey(); + FlowTaskEventHandler taskHandler = flowEventStrategy.getTaskHandler(key); + if (taskHandler != null) { + taskHandler.handleTask(task, processInstance.getBusinessKey()); + } + //办理意见 + taskService.addComment(completeTaskBo.getTaskId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), StringUtils.isBlank(completeTaskBo.getMessage()) ? "同意" : completeTaskBo.getMessage()); + //办理任务 + if (CollUtil.isNotEmpty(completeTaskBo.getVariables())) { + taskService.complete(completeTaskBo.getTaskId(), completeTaskBo.getVariables()); + } else { + taskService.complete(completeTaskBo.getTaskId()); + } + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()) + .processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + if (pi == null) { + UpdateBusinessStatusCmd updateBusinessStatusCmd = new UpdateBusinessStatusCmd(task.getProcessInstanceId(), BusinessStatusEnum.FINISH.getStatus()); + managementService.executeCommand(updateBusinessStatusCmd); + if (processHandler != null) { + processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.FINISH.getStatus(), false); + } + } else { + List list = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list(); + if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(completeTaskBo.getWfCopyList())) { + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), + LoginHelper.getLoginUser().getNickname() + "【抄送】给" + String.join(",", StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserName))); + taskService.complete(newTask.getId()); + List taskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + WorkflowUtils.createCopyTask(taskList, StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserId)); + } + sendMessage(list, processInstance.getName(), completeTaskBo.getMessageType(), null); + } + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + + /** + * 发送消息 + * + * @param list 任务 + * @param name 流程名称 + * @param messageType 消息类型 + * @param message 消息内容,为空则发送默认配置的消息内容 + */ + @Async + public void sendMessage(List list, String name, List messageType, String message) { + WorkflowUtils.sendMessage(list, name, messageType, message); + } + + /** + * 查询当前用户的待办任务 + * + * @param taskBo 参数 + */ + @Override + public TableDataInfo getTaskWaitByPage(TaskBo taskBo) { + PageQuery pageQuery = new PageQuery(); + pageQuery.setPageNum(taskBo.getPageNum()); + pageQuery.setPageSize(taskBo.getPageSize()); + QueryWrapper queryWrapper = new QueryWrapper<>(); + List roles = LoginHelper.getLoginUser().getRoles(); + String userId = String.valueOf(LoginHelper.getUserId()); + queryWrapper.eq("t.business_status_", BusinessStatusEnum.WAITING.getStatus()); + queryWrapper.eq("t.tenant_id_", TenantHelper.getTenantId()); + queryWrapper.and(w1 -> + w1.eq("t.assignee_", userId) + .or(w2 -> w2.isNull("t.assignee_") + .and(w3 -> w3.eq("t.user_id_", userId).or().in("t.group_id_", StreamUtils.toList(roles, RoleDTO::getRoleId)))) + ); + if (StringUtils.isNotBlank(taskBo.getName())) { + queryWrapper.like("t.name_", taskBo.getName()); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) { + queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName()); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) { + queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey()); + } + Page page = actTaskMapper.getTaskWaitByPage(pageQuery.build(), queryWrapper); + + List taskList = page.getRecords(); + for (TaskVo task : taskList) { + task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus())); + task.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId())); + task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null); + } + return new TableDataInfo<>(taskList, page.getTotal()); + } + + /** + * 查询当前租户所有待办任务 + * + * @param taskBo 参数 + */ + @Override + public TableDataInfo getAllTaskWaitByPage(TaskBo taskBo) { + TaskQuery query = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()); + if (StringUtils.isNotBlank(taskBo.getName())) { + query.taskNameLike("%" + taskBo.getName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) { + query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) { + query.processDefinitionKey(taskBo.getProcessDefinitionKey()); + } + query.orderByTaskCreateTime().desc(); + List taskList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize()); + List processInstanceList = null; + if (CollUtil.isNotEmpty(taskList)) { + Set processInstanceIds = StreamUtils.toSet(taskList, Task::getProcessInstanceId); + processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIds).list(); + } + List list = new ArrayList<>(); + for (Task task : taskList) { + TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class); + if (CollUtil.isNotEmpty(processInstanceList)) { + processInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> { + taskVo.setBusinessStatus(e.getBusinessStatus()); + taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus())); + taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey()); + taskVo.setProcessDefinitionName(e.getProcessDefinitionName()); + }); + } + taskVo.setAssignee(StringUtils.isNotBlank(task.getAssignee()) ? Long.valueOf(task.getAssignee()) : null); + taskVo.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId())); + taskVo.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null); + list.add(taskVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 查询当前用户的已办任务 + * + * @param taskBo 参数 + */ + @Override + public TableDataInfo getTaskFinishByPage(TaskBo taskBo) { + String userId = String.valueOf(LoginHelper.getUserId()); + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).taskTenantId(TenantHelper.getTenantId()).finished().orderByHistoricTaskInstanceStartTime().desc(); + if (StringUtils.isNotBlank(taskBo.getName())) { + query.taskNameLike("%" + taskBo.getName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) { + query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) { + query.processDefinitionKey(taskBo.getProcessDefinitionKey()); + } + List taskInstanceList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize()); + List historicProcessInstanceList = null; + if (CollUtil.isNotEmpty(taskInstanceList)) { + Set processInstanceIds = StreamUtils.toSet(taskInstanceList, HistoricTaskInstance::getProcessInstanceId); + historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceIds(processInstanceIds).list(); + } + List list = new ArrayList<>(); + for (HistoricTaskInstance task : taskInstanceList) { + TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class); + if (CollUtil.isNotEmpty(historicProcessInstanceList)) { + historicProcessInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> { + taskVo.setBusinessStatus(e.getBusinessStatus()); + taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus())); + taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey()); + taskVo.setProcessDefinitionName(e.getProcessDefinitionName()); + }); + } + taskVo.setAssignee(StringUtils.isNotBlank(task.getAssignee()) ? Long.valueOf(task.getAssignee()) : null); + list.add(taskVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 查询当前用户的抄送 + * + * @param taskBo 参数 + */ + @Override + public TableDataInfo getTaskCopyByPage(TaskBo taskBo) { + PageQuery pageQuery = new PageQuery(); + pageQuery.setPageNum(taskBo.getPageNum()); + pageQuery.setPageSize(taskBo.getPageSize()); + QueryWrapper queryWrapper = new QueryWrapper<>(); + String userId = String.valueOf(LoginHelper.getUserId()); + if (StringUtils.isNotBlank(taskBo.getName())) { + queryWrapper.like("t.name_", taskBo.getName()); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) { + queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName()); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) { + queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey()); + } + queryWrapper.eq("t.assignee_", userId); + Page page = actTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper); + + List taskList = page.getRecords(); + for (TaskVo task : taskList) { + task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus())); + task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null); + } + return new TableDataInfo<>(taskList, page.getTotal()); + } + + /** + * 查询当前租户所有已办任务 + * + * @param taskBo 参数 + */ + @Override + public TableDataInfo getAllTaskFinishByPage(TaskBo taskBo) { + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().taskTenantId(TenantHelper.getTenantId()).finished().orderByHistoricTaskInstanceStartTime().desc(); + if (StringUtils.isNotBlank(taskBo.getName())) { + query.taskNameLike("%" + taskBo.getName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) { + query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%"); + } + if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) { + query.processDefinitionKey(taskBo.getProcessDefinitionKey()); + } + List taskInstanceList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize()); + List historicProcessInstanceList = null; + if (CollUtil.isNotEmpty(taskInstanceList)) { + Set processInstanceIds = StreamUtils.toSet(taskInstanceList, HistoricTaskInstance::getProcessInstanceId); + historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceIds(processInstanceIds).list(); + } + List list = new ArrayList<>(); + for (HistoricTaskInstance task : taskInstanceList) { + TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class); + if (CollUtil.isNotEmpty(historicProcessInstanceList)) { + historicProcessInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> { + taskVo.setBusinessStatus(e.getBusinessStatus()); + taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus())); + taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey()); + taskVo.setProcessDefinitionName(e.getProcessDefinitionName()); + }); + } + taskVo.setAssignee(Convert.toLong(task.getAssignee())); + list.add(taskVo); + } + long count = query.count(); + return new TableDataInfo<>(list, count); + } + + /** + * 委派任务 + * + * @param delegateBo 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean delegateTask(DelegateBo delegateBo) { + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(delegateBo.getTaskId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult(); + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + try { + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.PENDING.getStatus(), "【" + LoginHelper.getLoginUser().getNickname() + "】委派给【" + delegateBo.getNickName() + "】"); + //委托任务 + taskService.delegateTask(delegateBo.getTaskId(), delegateBo.getUserId()); + //办理生成的任务记录 + taskService.complete(newTask.getId()); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 终止任务 + * + * @param terminationBo 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean terminationTask(TerminationBo terminationBo) { + Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(terminationBo.getTaskId()).singleResult(); + + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(task.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + if (ObjectUtil.isNotEmpty(historicProcessInstance) && BusinessStatusEnum.TERMINATION.getStatus().equals(historicProcessInstance.getBusinessStatus())) { + throw new ServiceException("该单据已终止!"); + } + try { + if (StringUtils.isBlank(terminationBo.getComment())) { + terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "终止了申请"); + } else { + terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "终止了申请:" + terminationBo.getComment()); + } + taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.TERMINATION.getStatus(), terminationBo.getComment()); + List list = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list(); + if (CollectionUtil.isNotEmpty(list)) { + List subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId())); + if (CollectionUtil.isNotEmpty(subTasks)) { + subTasks.forEach(e -> taskService.deleteTask(e.getId())); + } + runtimeService.deleteProcessInstance(task.getProcessInstanceId(), StrUtil.EMPTY); + } + runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.TERMINATION.getStatus()); + FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey()); + if (processHandler != null) { + processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.TERMINATION.getStatus(), false); + } + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + + /** + * 转办任务 + * + * @param transmitBo 参数 + */ + @Override + public boolean transferTask(TransmitBo transmitBo) { + Task task = taskService.createTaskQuery().taskId(transmitBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult(); + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + try { + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.TRANSFER.getStatus(), StringUtils.isNotBlank(transmitBo.getComment()) ? transmitBo.getComment() : LoginHelper.getUsername() + "转办了任务"); + taskService.complete(newTask.getId()); + taskService.setAssignee(task.getId(), transmitBo.getUserId()); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 会签任务加签 + * + * @param addMultiBo 参数 + */ + @Override + public boolean addMultiInstanceExecution(AddMultiBo addMultiBo) { + TaskQuery taskQuery = taskService.createTaskQuery(); + taskQuery.taskId(addMultiBo.getTaskId()); + taskQuery.taskTenantId(TenantHelper.getTenantId()); + if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) { + taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())); + } + Task task = taskQuery.singleResult(); + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + String taskDefinitionKey = task.getTaskDefinitionKey(); + String processInstanceId = task.getProcessInstanceId(); + String processDefinitionId = task.getProcessDefinitionId(); + + try { + MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey); + if (multiInstanceVo == null) { + throw new ServiceException("当前环节不是会签节点"); + } + if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) { + for (Long assignee : addMultiBo.getAssignees()) { + runtimeService.addMultiInstanceExecution(taskDefinitionKey, processInstanceId, Collections.singletonMap(multiInstanceVo.getAssignee(), assignee)); + } + } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) { + AddSequenceMultiInstanceCmd addSequenceMultiInstanceCmd = new AddSequenceMultiInstanceCmd(task.getExecutionId(), multiInstanceVo.getAssigneeList(), addMultiBo.getAssignees()); + managementService.executeCommand(addSequenceMultiInstanceCmd); + } + List assigneeNames = addMultiBo.getAssigneeNames(); + String username = LoginHelper.getUsername(); + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN.getStatus(), username + "加签【" + String.join(StringUtils.SEPARATOR, assigneeNames) + "】"); + taskService.complete(newTask.getId()); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 会签任务减签 + * + * @param deleteMultiBo 参数 + */ + @Override + public boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo) { + TaskQuery taskQuery = taskService.createTaskQuery(); + taskQuery.taskId(deleteMultiBo.getTaskId()); + taskQuery.taskTenantId(TenantHelper.getTenantId()); + if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) { + taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())); + } + Task task = taskQuery.singleResult(); + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + String taskDefinitionKey = task.getTaskDefinitionKey(); + String processInstanceId = task.getProcessInstanceId(); + String processDefinitionId = task.getProcessDefinitionId(); + try { + MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey); + if (multiInstanceVo == null) { + throw new ServiceException("当前环节不是会签节点"); + } + if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) { + for (String executionId : deleteMultiBo.getExecutionIds()) { + runtimeService.deleteMultiInstanceExecution(executionId, false); + } + for (String taskId : deleteMultiBo.getTaskIds()) { + historyService.deleteHistoricTaskInstance(taskId); + } + } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) { + DeleteSequenceMultiInstanceCmd deleteSequenceMultiInstanceCmd = new DeleteSequenceMultiInstanceCmd(task.getAssignee(), task.getExecutionId(), multiInstanceVo.getAssigneeList(), deleteMultiBo.getAssigneeIds()); + managementService.executeCommand(deleteSequenceMultiInstanceCmd); + } + List assigneeNames = deleteMultiBo.getAssigneeNames(); + String username = LoginHelper.getUsername(); + TaskEntity newTask = WorkflowUtils.createNewTask(task); + taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN_OFF.getStatus(), username + "减签【" + String.join(StringUtils.SEPARATOR, assigneeNames) + "】"); + taskService.complete(newTask.getId()); + return true; + } catch (Exception e) { + e.printStackTrace(); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 驳回审批 + * + * @param backProcessBo 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String backProcess(BackProcessBo backProcessBo) { + Task task = taskService.createTaskQuery().taskId(backProcessBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskAssignee(String.valueOf(LoginHelper.getUserId())).singleResult(); + if (ObjectUtil.isEmpty(task)) { + throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL); + } + if (task.isSuspended()) { + throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED); + } + try { + String processInstanceId = task.getProcessInstanceId(); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + //获取并行网关执行后保留的执行实例数据 + ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId()); + List executionEntities = managementService.executeCommand(childByExecutionIdCmd); + //校验单据 + if (BusinessStatusEnum.BACK.getStatus().equals(processInstance.getBusinessStatus())) { + throw new ServiceException("该单据已退回!"); + } + //判断是否有多个任务 + List taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).list(); + //申请人节点 + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).finished().orderByHistoricTaskInstanceEndTime().asc().list().get(0); + String backTaskDefinitionKey = historicTaskInstance.getTaskDefinitionKey(); + taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.BACK.getStatus(), StringUtils.isNotBlank(backProcessBo.getMessage()) ? backProcessBo.getMessage() : "退回"); + if (taskList.size() > 1) { + //当前多个任务驳回到单个节点 + runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdsToSingleActivityId(taskList.stream().map(Task::getTaskDefinitionKey).distinct().collect(Collectors.toList()), backTaskDefinitionKey).changeState(); + } else { + //当前单个节点驳回单个节点 + runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdTo(task.getTaskDefinitionKey(), backTaskDefinitionKey).changeState(); + } + List list = taskService.createTaskQuery().processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).list(); + for (Task t : list) { + taskService.setAssignee(t.getId(), historicTaskInstance.getAssignee()); + } + //发送消息 + String message = "您的【" + processInstance.getName() + "】单据已经被驳回,请您注意查收。"; + sendMessage(list, processInstance.getName(), backProcessBo.getMessageType(), message); + //删除流程实例垃圾数据 + for (ExecutionEntity executionEntity : executionEntities) { + DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId()); + managementService.executeCommand(deleteExecutionCmd); + } + runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.BACK.getStatus()); + FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey()); + if (processHandler != null) { + processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.BACK.getStatus(), false); + } + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + return task.getProcessInstanceId(); + } + + /** + * 修改任务办理人 + * + * @param taskIds 任务id + * @param userId 办理人id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateAssignee(String[] taskIds, String userId) { + try { + List list = taskService.createTaskQuery().taskIds(Arrays.asList(taskIds)).taskTenantId(TenantHelper.getTenantId()).list(); + for (Task task : list) { + taskService.setAssignee(task.getId(), userId); + } + } catch (Exception e) { + throw new ServiceException("修改失败:" + e.getMessage()); + } + return true; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java new file mode 100644 index 000000000..7b55ee5a1 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java @@ -0,0 +1,124 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.TestLeave; +import org.dromara.workflow.domain.bo.TestLeaveBo; +import org.dromara.workflow.domain.vo.TestLeaveVo; +import org.dromara.workflow.mapper.TestLeaveMapper; +import org.dromara.workflow.service.IActProcessInstanceService; +import org.dromara.workflow.service.ITestLeaveService; +import org.dromara.workflow.utils.WorkflowUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 请假Service业务层处理 + * + * @author may + * @date 2023-07-21 + */ +@RequiredArgsConstructor +@Service +public class TestLeaveServiceImpl implements ITestLeaveService { + + private final TestLeaveMapper baseMapper; + private final IActProcessInstanceService iActProcessInstanceService; + + /** + * 查询请假 + */ + @Override + public TestLeaveVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 查询请假列表 + */ + @Override + public TableDataInfo queryPageList(TestLeaveBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + TableDataInfo build = TableDataInfo.build(result); + List rows = build.getRows(); + if (CollUtil.isNotEmpty(rows)) { + List ids = StreamUtils.toList(rows, e -> String.valueOf(e.getId())); + WorkflowUtils.setProcessInstanceListVo(rows, ids, "id"); + } + return build; + } + + /** + * 查询请假列表 + */ + @Override + public List queryList(TestLeaveBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(TestLeaveBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getLeaveType()), TestLeave::getLeaveType, bo.getLeaveType()); + lqw.ge(bo.getStartLeaveDays() != null,TestLeave::getLeaveDays, bo.getStartLeaveDays()); + lqw.le(bo.getEndLeaveDays() != null,TestLeave::getLeaveDays, bo.getEndLeaveDays()); + lqw.orderByDesc(BaseEntity::getCreateTime); + return lqw; + } + + /** + * 新增请假 + */ + @Override + public TestLeave insertByBo(TestLeaveBo bo) { + TestLeave add = MapstructUtils.convert(bo, TestLeave.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return add; + } + + /** + * 修改请假 + */ + @Override + public TestLeave updateByBo(TestLeaveBo bo) { + TestLeave update = MapstructUtils.convert(bo, TestLeave.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0 ? update : null; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(TestLeave entity) { + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除请假 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids) { + List idList = StreamUtils.toList(ids, String::valueOf); + iActProcessInstanceService.deleteRuntimeProcessAndHisInstByBusinessKeys(idList); + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java new file mode 100644 index 000000000..620403aaa --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java @@ -0,0 +1,128 @@ +package org.dromara.workflow.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.workflow.domain.WfCategory; +import org.dromara.workflow.domain.bo.WfCategoryBo; +import org.dromara.workflow.domain.vo.WfCategoryVo; +import org.dromara.workflow.mapper.WfCategoryMapper; +import org.dromara.workflow.service.IWfCategoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; + +/** + * 流程分类Service业务层处理 + * + * @author may + * @date 2023-06-28 + */ +@RequiredArgsConstructor +@Service +public class WfCategoryServiceImpl implements IWfCategoryService { + + private final WfCategoryMapper baseMapper; + + private final RepositoryService repositoryService; + + /** + * 查询流程分类 + */ + @Override + public WfCategoryVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + + /** + * 查询流程分类列表 + */ + @Override + public List queryList(WfCategoryBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(WfCategoryBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), WfCategory::getCategoryName, bo.getCategoryName()); + lqw.eq(StringUtils.isNotBlank(bo.getCategoryCode()), WfCategory::getCategoryCode, bo.getCategoryCode()); + return lqw; + } + + /** + * 新增流程分类 + */ + @Override + public Boolean insertByBo(WfCategoryBo bo) { + WfCategory add = MapstructUtils.convert(bo, WfCategory.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改流程分类 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(WfCategoryBo bo) { + WfCategory update = MapstructUtils.convert(bo, WfCategory.class); + validEntityBeforeSave(update); + WfCategoryVo wfCategoryVo = baseMapper.selectVoById(bo.getId()); + List processDefinitionList = repositoryService.createProcessDefinitionQuery().processDefinitionCategory(wfCategoryVo.getCategoryCode()).list(); + for (ProcessDefinition processDefinition : processDefinitionList) { + repositoryService.setProcessDefinitionCategory(processDefinition.getId(), bo.getCategoryCode()); + } + List deploymentList = repositoryService.createDeploymentQuery().deploymentCategory(wfCategoryVo.getCategoryCode()).list(); + for (Deployment deployment : deploymentList) { + repositoryService.setDeploymentCategory(deployment.getId(), bo.getCategoryCode()); + } + List modelList = repositoryService.createModelQuery().modelCategory(wfCategoryVo.getCategoryCode()).list(); + for (Model model : modelList) { + model.setCategory(bo.getCategoryCode()); + repositoryService.saveModel(model); + } + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(WfCategory entity) { + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除流程分类 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 按照类别编码查询 + * + * @param categoryCode 分类比吗 + */ + @Override + public WfCategory queryByCategoryCode(String categoryCode) { + return baseMapper.selectOne(new LambdaQueryWrapper().eq(WfCategory::getCategoryCode, categoryCode)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java new file mode 100644 index 000000000..e30e553eb --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java @@ -0,0 +1,216 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.enums.UserStatus; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.system.domain.SysUser; +import org.dromara.system.domain.SysUserRole; +import org.dromara.system.domain.bo.SysUserBo; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.system.mapper.SysUserMapper; +import org.dromara.system.mapper.SysUserRoleMapper; +import org.dromara.workflow.domain.bo.SysUserMultiBo; +import org.dromara.workflow.domain.vo.MultiInstanceVo; +import org.dromara.workflow.domain.vo.TaskVo; +import org.dromara.workflow.service.IWorkflowUserService; +import org.dromara.workflow.utils.WorkflowUtils; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 工作流用户选人管理 业务处理层 + * + * @author may + */ +@RequiredArgsConstructor +@Service +public class WorkflowUserServiceImpl implements IWorkflowUserService { + + private final SysUserMapper sysUserMapper; + private final SysUserRoleMapper sysUserRoleMapper; + private final TaskService taskService; + private final RuntimeService runtimeService; + + /** + * 分页查询工作流选择加签人员 + * + * @param sysUserMultiBo 参数 + */ + @Override + @SuppressWarnings("unchecked") + public TableDataInfo getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo) { + Task task = taskService.createTaskQuery().taskId(sysUserMultiBo.getTaskId()).singleResult(); + if (task == null) { + throw new ServiceException("任务不存在"); + } + MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + if (multiInstance == null) { + return TableDataInfo.build(); + } + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + //检索条件 + queryWrapper.eq(StringUtils.isNotEmpty(sysUserMultiBo.getDeptId()), SysUser::getDeptId, sysUserMultiBo.getDeptId()); + queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode()); + if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) { + List assigneeList = (List) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList()); + queryWrapper.notIn(CollectionUtil.isNotEmpty(assigneeList), SysUser::getUserId, assigneeList); + } else { + List list = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List userIds = StreamUtils.toList(list, e -> Long.valueOf(e.getAssignee())); + queryWrapper.notIn(CollectionUtil.isNotEmpty(userIds), SysUser::getUserId, userIds); + } + queryWrapper.like(StringUtils.isNotEmpty(sysUserMultiBo.getUserName()), SysUser::getUserName, sysUserMultiBo.getUserName()); + queryWrapper.like(StringUtils.isNotEmpty(sysUserMultiBo.getNickName()), SysUser::getNickName, sysUserMultiBo.getNickName()); + Page page = new Page<>(sysUserMultiBo.getPageNum(), sysUserMultiBo.getPageSize()); + Page userPage = sysUserMapper.selectVoPage(page, queryWrapper); + return TableDataInfo.build(recordPage(userPage)); + } + + /** + * 查询工作流选择减签人员 + * + * @param taskId 任务id 任务id + */ + @Override + @SuppressWarnings("unchecked") + public List getWorkflowDeleteMultiInstanceList(String taskId) { + Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(taskId).singleResult(); + List taskList = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list(); + MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + List taskListVo = new ArrayList<>(); + if (multiInstance == null) { + return Collections.emptyList(); + } + List assigneeList = new ArrayList<>(); + if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) { + List variable = (List) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList()); + for (Object o : variable) { + assigneeList.add(Long.valueOf(o.toString())); + } + } + + if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) { + List userIds = StreamUtils.filter(assigneeList, e -> !String.valueOf(e).equals(task.getAssignee())); + List sysUsers = null; + if (CollectionUtil.isNotEmpty(userIds)) { + sysUsers = sysUserMapper.selectVoBatchIds(userIds); + } + for (Long userId : userIds) { + TaskVo taskVo = new TaskVo(); + taskVo.setId("串行会签"); + taskVo.setExecutionId("串行会签"); + taskVo.setProcessInstanceId(task.getProcessInstanceId()); + taskVo.setName(task.getName()); + taskVo.setAssignee(userId); + if (CollectionUtil.isNotEmpty(sysUsers)) { + sysUsers.stream().filter(u -> u.getUserId().toString().equals(userId.toString())).findFirst().ifPresent(u -> taskVo.setAssigneeName(u.getNickName())); + } + taskListVo.add(taskVo); + } + return taskListVo; + } else if (multiInstance.getType() instanceof ParallelMultiInstanceBehavior) { + List tasks = StreamUtils.filter(taskList, e -> StringUtils.isBlank(e.getParentTaskId()) && !e.getExecutionId().equals(task.getExecutionId()) && e.getTaskDefinitionKey().equals(task.getTaskDefinitionKey())); + if (CollectionUtil.isNotEmpty(tasks)) { + List userIds = StreamUtils.toList(tasks, e -> Long.valueOf(e.getAssignee())); + List sysUsers = null; + if (CollectionUtil.isNotEmpty(userIds)) { + sysUsers = sysUserMapper.selectVoBatchIds(userIds); + } + for (Task t : tasks) { + TaskVo taskVo = new TaskVo(); + taskVo.setId(t.getId()); + taskVo.setExecutionId(t.getExecutionId()); + taskVo.setProcessInstanceId(t.getProcessInstanceId()); + taskVo.setName(t.getName()); + taskVo.setAssignee(Long.valueOf(t.getAssignee())); + if (CollectionUtil.isNotEmpty(sysUsers)) { + sysUsers.stream().filter(u -> u.getUserId().toString().equals(t.getAssignee())).findFirst().ifPresent(e -> taskVo.setAssigneeName(e.getNickName())); + } + taskListVo.add(taskVo); + } + return taskListVo; + } + } + return Collections.emptyList(); + } + + /** + * 翻译部门 + * + * @param page 用户分页数据 + */ + private Page recordPage(Page page) { + List records = page.getRecords(); + if (CollUtil.isEmpty(records)) { + return page; + } + List collectDeptId = StreamUtils.toList(records, SysUserVo::getDeptId); + if (CollUtil.isEmpty(collectDeptId)) { + return page; + } + page.setRecords(records); + return page; + } + + /** + * 按照用户id查询用户 + * + * @param userIds 用户id + */ + @Override + public List getUserListByIds(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + // 检索条件 + queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode()); + queryWrapper.in(SysUser::getUserId, userIds); + return sysUserMapper.selectVoList(queryWrapper); + } + + /** + * 按照角色id查询关联用户id + * + * @param roleIds 角色id + */ + @Override + public List getUserRoleListByRoleIds(List roleIds) { + return sysUserRoleMapper.selectList(new LambdaQueryWrapper().in(SysUserRole::getRoleId, roleIds)); + } + + /** + * 分页查询用户 + * + * @param sysUserBo 参数 + * @param pageQuery 分页 + */ + @Override + public TableDataInfo getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(sysUserBo.getDeptId() != null, SysUser::getDeptId, sysUserBo.getDeptId()); + queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode()); + queryWrapper.like(StringUtils.isNotEmpty(sysUserBo.getUserName()), SysUser::getUserName, sysUserBo.getUserName()); + queryWrapper.like(StringUtils.isNotEmpty(sysUserBo.getNickName()), SysUser::getNickName, sysUserBo.getNickName()); + Page userPage = sysUserMapper.selectVoPage(pageQuery.build(), queryWrapper); + return TableDataInfo.build(recordPage(userPage)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java new file mode 100644 index 000000000..18969b168 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java @@ -0,0 +1,220 @@ +package org.dromara.workflow.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.workflow.domain.vo.MultiInstanceVo; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; +import org.flowable.editor.language.json.converter.BpmnJsonConverter; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.rmi.ServerException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 模型工具 + * + * @author may + */ +public class ModelUtils { + public ModelUtils() { + } + + public static BpmnModel xmlToBpmnModel(String xml) throws IOException { + if (xml == null) { + throw new ServerException("xml不能为空"); + } + try { + InputStream inputStream = new ByteArrayInputStream(StrUtil.utf8Bytes(xml)); + XMLInputFactory factory = XMLInputFactory.newInstance(); + XMLStreamReader reader = factory.createXMLStreamReader(inputStream); + return new BpmnXMLConverter().convertToBpmnModel(reader); + } catch (XMLStreamException e) { + throw new ServerException(e.getMessage()); + } + } + + /** + * bpmnModel转为xml + * + * @param jsonBytes json + */ + public static byte[] bpmnJsonToXmlBytes(byte[] jsonBytes) throws IOException { + if (jsonBytes == null) { + return new byte[0]; + } + // 1. json字节码转成 BpmnModel 对象 + ObjectMapper objectMapper = JsonUtils.getObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(jsonBytes); + BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode); + + if (bpmnModel.getProcesses().isEmpty()) { + return new byte[0]; + } + // 2.将bpmnModel转为xml + return new BpmnXMLConverter().convertToXML(bpmnModel); + } + + /** + * xml转为bpmnModel + * + * @param xmlBytes xml + */ + public static BpmnModel xmlToBpmnModel(byte[] xmlBytes) throws XMLStreamException { + ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(xmlBytes); + XMLInputFactory xif = XMLInputFactory.newInstance(); + XMLStreamReader xtr = xif.createXMLStreamReader(byteArrayInputStream); + return new BpmnXMLConverter().convertToBpmnModel(xtr); + } + + /** + * 校验模型 + * + * @param bpmnModel bpmn模型 + */ + public static void checkBpmnModel(BpmnModel bpmnModel) throws ServerException { + Collection flowElements = bpmnModel.getMainProcess().getFlowElements(); + + checkBpmnNode(flowElements, false); + + List subProcessList = flowElements.stream().filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList()); + if (!CollUtil.isEmpty(subProcessList)) { + for (SubProcess subProcess : subProcessList) { + Collection subProcessFlowElements = subProcess.getFlowElements(); + checkBpmnNode(subProcessFlowElements, true); + } + } + List multiInstanceVoList = new ArrayList<>(); + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof UserTask && ObjectUtil.isNotEmpty(((UserTask) flowElement).getLoopCharacteristics()) && StringUtils.isNotBlank(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem())) { + MultiInstanceVo multiInstanceVo = new MultiInstanceVo(); + multiInstanceVo.setAssigneeList(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem()); + multiInstanceVoList.add(multiInstanceVo); + } + } + + if (CollectionUtil.isNotEmpty(multiInstanceVoList) && multiInstanceVoList.size() > 1) { + Map> assigneeListGroup = StreamUtils.groupByKey(multiInstanceVoList, MultiInstanceVo::getAssigneeList); + for (Map.Entry> entry : assigneeListGroup.entrySet()) { + List value = entry.getValue(); + if (CollectionUtil.isNotEmpty(value) && value.size() > 1) { + String key = entry.getKey(); + throw new ServerException("会签人员集合【" + key + "】重复,请重新设置集合KEY"); + } + } + } + } + + /** + * 校验bpmn节点是否合法 + * + * @param flowElements 节点集合 + * @param subtask 是否子流程 + */ + private static void checkBpmnNode(Collection flowElements, boolean subtask) throws ServerException { + + if (CollUtil.isEmpty(flowElements)) { + throw new ServerException(subtask ? "子流程必须存在节点" : "必须存在节点!"); + } + + List startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList()); + if (CollUtil.isEmpty(startEventList)) { + throw new ServerException(subtask ? "子流程必须存在开始节点" : "必须存在开始节点!"); + } + + if (startEventList.size() > 1) { + throw new ServerException(subtask ? "子流程只能存在一个开始节点" : "只能存在一个开始节点!"); + } + + StartEvent startEvent = startEventList.get(0); + List outgoingFlows = startEvent.getOutgoingFlows(); + if (CollUtil.isEmpty(outgoingFlows)) { + throw new ServerException(subtask ? "子流程流程节点为空,请至少设计一条主线流程!" : "流程节点为空,请至少设计一条主线流程!"); + } + + FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement(); + if (!(targetFlowElement instanceof UserTask) && !subtask) { + throw new ServerException("开始节点后第一个节点必须是用户任务!"); + } + + List endEventList = flowElements.stream().filter(EndEvent.class::isInstance).map(EndEvent.class::cast).collect(Collectors.toList()); + if (CollUtil.isEmpty(endEventList)) { + throw new ServerException(subtask ? "子流程必须存在结束节点!" : "必须存在结束节点!"); + } + } + + /** + * 获取流程全部节点 + * + * @param processDefinitionId 流程定义id + */ + public static List getFlowElements(String processDefinitionId) { + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId); + List list = new ArrayList<>(); + List processes = bpmnModel.getProcesses(); + Collection flowElements = processes.get(0).getFlowElements(); + buildFlowElements(flowElements, list); + return list; + } + + /** + * 递归获取所有节点 + * + * @param flowElements 节点信息 + * @param list 集合 + */ + private static void buildFlowElements(Collection flowElements, List list) { + for (FlowElement flowElement : flowElements) { + list.add(flowElement); + if (flowElement instanceof SubProcess) { + Collection subFlowElements = ((SubProcess) flowElement).getFlowElements(); + buildFlowElements(subFlowElements, list); + } + } + } + + /** + * 获取全部扩展信息 + * + * @param processDefinitionId 流程定义id + */ + public Map> getExtensionElements(String processDefinitionId) { + Map> map = new HashMap<>(); + List flowElements = getFlowElements(processDefinitionId); + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof UserTask && CollUtil.isNotEmpty(flowElement.getExtensionElements())) { + map.putAll(flowElement.getExtensionElements()); + } + } + return map; + } + + /** + * 获取某个节点的扩展信息 + * + * @param processDefinitionId 流程定义id + * @param flowElementId 节点id + */ + public Map> getExtensionElement(String processDefinitionId, String flowElementId) { + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId); + Process process = bpmnModel.getMainProcess(); + FlowElement flowElement = process.getFlowElement(flowElementId); + return flowElement.getExtensionElements(); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java new file mode 100644 index 000000000..711d793b9 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java @@ -0,0 +1,341 @@ +package org.dromara.workflow.utils; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.mail.utils.MailUtils; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.common.websocket.dto.WebSocketMessageDto; +import org.dromara.common.websocket.utils.WebSocketUtils; +import org.dromara.system.domain.SysUserRole; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.common.enums.BusinessStatusEnum; +import org.dromara.workflow.common.enums.MessageTypeEnum; +import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.dromara.workflow.domain.ActHiProcinst; +import org.dromara.workflow.domain.ActHiTaskinst; +import org.dromara.workflow.domain.vo.MultiInstanceVo; +import org.dromara.workflow.domain.vo.ParticipantVo; +import org.dromara.workflow.domain.vo.ProcessInstanceVo; +import org.dromara.workflow.flowable.cmd.UpdateHiTaskInstCmd; +import org.dromara.workflow.mapper.ActHiTaskinstMapper; +import org.dromara.workflow.service.IActHiProcinstService; +import org.dromara.workflow.service.IWorkflowUserService; +import org.dromara.workflow.service.impl.WorkflowUserServiceImpl; +import org.flowable.bpmn.model.*; +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.identitylink.api.history.HistoricIdentityLink; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.util.*; + +import static org.dromara.workflow.common.constant.FlowConstant.*; + +/** + * 工作流工具 + * + * @author may + */ +public class WorkflowUtils { + + public WorkflowUtils() { + } + + private static final ProcessEngine PROCESS_ENGINE = SpringUtils.getBean(ProcessEngine.class); + private static final IWorkflowUserService I_WORKFLOW_USER_SERVICE = SpringUtils.getBean(WorkflowUserServiceImpl.class); + private static final IActHiProcinstService I_ACT_HI_PROCINST_SERVICE = SpringUtils.getBean(IActHiProcinstService.class); + private static final ActHiTaskinstMapper ACT_HI_TASKINST_MAPPER = SpringUtils.getBean(ActHiTaskinstMapper.class); + + /** + * 创建一个新任务 + * + * @param currentTask 参数 + */ + public static TaskEntity createNewTask(Task currentTask) { + TaskEntity task = null; + if (ObjectUtil.isNotEmpty(currentTask)) { + task = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask(); + task.setCategory(currentTask.getCategory()); + task.setDescription(currentTask.getDescription()); + task.setTenantId(currentTask.getTenantId()); + task.setAssignee(currentTask.getAssignee()); + task.setName(currentTask.getName()); + task.setProcessDefinitionId(currentTask.getProcessDefinitionId()); + task.setProcessInstanceId(currentTask.getProcessInstanceId()); + task.setTaskDefinitionKey(currentTask.getTaskDefinitionKey()); + task.setPriority(currentTask.getPriority()); + task.setCreateTime(new Date()); + task.setTenantId(TenantHelper.getTenantId()); + PROCESS_ENGINE.getTaskService().saveTask(task); + } + if (ObjectUtil.isNotNull(task)) { + UpdateHiTaskInstCmd updateHiTaskInstCmd = new UpdateHiTaskInstCmd(Collections.singletonList(task.getId()), task.getProcessDefinitionId(), task.getProcessInstanceId()); + PROCESS_ENGINE.getManagementService().executeCommand(updateHiTaskInstCmd); + } + return task; + } + + /** + * 抄送任务 + * + * @param parentTaskList 父级任务 + * @param userIds 人员id + */ + public static void createCopyTask(List parentTaskList, List userIds) { + List list = new ArrayList<>(); + for (Task parentTask : parentTaskList) { + for (Long userId : userIds) { + TaskEntity newTask = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask(); + newTask.setParentTaskId(parentTask.getId()); + newTask.setAssignee(userId.toString()); + newTask.setName("【抄送】-" + parentTask.getName()); + newTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); + newTask.setProcessInstanceId(parentTask.getProcessInstanceId()); + newTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); + newTask.setTenantId(TenantHelper.getTenantId()); + list.add(newTask); + } + } + PROCESS_ENGINE.getTaskService().bulkSaveTasks(list); + if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(parentTaskList)) { + String processInstanceId = parentTaskList.get(0).getProcessInstanceId(); + String processDefinitionId = parentTaskList.get(0).getProcessDefinitionId(); + List taskIds = StreamUtils.toList(list, Task::getId); + ActHiTaskinst actHiTaskinst = new ActHiTaskinst(); + actHiTaskinst.setProcDefId(processDefinitionId); + actHiTaskinst.setProcInstId(processInstanceId); + actHiTaskinst.setScopeType(TaskStatusEnum.COPY.getStatus()); + actHiTaskinst.setTenantId(TenantHelper.getTenantId()); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(ActHiTaskinst::getId, taskIds); + ACT_HI_TASKINST_MAPPER.update(actHiTaskinst, updateWrapper); + for (Task task : list) { + PROCESS_ENGINE.getTaskService().addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), StrUtil.EMPTY); + } + } + } + + /** + * 获取当前任务参与者 + * + * @param taskId 任务id + */ + public static ParticipantVo getCurrentTaskParticipant(String taskId) { + ParticipantVo participantVo = new ParticipantVo(); + List linksForTask = PROCESS_ENGINE.getHistoryService().getHistoricIdentityLinksForTask(taskId); + Task task = PROCESS_ENGINE.getTaskService().createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(taskId).singleResult(); + if (task != null && CollUtil.isNotEmpty(linksForTask)) { + List groupList = StreamUtils.filter(linksForTask, e -> StringUtils.isNotBlank(e.getGroupId())); + if (CollUtil.isNotEmpty(groupList)) { + List groupIds = StreamUtils.toList(groupList, e -> Long.valueOf(e.getGroupId())); + List sysUserRoles = I_WORKFLOW_USER_SERVICE.getUserRoleListByRoleIds(groupIds); + if (CollUtil.isNotEmpty(sysUserRoles)) { + participantVo.setGroupIds(groupIds); + List userIdList = StreamUtils.toList(sysUserRoles, SysUserRole::getUserId); + List sysUsers = I_WORKFLOW_USER_SERVICE.getUserListByIds(userIdList); + if (CollUtil.isNotEmpty(sysUsers)) { + List userIds = StreamUtils.toList(sysUsers, SysUserVo::getUserId); + List nickNames = StreamUtils.toList(sysUsers, SysUserVo::getNickName); + participantVo.setCandidate(userIds); + participantVo.setCandidateName(nickNames); + participantVo.setClaim(!StringUtils.isBlank(task.getAssignee())); + } + } + } else { + List candidateList = StreamUtils.filter(linksForTask, e -> FlowConstant.CANDIDATE.equals(e.getType())); + List userIdList = new ArrayList<>(); + for (HistoricIdentityLink historicIdentityLink : linksForTask) { + try { + userIdList.add(Long.valueOf(historicIdentityLink.getUserId())); + } catch (NumberFormatException ignored) { + + } + } + List sysUsers = I_WORKFLOW_USER_SERVICE.getUserListByIds(userIdList); + if (CollUtil.isNotEmpty(sysUsers)) { + List userIds = StreamUtils.toList(sysUsers, SysUserVo::getUserId); + List nickNames = StreamUtils.toList(sysUsers, SysUserVo::getNickName); + participantVo.setCandidate(userIds); + participantVo.setCandidateName(nickNames); + if (StringUtils.isBlank(task.getAssignee()) && CollUtil.isNotEmpty(candidateList)) { + participantVo.setClaim(false); + } + if (!StringUtils.isBlank(task.getAssignee()) && CollUtil.isNotEmpty(candidateList)) { + participantVo.setClaim(true); + } + } + } + } + return participantVo; + } + + /** + * 判断当前节点是否为会签节点 + * + * @param processDefinitionId 流程定义id + * @param taskDefinitionKey 流程定义id + */ + public static MultiInstanceVo isMultiInstance(String processDefinitionId, String taskDefinitionKey) { + BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId); + FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey); + MultiInstanceVo multiInstanceVo = new MultiInstanceVo(); + //判断是否为并行会签节点 + if (flowNode.getBehavior() instanceof ParallelMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) { + Expression collectionExpression = behavior.getCollectionExpression(); + String assigneeList = collectionExpression.getExpressionText(); + String assignee = behavior.getCollectionElementVariable(); + multiInstanceVo.setType(behavior); + multiInstanceVo.setAssignee(assignee); + multiInstanceVo.setAssigneeList(assigneeList); + return multiInstanceVo; + //判断是否为串行会签节点 + } else if (flowNode.getBehavior() instanceof SequentialMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) { + Expression collectionExpression = behavior.getCollectionExpression(); + String assigneeList = collectionExpression.getExpressionText(); + String assignee = behavior.getCollectionElementVariable(); + multiInstanceVo.setType(behavior); + multiInstanceVo.setAssignee(assignee); + multiInstanceVo.setAssigneeList(assigneeList); + return multiInstanceVo; + } + return null; + } + + /** + * 获取当前流程状态 + * + * @param taskId 任务id + */ + public static String getBusinessStatusByTaskId(String taskId) { + HistoricTaskInstance historicTaskInstance = PROCESS_ENGINE.getHistoryService().createHistoricTaskInstanceQuery().taskId(taskId).taskTenantId(TenantHelper.getTenantId()).singleResult(); + HistoricProcessInstance historicProcessInstance = PROCESS_ENGINE.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(historicTaskInstance.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + return historicProcessInstance.getBusinessStatus(); + } + + /** + * 获取当前流程状态 + * + * @param processInstanceId 流程实例id + */ + public static String getBusinessStatus(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = PROCESS_ENGINE.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).processInstanceTenantId(TenantHelper.getTenantId()).singleResult(); + return historicProcessInstance.getBusinessStatus(); + } + + /** + * 设置流程实例对象 + * + * @param obj 业务对象 + * @param businessKey 业务id + */ + public static void setProcessInstanceVo(Object obj, String businessKey) { + if (StringUtils.isBlank(businessKey)) { + return; + } + ActHiProcinst actHiProcinst = I_ACT_HI_PROCINST_SERVICE.selectByBusinessKey(businessKey); + if (actHiProcinst == null) { + ProcessInstanceVo processInstanceVo = new ProcessInstanceVo(); + processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus()); + ReflectUtils.invokeSetter(obj, PROCESS_INSTANCE_VO, processInstanceVo); + return; + } + ProcessInstanceVo processInstanceVo = BeanUtil.toBean(actHiProcinst, ProcessInstanceVo.class); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus())); + ReflectUtils.invokeSetter(obj, PROCESS_INSTANCE_VO, processInstanceVo); + } + + /** + * 设置流程实例对象 + * + * @param obj 业务对象 + * @param idList 业务id + * @param fieldName 主键属性名称 + */ + public static void setProcessInstanceListVo(Object obj, List idList, String fieldName) { + if (CollUtil.isEmpty(idList)) { + return; + } + List actHiProcinstList = I_ACT_HI_PROCINST_SERVICE.selectByBusinessKeyIn(idList); + if (obj instanceof Collection collection) { + for (Object o : collection) { + String fieldValue = ReflectUtils.invokeGetter(o, fieldName).toString(); + if (CollUtil.isEmpty(actHiProcinstList)) { + ProcessInstanceVo processInstanceVo = new ProcessInstanceVo(); + processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus()); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus())); + ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo); + } else { + ActHiProcinst actHiProcinst = actHiProcinstList.stream().filter(e -> e.getBusinessKey().equals(fieldValue)).findFirst().orElse(null); + if (ObjectUtil.isNotEmpty(actHiProcinst)) { + ProcessInstanceVo processInstanceVo = BeanUtil.toBean(actHiProcinst, ProcessInstanceVo.class); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus())); + ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo); + } else { + ProcessInstanceVo processInstanceVo = new ProcessInstanceVo(); + processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus()); + processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus())); + ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo); + } + } + } + } + } + + /** + * 发送消息 + * + * @param list 任务 + * @param name 流程名称 + * @param messageType 消息类型 + * @param message 消息内容,为空则发送默认配置的消息内容 + */ + public static void sendMessage(List list, String name, List messageType, String message) { + Set userIds = new HashSet<>(); + if (StringUtils.isBlank(message)) { + message = "有新的【" + name + "】单据已经提交至您的待办,请您及时处理。"; + } + for (Task t : list) { + ParticipantVo taskParticipant = WorkflowUtils.getCurrentTaskParticipant(t.getId()); + if (CollUtil.isNotEmpty(taskParticipant.getGroupIds())) { + List sysUserRoles = I_WORKFLOW_USER_SERVICE.getUserRoleListByRoleIds(taskParticipant.getGroupIds()); + if (CollUtil.isNotEmpty(sysUserRoles)) { + userIds.addAll(StreamUtils.toList(sysUserRoles, SysUserRole::getUserId)); + } + } + List candidate = taskParticipant.getCandidate(); + if (CollUtil.isNotEmpty(candidate)) { + userIds.addAll(candidate); + } + } + if (CollUtil.isNotEmpty(userIds)) { + List sysUserVoList = I_WORKFLOW_USER_SERVICE.getUserListByIds(new ArrayList<>(userIds)); + for (String code : messageType) { + if (code.equals(MessageTypeEnum.SYSTEM_MESSAGE.getCode())) { + WebSocketMessageDto dto = new WebSocketMessageDto(); + dto.setSessionKeys(new ArrayList<>(userIds)); + dto.setMessage(message); + WebSocketUtils.publishMessage(dto); + } + if (code.equals(MessageTypeEnum.EMAIL_MESSAGE.getCode())) { + MailUtils.sendText(StreamUtils.join(sysUserVoList, SysUserVo::getEmail), "单据审批提醒", message); + } + if (code.equals(MessageTypeEnum.SMS_MESSAGE.getCode())) { + //todo 短信发送 + } + } + } + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md new file mode 100644 index 000000000..c938b1e50 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md @@ -0,0 +1,3 @@ +java包使用 `.` 分割 resource 目录使用 `/` 分割 +
+此文件目的 防止文件夹粘连找不到 `xml` 文件 \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml new file mode 100644 index 000000000..44814ec35 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml new file mode 100644 index 000000000..51a095e35 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml new file mode 100644 index 000000000..64d90e610 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml new file mode 100644 index 000000000..e824e3f39 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml new file mode 100644 index 000000000..fb05c24ad --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/script/bpmn/请假流程(会签)-leave5.zip b/script/bpmn/请假流程(会签)-leave5.zip new file mode 100644 index 000000000..afe031af7 Binary files /dev/null and b/script/bpmn/请假流程(会签)-leave5.zip differ diff --git a/script/bpmn/请假流程(包容网关)-leave4.zip b/script/bpmn/请假流程(包容网关)-leave4.zip new file mode 100644 index 000000000..9df55d918 Binary files /dev/null and b/script/bpmn/请假流程(包容网关)-leave4.zip differ diff --git a/script/bpmn/请假流程(子流程)-leave6.zip b/script/bpmn/请假流程(子流程)-leave6.zip new file mode 100644 index 000000000..ca53c3bb6 Binary files /dev/null and b/script/bpmn/请假流程(子流程)-leave6.zip differ diff --git a/script/bpmn/请假流程(并行网关)-leave3.zip b/script/bpmn/请假流程(并行网关)-leave3.zip new file mode 100644 index 000000000..ab10060bc Binary files /dev/null and b/script/bpmn/请假流程(并行网关)-leave3.zip differ diff --git a/script/bpmn/请假流程(排他网关)-leave2.zip b/script/bpmn/请假流程(排他网关)-leave2.zip new file mode 100644 index 000000000..9e151e22d Binary files /dev/null and b/script/bpmn/请假流程(排他网关)-leave2.zip differ diff --git a/script/bpmn/请假流程(普通流程)-leave1.zip b/script/bpmn/请假流程(普通流程)-leave1.zip new file mode 100644 index 000000000..2edee6a6e Binary files /dev/null and b/script/bpmn/请假流程(普通流程)-leave1.zip differ diff --git a/script/bpmn/请假流程(监听)-leave7.zip b/script/bpmn/请假流程(监听)-leave7.zip new file mode 100644 index 000000000..0a71e291d Binary files /dev/null and b/script/bpmn/请假流程(监听)-leave7.zip differ diff --git a/script/sql/flowable.sql b/script/sql/flowable.sql new file mode 100644 index 000000000..b06464714 --- /dev/null +++ b/script/sql/flowable.sql @@ -0,0 +1,65 @@ +insert into sys_menu values('11616', '工作流' , '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11617', '模型管理', '11616', '2', 'model', 'workflow/model/index', '', '1', '1', 'C', '0', '0', 'workflow:model:list', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); + + +-- 流程分类管理相关按钮 +insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, sysdate(), null, null, ''); +-- 请假单信息 +DROP TABLE if EXISTS test_leave; +create table test_leave +( + id bigint not null comment '主键', + leave_type varchar(255) not null comment '请假类型', + start_date datetime not null comment '开始时间', + end_date datetime not null comment '结束时间', + leave_days int(10) not null comment '请假天数', + remark varchar(255) null comment '请假原因', + create_dept bigint null comment '创建部门', + create_by bigint null comment '创建者', + create_time datetime null comment '创建时间', + update_by bigint null comment '更新者', + update_time datetime null comment '更新时间', + tenant_id varchar(20) default '000000' null comment '租户编号', + PRIMARY KEY (id) USING BTREE +) ENGINE = InnoDB COMMENT = '请假申请表'; + +-- 流程分类信息表 +DROP TABLE if EXISTS wf_category; +create table wf_category +( + id bigint not null comment '主键' + primary key, + category_name varchar(255) null comment '分类名称', + category_code varchar(255) null comment '分类编码', + parent_id bigint null comment '父级id', + sort_num int(19) null comment '排序', + tenant_id varchar(20) default '000000' null comment '租户编号', + create_dept bigint null comment '创建部门', + create_by bigint null comment '创建者', + create_time datetime null comment '创建时间', + update_by bigint null comment '更新者', + update_time datetime null comment '更新时间', + constraint uni_category_code + unique (category_code) +) engine=innodb comment= '流程分类'; + +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '请假申请', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, sysdate(), NULL, NULL, '请假申请菜单'); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '请假申请查询', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '请假申请新增', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '请假申请修改', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '请假申请删除', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '请假申请导出', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, sysdate(), NULL, NULL, ''); diff --git a/script/sql/oracle/flowable.sql b/script/sql/oracle/flowable.sql new file mode 100644 index 000000000..308c7c479 --- /dev/null +++ b/script/sql/oracle/flowable.sql @@ -0,0 +1,93 @@ +insert into sys_menu values('11616', '工作流' , '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11617', '模型管理', '11616', '2', 'model', 'workflow/model/index', '', '1', '1', 'C', '0', '0', 'workflow:model:list', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); +insert into sys_menu values('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate, NULL, NULL, ''); + + +-- 流程分类管理相关按钮 +insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, sysdate, null, null, ''); +insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1, sysdate, null, null, ''); +insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, sysdate, null, null, ''); +insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, sysdate, null, null, ''); +insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, sysdate, null, null, ''); + +-- 请假单信息 +create table TEST_LEAVE +( + ID NUMBER(20) not null + constraint PK_TEST_LEAVE + primary key, + LEAVE_TYPE VARCHAR2(255), + START_DATE DATE, + END_DATE DATE, + LEAVE_DAYS NUMBER(10), + REMARK VARCHAR2(255), + CREATE_DEPT NUMBER(20), + CREATE_BY NUMBER(20), + CREATE_TIME DATE, + UPDATE_BY NUMBER(20), + UPDATE_TIME DATE, + TENANT_ID VARCHAR2(255) default '000000' +); + +comment on table TEST_LEAVE is '请假申请表' +comment on column TEST_LEAVE.ID is '主键' +comment on column TEST_LEAVE.LEAVE_TYPE is '请假类型' +comment on column TEST_LEAVE.START_DATE is '开始时间' +comment on column TEST_LEAVE.END_DATE is '结束时间' +comment on column TEST_LEAVE.LEAVE_DAYS is '请假天数' +comment on column TEST_LEAVE.REMARK is '请假原因' +comment on column TEST_LEAVE.CREATE_DEPT is '创建部门' +comment on column TEST_LEAVE.CREATE_BY is '创建者' +comment on column TEST_LEAVE.CREATE_TIME is '创建时间' +comment on column TEST_LEAVE.UPDATE_BY is '更新者' +comment on column TEST_LEAVE.UPDATE_TIME is '更新时间' +comment on column TEST_LEAVE.TENANT_ID is '租户编号' + +-- 流程分类信息表 +create table WF_CATEGORY +( + ID NUMBER(20) not null + constraint PK_WF_CATEGORY + primary key, + CATEGORY_NAME VARCHAR2(255), + CATEGORY_CODE VARCHAR2(255) + constraint UNI_CATEGORY_CODE + unique, + PARENT_ID NUMBER(20), + SORT_NUM NUMBER(10), + TENANT_ID NUMBER(20), + CREATE_DEPT NUMBER(20), + CREATE_BY NUMBER(20), + CREATE_TIME DATE, + UPDATE_BY NUMBER(20), + UPDATE_TIME DATE +); + +comment on table WF_CATEGORY is '流程分类' +comment on column WF_CATEGORY.ID is '主键' +comment on column WF_CATEGORY.CATEGORY_NAME is '分类名称' +comment on column WF_CATEGORY.CATEGORY_CODE is '分类编码' +comment on column WF_CATEGORY.PARENT_ID is '父级id' +comment on column WF_CATEGORY.SORT_NUM is '排序' +comment on column WF_CATEGORY.TENANT_ID is '租户编号' +comment on column WF_CATEGORY.CREATE_DEPT is '创建部门' +comment on column WF_CATEGORY.CREATE_BY is '创建者' +comment on column WF_CATEGORY.CREATE_TIME is '创建时间' +comment on column WF_CATEGORY.UPDATE_BY is '更新者' +comment on column WF_CATEGORY.UPDATE_TIME is '更新时间' + +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '请假申请', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, sysdate, NULL, NULL, '请假申请菜单'); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '请假申请查询', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, sysdate, NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '请假申请新增', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, sysdate, NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '请假申请修改', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, sysdate, NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '请假申请删除', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, sysdate, NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '请假申请导出', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, sysdate, NULL, NULL, ''); diff --git a/script/sql/postgres/flowable.sql b/script/sql/postgres/flowable.sql new file mode 100644 index 000000000..aa68592b2 --- /dev/null +++ b/script/sql/postgres/flowable.sql @@ -0,0 +1,122 @@ +insert into sys_menu values('11616', '工作流' , '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11617', '模型管理', '11616', '2', 'model', 'workflow/model/index', '', '1', '1', 'C', '0', '0', 'workflow:model:list', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); +insert into sys_menu values('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, ''); + + +-- 流程分类管理相关按钮 +insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, now(), null, null, ''); +insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1, now(), null, null, ''); +insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, now(), null, null, ''); +insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, now(), null, null, ''); +insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, now(), null, null, ''); +-- 请假单信息 +DROP TABLE if EXISTS test_leave; +create table test_leave +( + id bigint not null + constraint test_leave_pk + primary key, + leave_type varchar(255), + start_date timestamp, + end_date timestamp, + leave_days bigint, + remark varchar(255), + create_dept bigint, + create_by bigint, + create_time timestamp, + update_by bigint, + update_time timestamp, + tenant_id varchar(20) +); + +comment on table test_leave is '请假申请表'; + +comment on column test_leave.id is '主键'; + +comment on column test_leave.leave_type is '请假类型'; + +comment on column test_leave.start_date is '开始时间'; + +comment on column test_leave.end_date is '结束时间'; + +comment on column test_leave.remark is '请假原因'; + +comment on column test_leave.create_dept is '创建部门'; + +comment on column test_leave.create_by is '创建者'; + +comment on column test_leave.create_time is '创建时间'; + +comment on column test_leave.update_by is '更新者'; + +comment on column test_leave.update_time is '更新时间'; + +comment on column test_leave.tenant_id is '租户编码'; + +alter table test_leave + owner to postgres; + +-- 流程分类信息表 +DROP TABLE if EXISTS wf_category; +create table wf_category +( + id bigint not null + constraint wf_category_pk + primary key, + category_name varchar(255), + category_code varchar(255), + parent_id bigint, + sort_num bigint, + tenant_id bigint, + create_dept bigint, + create_by bigint, + create_time timestamp, + update_by bigint, + update_time timestamp +); + +comment on table wf_category is '流程分类'; + +comment on column wf_category.id is '主键'; + +comment on column wf_category.category_name is '分类名称'; + +comment on column wf_category.category_code is '分类编码'; + +comment on column wf_category.parent_id is '父级id'; + +comment on column wf_category.sort_num is '排序'; + +comment on column wf_category.tenant_id is '租户id'; + +comment on column wf_category.create_dept is '创建部门'; + +comment on column wf_category.create_by is '创建者'; + +comment on column wf_category.create_time is '创建时间'; + +comment on column wf_category.update_by is '修改者'; + +comment on column wf_category.update_time is '修改时间'; + +alter table wf_category + owner to postgres; + +create unique index uni_category_code + on wf_category (category_code); + +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '请假申请', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, now(), NULL, NULL, '请假申请菜单'); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '请假申请查询', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '请假申请新增', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '请假申请修改', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '请假申请删除', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '请假申请导出', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, now(), NULL, NULL, ''); diff --git a/script/sql/sqlserver/flowable.sql b/script/sql/sqlserver/flowable.sql new file mode 100644 index 000000000..2c3f706a0 --- /dev/null +++ b/script/sql/sqlserver/flowable.sql @@ -0,0 +1,155 @@ +insert into sys_menu values('11616', '工作流' , '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11617', '模型管理', '11616', '2', 'model', 'workflow/model/index', '', '1', '1', 'C', '0', '0', 'workflow:model:list', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); +insert into sys_menu values('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, getdate(), NULL, NULL, ''); + + +-- 流程分类管理相关按钮 +insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, getdate(), null, null, ''); +insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1, getdate(), null, null, ''); +insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, getdate(), null, null, ''); +insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, getdate(), null, null, ''); +insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, getdate(), null, null, ''); +-- 请假单信息 +DROP TABLE if EXISTS test_leave; +create table test_leave +( + id bigint not null + primary key, + leave_type nvarchar(255) not null, + start_date datetime2 not null, + end_date datetime2 not null, + leave_days int not null, + remark nvarchar(255), + create_dept bigint, + create_by bigint, + create_time datetime2, + update_by bigint, + update_time datetime2, + tenant_id nvarchar(20) default '000000' +) +go + +exec sp_addextendedproperty 'MS_Description', N'请假申请表', 'SCHEMA', 'dbo', 'TABLE', 'test_leave' +go + +exec sp_addextendedproperty 'MS_Description', N'主键', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'id' +go + +exec sp_addextendedproperty 'MS_Description', N'请假类型', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'leave_type' +go + +exec sp_addextendedproperty 'MS_Description', N'开始时间', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'start_date' +go + +exec sp_addextendedproperty 'MS_Description', N'结束时间', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'end_date' +go + +exec sp_addextendedproperty 'MS_Description', N'请假天数', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'leave_days' +go + +exec sp_addextendedproperty 'MS_Description', N'请假原因', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'remark' +go + +exec sp_addextendedproperty 'MS_Description', N'创建部门', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'create_dept' +go + +exec sp_addextendedproperty 'MS_Description', N'创建者', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'create_by' +go + +exec sp_addextendedproperty 'MS_Description', N'创建时间', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'create_time' +go + +exec sp_addextendedproperty 'MS_Description', N'更新者', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'update_by' +go + +exec sp_addextendedproperty 'MS_Description', N'更新时间', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', + 'update_time' +go + +exec sp_addextendedproperty 'MS_Description', N'租户编号', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'tenant_id' +go + +-- 流程分类信息表 +DROP TABLE if EXISTS wf_category; +create table wf_category +( + id bigint not null + primary key, + category_name nvarchar(255), + category_code nvarchar(255) + constraint uni_category_code + unique, + parent_id bigint, + sort_num int, + tenant_id nvarchar(20) default '000000', + create_dept bigint, + create_by bigint, + create_time datetime2, + update_by bigint, + update_time datetime2 +) +go + +exec sp_addextendedproperty 'MS_Description', N'流程分类', 'SCHEMA', 'dbo', 'TABLE', 'wf_category' +go + +exec sp_addextendedproperty 'MS_Description', N'主键', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'id' +go + +exec sp_addextendedproperty 'MS_Description', N'分类名称', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'category_name' +go + +exec sp_addextendedproperty 'MS_Description', N'分类编码', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'category_code' +go + +exec sp_addextendedproperty 'MS_Description', N'父级id', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'parent_id' +go + +exec sp_addextendedproperty 'MS_Description', N'排序', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'sort_num' +go + +exec sp_addextendedproperty 'MS_Description', N'租户编号', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'tenant_id' +go + +exec sp_addextendedproperty 'MS_Description', N'创建部门', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'create_dept' +go + +exec sp_addextendedproperty 'MS_Description', N'创建者', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'create_by' +go + +exec sp_addextendedproperty 'MS_Description', N'创建时间', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'create_time' +go + +exec sp_addextendedproperty 'MS_Description', N'更新者', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'update_by' +go + +exec sp_addextendedproperty 'MS_Description', N'更新时间', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', + 'update_time' +go + + +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '请假申请', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, getdate(), NULL, NULL, '请假申请菜单'); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '请假申请查询', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, getdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '请假申请新增', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, getdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '请假申请修改', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, getdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '请假申请删除', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, getdate(), NULL, NULL, ''); +INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '请假申请导出', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, getdate(), NULL, NULL, '');