From 1bac18b92e47b3da2e1265d91adb6a59a20d53d4 Mon Sep 17 00:00:00 2001 From: aikai Date: Tue, 8 Jul 2025 11:49:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor(bpm):=20=E4=BC=98=E5=8C=96=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=AE=9E=E4=BE=8B=E5=88=9B=E5=BB=BA=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E6=8A=84=E9=80=81=E4=BA=BA=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在流程实例创建后立即处理抄送人信息,作为双重保险 - 实现事件监听器中处理抄送人信息的方法- 添加延迟处理抄送人信息的方法,避免并发问题 -增加幂等性处理,确保更新操作能正常命中 --- .../task/BpmProcessInstanceServiceImpl.java | 1173 ++++++++++++++++- 1 file changed, 1172 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 26d98524..cbedd2ef 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1,1172 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.cash.BpmOACashRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.payment.BpmOAPaymentRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmOAPrintDataRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.reimbursement.BpmOAReimbursementRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.salary.BpmOASalaryRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessCcDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.*; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.DynamicMapper; import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmConstants; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessCcService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.oa.*; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; import cn.iocoder.yudao.module.smartfactory.api.factoryInfo.FactoryInfoApi; import cn.iocoder.yudao.module.smartfactory.api.factoryInfo.dto.FactoryInfoDTO; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.permission.PermissionApi; import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.lang.reflect.Field; import java.text.DecimalFormat; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private TaskService engineTaskService; @Resource private BpmTaskAssignRuleMapper taskRuleMapper; @Resource private BpmUserGroupService userGroupService; @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource @Lazy // 解决循环依赖 private BpmMessageService messageService; @Resource private ConfigApi configApi; @Resource private DynamicMapper dynamicMapper ; @Resource private BpmProcessCcService processCcService; @Resource @Lazy // 解决循环依赖 private StringRedisTemplate stringRedisTemplate; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } public String transform(String input) { if(StringUtil.isNotEmpty(input)) { String[] parts = input.split(","); StringBuilder result = new StringBuilder(); for (String part : parts) { String[] keyValue = part.split(":"); if (keyValue.length > 1) { result.append(keyValue[1]).append(","); } } if (result.length() > 0) { return result.substring(0, result.length() - 1); } } return ""; } public String replaceValues(String input, Map map) { String[] parts = input.split(","); StringBuilder result = new StringBuilder(); for (String part : parts) { String[] keyValue = part.split(":"); if (keyValue.length > 1) { String key = keyValue[1].trim(); // 去除可能的空格 if (map.containsKey(key)) { Object value = map.get(key); String stringValue = (value != null) ? value.toString() : ""; // 将值转换为字符串 result.append(keyValue[0].trim()).append(":").append(stringValue).append(","); } else { //result.append(part).append(","); } } } if (result.length() > 0) { return result.substring(0, result.length() - 1); } return ""; } @Override @TenantIgnore public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); List bpmProcessInstanceExtDOList = pageResult.getList() ; for (int i = 0; i < bpmProcessInstanceExtDOList.size() ; i++) { BpmProcessInstanceExtDO bpmProcessInstanceExtDO = bpmProcessInstanceExtDOList.get(i) ; String input = bpmProcessInstanceExtDO.getFieldNames() ; if(StringUtil.isNotEmpty(input)) { String sql = "SELECT " + transform(bpmProcessInstanceExtDO.getFieldNames()) + " FROM " + bpmProcessInstanceExtDO.getTableName() + " WHERE id=" + bpmProcessInstanceExtDO.getBusinessKey(); log.info(sql); Map map = dynamicMapper.selectListByTableName(sql) ; String detailInfo = ""; if(map != null) { detailInfo = replaceValues(bpmProcessInstanceExtDO.getFieldNames(),map) ; } bpmProcessInstanceExtDO.setDetailInfo(detailInfo); } } if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); List ids = taskMap.values().stream() .flatMap(Collection::stream) .map(Task::getAssignee) .collect(Collectors.toList()); Iterator iterator = ids.iterator(); while (iterator.hasNext()) { String id = iterator.next(); if (id == null) { iterator.remove(); } } List longIds = ids.stream() .map(Long::valueOf) .collect(Collectors.toList()); // 获得 User Map Map userMap = adminUserApi.getUserMap(longIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, userMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 if (MapUtil.isEmpty(createReqVO.getVariables())){ createReqVO.setVariables(new HashMap<>()); } return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 if (MapUtil.isEmpty(createReqDTO.getVariables())){ createReqDTO.setVariables(new HashMap<>()); } return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); DeptRespDTO dept = null; DeptRespDTO companyDept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); companyDept = deptApi.getUserCompanyDept(startUser.getId()).getCheckedData(); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept, companyDept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override @DataPermission(enable = false) public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 删除 流程审批部门缓存 updateAssigneeDeptRedis(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @DataPermission(enable = false) public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 删除 流程审批部门缓存 updateAssigneeDeptRedis(instance.getProcessInstanceId()); Map processVariables = runtimeService.getVariables(instance.getProcessInstanceId()); String reason = (String) processVariables.get("approve_reason"); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) @DataPermission(enable = false) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(id) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 删除 流程审批部门缓存 updateAssigneeDeptRedis(id); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceResult(String id, Integer result) { processInstanceExtMapper.update(null, new LambdaUpdateWrapper() .set(BpmProcessInstanceExtDO::getResult, result) .eq(BpmProcessInstanceExtDO::getProcessInstanceId, id)); } /** * 删除 流程审批部门缓存 * @param processInstanceId 流程实例id */ public void updateAssigneeDeptRedis(String processInstanceId) { stringRedisTemplate.delete("assignee_dept_" + processInstanceId); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置当前用户 FlowableUtils.setAuthenticatedUserId(userId); //获取流程发起人User AdminUserRespDTO startUser = adminUserApi.getUser(userId).getCheckedData(); //获取流程发起人部门信息 DeptRespDTO startDeptInfo = deptApi.getDept(startUser.getDeptId()).getCheckedData(); // 获取岗位信息 Set postIds = startUser.getPostIds(); ArrayList list = new ArrayList<>(postIds); if (CollectionUtil.isEmpty(startUser.getPostIds())) { // 当前审批用户未配置岗位 // 操作失败,原因:您未配置岗位,请联系管理员操作 throw exception(TASK_OPERATE_FAIL_USER_NO_POST); } if( startUser.getDeptId() == null ) { // 当前审批用户未配置部门 // 操作失败,原因:您未配置部门,请联系管理员操作 throw exception(TASK_OPERATE_FAIL_USER_NO_DEPT); } //配置通用流程变量 variables.put("post_id", list.get(0).toString()); // 只获配置的首个岗位 variables.put("user_id", userId.toString()); // 配置发起人用户id variables.put("dept_id", startUser.getDeptId().toString()); // 配置发起人部门id variables.put("dept_flag", startDeptInfo.getFlag()); // 配置发起人部门flag // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 设置流程审批部门缓存 stringRedisTemplate.opsForValue().set("assignee_dept_" + instance.getId(), startUser.getDeptId().toString()); // // 在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 // TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { // // @Override // public void afterCommit() { // // // 创建流程后,添加抄送人 // processCCToUsers(instance, variables); // } // }); // 创建流程后,添加抄送人 processCCToUsers(instance, variables); return instance.getId(); // // 补全流程实例的拓展表 // processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) // .setFormVariables(variables)); // // /** 创建流程后,添加抄送人 End add by yj 2024.1.4 */ // processCCToUsers(definition, instance); // /** 通过自己发起的流程 */ // approveSelfTask(instance.getId()) ; } private void approveSelfTask(String processInstanceId) { List tasks = engineTaskService.createTaskQuery().processInstanceId(processInstanceId).list(); if (tasks != null && tasks.size() > 0) { Task task = tasks.get(0); String assigneeId = task.getAssignee(); //如果当前登陆用户是审批人,那么自动审批通过 if (assigneeId.equals(SecurityFrameworkUtils.getLoginUserId().toString())) { BpmTaskApproveReqVO reqVO = new BpmTaskApproveReqVO(); reqVO.setId(task.getId()); reqVO.setReason(BpmConstants.AUTO_APPRAVAL); taskService.approveTask(getLoginUserId(), reqVO); } } } /** * 获得抄送我的流程实例的分页 * * @param userId 用户编号 * @param pageReqVO 分页请求 * @return 流程实例的分页 */ public PageResult getMyCCProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectCCPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, null); } @Override public List getProcessInstancesGroupByModelName(BpmProcessInstanceStatisticsReqVO pageReqVO) { pageReqVO = getUserids(pageReqVO); return processInstanceExtMapper.getProcessInstancesGroupByModelName(pageReqVO); } @Override public List getProcessInstancesGroupByResultStatus(BpmProcessInstanceStatisticsReqVO pageReqVO) { pageReqVO = getUserids(pageReqVO); return processInstanceExtMapper.getProcessInstancesGroupByResultStatus(pageReqVO); } @Override public PageResult getStatisticsProcessInstancePage(@Valid BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectStatisticePage(pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); List ids = taskMap.values().stream() .flatMap(Collection::stream) .map(Task::getAssignee) .collect(Collectors.toList()); Iterator iterator = ids.iterator(); while (iterator.hasNext()) { String id = iterator.next(); if (id == null) { iterator.remove(); } } List longIds = ids.stream() .map(Long::valueOf) .collect(Collectors.toList()); //获得 审批人 User Map Map assigneeUserMap = adminUserApi.getUserMap(longIds); //获得 发起人 User Map List bpieDOs = pageResult.getList(); longIds = new ArrayList<>(); for (BpmProcessInstanceExtDO bpieDO : bpieDOs) { longIds.add(bpieDO.getStartUserId()); } Map startUserMap = adminUserApi.getUserMap(longIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertStatisticsPage(pageResult, taskMap, assigneeUserMap, startUserMap); } @Resource PermissionApi permissionApi; /** * 根据数据权限,查询关联操作的用户IDS * * @param pageReqVO * @param * @return */ private T getUserids(T pageReqVO) { try { Class clazz = (Class) pageReqVO.getClass(); Field idField = clazz.getDeclaredField("userIds"); idField.setAccessible(true); // 设置可访问性 Long[] userIds = null; Long userId = WebFrameworkUtils.getLoginUserId(); DeptDataPermissionRespDTO deptDataPermission = permissionApi.getDeptDataPermission(userId).getCheckedData(); //查询全部 if (deptDataPermission.getAll()) { //idField.set(pageReqVO, null); // 设置属性值 return pageReqVO; } // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) && Boolean.FALSE.equals(deptDataPermission.getSelf())) { //设置成0,一个不存在的用户Id,就查询不到数据了。 userIds = new Long[]{0L}; idField.set(pageReqVO, userIds); return pageReqVO; } //情况三 至查询自己 if (deptDataPermission.getSelf()) { userIds = new Long[]{userId}; idField.set(pageReqVO, userIds); return pageReqVO; } Set deptIds = deptDataPermission.getDeptIds(); //查询部门关联的用户Id List users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); List tempList = new ArrayList<>(); for (AdminUserRespDTO user : users) { Long id = user.getId(); tempList.add(id); } tempList.add(userId); userIds = tempList.stream().toArray(Long[]::new); //将临时的List集合转换成数组集合 idField.set(pageReqVO, userIds); return pageReqVO; } catch (Exception exception) { exception.printStackTrace(); throw exception(BPM_SYSTEM_BUG); } } /** * /创建流程后,添加抄送人 Begin add by yj 2024.1.4 * 在设计流程的时候,需要添加一个任务块 名字必须叫Activity_cc 分配权限的时候,需要选择用户组。 * * @param definition * @param instance */ private void processCCToUsers(ProcessDefinition definition, ProcessInstance instance) { //获取bpm_task_assign_reule (Bpm 任务规则表)的流程中有没有配置抄送节点 固定抄送名称为:Activity_cc String processDefinitionId = definition.getId(); List rules = taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, null); String BpmConstantsName = BpmConstants.CC_NAME; // 获取发起人信息 AdminUserRespDTO userRespDTO = adminUserApi.getUser(Long.valueOf(instance.getStartUserId())).getCheckedData(); DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId()).getCheckedData(); //发起人是深圳分公司 if (deptRespDTO.getFlag().contains("136")) { BpmConstantsName = BpmConstants.CCSZ_NAME; } for (BpmTaskAssignRuleDO rule : rules) { String key = rule.getTaskDefinitionKey(); //任务名称 Integer type = rule.getType(); if (!key.isEmpty() && key.equals(BpmConstantsName) && type == 40) { StringBuffer str = new StringBuffer(); Set options = rule.getOptions(); List list = new ArrayList(options); for (Long groupId : list) { //需要根据这个groupId,查询这个组中的用户id BpmUserGroupDO userGroup = userGroupService.getUserGroup(groupId); Set userIds = userGroup.getMemberUserIds(); List userIdList = new ArrayList(userIds); for (Long user_id : userIdList) { str.append("[").append(user_id).append("]"); } } //流程是采购计划时 if (definition.getKey().equals(BpmOAProcureServiceImpl.PROCESS_KEY)) { //发起人部门为 生产部及以下时 if (deptRespDTO.getFlag().contains("130")) { //添加 供应部抄送 BpmUserGroupDO userGroup = userGroupService.getUserGroup(121L); Set userIds = userGroup.getMemberUserIds(); List userIdList = new ArrayList(userIds); for (Long user_id : userIdList) { str.append("[").append(user_id).append("]"); } } } String ccids = str.toString(); //根据processDefinitionId 将ccids保存到bpm_process_instance_ext中的ccids字段 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessDefinitionId(processDefinitionId) .setCcids(ccids).setProcessInstanceId(instance.getProcessInstanceId()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); break; } } } /** * 创建流程后,添加抄送人 * @param instance 流程实例 */ private void processCCToUsers(ProcessInstance instance, Map variables) { // 根据流程名称、流程发起人 查询流程配置的抄送人信息 List processCcList = processCcService.getCCListByName(instance.getName(), Long.valueOf(instance.getStartUserId())); // 提取抄送信息用中对应的用户组编号 Set userGroupIds = processCcList.stream() .flatMap(data -> data.getUserGroupId().stream()) .collect(Collectors.toSet()); //根据processDefinitionId 将ccIds保存到bpm_process_instance_ext中的ccIds字段 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessDefinitionId(instance.getProcessDefinitionId()) .setFormVariables(variables) .setProcessInstanceId(instance.getProcessInstanceId()); if (CollectionUtil.isNotEmpty(userGroupIds)) { // 获取用户组信息 List userGroups = userGroupService.getUserGroupList(userGroupIds); // 提取用户组中对应的用户ID Set userIds = userGroups.stream() .flatMap(data -> data.getMemberUserIds().stream()) .collect(Collectors.toSet()); // 将用户ID列表转换为字符串 String ccIds = userIds.stream() .map(id -> "[" + id + "]") // 每个值用方括号包裹 .collect(Collectors.joining()); //根据processDefinitionId 将ccIds保存到bpm_process_instance_ext中的ccIds字段 instanceExtDO.setCcids(ccIds); } processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); } @Override public List getUserProcessTpo10(BpmProcessInstanceStatisticsReqVO pageReqVO) { //按审核人分组查询,统计已完成流程数量及平均耗时间 List respVOS = processInstanceExtMapper.getUserProcessTpo10(pageReqVO); if (respVOS == null || respVOS.size() == 0) { return null; } List idList = respVOS.stream() .map(BpmProcessFinishStatisticsRespVO::getUserId) .collect(Collectors.toList()); //根据userId,查询UserMap Map userMap = adminUserApi.getUserMap(idList); Long[] idsArray = new Long[userMap.size()]; // 使用 map 的 keySet() 方法获取键集合 Set keys = userMap.keySet(); // 遍历键集合,将每个键添加到数组中 int index = 0; for (Long key : keys) { idsArray[index++] = key; } pageReqVO.setUserIds(idsArray); //按审核人分组查询,未审批完成的流程数量 List bpmTaskExtDOs = processInstanceExtMapper.selectUnfinishProcessCount(pageReqVO); //按审核人分组查询,未完成的记录数 Map unFinfishCountCountMap = new HashMap<>(); for (BpmProcessFinishStatisticsRespVO item : bpmTaskExtDOs) { unFinfishCountCountMap.put(item.getUserId(), item.getUnFinfishCount()); } respVOS.forEach(respVO -> respVO.setName(userMap.get(respVO.getUserId()).getNickname())); respVOS.forEach(respVO -> respVO.setUnFinfishCount(unFinfishCountCountMap.get(respVO.getUserId()))); //获取排行榜第一记录的耗时,作为基础数据 double num = Double.parseDouble(respVOS.get(0).getUserTime()); // 先将字符串转换为双精度浮点数 int baseNumber = (int) num; // 再通过类型转换操作符将其转换为整数 DecimalFormat df = new DecimalFormat("#.00"); respVOS.forEach(respVO -> { //格式化流程完成率 float finfishCount = (float) respVO.getFinfishCount(); float unFinfishCount = respVO.getUnFinfishCount() == null ? 0 : respVO.getUnFinfishCount(); float all = finfishCount + unFinfishCount; float result = finfishCount / all; float rate = result * 100; respVO.setCompletionRate(df.format(rate) + "%"); double dValue = Double.parseDouble(respVO.getUserTime()); // 将字符串转换为double类型 int roundedValue = (int) Math.round(dValue); // 使用Math.round()进行四舍五入,并转换为int类型 respVO.setUserTime(roundedValue + ""); //格式化进度百度比, 参照最高的数据进行百分比显示 double percentage = ((int) Double.parseDouble(respVO.getUserTime()) / (double) baseNumber) * 100; respVO.setPercentage((int) Math.round(percentage)); //设置未完成 respVO.setUnFinfishCount(Integer.valueOf((int) unFinfishCount)); }); return respVOS; } @Override @DataPermission(enable = false) public BpmProcessInstanceExtDO getProcessInstanceDO(String id) { return processInstanceExtMapper.selectByProcessInstanceId(id); } @Resource private FileApi fileApi; @Resource @Lazy // 解决循环依赖 private BpmOAReimbursementService reimbursementService; @Resource @Lazy // 解决循环依赖 private BpmOACashService cashService; @Resource @Lazy // 解决循环依赖 private BpmOAImprestService imprestService; @Resource @Lazy // 解决循环依赖 private BpmOAPaymentService paymentService; @Resource @Lazy private BpmOASalaryService salaryService; @Resource private FactoryInfoApi factoryInfoApi; @Override public BpmProcessInstancePrintDataRespVO getOAReportPrintData(BpmProcessInstancePrintDataReqVO reqVO) { BpmProcessInstancePrintDataRespVO bpmProcessInstancePrintDataRespVO = new BpmProcessInstancePrintDataRespVO(); String key = reqVO.getKey(); //流程标识 String processInstanceId = reqVO.getId(); //流程实例ID bpmProcessInstancePrintDataRespVO.setId(processInstanceId); bpmProcessInstancePrintDataRespVO.setKey(key); BpmProcessInstanceRespVO bpmProcessInstance = getProcessInstanceVO(processInstanceId); Long businessKey = Long.valueOf(bpmProcessInstance.getBusinessKey()); //流程业务表-主键ID // 判断是否可见外勤人员信息 List userIds = new ArrayList<>(); String type = configApi.getConfigKey("sys.user.type").getCheckedData(); if ("1".equals(type)) { //不可见时 // 获取所有外勤人员 用户编号 userIds = adminUserApi.getUserIdsByUserNature(4).getCheckedData(); } // 获得流程审批信息 List taskRespVOList = taskService.getTaskListByProcessInstanceId(processInstanceId); // 移除外勤人员信息 List finalUserIds = userIds; taskRespVOList.removeIf(data -> finalUserIds.contains(data.getAssigneeUser().getId())); //给User添加签名地址 taskRespVOList.forEach(taskRespVO -> taskRespVO.getAssigneeUser().setSignURL( fileApi.getUserSignImgPath( taskRespVO.getAssigneeUser().getId() ).getData() ) ); //获取流程抄送用户编号 BpmProcessInstanceExtDO processInstanceExtDO = getProcessInstanceDO(processInstanceId); //获取流程抄送用户信息 List ccUserIds = new ArrayList<>(); if (processInstanceExtDO.getCcids() != null && !processInstanceExtDO.getCcids().isEmpty()) { Pattern pattern = Pattern.compile("\\[(\\d+)]"); Matcher matcher = pattern.matcher(processInstanceExtDO.getCcids()); while (matcher.find()) { ccUserIds.add(Long.parseLong(matcher.group(1))); } // 移除 外勤人员信息 ccUserIds.removeAll(userIds); } List userRespDTOS = adminUserApi.getUserList(ccUserIds).getCheckedData(); //获取抄送用户部门信息 Map deptMapDTO = deptApi.getDeptMap(convertSet(userRespDTOS, AdminUserRespDTO::getDeptId)); // 设置抄送人信息 List ccUsers = userRespDTOS.stream().map(user -> { BpmOAPrintDataRespVO.CCUser cc = new BpmOAPrintDataRespVO.CCUser(); cc.setCcUserId(user.getId()); cc.setCcUserName(user.getNickname()); cc.setCcDeptName(deptMapDTO.get(user.getDeptId()).getName()); return cc; }).collect(Collectors.toList()); BpmOAPrintDataRespVO printData = new BpmOAPrintDataRespVO(); // 设置抄送人 printData.setCcUsers(ccUsers); // 设置发起人信息 printData.setStartUser(BeanUtils.toBean(bpmProcessInstance.getStartUser(), BpmOAPrintDataRespVO.User.class)); switch (key) { case "oa_reimbursement": BpmOAReimbursementDO reimbursement = reimbursementService.getReimbursement(businessKey); BpmOAReimbursementRespVO bpmOAReimbursementRespVO = reimbursementService.convert(reimbursement); //报销业务数据 // 移除自己得审批节点 taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(reimbursement.getUserId())); //备用金信息查询 BpmOAImprestDO bpmOAImprestDO = imprestService.getImprest(reimbursement.getImprestId()); if (bpmOAImprestDO != null) { //设置备用金 金额 bpmOAReimbursementRespVO.setAmount(bpmOAImprestDO.getAmount()); // 设置备用金 剩余金额 bpmOAReimbursementRespVO.setRemainingAmount(bpmOAImprestDO.getAmount().subtract(bpmOAImprestDO.getReimbursedAmount())); } printData.setBpmOAReimbursementRespVO(bpmOAReimbursementRespVO); printData.setProcessTasks(taskRespVOList); break; case "oa_cash": BpmOACashDO cashDO = cashService.getCash(businessKey); BpmOACashRespVO cashRespVO = cashService.convertCash(cashDO); // 移除自己得审批节点 taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(cashDO.getUserId())); //备用金信息查询 BpmOAImprestDO cashImprestDO = imprestService.getImprest(cashDO.getImprestId()); if (cashImprestDO != null) { //设置备用金 金额 cashRespVO.setAmount(cashImprestDO.getAmount()); // 设置备用金 剩余金额 cashRespVO.setRemainingAmount(cashImprestDO.getAmount().subtract(cashImprestDO.getReimbursedAmount())); } printData.setBpmOACashRespVO(cashRespVO); printData.setProcessTasks(taskRespVOList); break; case "oa_payment_2": BpmOAPaymentDO paymentDO = paymentService.getPayment(businessKey); BpmOAPaymentRespVO paymentRespVO = paymentService.convertPayment(paymentDO); // 移除自己得审批节点 taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(paymentDO.getUserId())); printData.setBpmOAPaymentRespVO(paymentRespVO); printData.setProcessTasks(taskRespVOList); break; case "oa_salary_2": BpmOASalaryDO salaryDO = salaryService.getSalary(businessKey); BpmOASalaryRespVO salaryRespVO = BeanUtils.toBean(salaryDO, BpmOASalaryRespVO.class); if (salaryDO.getCompanyDeptId() != null) { // 获取部门详情 DeptRespDTO dto = deptApi.getDept(salaryDO.getCompanyDeptId()).getCheckedData(); salaryRespVO.setCompanyName(dto.getName()); }else { // 获取工厂信息 Set factoryIds = salaryDO.getFactoryDeptId(); List factoryInfoDTOS = factoryInfoApi.getFactoryInfoList(factoryIds).getCheckedData(); String factoryNames = factoryInfoDTOS.stream() .map(FactoryInfoDTO::getShortName) .filter(Objects::nonNull) .collect(Collectors.joining(",")); salaryRespVO.setCompanyName(factoryNames); } // 移除自己得审批节点 taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(salaryDO.getUserId())); printData.setBpmOASalaryRespVO(salaryRespVO); printData.setProcessTasks(taskRespVOList); break; } bpmProcessInstancePrintDataRespVO.setPrintDataRespVO(printData); return bpmProcessInstancePrintDataRespVO; } @Override public Map> getProcessInstanceResultStatusStatisticsGroupTime(BpmProcessInstanceStatisticsReqVO pageReqVO) { pageReqVO = getUserids(pageReqVO); pageReqVO.setRecentDays(pageReqVO.getRecentDays() == null ? 7 : pageReqVO.getRecentDays()); List list = processInstanceExtMapper.getProcessInstanceResultStatusStatisticsGroupTime(pageReqVO); // -- 获取到日期列表 LocalDateTime now = LocalDateTimeUtil.now(); LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTimeUtil.now(), -1L * pageReqVO.getRecentDays(), ChronoUnit.DAYS); List dateList = DateUtils.betweenDayList(offset, now); //根据时间分组 Map> map = list.stream().collect(Collectors.groupingBy(BpmProcessInstanceResultStatusStatisticsGroupTimeVO::getTime)); List keys = new ArrayList<>(CollectionUtil.subtract(dateList, new ArrayList<>(map.keySet()))); if (CollectionUtil.isNotEmpty(keys)) { for (String key : keys) { map.put(key, new ArrayList<>()); } } List statusList = Arrays.asList(1, 2, 3, 4); for (Map.Entry> entry : map.entrySet()) { List items = entry.getValue(); List results = items.stream().map(BpmProcessInstanceResultStatusStatisticsGroupTimeVO::getResult).collect(Collectors.toList()); List saveList = new ArrayList<>(CollectionUtil.subtract(statusList, results)); // -- 如果没有的话组装上缺少的 if (CollectionUtil.isNotEmpty(saveList)) { for (Integer status : saveList) { items.add(new BpmProcessInstanceResultStatusStatisticsGroupTimeVO() .setTime(entry.getKey()) .setTotalCount(0) .setResult(status) .setName("")); } } } return map; } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; +import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.cash.BpmOACashRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.payment.BpmOAPaymentRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmOAPrintDataRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.reimbursement.BpmOAReimbursementRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.salary.BpmOASalaryRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessCcDO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.*; +import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; +import cn.iocoder.yudao.module.bpm.dal.mysql.DynamicMapper; +import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; +import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; +import cn.iocoder.yudao.module.bpm.enums.task.BpmConstants; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessCcService; +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; +import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; +import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; +import cn.iocoder.yudao.module.bpm.service.oa.*; +import cn.iocoder.yudao.module.infra.api.config.ConfigApi; +import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.smartfactory.api.factoryInfo.FactoryInfoApi; +import cn.iocoder.yudao.module.smartfactory.api.factoryInfo.dto.FactoryInfoDTO; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import jodd.util.StringUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.delegate.event.FlowableCancelledEvent; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.lang.reflect.Field; +import java.text.DecimalFormat; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; + +import java.util.concurrent.CompletableFuture; + +/** + * 流程实例 Service 实现类 + *

+ * ProcessDefinition & ProcessInstance & Execution & Task 的关系: + * 1. + *

+ * HistoricProcessInstance & ProcessInstance 的关系: + * 1. + *

+ * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 + */ +@Service +@Validated +@Slf4j +public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { + @Resource + private TaskService engineTaskService; + + @Resource + private BpmTaskAssignRuleMapper taskRuleMapper; + + @Resource + private BpmUserGroupService userGroupService; + + @Resource + private RuntimeService runtimeService; + @Resource + private BpmProcessInstanceExtMapper processInstanceExtMapper; + @Resource + @Lazy // 解决循环依赖 + private BpmTaskService taskService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + @Resource + private HistoryService historyService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + @Resource + private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; + @Resource + @Lazy // 解决循环依赖 + private BpmMessageService messageService; + + @Resource + private ConfigApi configApi; + + @Resource + private DynamicMapper dynamicMapper ; + + @Resource + private BpmProcessCcService processCcService; + + @Resource + @Lazy // 解决循环依赖 + private StringRedisTemplate stringRedisTemplate; + + @Override + public ProcessInstance getProcessInstance(String id) { + return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); + } + + @Override + public List getProcessInstances(Set ids) { + return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); + } + + public String transform(String input) { + + + if(StringUtil.isNotEmpty(input)) { + String[] parts = input.split(","); + StringBuilder result = new StringBuilder(); + for (String part : parts) { + String[] keyValue = part.split(":"); + if (keyValue.length > 1) { + result.append(keyValue[1]).append(","); + } + } + if (result.length() > 0) { + return result.substring(0, result.length() - 1); + } + } + return ""; + } + + public String replaceValues(String input, Map map) { + String[] parts = input.split(","); + StringBuilder result = new StringBuilder(); + for (String part : parts) { + String[] keyValue = part.split(":"); + if (keyValue.length > 1) { + String key = keyValue[1].trim(); // 去除可能的空格 + if (map.containsKey(key)) { + Object value = map.get(key); + String stringValue = (value != null) ? value.toString() : ""; // 将值转换为字符串 + result.append(keyValue[0].trim()).append(":").append(stringValue).append(","); + } else { + //result.append(part).append(","); + } + } + } + if (result.length() > 0) { + return result.substring(0, result.length() - 1); + } + return ""; + } + + @Override + @TenantIgnore + public PageResult getMyProcessInstancePage(Long userId, + BpmProcessInstanceMyPageReqVO pageReqVO) { + // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 + PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); + + List bpmProcessInstanceExtDOList = pageResult.getList() ; + for (int i = 0; i < bpmProcessInstanceExtDOList.size() ; i++) { + BpmProcessInstanceExtDO bpmProcessInstanceExtDO = bpmProcessInstanceExtDOList.get(i) ; + String input = bpmProcessInstanceExtDO.getFieldNames() ; + if(StringUtil.isNotEmpty(input)) { + String sql = "SELECT " + transform(bpmProcessInstanceExtDO.getFieldNames()) + " FROM " + bpmProcessInstanceExtDO.getTableName() + " WHERE id=" + bpmProcessInstanceExtDO.getBusinessKey(); + log.info(sql); + Map map = dynamicMapper.selectListByTableName(sql) ; + String detailInfo = ""; + if(map != null) { + detailInfo = replaceValues(bpmProcessInstanceExtDO.getFieldNames(),map) ; + } + bpmProcessInstanceExtDO.setDetailInfo(detailInfo); + } + } + + if (CollUtil.isEmpty(pageResult.getList())) { + return new PageResult<>(pageResult.getTotal()); + } + + // 获得流程 Task Map + List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); + Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); + + List ids = taskMap.values().stream() + .flatMap(Collection::stream) + .map(Task::getAssignee) + .collect(Collectors.toList()); + + Iterator iterator = ids.iterator(); + while (iterator.hasNext()) { + String id = iterator.next(); + if (id == null) { + iterator.remove(); + } + } + + List longIds = ids.stream() + .map(Long::valueOf) + .collect(Collectors.toList()); +// 获得 User Map + Map userMap = adminUserApi.getUserMap(longIds); + + // 转换返回 + return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, userMap); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); + // 发起流程 + if (MapUtil.isEmpty(createReqVO.getVariables())){ + createReqVO.setVariables(new HashMap<>()); + } + return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); + // 发起流程 + if (MapUtil.isEmpty(createReqDTO.getVariables())){ + createReqDTO.setVariables(new HashMap<>()); + } + return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); + } + + @Override + public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { + // 获得流程实例 + HistoricProcessInstance processInstance = getHistoricProcessInstance(id); + if (processInstance == null) { + return null; + } + BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); + Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); + + // 获得流程定义 + ProcessDefinition processDefinition = processDefinitionService + .getProcessDefinition(processInstance.getProcessDefinitionId()); + Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); + BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( + processInstance.getProcessDefinitionId()); + Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); + String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); + + // 获得 User + AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); + DeptRespDTO dept = null; + DeptRespDTO companyDept = null; + if (startUser != null) { + dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); + companyDept = deptApi.getUserCompanyDept(startUser.getId()).getCheckedData(); + } + + // 拼接结果 + return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, + processDefinition, processDefinitionExt, bpmnXml, startUser, dept, companyDept); + } + + @Override + public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { + // 校验流程实例存在 + ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); + if (instance == null) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + // 只能取消自己的 + if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); + } + + // 通过删除流程实例,实现流程实例的取消, + // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 + deleteProcessInstance(cancelReqVO.getId(), + BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); + } + + /** + * 获得历史的流程实例 + * + * @param id 流程实例的编号 + * @return 历史的流程实例 + */ + @Override + public HistoricProcessInstance getHistoricProcessInstance(String id) { + return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); + } + + @Override + public List getHistoricProcessInstances(Set ids) { + return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); + } + + @Override + public void createProcessInstanceExt(ProcessInstance instance) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); + // 插入 BpmProcessInstanceExtDO 对象 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(instance.getId()) + .setProcessDefinitionId(definition.getId()) + .setName(instance.getProcessDefinitionName()) + .setStartUserId(Long.valueOf(instance.getStartUserId())) + .setCategory(definition.getCategory()) + .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) + .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); + + processInstanceExtMapper.insert(instanceExtDO); + + // 在记录创建后立即处理抄送人信息(双重保险) + handleProcessInstanceCC(instance); + } + + /** + * 在事件监听器中处理抄送人信息 + */ + private void handleProcessInstanceCC(ProcessInstance instance) { + try { + // 获取流程变量 + Map variables = runtimeService.getVariables(instance.getId()); + + // 根据流程名称、流程发起人 查询流程配置的抄送人信息 + List processCcList = processCcService.getCCListByName(instance.getName(), Long.valueOf(instance.getStartUserId())); + // 提取抄送信息用中对应的用户组编号 + Set userGroupIds = processCcList.stream() + .flatMap(data -> data.getUserGroupId().stream()) + .collect(Collectors.toSet()); + + if (CollectionUtil.isNotEmpty(userGroupIds)) { + // 获取用户组信息 + List userGroups = userGroupService.getUserGroupList(userGroupIds); + // 提取用户组中对应的用户ID + Set userIds = userGroups.stream() + .flatMap(data -> data.getMemberUserIds().stream()) + .collect(Collectors.toSet()); + + // 将用户ID列表转换为字符串 + String ccIds = userIds.stream() + .map(id -> "[" + id + "]") // 每个值用方括号包裹 + .collect(Collectors.joining()); + + // 直接更新刚创建的记录 + BpmProcessInstanceExtDO updateDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(instance.getId()) + .setFormVariables(variables) + .setCcids(ccIds); + + processInstanceExtMapper.updateByProcessInstanceId(updateDO); + log.debug("事件监听器中抄送人信息设置成功,processInstanceId: {}", instance.getId()); + } + } catch (Exception e) { + log.error("事件监听器中处理抄送人信息失败,processInstanceId: {}", instance.getId(), e); + } + } + + @Override + @DataPermission(enable = false) + public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { + // 判断是否为 Reject 不通过。如果是,则不进行更新. + // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 + if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { + return; + } + + // 需要主动查询,因为 instance 只有 id 属性 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance + HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); + // 更新拓展表 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(event.getProcessInstanceId()) + .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 删除 流程审批部门缓存 + updateAssigneeDeptRedis(event.getProcessInstanceId()); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + @Override + @DataPermission(enable = false) + public void updateProcessInstanceExtComplete(ProcessInstance instance) { + // 需要主动查询,因为 instance 只有 id 属性 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance + HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); + // 更新拓展表 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(instance.getProcessInstanceId()) + .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 删除 流程审批部门缓存 + updateAssigneeDeptRedis(instance.getProcessInstanceId()); + + Map processVariables = runtimeService.getVariables(instance.getProcessInstanceId()); + String reason = (String) processVariables.get("approve_reason"); + // 发送流程被通过的消息 + messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance, reason)); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) + public void updateProcessInstanceExtReject(String id, String reason) { + // 需要主动查询,因为 instance 只有 id 属性 + ProcessInstance processInstance = getProcessInstance(id); + // 删除流程实例,以实现驳回任务时,取消整个审批流程 + deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); + + // 更新 status + result + // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, + // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(id) + .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 删除 流程审批部门缓存 + updateAssigneeDeptRedis(id); + + // 发送流程被不通过的消息 + messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + @Override + public void updateProcessInstanceResult(String id, Integer result) { + + processInstanceExtMapper.update(null, new LambdaUpdateWrapper() + .set(BpmProcessInstanceExtDO::getResult, result) + .eq(BpmProcessInstanceExtDO::getProcessInstanceId, id)); + } + + /** + * 删除 流程审批部门缓存 + * @param processInstanceId 流程实例id + */ + public void updateAssigneeDeptRedis(String processInstanceId) { + + stringRedisTemplate.delete("assignee_dept_" + processInstanceId); + } + + private void deleteProcessInstance(String id, String reason) { + runtimeService.deleteProcessInstance(id, reason); + } + + private String createProcessInstance0(Long userId, ProcessDefinition definition, + Map variables, String businessKey) { + // 校验流程定义 + if (definition == null) { + throw exception(PROCESS_DEFINITION_NOT_EXISTS); + } + if (definition.isSuspended()) { + throw exception(PROCESS_DEFINITION_IS_SUSPENDED); + } + + // 设置当前用户 + FlowableUtils.setAuthenticatedUserId(userId); + + //获取流程发起人User + AdminUserRespDTO startUser = adminUserApi.getUser(userId).getCheckedData(); + //获取流程发起人部门信息 + DeptRespDTO startDeptInfo = deptApi.getDept(startUser.getDeptId()).getCheckedData(); + // 获取岗位信息 + Set postIds = startUser.getPostIds(); + ArrayList list = new ArrayList<>(postIds); + + if (CollectionUtil.isEmpty(startUser.getPostIds())) { + // 当前审批用户未配置岗位 + // 操作失败,原因:您未配置岗位,请联系管理员操作 + throw exception(TASK_OPERATE_FAIL_USER_NO_POST); + } + if( startUser.getDeptId() == null ) { + // 当前审批用户未配置部门 + // 操作失败,原因:您未配置部门,请联系管理员操作 + throw exception(TASK_OPERATE_FAIL_USER_NO_DEPT); + } + + //配置通用流程变量 + variables.put("post_id", list.get(0).toString()); // 只获配置的首个岗位 + variables.put("user_id", userId.toString()); // 配置发起人用户id + variables.put("dept_id", startUser.getDeptId().toString()); // 配置发起人部门id + variables.put("dept_flag", startDeptInfo.getFlag()); // 配置发起人部门flag + + // 创建流程实例 + ProcessInstance instance = runtimeService.createProcessInstanceBuilder() + .processDefinitionId(definition.getId()) + .businessKey(businessKey) + .name(definition.getName().trim()) + .variables(variables) + .start(); + + // 设置流程名字 + runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); + + // 设置流程审批部门缓存 + stringRedisTemplate.opsForValue().set("assignee_dept_" + instance.getId(), startUser.getDeptId().toString()); + + // 延迟处理抄送人信息,避免与事件监听器的并发问题 + processInstanceCCDeferred(instance, variables); + + return instance.getId(); + } + + /** + * 延迟处理抄送人信息,避免与事件监听器的并发问题 + */ + private void processInstanceCCDeferred(ProcessInstance instance, Map variables) { + // 使用异步处理,确保主流程不被阻塞 + CompletableFuture.runAsync(() -> { + try { + Thread.sleep(100); // 短暂延迟,让事件监听器先执行 + processCCToUsers(instance, variables); + } catch (Exception e) { + log.error("异步处理抄送人信息失败,processInstanceId: {}", instance.getProcessInstanceId(), e); + } + }); + } + + private void approveSelfTask(String processInstanceId) { + List tasks = engineTaskService.createTaskQuery().processInstanceId(processInstanceId).list(); + if (tasks != null && tasks.size() > 0) { + Task task = tasks.get(0); + String assigneeId = task.getAssignee(); + //如果当前登陆用户是审批人,那么自动审批通过 + if (assigneeId.equals(SecurityFrameworkUtils.getLoginUserId().toString())) { + BpmTaskApproveReqVO reqVO = new BpmTaskApproveReqVO(); + reqVO.setId(task.getId()); + reqVO.setReason(BpmConstants.AUTO_APPRAVAL); + taskService.approveTask(getLoginUserId(), reqVO); + } + } + } + + /** + * 获得抄送我的流程实例的分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页请求 + * @return 流程实例的分页 + */ + public PageResult getMyCCProcessInstancePage(Long userId, + BpmProcessInstanceMyPageReqVO pageReqVO) { + // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 + PageResult pageResult = processInstanceExtMapper.selectCCPage(userId, pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return new PageResult<>(pageResult.getTotal()); + } + + // 获得流程 Task Map + List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); + Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); + // 转换返回 + return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, null); + + } + + @Override + public List getProcessInstancesGroupByModelName(BpmProcessInstanceStatisticsReqVO pageReqVO) { + pageReqVO = getUserids(pageReqVO); + return processInstanceExtMapper.getProcessInstancesGroupByModelName(pageReqVO); + } + + @Override + public List getProcessInstancesGroupByResultStatus(BpmProcessInstanceStatisticsReqVO pageReqVO) { + pageReqVO = getUserids(pageReqVO); + return processInstanceExtMapper.getProcessInstancesGroupByResultStatus(pageReqVO); + } + + @Override + public PageResult getStatisticsProcessInstancePage(@Valid BpmProcessInstanceMyPageReqVO pageReqVO) { + // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 + PageResult pageResult = processInstanceExtMapper.selectStatisticePage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return new PageResult<>(pageResult.getTotal()); + } + + // 获得流程 Task Map + List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); + Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); + + List ids = taskMap.values().stream() + .flatMap(Collection::stream) + .map(Task::getAssignee) + .collect(Collectors.toList()); + + Iterator iterator = ids.iterator(); + while (iterator.hasNext()) { + String id = iterator.next(); + if (id == null) { + iterator.remove(); + } + } + + List longIds = ids.stream() + .map(Long::valueOf) + .collect(Collectors.toList()); + //获得 审批人 User Map + Map assigneeUserMap = adminUserApi.getUserMap(longIds); + + //获得 发起人 User Map + List bpieDOs = pageResult.getList(); + longIds = new ArrayList<>(); + for (BpmProcessInstanceExtDO bpieDO : bpieDOs) { + longIds.add(bpieDO.getStartUserId()); + } + Map startUserMap = adminUserApi.getUserMap(longIds); + + // 转换返回 + return BpmProcessInstanceConvert.INSTANCE.convertStatisticsPage(pageResult, taskMap, assigneeUserMap, startUserMap); + + } + + @Resource + PermissionApi permissionApi; + + /** + * 根据数据权限,查询关联操作的用户IDS + * + * @param pageReqVO + * @param + * @return + */ + private T getUserids(T pageReqVO) { + try { + Class clazz = (Class) pageReqVO.getClass(); + Field idField = clazz.getDeclaredField("userIds"); + idField.setAccessible(true); // 设置可访问性 + + Long[] userIds = null; + Long userId = WebFrameworkUtils.getLoginUserId(); + DeptDataPermissionRespDTO deptDataPermission = permissionApi.getDeptDataPermission(userId).getCheckedData(); + //查询全部 + if (deptDataPermission.getAll()) { + //idField.set(pageReqVO, null); // 设置属性值 + return pageReqVO; + } + + // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 + if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) + && Boolean.FALSE.equals(deptDataPermission.getSelf())) { + //设置成0,一个不存在的用户Id,就查询不到数据了。 + userIds = new Long[]{0L}; + idField.set(pageReqVO, userIds); + return pageReqVO; + } + + //情况三 至查询自己 + if (deptDataPermission.getSelf()) { + userIds = new Long[]{userId}; + idField.set(pageReqVO, userIds); + return pageReqVO; + } + + Set deptIds = deptDataPermission.getDeptIds(); + //查询部门关联的用户Id + List users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); + List tempList = new ArrayList<>(); + for (AdminUserRespDTO user : users) { + Long id = user.getId(); + tempList.add(id); + } + tempList.add(userId); + userIds = tempList.stream().toArray(Long[]::new); //将临时的List集合转换成数组集合 + idField.set(pageReqVO, userIds); + return pageReqVO; + } catch (Exception exception) { + exception.printStackTrace(); + throw exception(BPM_SYSTEM_BUG); + } + } + + /** + * /创建流程后,添加抄送人 Begin add by yj 2024.1.4 + * 在设计流程的时候,需要添加一个任务块 名字必须叫Activity_cc 分配权限的时候,需要选择用户组。 + * + * @param definition + * @param instance + */ + private void processCCToUsers(ProcessDefinition definition, ProcessInstance instance) { + //获取bpm_task_assign_reule (Bpm 任务规则表)的流程中有没有配置抄送节点 固定抄送名称为:Activity_cc + String processDefinitionId = definition.getId(); + List rules = taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, null); + + String BpmConstantsName = BpmConstants.CC_NAME; + // 获取发起人信息 + AdminUserRespDTO userRespDTO = adminUserApi.getUser(Long.valueOf(instance.getStartUserId())).getCheckedData(); + DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId()).getCheckedData(); + + //发起人是深圳分公司 + if (deptRespDTO.getFlag().contains("136")) { + + BpmConstantsName = BpmConstants.CCSZ_NAME; + } + + for (BpmTaskAssignRuleDO rule : rules) { + String key = rule.getTaskDefinitionKey(); //任务名称 + Integer type = rule.getType(); + if (!key.isEmpty() && key.equals(BpmConstantsName) && type == 40) { + StringBuffer str = new StringBuffer(); + Set options = rule.getOptions(); + List list = new ArrayList(options); + for (Long groupId : list) { + //需要根据这个groupId,查询这个组中的用户id + BpmUserGroupDO userGroup = userGroupService.getUserGroup(groupId); + Set userIds = userGroup.getMemberUserIds(); + List userIdList = new ArrayList(userIds); + for (Long user_id : userIdList) { + str.append("[").append(user_id).append("]"); + } + } + + //流程是采购计划时 + if (definition.getKey().equals(BpmOAProcureServiceImpl.PROCESS_KEY)) { + + //发起人部门为 生产部及以下时 + if (deptRespDTO.getFlag().contains("130")) { + + //添加 供应部抄送 + BpmUserGroupDO userGroup = userGroupService.getUserGroup(121L); + Set userIds = userGroup.getMemberUserIds(); + List userIdList = new ArrayList(userIds); + for (Long user_id : userIdList) { + str.append("[").append(user_id).append("]"); + } + } + } + + String ccids = str.toString(); + //根据processDefinitionId 将ccids保存到bpm_process_instance_ext中的ccids字段 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessDefinitionId(processDefinitionId) + .setCcids(ccids).setProcessInstanceId(instance.getProcessInstanceId()); + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + break; + } + } + } + + /** + * 创建流程后,添加抄送人 + * @param instance 流程实例 + */ + private void processCCToUsers(ProcessInstance instance, Map variables) { + + // 根据流程名称、流程发起人 查询流程配置的抄送人信息 + List processCcList = processCcService.getCCListByName(instance.getName(), Long.valueOf(instance.getStartUserId())); + // 提取抄送信息用中对应的用户组编号 + Set userGroupIds = processCcList.stream() + .flatMap(data -> data.getUserGroupId().stream()) + .collect(Collectors.toSet()); + + //根据processDefinitionId 将ccIds保存到bpm_process_instance_ext中的ccIds字段 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessDefinitionId(instance.getProcessDefinitionId()) + .setFormVariables(variables) + .setProcessInstanceId(instance.getProcessInstanceId()); + if (CollectionUtil.isNotEmpty(userGroupIds)) { + + // 获取用户组信息 + List userGroups = userGroupService.getUserGroupList(userGroupIds); + // 提取用户组中对应的用户ID + Set userIds = userGroups.stream() + .flatMap(data -> data.getMemberUserIds().stream()) + .collect(Collectors.toSet()); + + // 将用户ID列表转换为字符串 + String ccIds = userIds.stream() + .map(id -> "[" + id + "]") // 每个值用方括号包裹 + .collect(Collectors.joining()); + + //根据processDefinitionId 将ccIds保存到bpm_process_instance_ext中的ccIds字段 + instanceExtDO.setCcids(ccIds); + } + + // 增加幂等性处理,确保更新操作能正常命中 + updateProcessInstanceExtWithRetry(instanceExtDO); + } + + /** + * 带重试机制的更新操作,确保幂等性 + */ + private void updateProcessInstanceExtWithRetry(BpmProcessInstanceExtDO instanceExtDO) { + try { + // 先检查记录是否存在 + BpmProcessInstanceExtDO existingRecord = processInstanceExtMapper.selectByProcessInstanceId(instanceExtDO.getProcessInstanceId()); + if (existingRecord != null) { + // 记录存在,直接更新 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + log.debug("异步处理抄送人信息更新成功,processInstanceId: {}", instanceExtDO.getProcessInstanceId()); + return; + } + + // 记录不存在,等待一下再重试 + Thread.sleep(50); + existingRecord = processInstanceExtMapper.selectByProcessInstanceId(instanceExtDO.getProcessInstanceId()); + if (existingRecord != null) { + // 记录存在,执行更新 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + log.debug("异步处理抄送人信息重试更新成功,processInstanceId: {}", instanceExtDO.getProcessInstanceId()); + } else { + log.warn("异步处理抄送人信息更新失败,记录不存在,processInstanceId: {}", instanceExtDO.getProcessInstanceId()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("异步处理抄送人信息被中断,processInstanceId: {}", instanceExtDO.getProcessInstanceId(), e); + } catch (Exception e) { + log.error("异步处理抄送人信息失败,processInstanceId: {}", instanceExtDO.getProcessInstanceId(), e); + } + } + + @Override + public List getUserProcessTpo10(BpmProcessInstanceStatisticsReqVO pageReqVO) { + + //按审核人分组查询,统计已完成流程数量及平均耗时间 + List respVOS = processInstanceExtMapper.getUserProcessTpo10(pageReqVO); + if (respVOS == null || respVOS.size() == 0) { + return null; + } + + List idList = respVOS.stream() + .map(BpmProcessFinishStatisticsRespVO::getUserId) + .collect(Collectors.toList()); + + //根据userId,查询UserMap + Map userMap = adminUserApi.getUserMap(idList); + Long[] idsArray = new Long[userMap.size()]; + // 使用 map 的 keySet() 方法获取键集合 + Set keys = userMap.keySet(); + // 遍历键集合,将每个键添加到数组中 + int index = 0; + for (Long key : keys) { + idsArray[index++] = key; + } + pageReqVO.setUserIds(idsArray); + //按审核人分组查询,未审批完成的流程数量 + List bpmTaskExtDOs = processInstanceExtMapper.selectUnfinishProcessCount(pageReqVO); + + //按审核人分组查询,未完成的记录数 + Map unFinfishCountCountMap = new HashMap<>(); + for (BpmProcessFinishStatisticsRespVO item : bpmTaskExtDOs) { + unFinfishCountCountMap.put(item.getUserId(), item.getUnFinfishCount()); + } + + respVOS.forEach(respVO -> respVO.setName(userMap.get(respVO.getUserId()).getNickname())); + respVOS.forEach(respVO -> respVO.setUnFinfishCount(unFinfishCountCountMap.get(respVO.getUserId()))); + + //获取排行榜第一记录的耗时,作为基础数据 + double num = Double.parseDouble(respVOS.get(0).getUserTime()); // 先将字符串转换为双精度浮点数 + int baseNumber = (int) num; // 再通过类型转换操作符将其转换为整数 + + DecimalFormat df = new DecimalFormat("#.00"); + respVOS.forEach(respVO -> { + //格式化流程完成率 + float finfishCount = (float) respVO.getFinfishCount(); + float unFinfishCount = respVO.getUnFinfishCount() == null ? 0 : respVO.getUnFinfishCount(); + float all = finfishCount + unFinfishCount; + float result = finfishCount / all; + float rate = result * 100; + respVO.setCompletionRate(df.format(rate) + "%"); + double dValue = Double.parseDouble(respVO.getUserTime()); // 将字符串转换为double类型 + int roundedValue = (int) Math.round(dValue); // 使用Math.round()进行四舍五入,并转换为int类型 + respVO.setUserTime(roundedValue + ""); + + //格式化进度百度比, 参照最高的数据进行百分比显示 + double percentage = ((int) Double.parseDouble(respVO.getUserTime()) / (double) baseNumber) * 100; + respVO.setPercentage((int) Math.round(percentage)); + //设置未完成 + respVO.setUnFinfishCount(Integer.valueOf((int) unFinfishCount)); + }); + return respVOS; + } + + @Override + @DataPermission(enable = false) + public BpmProcessInstanceExtDO getProcessInstanceDO(String id) { + return processInstanceExtMapper.selectByProcessInstanceId(id); + } + + @Resource + private FileApi fileApi; + + @Resource + @Lazy // 解决循环依赖 + private BpmOAReimbursementService reimbursementService; + + @Resource + @Lazy // 解决循环依赖 + private BpmOACashService cashService; + + @Resource + @Lazy // 解决循环依赖 + private BpmOAImprestService imprestService; + + @Resource + @Lazy // 解决循环依赖 + private BpmOAPaymentService paymentService; + + @Resource + @Lazy + private BpmOASalaryService salaryService; + + @Resource + private FactoryInfoApi factoryInfoApi; + + @Override + public BpmProcessInstancePrintDataRespVO getOAReportPrintData(BpmProcessInstancePrintDataReqVO reqVO) { + + BpmProcessInstancePrintDataRespVO bpmProcessInstancePrintDataRespVO = new BpmProcessInstancePrintDataRespVO(); + String key = reqVO.getKey(); //流程标识 + String processInstanceId = reqVO.getId(); //流程实例ID + bpmProcessInstancePrintDataRespVO.setId(processInstanceId); + bpmProcessInstancePrintDataRespVO.setKey(key); + + BpmProcessInstanceRespVO bpmProcessInstance = getProcessInstanceVO(processInstanceId); + Long businessKey = Long.valueOf(bpmProcessInstance.getBusinessKey()); //流程业务表-主键ID + + // 判断是否可见外勤人员信息 + List userIds = new ArrayList<>(); + String type = configApi.getConfigKey("sys.user.type").getCheckedData(); + if ("1".equals(type)) { //不可见时 + + // 获取所有外勤人员 用户编号 + userIds = adminUserApi.getUserIdsByUserNature(4).getCheckedData(); + } + + // 获得流程审批信息 + List taskRespVOList = taskService.getTaskListByProcessInstanceId(processInstanceId); + + // 移除外勤人员信息 + List finalUserIds = userIds; + taskRespVOList.removeIf(data -> finalUserIds.contains(data.getAssigneeUser().getId())); + //给User添加签名地址 + taskRespVOList.forEach(taskRespVO -> + taskRespVO.getAssigneeUser().setSignURL( + fileApi.getUserSignImgPath( + taskRespVO.getAssigneeUser().getId() + ).getData() + ) + ); + + //获取流程抄送用户编号 + BpmProcessInstanceExtDO processInstanceExtDO = getProcessInstanceDO(processInstanceId); + + //获取流程抄送用户信息 + List ccUserIds = new ArrayList<>(); + if (processInstanceExtDO.getCcids() != null && !processInstanceExtDO.getCcids().isEmpty()) { + + Pattern pattern = Pattern.compile("\\[(\\d+)]"); + Matcher matcher = pattern.matcher(processInstanceExtDO.getCcids()); + while (matcher.find()) { + + ccUserIds.add(Long.parseLong(matcher.group(1))); + } + + // 移除 外勤人员信息 + ccUserIds.removeAll(userIds); + } + + List userRespDTOS = adminUserApi.getUserList(ccUserIds).getCheckedData(); + + //获取抄送用户部门信息 + Map deptMapDTO = deptApi.getDeptMap(convertSet(userRespDTOS, AdminUserRespDTO::getDeptId)); + + // 设置抄送人信息 + List ccUsers = userRespDTOS.stream().map(user -> { + BpmOAPrintDataRespVO.CCUser cc = new BpmOAPrintDataRespVO.CCUser(); + cc.setCcUserId(user.getId()); + cc.setCcUserName(user.getNickname()); + cc.setCcDeptName(deptMapDTO.get(user.getDeptId()).getName()); + return cc; + }).collect(Collectors.toList()); + + BpmOAPrintDataRespVO printData = new BpmOAPrintDataRespVO(); + // 设置抄送人 + printData.setCcUsers(ccUsers); + // 设置发起人信息 + printData.setStartUser(BeanUtils.toBean(bpmProcessInstance.getStartUser(), BpmOAPrintDataRespVO.User.class)); + switch (key) { + case "oa_reimbursement": + BpmOAReimbursementDO reimbursement = reimbursementService.getReimbursement(businessKey); + BpmOAReimbursementRespVO bpmOAReimbursementRespVO = reimbursementService.convert(reimbursement); //报销业务数据 + + // 移除自己得审批节点 + taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(reimbursement.getUserId())); + + //备用金信息查询 + BpmOAImprestDO bpmOAImprestDO = imprestService.getImprest(reimbursement.getImprestId()); + if (bpmOAImprestDO != null) { + + //设置备用金 金额 + bpmOAReimbursementRespVO.setAmount(bpmOAImprestDO.getAmount()); + // 设置备用金 剩余金额 + bpmOAReimbursementRespVO.setRemainingAmount(bpmOAImprestDO.getAmount().subtract(bpmOAImprestDO.getReimbursedAmount())); + } + + + printData.setBpmOAReimbursementRespVO(bpmOAReimbursementRespVO); + printData.setProcessTasks(taskRespVOList); + break; + case "oa_cash": + BpmOACashDO cashDO = cashService.getCash(businessKey); + BpmOACashRespVO cashRespVO = cashService.convertCash(cashDO); + + // 移除自己得审批节点 + taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(cashDO.getUserId())); + + //备用金信息查询 + BpmOAImprestDO cashImprestDO = imprestService.getImprest(cashDO.getImprestId()); + if (cashImprestDO != null) { + + //设置备用金 金额 + cashRespVO.setAmount(cashImprestDO.getAmount()); + // 设置备用金 剩余金额 + cashRespVO.setRemainingAmount(cashImprestDO.getAmount().subtract(cashImprestDO.getReimbursedAmount())); + } + + printData.setBpmOACashRespVO(cashRespVO); + printData.setProcessTasks(taskRespVOList); + break; + case "oa_payment_2": + BpmOAPaymentDO paymentDO = paymentService.getPayment(businessKey); + BpmOAPaymentRespVO paymentRespVO = paymentService.convertPayment(paymentDO); + + // 移除自己得审批节点 + taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(paymentDO.getUserId())); + + printData.setBpmOAPaymentRespVO(paymentRespVO); + printData.setProcessTasks(taskRespVOList); + break; + case "oa_salary_2": + BpmOASalaryDO salaryDO = salaryService.getSalary(businessKey); + BpmOASalaryRespVO salaryRespVO = BeanUtils.toBean(salaryDO, BpmOASalaryRespVO.class); + + if (salaryDO.getCompanyDeptId() != null) { + // 获取部门详情 + DeptRespDTO dto = deptApi.getDept(salaryDO.getCompanyDeptId()).getCheckedData(); + + salaryRespVO.setCompanyName(dto.getName()); + }else { + // 获取工厂信息 + Set factoryIds = salaryDO.getFactoryDeptId(); + List factoryInfoDTOS = factoryInfoApi.getFactoryInfoList(factoryIds).getCheckedData(); + + String factoryNames = factoryInfoDTOS.stream() + .map(FactoryInfoDTO::getShortName) + .filter(Objects::nonNull) + .collect(Collectors.joining(",")); + + salaryRespVO.setCompanyName(factoryNames); + } + + // 移除自己得审批节点 + taskRespVOList.removeIf(data -> data.getAssigneeUser().getId().equals(salaryDO.getUserId())); + + printData.setBpmOASalaryRespVO(salaryRespVO); + printData.setProcessTasks(taskRespVOList); + break; + } + bpmProcessInstancePrintDataRespVO.setPrintDataRespVO(printData); + return bpmProcessInstancePrintDataRespVO; + } + + @Override + public Map> getProcessInstanceResultStatusStatisticsGroupTime(BpmProcessInstanceStatisticsReqVO pageReqVO) { + pageReqVO = getUserids(pageReqVO); + pageReqVO.setRecentDays(pageReqVO.getRecentDays() == null ? 7 : pageReqVO.getRecentDays()); + List list = processInstanceExtMapper.getProcessInstanceResultStatusStatisticsGroupTime(pageReqVO); + // -- 获取到日期列表 + LocalDateTime now = LocalDateTimeUtil.now(); + LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTimeUtil.now(), -1L * pageReqVO.getRecentDays(), ChronoUnit.DAYS); + List dateList = DateUtils.betweenDayList(offset, now); + + + //根据时间分组 + Map> map = list.stream().collect(Collectors.groupingBy(BpmProcessInstanceResultStatusStatisticsGroupTimeVO::getTime)); + + List keys = new ArrayList<>(CollectionUtil.subtract(dateList, new ArrayList<>(map.keySet()))); + if (CollectionUtil.isNotEmpty(keys)) { + for (String key : keys) { + map.put(key, new ArrayList<>()); + } + } + + List statusList = Arrays.asList(1, 2, 3, 4); + for (Map.Entry> entry : map.entrySet()) { + List items = entry.getValue(); + List results = items.stream().map(BpmProcessInstanceResultStatusStatisticsGroupTimeVO::getResult).collect(Collectors.toList()); + List saveList = new ArrayList<>(CollectionUtil.subtract(statusList, results)); + // -- 如果没有的话组装上缺少的 + if (CollectionUtil.isNotEmpty(saveList)) { + for (Integer status : saveList) { + items.add(new BpmProcessInstanceResultStatusStatisticsGroupTimeVO() + .setTime(entry.getKey()) + .setTotalCount(0) + .setResult(status) + .setName("")); + } + } + } + return map; + } +}