From 311488fa6c96f4d47f7f2a64f5518247d69a674b Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Fri, 29 Aug 2025 15:18:49 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=89=93=E5=8D=B0=E6=A8=A1=E6=9D=BF=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/vo/model/BpmModelMetaInfoVO.java | 17 +++++++++++++++++ .../definition/BpmProcessDefinitionInfoDO.java | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index 943a82d546..5284281f10 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -100,6 +100,10 @@ public class BpmModelMetaInfoVO { @Schema(description = "任务后置通知设置", example = "{}") private HttpRequestSetting taskAfterTriggerSetting; + @Schema(description = "自定义打印模板设置", example = "{}") + @Valid + private PrintTemplateSetting printTemplateSetting; + @Schema(description = "流程 ID 规则") @Data @Valid @@ -180,4 +184,17 @@ public class BpmModelMetaInfoVO { } + @Schema(description = "自定义打印模板设置") + @Data + public static class PrintTemplateSetting { + + @Schema(description = "是否自定义打印模板", example = "false") + @NotNull(message = "是否自定义打印模板不能为空") + private Boolean enable; + + @Schema(description = "打印模板", example = "

") + private String template; + + } + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 37e2c4462d..6fd905ee3d 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -224,4 +224,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; + /** + * 自定义打印模板设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting; + } From acbe083c7f0fa41fce13fd6c7744094002719992 Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Tue, 2 Sep 2025 17:02:58 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E6=89=93=E5=8D=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceController.java | 8 + .../instance/BpmProcessPrintDataRespVO.java | 65 ++++++++ .../task/BpmProcessInstanceStatusEnum.java | 5 + .../bpm/enums/task/BpmTaskStatusEnum.java | 5 + .../task/BpmProcessInstanceService.java | 8 + .../task/BpmProcessInstanceServiceImpl.java | 143 ++++++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 8a3777772e..b66555cda3 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -196,4 +196,12 @@ public class BpmProcessInstanceController { return success(processInstanceService.getProcessInstanceBpmnModelView(id)); } + @GetMapping("/get-print-data") + @Operation(summary = "获得打印数据") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult getPrintData(@RequestParam("processInstanceId") String processInstanceId) { + return success(processInstanceService.getPrintData(getLoginUserId(), processInstanceId)); + } + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java new file mode 100644 index 0000000000..a7897f1845 --- /dev/null +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + + +@Schema(description = "管理后台 - 打印数据 Response VO") +@Data +public class BpmProcessPrintDataRespVO { + + private Boolean printTemplateEnable; + + private Integer processStatus; + + private String processStatusShow; + + private String processInstanceId; + + private String processBusinessKey; + + private String processName; + + private String startUser; + + private String startUserDept; + + private String startTime; + + private List approveNodes; + + private List formFields; + + private String printTemplateHtml; + + @Data + public static class ApproveNode { + + private String nodeName; + + private String nodeDesc; + + private String signUrl; + + private String nodeId; + + } + + @Data + public static class FormField { + + private String formId; + + private String formName; + + private String formType; + + private String formValue; + + private String formValueShow; + + } + +} diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index 5b42df50fe..87be32633b 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.enums.task; +import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; @@ -47,4 +48,8 @@ public enum BpmProcessInstanceStatusEnum implements ArrayValuable { APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); } + public static BpmProcessInstanceStatusEnum valueOf(Integer status) { + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); + } + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index 9ba3b5cb3a..0f69c42795 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.enums.task; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; @@ -68,4 +69,8 @@ public enum BpmTaskStatusEnum { return ObjUtil.equal(status, CANCEL.getStatus()); } + public static BpmTaskStatusEnum valueOf(Integer status) { + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); + } + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index abba2245e2..33e667716b 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -113,6 +113,14 @@ public interface BpmProcessInstanceService { */ BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); + /** + * 获取流程打印所需数据 + * @param loginUserId 打印人 + * @param processInstanceId 流程实例id + * @return 打印所需数据 + */ + BpmProcessPrintDataRespVO getPrintData(Long loginUserId, String processInstanceId); + // ========== Update 写入相关方法 ========== /** diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 9dfb98725c..153e51bfc3 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -2,9 +2,13 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; @@ -57,6 +61,10 @@ import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstanceBuilder; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -726,6 +734,141 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService userMap, deptMap); } + @Override + public BpmProcessPrintDataRespVO getPrintData(Long loginUserId, String processInstanceId) { + // TODO 方法抽离 + // 流程实例 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(processInstanceId); + if (historicProcessInstance == null) { + throw exception(PROCESS_INSTANCE_NOT_EXISTS); + } + // 准备数据 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService + .getProcessDefinitionInfo(historicProcessInstance.getProcessDefinitionId()); + BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting = processDefinitionInfo.getPrintTemplateSetting(); + List formFieldList = processDefinitionInfo.getFormFields(); + List formFieldObjList = formFieldList.stream().map(JSONUtil::parseObj).toList(); + List tasks = historyService.createHistoricTaskInstanceQuery() + .finished() + .includeTaskLocalVariables() + .processInstanceId(processInstanceId) + .taskVariableValueNotEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, + BpmTaskStatusEnum.CANCEL.getStatus()) + .orderByHistoricTaskInstanceStartTime().asc().list(); + Set userIds = convertSet(tasks, item -> Long.valueOf(item.getAssignee())); + userIds.add(loginUserId); + Map userMap = adminUserApi.getUserMap(userIds); + HashMap printDataMap = new HashMap<>(8 + formFieldList.size()); + // 返回打印所需数据 + BpmProcessPrintDataRespVO printData = new BpmProcessPrintDataRespVO(); + // 打印模板是否开启 + printData.setPrintTemplateEnable(printTemplateSetting != null && Boolean.TRUE.equals(printTemplateSetting.getEnable())); + // 流程相关数据 + printData.setProcessStatus(FlowableUtils.getProcessInstanceStatus(historicProcessInstance)); + printData.setProcessStatusShow(BpmProcessInstanceStatusEnum.valueOf(printData.getProcessStatus()).getDesc()); + printData.setProcessInstanceId(historicProcessInstance.getId()); + printData.setProcessName(historicProcessInstance.getName()); + printData.setProcessBusinessKey(historicProcessInstance.getBusinessKey()); + printData.setStartTime(DatePattern.NORM_DATETIME_MINUTE_FORMAT.format(historicProcessInstance.getStartTime())); + // 发起人 + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(historicProcessInstance.getStartUserId())); + DeptRespDTO dept = deptApi.getDept(startUser.getDeptId()); + printData.setStartUser(startUser.getNickname()); + printData.setStartUserDept(dept.getName()); + // 审批历史 + List approveNodes = new ArrayList<>(tasks.size()); + tasks.forEach(item -> { + Map taskLocalVariables = item.getTaskLocalVariables(); + BpmProcessPrintDataRespVO.ApproveNode approveNode = new BpmProcessPrintDataRespVO.ApproveNode(); + approveNode.setNodeName(item.getName()); + approveNode.setNodeId(item.getId()); + approveNode.setSignUrl((String) taskLocalVariables.getOrDefault(BpmnVariableConstants.TASK_SIGN_PIC_URL, "")); + approveNode.setNodeDesc(StrUtil.format("{} / {} / {} / {} / {}", + userMap.get(Long.valueOf(item.getAssignee())).getNickname(), + item.getName(), + DateUtil.formatDateTime(item.getEndTime()), + BpmTaskStatusEnum.valueOf((Integer) taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_STATUS)).getName(), + taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_REASON))); + approveNodes.add(approveNode); + }); + printData.setApproveNodes(approveNodes); + // 表单数据 + Map processVariables = historicProcessInstance.getProcessVariables(); + List formFields = new ArrayList<>(formFieldList.size()); + formFieldObjList.forEach(item -> { + BpmProcessPrintDataRespVO.FormField formField = new BpmProcessPrintDataRespVO.FormField(); + formField.setFormName(item.getStr("title")); + formField.setFormType(item.getStr("type")); + formField.setFormId(item.getStr("field")); + formField.setFormValue(processVariables.get(item.getStr("field")).toString()); + // TODO 根据不同类型的表单展示,下面为图片和输入框示例 + if (formField.getFormType().equals("input")) { + formField.setFormValueShow(processVariables.get(item.getStr("field")).toString()); + } else if (formField.getFormType().equals("UploadImg")) { + formField.setFormValueShow(""); + } else { + formField.setFormValueShow("此类型表单展示未完善"); + } + printDataMap.put(formField.getFormId(), formField.getFormValueShow()); + formFields.add(formField); + }); + printData.setFormFields(formFields); + // 数据映射 + printDataMap.put("processName", printData.getProcessName()); + printDataMap.put("printUsername", userMap.get(loginUserId).getNickname()); + printDataMap.put("processNum", printData.getProcessInstanceId()); + printDataMap.put("printTime", DatePattern.NORM_DATETIME_MINUTE_FORMAT.format(new DateTime())); + printDataMap.put("startUser", printData.getStartUser()); + printDataMap.put("startTime", printData.getStartTime()); + printDataMap.put("startUserDept", printData.getStartUserDept()); + printDataMap.put("processStatus", printData.getProcessStatusShow()); + // 自定义模板 + if (printData.getPrintTemplateEnable() && printTemplateSetting != null) { + Document document = Jsoup.parse(printTemplateSetting.getTemplate()); + // 添加table的border + Elements tables = document.select("table"); + tables.forEach(item -> { + item.attr("border", "1"); + }); + // 替换所有mention + Elements mention = document.getElementsByAttributeValue("data-w-e-type", "mention"); + mention.forEach(item -> { + String mentionId = JSONUtil.parseObj(URLUtil.decode(item.attr("data-info"))).getStr("id"); + item.html(printDataMap.get(mentionId)); + }); + // 替换流程记录 + Elements processRecords = document.getElementsByAttributeValue("data-w-e-type", "process-record"); + Element processRecordElement = null; + if (!processRecords.isEmpty()) { + processRecordElement = new Element("table", "") + .attr("style", "width:100%;") + .attr("border", "1"); + Element tbody = new Element("tbody", ""); + Element rowHead = new Element("tr", ""); + rowHead.appendChild(new Element("td", "") + .attr("colspan", "2") + .attr("width", "auto") + .attr("style", "text-align: center;") + .html("流程节点")); + tbody.appendChild(rowHead); + for (BpmProcessPrintDataRespVO.ApproveNode item : printData.getApproveNodes()) { + Element row = new Element("tr", ""); + row.appendChild(new Element("td", "") + .html(item.getNodeName())); + row.appendChild(new Element("td", "") + .html(item.getNodeDesc())); + tbody.appendChild(row); + } + processRecordElement.appendChild(tbody); + } + for (Element item : processRecords) { + item.html(processRecordElement.outerHtml()); + } + printData.setPrintTemplateHtml(document.html()); + } + return printData; + } + // ========== Update 写入相关方法 ========== @Override From 21f62b17b15957854dce21483dc5874419daa1e3 Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Tue, 2 Sep 2025 17:08:06 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E8=A1=A8=E5=8D=95=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E6=97=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 153e51bfc3..d5c0a10931 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -800,12 +800,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService formField.setFormName(item.getStr("title")); formField.setFormType(item.getStr("type")); formField.setFormId(item.getStr("field")); - formField.setFormValue(processVariables.get(item.getStr("field")).toString()); + formField.setFormValue(processVariables.getOrDefault(item.getStr("field"), "").toString()); // TODO 根据不同类型的表单展示,下面为图片和输入框示例 if (formField.getFormType().equals("input")) { - formField.setFormValueShow(processVariables.get(item.getStr("field")).toString()); + formField.setFormValueShow(processVariables.getOrDefault(item.getStr("field"), "").toString()); } else if (formField.getFormType().equals("UploadImg")) { - formField.setFormValueShow(""); + formField.setFormValueShow(""); } else { formField.setFormValueShow("此类型表单展示未完善"); }