Commit acbe083c7f0fa41fce13fd6c7744094002719992
1 parent
311488fa
feat: 实现流程打印功能
Showing
6 changed files
with
234 additions
and
0 deletions
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
| ... | ... | @@ -196,4 +196,12 @@ public class BpmProcessInstanceController { |
| 196 | 196 | return success(processInstanceService.getProcessInstanceBpmnModelView(id)); |
| 197 | 197 | } |
| 198 | 198 | |
| 199 | + @GetMapping("/get-print-data") | |
| 200 | + @Operation(summary = "获得打印数据") | |
| 201 | + @Parameter(name = "id", description = "流程实例的编号", required = true) | |
| 202 | + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") | |
| 203 | + public CommonResult<BpmProcessPrintDataRespVO> getPrintData(@RequestParam("processInstanceId") String processInstanceId) { | |
| 204 | + return success(processInstanceService.getPrintData(getLoginUserId(), processInstanceId)); | |
| 205 | + } | |
| 206 | + | |
| 199 | 207 | } | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - 打印数据 Response VO") | |
| 10 | +@Data | |
| 11 | +public class BpmProcessPrintDataRespVO { | |
| 12 | + | |
| 13 | + private Boolean printTemplateEnable; | |
| 14 | + | |
| 15 | + private Integer processStatus; | |
| 16 | + | |
| 17 | + private String processStatusShow; | |
| 18 | + | |
| 19 | + private String processInstanceId; | |
| 20 | + | |
| 21 | + private String processBusinessKey; | |
| 22 | + | |
| 23 | + private String processName; | |
| 24 | + | |
| 25 | + private String startUser; | |
| 26 | + | |
| 27 | + private String startUserDept; | |
| 28 | + | |
| 29 | + private String startTime; | |
| 30 | + | |
| 31 | + private List<ApproveNode> approveNodes; | |
| 32 | + | |
| 33 | + private List<FormField> formFields; | |
| 34 | + | |
| 35 | + private String printTemplateHtml; | |
| 36 | + | |
| 37 | + @Data | |
| 38 | + public static class ApproveNode { | |
| 39 | + | |
| 40 | + private String nodeName; | |
| 41 | + | |
| 42 | + private String nodeDesc; | |
| 43 | + | |
| 44 | + private String signUrl; | |
| 45 | + | |
| 46 | + private String nodeId; | |
| 47 | + | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Data | |
| 51 | + public static class FormField { | |
| 52 | + | |
| 53 | + private String formId; | |
| 54 | + | |
| 55 | + private String formName; | |
| 56 | + | |
| 57 | + private String formType; | |
| 58 | + | |
| 59 | + private String formValue; | |
| 60 | + | |
| 61 | + private String formValueShow; | |
| 62 | + | |
| 63 | + } | |
| 64 | + | |
| 65 | +} | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.enums.task; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 5 | 6 | import lombok.AllArgsConstructor; |
| ... | ... | @@ -47,4 +48,8 @@ public enum BpmProcessInstanceStatusEnum implements ArrayValuable<Integer> { |
| 47 | 48 | APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); |
| 48 | 49 | } |
| 49 | 50 | |
| 51 | + public static BpmProcessInstanceStatusEnum valueOf(Integer status) { | |
| 52 | + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); | |
| 53 | + } | |
| 54 | + | |
| 50 | 55 | } | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.enums.task; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 3 | 4 | import cn.hutool.core.util.ObjUtil; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 5 | 6 | import lombok.AllArgsConstructor; |
| ... | ... | @@ -68,4 +69,8 @@ public enum BpmTaskStatusEnum { |
| 68 | 69 | return ObjUtil.equal(status, CANCEL.getStatus()); |
| 69 | 70 | } |
| 70 | 71 | |
| 72 | + public static BpmTaskStatusEnum valueOf(Integer status) { | |
| 73 | + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); | |
| 74 | + } | |
| 75 | + | |
| 71 | 76 | } | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
| ... | ... | @@ -113,6 +113,14 @@ public interface BpmProcessInstanceService { |
| 113 | 113 | */ |
| 114 | 114 | BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); |
| 115 | 115 | |
| 116 | + /** | |
| 117 | + * 获取流程打印所需数据 | |
| 118 | + * @param loginUserId 打印人 | |
| 119 | + * @param processInstanceId 流程实例id | |
| 120 | + * @return 打印所需数据 | |
| 121 | + */ | |
| 122 | + BpmProcessPrintDataRespVO getPrintData(Long loginUserId, String processInstanceId); | |
| 123 | + | |
| 116 | 124 | // ========== Update 写入相关方法 ========== |
| 117 | 125 | |
| 118 | 126 | /** | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.collection.ListUtil; |
| 5 | +import cn.hutool.core.date.DatePattern; | |
| 6 | +import cn.hutool.core.date.DateTime; | |
| 5 | 7 | import cn.hutool.core.date.DateUtil; |
| 6 | 8 | import cn.hutool.core.lang.Assert; |
| 7 | 9 | import cn.hutool.core.util.*; |
| 10 | +import cn.hutool.json.JSONObject; | |
| 11 | +import cn.hutool.json.JSONUtil; | |
| 8 | 12 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | 13 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 10 | 14 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| ... | ... | @@ -57,6 +61,10 @@ import org.flowable.engine.runtime.ProcessInstance; |
| 57 | 61 | import org.flowable.engine.runtime.ProcessInstanceBuilder; |
| 58 | 62 | import org.flowable.task.api.Task; |
| 59 | 63 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 64 | +import org.jsoup.Jsoup; | |
| 65 | +import org.jsoup.nodes.Document; | |
| 66 | +import org.jsoup.nodes.Element; | |
| 67 | +import org.jsoup.select.Elements; | |
| 60 | 68 | import org.springframework.context.annotation.Lazy; |
| 61 | 69 | import org.springframework.stereotype.Service; |
| 62 | 70 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -726,6 +734,141 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService |
| 726 | 734 | userMap, deptMap); |
| 727 | 735 | } |
| 728 | 736 | |
| 737 | + @Override | |
| 738 | + public BpmProcessPrintDataRespVO getPrintData(Long loginUserId, String processInstanceId) { | |
| 739 | + // TODO 方法抽离 | |
| 740 | + // 流程实例 | |
| 741 | + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(processInstanceId); | |
| 742 | + if (historicProcessInstance == null) { | |
| 743 | + throw exception(PROCESS_INSTANCE_NOT_EXISTS); | |
| 744 | + } | |
| 745 | + // 准备数据 | |
| 746 | + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService | |
| 747 | + .getProcessDefinitionInfo(historicProcessInstance.getProcessDefinitionId()); | |
| 748 | + BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting = processDefinitionInfo.getPrintTemplateSetting(); | |
| 749 | + List<String> formFieldList = processDefinitionInfo.getFormFields(); | |
| 750 | + List<JSONObject> formFieldObjList = formFieldList.stream().map(JSONUtil::parseObj).toList(); | |
| 751 | + List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery() | |
| 752 | + .finished() | |
| 753 | + .includeTaskLocalVariables() | |
| 754 | + .processInstanceId(processInstanceId) | |
| 755 | + .taskVariableValueNotEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, | |
| 756 | + BpmTaskStatusEnum.CANCEL.getStatus()) | |
| 757 | + .orderByHistoricTaskInstanceStartTime().asc().list(); | |
| 758 | + Set<Long> userIds = convertSet(tasks, item -> Long.valueOf(item.getAssignee())); | |
| 759 | + userIds.add(loginUserId); | |
| 760 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 761 | + HashMap<String, String> printDataMap = new HashMap<>(8 + formFieldList.size()); | |
| 762 | + // 返回打印所需数据 | |
| 763 | + BpmProcessPrintDataRespVO printData = new BpmProcessPrintDataRespVO(); | |
| 764 | + // 打印模板是否开启 | |
| 765 | + printData.setPrintTemplateEnable(printTemplateSetting != null && Boolean.TRUE.equals(printTemplateSetting.getEnable())); | |
| 766 | + // 流程相关数据 | |
| 767 | + printData.setProcessStatus(FlowableUtils.getProcessInstanceStatus(historicProcessInstance)); | |
| 768 | + printData.setProcessStatusShow(BpmProcessInstanceStatusEnum.valueOf(printData.getProcessStatus()).getDesc()); | |
| 769 | + printData.setProcessInstanceId(historicProcessInstance.getId()); | |
| 770 | + printData.setProcessName(historicProcessInstance.getName()); | |
| 771 | + printData.setProcessBusinessKey(historicProcessInstance.getBusinessKey()); | |
| 772 | + printData.setStartTime(DatePattern.NORM_DATETIME_MINUTE_FORMAT.format(historicProcessInstance.getStartTime())); | |
| 773 | + // 发起人 | |
| 774 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(historicProcessInstance.getStartUserId())); | |
| 775 | + DeptRespDTO dept = deptApi.getDept(startUser.getDeptId()); | |
| 776 | + printData.setStartUser(startUser.getNickname()); | |
| 777 | + printData.setStartUserDept(dept.getName()); | |
| 778 | + // 审批历史 | |
| 779 | + List<BpmProcessPrintDataRespVO.ApproveNode> approveNodes = new ArrayList<>(tasks.size()); | |
| 780 | + tasks.forEach(item -> { | |
| 781 | + Map<String, Object> taskLocalVariables = item.getTaskLocalVariables(); | |
| 782 | + BpmProcessPrintDataRespVO.ApproveNode approveNode = new BpmProcessPrintDataRespVO.ApproveNode(); | |
| 783 | + approveNode.setNodeName(item.getName()); | |
| 784 | + approveNode.setNodeId(item.getId()); | |
| 785 | + approveNode.setSignUrl((String) taskLocalVariables.getOrDefault(BpmnVariableConstants.TASK_SIGN_PIC_URL, "")); | |
| 786 | + approveNode.setNodeDesc(StrUtil.format("{} / {} / {} / {} / {}", | |
| 787 | + userMap.get(Long.valueOf(item.getAssignee())).getNickname(), | |
| 788 | + item.getName(), | |
| 789 | + DateUtil.formatDateTime(item.getEndTime()), | |
| 790 | + BpmTaskStatusEnum.valueOf((Integer) taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_STATUS)).getName(), | |
| 791 | + taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_REASON))); | |
| 792 | + approveNodes.add(approveNode); | |
| 793 | + }); | |
| 794 | + printData.setApproveNodes(approveNodes); | |
| 795 | + // 表单数据 | |
| 796 | + Map<String, Object> processVariables = historicProcessInstance.getProcessVariables(); | |
| 797 | + List<BpmProcessPrintDataRespVO.FormField> formFields = new ArrayList<>(formFieldList.size()); | |
| 798 | + formFieldObjList.forEach(item -> { | |
| 799 | + BpmProcessPrintDataRespVO.FormField formField = new BpmProcessPrintDataRespVO.FormField(); | |
| 800 | + formField.setFormName(item.getStr("title")); | |
| 801 | + formField.setFormType(item.getStr("type")); | |
| 802 | + formField.setFormId(item.getStr("field")); | |
| 803 | + formField.setFormValue(processVariables.get(item.getStr("field")).toString()); | |
| 804 | + // TODO 根据不同类型的表单展示,下面为图片和输入框示例 | |
| 805 | + if (formField.getFormType().equals("input")) { | |
| 806 | + formField.setFormValueShow(processVariables.get(item.getStr("field")).toString()); | |
| 807 | + } else if (formField.getFormType().equals("UploadImg")) { | |
| 808 | + formField.setFormValueShow("<img style=\"max-width: 600px\" src=\"" + processVariables.get(item.getStr("field")).toString() + "\" />"); | |
| 809 | + } else { | |
| 810 | + formField.setFormValueShow("此类型表单展示未完善"); | |
| 811 | + } | |
| 812 | + printDataMap.put(formField.getFormId(), formField.getFormValueShow()); | |
| 813 | + formFields.add(formField); | |
| 814 | + }); | |
| 815 | + printData.setFormFields(formFields); | |
| 816 | + // 数据映射 | |
| 817 | + printDataMap.put("processName", printData.getProcessName()); | |
| 818 | + printDataMap.put("printUsername", userMap.get(loginUserId).getNickname()); | |
| 819 | + printDataMap.put("processNum", printData.getProcessInstanceId()); | |
| 820 | + printDataMap.put("printTime", DatePattern.NORM_DATETIME_MINUTE_FORMAT.format(new DateTime())); | |
| 821 | + printDataMap.put("startUser", printData.getStartUser()); | |
| 822 | + printDataMap.put("startTime", printData.getStartTime()); | |
| 823 | + printDataMap.put("startUserDept", printData.getStartUserDept()); | |
| 824 | + printDataMap.put("processStatus", printData.getProcessStatusShow()); | |
| 825 | + // 自定义模板 | |
| 826 | + if (printData.getPrintTemplateEnable() && printTemplateSetting != null) { | |
| 827 | + Document document = Jsoup.parse(printTemplateSetting.getTemplate()); | |
| 828 | + // 添加table的border | |
| 829 | + Elements tables = document.select("table"); | |
| 830 | + tables.forEach(item -> { | |
| 831 | + item.attr("border", "1"); | |
| 832 | + }); | |
| 833 | + // 替换所有mention | |
| 834 | + Elements mention = document.getElementsByAttributeValue("data-w-e-type", "mention"); | |
| 835 | + mention.forEach(item -> { | |
| 836 | + String mentionId = JSONUtil.parseObj(URLUtil.decode(item.attr("data-info"))).getStr("id"); | |
| 837 | + item.html(printDataMap.get(mentionId)); | |
| 838 | + }); | |
| 839 | + // 替换流程记录 | |
| 840 | + Elements processRecords = document.getElementsByAttributeValue("data-w-e-type", "process-record"); | |
| 841 | + Element processRecordElement = null; | |
| 842 | + if (!processRecords.isEmpty()) { | |
| 843 | + processRecordElement = new Element("table", "") | |
| 844 | + .attr("style", "width:100%;") | |
| 845 | + .attr("border", "1"); | |
| 846 | + Element tbody = new Element("tbody", ""); | |
| 847 | + Element rowHead = new Element("tr", ""); | |
| 848 | + rowHead.appendChild(new Element("td", "") | |
| 849 | + .attr("colspan", "2") | |
| 850 | + .attr("width", "auto") | |
| 851 | + .attr("style", "text-align: center;") | |
| 852 | + .html("流程节点")); | |
| 853 | + tbody.appendChild(rowHead); | |
| 854 | + for (BpmProcessPrintDataRespVO.ApproveNode item : printData.getApproveNodes()) { | |
| 855 | + Element row = new Element("tr", ""); | |
| 856 | + row.appendChild(new Element("td", "") | |
| 857 | + .html(item.getNodeName())); | |
| 858 | + row.appendChild(new Element("td", "") | |
| 859 | + .html(item.getNodeDesc())); | |
| 860 | + tbody.appendChild(row); | |
| 861 | + } | |
| 862 | + processRecordElement.appendChild(tbody); | |
| 863 | + } | |
| 864 | + for (Element item : processRecords) { | |
| 865 | + item.html(processRecordElement.outerHtml()); | |
| 866 | + } | |
| 867 | + printData.setPrintTemplateHtml(document.html()); | |
| 868 | + } | |
| 869 | + return printData; | |
| 870 | + } | |
| 871 | + | |
| 729 | 872 | // ========== Update 写入相关方法 ========== |
| 730 | 873 | |
| 731 | 874 | @Override | ... | ... |