diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/dto/TaskDTO.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/dto/TaskDTO.java index 0e381a6..90088a0 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/dto/TaskDTO.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/dto/TaskDTO.java @@ -55,6 +55,18 @@ public class TaskDTO implements java.io.Serializable { /** 业务标题*/ private String bpmBizTitle; + /** 截止时间 */ + private String deadline; + /** 截止时间是否逾期 */ + private Boolean deadlineOverdue; + /** 截止时间逾期天数 */ + private Long overdueDays; + /** 截止时间是否临近 */ + private Boolean deadlineSoon; + /** 截止时间剩余天数 */ + private Long remainingDays; + /** 截止时间提醒文案 */ + private String deadlineWarnText; /** 流程状态 未发起、处理中、处理完成*/ private String bpmStatus; /** 超时提醒*/ @@ -88,4 +100,4 @@ public class TaskDTO implements java.io.Serializable { } - \ No newline at end of file + diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/ActivitiServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/ActivitiServiceImpl.java index c100c40..b25a1df 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/ActivitiServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/ActivitiServiceImpl.java @@ -296,6 +296,7 @@ public class ActivitiServiceImpl implements ActivitiService { Set needSet = new HashSet(); needSet.add(WorkFlowGlobals.BPM_BIZ_TITLE); needSet.add(WorkFlowGlobals.BPM_DES_FORM_CODE); + needSet.add("deadline"); Map processVariables = runtimeService.getVariables(processInstanceId,needSet); //流程定义 @@ -309,6 +310,21 @@ public class ActivitiServiceImpl implements ActivitiService { dto.setTaskId(t.getTaskDefinitionKey()); dto.setTaskEndTime(t.getDueDate()); dto.setProcessInstanceId(t.getProcessInstanceId()); + String deadline = oConvertUtils.getString(processVariables.get("deadline")); + dto.setDeadline(deadline); + Long deadlineGapDays = calcDeadlineGapDays(deadline); + dto.setDeadlineOverdue(false); + dto.setDeadlineSoon(false); + if (deadlineGapDays != null && deadlineGapDays < 0) { + Long overdueDays = Math.abs(deadlineGapDays); + dto.setDeadlineOverdue(true); + dto.setOverdueDays(overdueDays); + dto.setDeadlineWarnText("已逾期" + overdueDays + "天"); + } else if (deadlineGapDays != null && deadlineGapDays <= 3) { + dto.setDeadlineSoon(true); + dto.setRemainingDays(deadlineGapDays); + dto.setDeadlineWarnText(deadlineGapDays == 0 ? "今天截止" : "剩余" + deadlineGapDays + "天"); + } dto.setProcessApplyUserId(applyUserId); // dto.setProcessApplyUserName(ownerSysUser!=null?ownerSysUser.getRealname():""); dto.setProcessDefinitionId(processInstance != null ? processInstance.getProcessDefinitionId() : null); @@ -545,10 +561,33 @@ public class ActivitiServiceImpl implements ActivitiService { } - + + /** + * 计算截止日期与今天的天数差,负数表示已逾期 + * @param deadline + * @return + */ + private Long calcDeadlineGapDays(String deadline) { + if (oConvertUtils.isEmpty(deadline)) { + return null; + } + try { + String deadlineText = deadline.trim(); + if (deadlineText.length() < 10) { + return null; + } + String deadlineDateText = deadlineText.substring(0, 10).replace("/", "-").replace(".", "-"); + java.time.LocalDate deadlineDate = java.time.LocalDate.parse(deadlineDateText); + return java.time.temporal.ChronoUnit.DAYS.between(java.time.LocalDate.now(), deadlineDate); + } catch (Exception e) { + log.warn("待办任务deadline解析失败:{}", deadline); + return null; + } + } + /** * 获取业务ID - * + * * @param task * @return */ diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java index 9dbddda..966df5b 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java @@ -46,6 +46,11 @@ import java.util.stream.Stream; @RequestMapping("/act/process/extActProcessNode") @Slf4j public class ExtActProcessNodeController { + private static final String BUSINESS_ID = "business_id"; + private static final String BUSINESS_TABLE_NAME = "business_tablename"; + private static final String BUSINESS_ID_CAMEL = "businessId"; + private static final String BUSINESS_TABLE_NAME_CAMEL = "businessTablename"; + @Autowired private IExtActProcessNodeService extActProcessNodeService; @Autowired @@ -359,9 +364,20 @@ public class ExtActProcessNodeController { String formUrlMobile = activitiService.getHisVarinst(WorkFlowGlobals.BPM_FORM_CONTENT_URL_MOBILE,procInstId); String dataId = activitiService.getHisVarinst(WorkFlowGlobals.BPM_DATA_ID,procInstId); String tableName = activitiService.getHisVarinst(WorkFlowGlobals.BPM_FORM_KEY,procInstId); + // 历史/抄送查看需要真实业务表数据,补齐发起流程时写入的业务ID和业务表名变量。 + String businessId = getHisVarinstByNames(procInstId, BUSINESS_ID, BUSINESS_ID_CAMEL); + String businessTableName = getHisVarinstByNames(procInstId, BUSINESS_TABLE_NAME, BUSINESS_TABLE_NAME_CAMEL); Map variablesMap = new HashMap(); variablesMap.put(WorkFlowGlobals.BPM_DATA_ID, dataId); variablesMap.put(WorkFlowGlobals.BPM_FORM_KEY, tableName); + if (oConvertUtils.isNotEmpty(businessId)) { + variablesMap.put(BUSINESS_ID, businessId); + variablesMap.put(BUSINESS_ID_CAMEL, businessId); + } + if (oConvertUtils.isNotEmpty(businessTableName)) { + variablesMap.put(BUSINESS_TABLE_NAME, businessTableName); + variablesMap.put(BUSINESS_TABLE_NAME_CAMEL, businessTableName); + } Map map = new HashMap(); map.put("formUrl", formUrl); map.put("formUrlMobile", formUrlMobile); @@ -375,6 +391,16 @@ public class ExtActProcessNodeController { result.setSuccess(true); return result; } + + private String getHisVarinstByNames(String procInstId, String... varNames) { + for (String varName : varNames) { + String value = activitiService.getHisVarinst(varName, procInstId); + if (oConvertUtils.isNotEmpty(value)) { + return value; + } + } + return null; + } /** diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java index c06789c..502848f 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java @@ -7,6 +7,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StreamUtils; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowElementsContainer; +import org.flowable.bpmn.model.Task; import org.flowable.common.engine.api.FlowableException; import org.flowable.engine.IdentityService; import org.flowable.engine.RepositoryService; @@ -51,6 +56,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.servlet.http.HttpServletRequest; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.sql.Blob; import java.sql.Clob; import java.util.*; @@ -183,6 +193,11 @@ public class ExtActProcessServiceImpl extends ServiceImpl processNodeInfoMap = parseProcessNodeInfoMap(processDescriptor); + DesUtils.checkNodeDuplicate(toDesignerNodeString(processNodeInfoMap.values())); ExtActProcess process = extActProcessMapper.selectById(processDefinitionId); //第一次进入设计流程的时候,processDefinitionId始终为零,导致流程保存多份 if ("0".equals(processDefinitionId) && process == null) { @@ -1351,19 +1368,16 @@ public class ExtActProcessServiceImpl extends ServiceImpl oldNodeList = extActProcessNodeMapper.queryAllNodesByProcessKey(processkey); - List newNodeList = new ArrayList<>(); - if (nodes != null && nodes.length() > 3) { - String[] temp = nodes.split("@@@"); - for (int i = 0; i < temp.length; i++) { - ExtActProcessNode processnode = null; - String[] fileds = temp[i].split("###"); - String tid = fileds[0].substring(3); - String name = fileds[1].substring(9); - newNodeList.add(tid); + Map oldNodeMap = oldNodeList.stream() + .collect(Collectors.toMap(ExtActProcessNode::getProcessNodeCode, node -> node, (left, right) -> left)); + // newNodeList 是 XML 中真实存在的可配置任务节点,后续只删除不在此清单中的旧配置。 + List newNodeList = new ArrayList<>(processNodeInfoMap.keySet()); + for (ProcessNodeInfo nodeInfo : processNodeInfoMap.values()) { + String tid = nodeInfo.getId(); + String name = nodeInfo.getName(); + ExtActProcessNode processnode = oldNodeMap.get(tid); // 2.查询节点是否存在 - processnode = oldNodeList.stream().filter(node -> node.getProcessNodeCode().equals(tid)).findFirst().orElse(null); - //processnode = extActProcessNodeMapper.queryByNodeCodeAndProcessKey(processkey, tid); if (processnode == null) { processnode = new ExtActProcessNode(); processnode.setProcessNodeCode(tid); @@ -1385,7 +1399,6 @@ public class ExtActProcessServiceImpl extends ServiceImpl needToDeleteNodes = oldNodeList.stream().filter(element -> !newNodeList.contains(element.getProcessNodeCode()) && !element.getProcessNodeCode().toLowerCase().contains("start")) + List needToDeleteNodes = oldNodeList.stream().filter(element -> !newNodeList.contains(element.getProcessNodeCode())) .map(ExtActProcessNode::getProcessNodeCode).collect(Collectors.toList()); if(!CollectionUtils.isEmpty(needToDeleteNodes)){ extActProcessNodeMapper.deleteNodeByProcessId(process.getId(), needToDeleteNodes); } - } //update-end---author:scott ---date::2023-11-18 for:【QQYUN-7020】流程设计器里面节点删除了,但是配置里面节点未删除 return j; } + + /** + * 从 BPMN XML 解析流程配置节点。XML 解析失败时直接中断保存,避免按不完整节点清单删除已有配置。 + */ + private Map parseProcessNodeInfoMap(String processDescriptor) throws Exception { + if (oConvertUtils.isEmpty(processDescriptor)) { + throw new Exception("Process XML cannot be empty"); + } + XMLInputFactory xif = XMLInputFactory.newInstance(); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); + try (InputStreamReader xmlIn = new InputStreamReader(new ByteArrayInputStream(processDescriptor.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8)) { + XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn); + BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr); + Map nodeInfoMap = new LinkedHashMap<>(); + for (org.flowable.bpmn.model.Process process : bpmnModel.getProcesses()) { + collectProcessNodeInfos(process, nodeInfoMap); + } + return nodeInfoMap; + } catch (Exception e) { + log.error("Parse BPMN XML process nodes failed", e); + throw new Exception("Parse BPMN XML process nodes failed, please check process designer XML", e); + } + } + + private void collectProcessNodeInfos(FlowElementsContainer container, Map nodeInfoMap) { + if (container == null || CollectionUtils.isEmpty(container.getFlowElements())) { + return; + } + for (FlowElement flowElement : container.getFlowElements()) { + if (isConfigurableProcessNode(flowElement) && oConvertUtils.isNotEmpty(flowElement.getId())) { + nodeInfoMap.put(flowElement.getId(), new ProcessNodeInfo(flowElement.getId(), oConvertUtils.getString(flowElement.getName(), ""))); + } + // 子流程本身不作为配置节点保存,但要递归进入子流程内部收集任务节点。 + if (flowElement instanceof FlowElementsContainer) { + collectProcessNodeInfos((FlowElementsContainer) flowElement, nodeInfoMap); + } + } + } + + private boolean isConfigurableProcessNode(FlowElement flowElement) { + // 只同步任务节点,排除开始、结束事件以及 bpmn:SubProcess 容器。 + return flowElement instanceof Task; + } + + private String toDesignerNodeString(Collection nodeInfos) { + if (CollectionUtils.isEmpty(nodeInfos)) { + return ""; + } + StringBuilder nodes = new StringBuilder(); + for (ProcessNodeInfo nodeInfo : nodeInfos) { + nodes.append("id=").append(nodeInfo.getId()).append("###nodeName=").append(nodeInfo.getName()).append("@@@"); + } + return nodes.toString(); + } + + private static class ProcessNodeInfo { + private final String id; + private final String name; + + private ProcessNodeInfo(String id, String name) { + this.id = id; + this.name = name; + } + + private String getId() { + return id; + } + + private String getName() { + return name; + } + } } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/bgpartymatter/controller/BgPartymatterController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/bgpartymatter/controller/BgPartymatterController.java index 3253192..6bbb384 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/bgpartymatter/controller/BgPartymatterController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/bgpartymatter/controller/BgPartymatterController.java @@ -36,6 +36,7 @@ import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -106,6 +107,7 @@ public class BgPartymatterController extends JeecgController edit(@RequestBody BgPartymatter bgPartymatter) { + fillGeneratedNumber(bgPartymatter); bgPartymatterService.updateById(bgPartymatter); return Result.OK("编辑成功!"); } @@ -139,11 +142,24 @@ public class BgPartymatterController extends JeecgController