diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java index 82cda512c..837b5b496 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java @@ -17,11 +17,13 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; @@ -180,6 +182,17 @@ public interface ProcessInstanceApi { CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + /** + * 更新流程实例中的业务自定义变量集合 + * + * @param dto + * @return + */ + @Operation(summary = "更新流程实例中的业务自定义变量集合") + @PostMapping("/api/process/instance/biz/custom/variables/update") + @InvokeMode(SYNC) + CommonResponse updateProcessBizCustomVariables(@Validated @RequestBody BpmnProcessInstanceVariablesUpdateDTO dto); + /** * 查询所有的审批流 * @@ -358,4 +371,15 @@ public interface ProcessInstanceApi { @Manageable @InvokeMode(SYNC) CommonResponse> getProcessLogByInstanceIdAndPersonId(@Validated @RequestBody LogApproveSearchDTO dto); + + /** + * 获取流程实例的条件字段信息,仅用于同意抽屉展示 + * + * @param processInstanceId + * @return + */ + @Operation(summary = "获取流程实例的条件字段信息, 仅用于同意抽屉展示") + @GetMapping("/api/process/instance/conditions") + @InvokeMode(SYNC) + CommonResponse> getConditions(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId); } diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java index 9dec9a0a9..38ff9b3ba 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java @@ -4,8 +4,13 @@ import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.Print4ProcessLogDTO; import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintProcessLogPdfDTO; import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.QueryProcessLogPdfDTO; +import cn.axzo.workflow.common.model.response.bpmn.process.PrintData4LogVO; +import cn.axzo.workflow.common.model.response.print.ProcessLogPdfResultDTO; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import org.springframework.validation.annotation.Validated; @@ -66,7 +71,7 @@ public interface PrintAdminApi { * 获取指定流程下用于替换打印的相关变量 * * @param processInstanceId - * @return + * @return 仅是 kv 集合, */ @Operation(summary = "获取指定流程下用于替换打印的相关变量") @GetMapping("/api/print/admin/field/variables") @@ -74,4 +79,40 @@ public interface PrintAdminApi { @InvokeMode(SYNC) CommonResponse> getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId, @RequestParam(required = false, defaultValue = "true") Boolean throwException); + + /** + * 获取用于打印审批日志公共模板的数据 + * + * @param dto + * @return + */ + @Operation(summary = "获取用于打印审批日志公共模板的数据") + @PostMapping("/api/print/admin/process/log/data/v2") + @Manageable + @InvokeMode(SYNC) + CommonResponse getPrintDataForProcessLog(@Validated @RequestBody Print4ProcessLogDTO dto); + + /** + * 后端请求指定流程日志 PDF 文件生成, 实现是异步的。 + *

+ * 请使用 {@link PrintAdminApi#queryProcessLogPdfResult(QueryProcessLogPdfDTO)} 函数查询, + * 或者使用 {@link cn.axzo.nanopart.doc.api.conversion.DocConversionApi#queryConvertResultByBiz} 函数查询,bizCode:固定为"workflow-process-log", bizKey:为实例 ID + * + * @return + */ + @Operation(summary = "后端请求指定流程日志 PDF 文件生成") + @PostMapping("/api/print/admin/process/log/pdf") + @InvokeMode(SYNC) + CommonResponse createProcessLogPdf(@Validated @RequestBody PrintProcessLogPdfDTO dto); + + /** + * 后端查询指定审批日志 PDF 文件的生成结果 + * + * @param dto + * @return + */ + @Operation(summary = "后端查询指定审批日志 PDF 文件的生成结果") + @PostMapping("/api/print/admin/process/log/pdf/result") + @InvokeMode(SYNC) + CommonResponse queryProcessLogPdfResult(@Validated @RequestBody QueryProcessLogPdfDTO dto); } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java index 07d01a019..1968f7e27 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java @@ -44,6 +44,7 @@ public enum BpmnTaskRespCode implements IModuleRespCode { REMIND_TASK_TOO_MANY("027", "催办任务数据异常"), PROCESS_SET_ASSIGNEE_PARAM_ERROR("028", "当前审批业务审批人模型中 NodeId 必传(topNodeId/nodeId均可), 触发 ID: 【{}】"), TASK_OPERATION_PARAM_INVALID("029", "流程实例 ID 与任务 ID 必须二选一"), + COOPERATION_NOT_EXIST_WITH_NODE("030", "查询审批人时,组织不存在"), ; private final String code; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java index 073d3e6b6..bf6a142e3 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java @@ -21,6 +21,8 @@ public enum ConvertorRespCode implements IModuleRespCode { CONVERTOR_OPERATION_NUMBER_TYPE_ERROR("006", "条件节点(数字)运算符【{}】暂不支持"), CONVERTOR_OPERATION_RADIO_TYPE_ERROR("007", "条件节点(单选)运算符【{}】暂不支持"), CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR("008", "条件节点(复选)运算符【{}】暂不支持"), + CREATE_BPMN_USER_TASK_ERROR("009", "创建 UserTask 失败, 不支持的节点模式【{}】"), + CREATE_BPMN_PRE_SIGN_ERROR("010", "创建前加签节点失败,原因:【{}】"), ; private final String code; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java index 6d30f47be..f86508f1d 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java @@ -42,9 +42,12 @@ public interface BpmnConstants { @Deprecated String OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT = "[_ASSIGNEE_INFO_SNAPSHOT_]"; String INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT = "[_ACTIVITY_INFO_SNAPSHOT_]"; + String INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN = "[_FORWARD_COUNTERSIGN_]"; + String INTERNAL_ACTIVITY_BACK_COUNTERSIGN = "[_BACK_COUNTERSIGN_]"; String BIZ_NODE_ALTER = "[_BIZ_NODE_ALTER_]"; String INITIATOR_SPECIFY = "[_INITIATOR_SPECIFY_]"; String SIGNATURE_COLLECTION = "[_SIGNATURE_COLLECTION_]"; + String COUNTERSIGN_COUNT = "[_COUNTERSIGN_COUNT_]"; String PROCESS_PREFIX = "Flowable"; @Deprecated String OLD_TASK_ASSIGNEE_SKIP_FLAT = "taskSkip"; @@ -114,6 +117,7 @@ public interface BpmnConstants { String CONFIG_ACTIVITY_SIGNATURE = "signature"; String CONFIG_FIELD_META = "field"; String CONFIG_FIELD_PERMISSION = "fieldPermission"; + String CONFIG_CONDITION_PERMISSION = "conditionPermission"; String CONFIG_FIELD_OPTION = "option"; String CONFIG_NODE_TYPE = "nodeType"; String CONFIG_BUTTON_TYPE_INITIATOR = "initiator"; @@ -123,6 +127,7 @@ public interface BpmnConstants { String CONFIG_SIGN_TYPE = "signType"; String CONFIG_AREA_FILTER_ENABLE = "areaFilterEnable"; String CONFIG_SPECIALTY_FILTER_ENABLE = "specialtyFilterEnable"; + String CONFIG_ONLY_IN_PROJECT_ENABLE = "onlyInProjectEnable"; String ELEMENT_ATTRIBUTE_NAME = "name"; String ELEMENT_ATTRIBUTE_VALUE = "value"; String ELEMENT_ATTRIBUTE_DESC = "desc"; @@ -155,6 +160,7 @@ public interface BpmnConstants { String NUMBER_OF_INSTANCES = "nrOfInstances"; String MULTI_INSTANCE_LOOP_COUNTER = "loopCounter"; String TASK_COMPLETE_OPERATION_TYPE = "_TASK_COMPLETE_TYPE"; + String TASK_LOG_NODE_HAS_BEEN_HIDDEN = "_TASK_LOG_HIDDEN"; String TASK_ATTACHMENTS_VAR_NAME = "TASK_ATTACHMENTS"; /** @@ -249,6 +255,18 @@ public interface BpmnConstants { * 签署业务发起流程实例时,重新选择的文档tag 集合 */ String SIGN_PROCESS_ENABLE_DOC_IDS = "[_SIGN_PROCESS_ENABLE_DOC_IDS_]"; + /** + * 签署业务自定义业务传入的文档集合 + */ + String SIGN_BIZ_CUSTOM_DOCS = "[_SIGN_BIZ_CUSTOM_DOCS_]"; + /** + * 签署业务自定义文档的顺序位置类型 + */ + String SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE = "[_SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE_]"; + /** + * 签署业务,业务对所有文档顺序排序 + */ + String SIGN_BIZ_BASED_FILE_TAG_ORDER = "[_SIGN_BIZ_BASED_FILE_TAG_ORDER_]"; /** * 签署业务,基于业务自定义变量的传入 */ @@ -262,4 +280,12 @@ public interface BpmnConstants { * 提级审批变量标识 */ String SUPPORT_UPGRADE_VARIABLE = "[_SUPPORT_UPGRADE_]"; + /** + * 前加签节点 ID 片段 + */ + String FORWARD_ACTIVITY_FRAGMENT = "[forward_sign]"; + /** + * 后加签节点 ID 片段 + */ + String BACK_ACTIVITY_FRAGMENT = "[back_sign]"; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java index b0ae9caa8..d8a6ed3ff 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java @@ -29,14 +29,20 @@ public interface VariableConstants { //=============== 打印时的变量集合中 key 的命名 ================= String VAR_PREFIX = "业务变量"; + String PRINT_VAR_PROCESS_NAME = "processName"; + String PRINT_VAR_PROCESS_NAME_DESC = "审批名称"; String PRINT_VAR_PROCESS_DEFINITION_KEY = "processDefinitionKey"; String PRINT_VAR_PROCESS_DEFINITION_KEY_DESC = "业务名称"; + String PRINT_VAR_PROCESS_BELONG_TENANT_ID = "tenantId"; + String PRINT_VAR_PROCESS_BELONG_TENANT_ID_DESC = "所属租户"; String PRINT_VAR_PROCESS_INSTANCE_ID = "processInstanceId"; String PRINT_VAR_PROCESS_INSTANCE_ID_DESC = "审批编号"; String PRINT_VAR_PROCESS_START_TIME = "startTime"; String PRINT_VAR_PROCESS_START_TIME_DESC = "发起时间"; String PRINT_VAR_PROCESS_END_TIME = "endTime"; String PRINT_VAR_PROCESS_END_TIME_DESC = "审批结束时间"; + String PRINT_VAR_PROCESS_RESULT = "processResult"; + String PRINT_VAR_PROCESS_RESULT_DESC = "审批结果"; String PRINT_VAR_PROCESS_INITIATOR = "initiator"; String PRINT_VAR_PROCESS_INITIATOR_DESC = "发起者"; String PRINT_VAR_PROCESS_INITIATOR_NAME = "initiatorName"; @@ -45,12 +51,16 @@ public interface VariableConstants { String PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC = "发起人岗位"; String PRINT_VAR_PROCESS_INITIATOR_PHONE = "initiatorPhone"; String PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC = "发起人联系方式"; + String PRINT_VAR_PROCESS_INITIATOR_UNIT = "initiatorUnit"; + String PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC = "发起人单位"; String PRINT_VAR_PROCESS_LOGS = "processLogs"; String PRINT_VAR_PROCESS_LOGS_DESC = "审批日志"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE = "activityNodeType"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE_DESC = "节点类型"; String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME = "activityName"; String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC = "节点名称"; String PRINT_VAR_PROCESS_LOG_APPROVER_NAME = "approverName"; - String PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC = "审批人"; + String PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC = "姓名"; String PRINT_VAR_PROCESS_LOG_UNIT = "unit"; String PRINT_VAR_PROCESS_LOG_UNIT_DESC = "单位"; String PRINT_VAR_PROCESS_LOG_POSITION = "position"; @@ -61,4 +71,10 @@ public interface VariableConstants { String PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC = "审批时间"; String PRINT_VAR_PROCESS_LOG_SIGNATURE = "signature"; String PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC = "电子签名"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT = "activityResult"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC = "审批结果"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME = "activityOperationTime"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC = "日期"; + String PRINT_VAR_PROCESS_LOG_OPERATION = "operationDesc"; + String PRINT_VAR_PROCESS_LOG_OPERATION_DESC = "操作描述"; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java index 8684c34a5..5ce5ff1e1 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java @@ -12,6 +12,7 @@ public enum BpmnFlowNodeMode { GENERAL("GENERAL", "普通节点"), OR("OR", "或签节点"), AND("AND", "会签节点"), + SEQUENCE("SEQUENCE", "顺序节点"), EXCEPTIONAL("EXCEPTIONAL", "异常"), @JsonEnumDefaultValue UNKNOWN("UNKNOWN", "未知"), diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java index 4ae46cef7..a819d30e2 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java @@ -19,6 +19,7 @@ public enum BpmnProcessInstanceResultEnum { UPGRADED("UPGRADED", "已提级"), COMMENTED("COMMENTED", "已评论"), DELETED("DELETED", "已删除"), + HIDDEN("HIDDEN", "已隐藏"), @JsonEnumDefaultValue UNKNOWN("UNKNOWN", "未知"), ; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowApproverCalcException.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowApproverCalcException.java new file mode 100644 index 000000000..8956569cc --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowApproverCalcException.java @@ -0,0 +1,75 @@ +package cn.axzo.workflow.common.exception; + +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.framework.domain.web.code.IRespCode; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; + +/** + * 流程内部计算审批人的异常 + * + * @author wangli + * @since 2025-11-03 10:11 + */ +@Slf4j +public class WorkflowApproverCalcException extends ServiceException { + + private String code; + + public WorkflowApproverCalcException(IRespCode code) { + super(code.getMessage()); + this.code = code.getRespCode(); + } + + public WorkflowApproverCalcException(IRespCode code, String... params) { + super(doFormat(code.getCode(), code.getMessage(), params)); + this.code = code.getRespCode(); + } + + public WorkflowApproverCalcException(IRespCode code, Throwable cause, String... params) { + super(doFormat(code.getCode(), code.getMessage(), params), cause); + this.code = code.getRespCode(); + } + + @Override + public String getCode() { + return this.code; + } + + /** + * 将错误编号对应的消息使用 params 进行格式化。 + * + * @param code 错误编号 + * @param messagePattern 消息模版 + * @param params 参数 + * @return 格式化后的提示 + */ + @VisibleForTesting + private static String doFormat(String code, String messagePattern, Object... params) { + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + int i = 0; + int j; + int l; + for (l = 0; l < params.length; l++) { + j = messagePattern.indexOf("{}", i); + if (j == -1) { + log.warn("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + if (i == 0) { + return messagePattern; + } else { + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } + } else { + sbuf.append(messagePattern, i, j); + sbuf.append(params[l]); + i = j + 2; + } + } + if (messagePattern.indexOf("{}", i) != -1) { + log.warn("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + } + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java index 0106ffb23..499631039 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java @@ -40,6 +40,11 @@ public class CooperationOrgDTO implements Serializable { */ private List includeSpecialtyCodes; + /** + * 该参数仅应用于节点的高级设置中的"仅支持工程内人员参与审批” + */ + private List projectIds; + /** * 企业组织架构范围 **/ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CustomDocDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CustomDocDTO.java new file mode 100644 index 000000000..748b25ba2 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CustomDocDTO.java @@ -0,0 +1,61 @@ +package cn.axzo.workflow.common.model.dto; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * 签署文件记录信息 + * + * @author wangli + * @since 2025-04-03 11:21 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CustomDocDTO implements Serializable { + + private static final long serialVersionUID = -8709597975507074853L; + /** + * 该属性内部使用,无需赋值 + */ + private Long id; + /** + * 文件名称 + */ + @NotBlank(message = "业务自定义文件名称不能为空") + private String fileName; + + /** + * 文件的标签 + */ + private String fileTag; + + /** + * 如果业务是使用在线文档,则一定会有 wps code,如果有则传入 + *

+ * wps 文件的标识,通过{@link cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi#createFile(cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateFileRequest)} 接口创建文件后返回的 fileCode + */ + private String fileCode; + /** + * 不管是在线文件还是本地上传,必须包含 oss 地址的文件标识 + */ + @NotBlank(message = "业务自定义文件的 oss key 不能为空") + private String fileKey; + + /** + * 文件的类型,如果要替换变量,支持 docx 格式,doc 格式不支持。 + */ + @NotNull(message = "业务自定义文件的类型不能为空") + private FileTypeEnum fileType; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java index 183722818..708510905 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.Date; import java.util.List; /** @@ -39,7 +40,11 @@ public class SignatureDTO implements Serializable { @Accessors(chain = true) public static class SignDetail implements Serializable { private static final long serialVersionUID = 1L; + //审批人姓名 + private String approverName; private String signature; private String advice; + private String result; + private Date operationTime; } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java index ca44ab03c..6e1183625 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java @@ -10,6 +10,7 @@ import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; import cn.axzo.workflow.common.enums.CooperateShipTypeEnum; import cn.axzo.workflow.common.enums.InitiatorSpecifiedRangeEnum; import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -201,6 +202,12 @@ public class BpmnJsonNodeProperty { @ApiModelProperty(value = "表单字段权限控制") private List fieldPermission; + /** + * 条件字段权限配置 + */ + @ApiModelProperty(value = "条件字段权限控制") + private List conditionPermission; + /** * 区域过滤开关 */ @@ -213,4 +220,9 @@ public class BpmnJsonNodeProperty { @ApiModelProperty(value = "专业过滤开关", notes = "true: 开启专业过滤, false: 关闭专业过滤") private Boolean specialtyFilterEnable; + /** + * 工程内人员开关 + */ + @ApiModelProperty(value = "仅工程内人员开关", notes = "true: 仅工程内人员, false: 非仅工程内人员") + private Boolean onlyInProjectEnable; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/Print4ProcessLogDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/Print4ProcessLogDTO.java new file mode 100644 index 000000000..87dc3ae33 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/Print4ProcessLogDTO.java @@ -0,0 +1,32 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 获取内置公共模板打印数据的入参模型 + * + * @author wangli + * @since 2025-10-30 10:43 + */ +@ApiModel("获取内置公共模板打印数据的入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Print4ProcessLogDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java index 3775f63e5..3c6deb6df 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java @@ -35,6 +35,11 @@ public class PrintFieldQueryDTO { @ApiModelProperty(value = "租户 ID") private String tenantId; + /** + * 是否过滤仅支持打印字段 + */ + @ApiModelProperty(value = "是否过滤仅支持打印字段") + private Boolean filterEnablePrint; /** * 是否抛出内部异常 */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintProcessLogPdfDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintProcessLogPdfDTO.java new file mode 100644 index 000000000..2c48315a0 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintProcessLogPdfDTO.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 请求审批日志转 pdf 的入参模型 + * + * @author wangli + * @since 2025-10-31 17:15 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PrintProcessLogPdfDTO { + + /** + * 审批实例 ID + */ + @ApiModelProperty(value = "审批实例 ID") + @NotBlank(message = "审批实例 ID 不能为空") + private String processInstanceId; + + /** + * 实例日志访问者的personId + */ + @ApiModelProperty(value = "访问者的 PersonId") + @NotBlank(message = "访问者的 personId 不能为空") + private String personId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/QueryProcessLogPdfDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/QueryProcessLogPdfDTO.java new file mode 100644 index 000000000..734d5f6d2 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/QueryProcessLogPdfDTO.java @@ -0,0 +1,30 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 查询审批日志转 pdf 的入参模型 + * + * @author wangli + * @since 2025-10-31 17:15 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class QueryProcessLogPdfDTO { + + /** + * 审批实例 ID + */ + @ApiModelProperty(value = "审批实例 ID") + @NotBlank(message = "审批实例 ID 不能为空") + private String processInstanceId; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java index 1b57e3a1a..dea65772e 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.common.model.request.bpmn.process; import cn.axzo.workflow.common.constant.BpmnConstants; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -10,6 +11,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.util.CollectionUtils; import javax.validation.Valid; import javax.validation.constraints.NotBlank; @@ -120,6 +122,30 @@ public class BpmnProcessInstanceCreateDTO extends BpmnProcessInstanceCreateWithF @ApiModelProperty(value = "签署业务发起时,选择的文档") private List docIds; + /** + * "签字业务"专用 + *

+ * 业务自定义的文档 + */ + @ApiModelProperty(value = "业务自定义文档") + private List customDocs; + + /** + * "签字业务"专用,自定义文档的顺序位置信息,在流程模板配置文档之前还是之后, 该属性与{@link BpmnProcessInstanceCreateDTO#basedFileTagOrder} 互斥 + *

+ * 可选值:first(之前)、last(之后), 如果为空,默认为 last + */ + @ApiModelProperty(value = "自定义文档顺序位置", notes = "可选值:first(之前)、last(之后), 如果为空,默认为 last") + private String docAddOrderType = "last"; + + /** + * "签字业务"专用, 该属性与{@link BpmnProcessInstanceCreateDTO#docAddOrderType} 互斥 + *

+ * 业务对所有文档的顺序覆盖,必须对全量文档进行设置 + */ + @ApiModelProperty(value = "业务对所有文档的顺序覆盖") + public List basedFileTagOrder; + /** * 仅针对签署业务,设置审批完成后的最终签署人列表,该属性仅为透传,业务消费时,请从 MQ 广播事件中的 variables 中通过 key= {@link BpmnConstants#SIGNATORIES } 获取 */ @@ -132,4 +158,14 @@ public class BpmnProcessInstanceCreateDTO extends BpmnProcessInstanceCreateWithF } return null; } + + public List getCustomDocs() { + if (CollectionUtils.isEmpty(customDocs)) { + return customDocs; + } + for (int i = 0; i < customDocs.size(); i++) { + customDocs.get(i).setId(i - (i + 1L)); // 负数 ID,避免冲突 + } + return customDocs; + } } \ No newline at end of file diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesUpdateDTO.java new file mode 100644 index 000000000..758142593 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesUpdateDTO.java @@ -0,0 +1,38 @@ +package cn.axzo.workflow.common.model.request.bpmn.process; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * 更新流程实例中变量集合的入参模型 + * + * @author wangli + * @since 2025-10-24 10:56 + */ +@ApiModel("更新流程实例中变量集合的入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnProcessInstanceVariablesUpdateDTO { + + /** + * 流程实例 ID + */ + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 业务管理中定义变量的入参, 如果 key 在创建时已存在,则进行覆盖更新,否则新增变量 + *

+ * 对应创建流程实例中的 {@link BpmnProcessInstanceCreateDTO#bizCustomVariables} 属性 + */ + private Map bizCustomVariables; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java index 731913a0a..0cfe59964 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java @@ -16,6 +16,7 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.List; +import java.util.Map; /** * 审批任务节点的入参模型 @@ -103,4 +104,11 @@ public class BpmnTaskAuditDTO { */ @Deprecated private String operationDesc; + + /** + * 更新或新增流程变量, + *

+ * 如果不修改,则map 的 value 设置为 null,内部会自动过滤 + */ + private Map variables; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/ConditionPermissionMetaInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/ConditionPermissionMetaInfo.java new file mode 100644 index 000000000..daad1dd0b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/ConditionPermissionMetaInfo.java @@ -0,0 +1,105 @@ +package cn.axzo.workflow.common.model.request.form; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 表单字段权限信息 + * + * @author wangli + * @since 2024-11-07 11:09 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ConditionPermissionMetaInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 条件标识 + */ + private String conditionCode; + + /** + * 条件名称 + */ + private String conditionName; + + /** + * 单选框 + * 条件类型(text/number/checkbox/radio) + */ + private String conditionType; + /** + * 可编辑必填 + */ + @Builder.Default + private Boolean required = false; + + /** + * 可编辑非必填 + */ + @Builder.Default + private Boolean editable = false; + + /** + * 只读 + */ + @Builder.Default + private Boolean readonly = true; + + /** + * 隐藏 + */ + @Builder.Default + private Boolean hidden = false; + + /** + * 前端回显字段,后端不做任何消费逻辑 + */ + private String value; + + /** + * 类型是单选复选时的选项值 + */ + private String options; + + + // 将对象的属性转换为对应的整数表示 + public int toBinary() { + int binaryValue = 0; + binaryValue |= (required ? 1 : 0) << 3; + binaryValue |= (editable ? 1 : 0) << 2; + binaryValue |= (readonly ? 1 : 0) << 1; + binaryValue |= (hidden ? 1 : 0); + return binaryValue; + } + + // 从整数表示还原出对象 + public static ConditionPermissionMetaInfo fromBinary(String conditionCode, String conditionName, String conditionType, int binaryValue) { + boolean required = ((binaryValue >> 3) & 1) == 1; + boolean editable = ((binaryValue >> 2) & 1) == 1; + boolean readonly = ((binaryValue >> 1) & 1) == 1; + boolean hidden = (binaryValue & 1) == 1; + return new ConditionPermissionMetaInfo(conditionCode, conditionName, conditionType, required, editable, readonly, hidden, null, null); + } + + public ConditionPermissionMetaInfo toReadonly() { + if (required || editable || readonly) { + setRequired(false); + setEditable(false); + setReadonly(true); + setHidden(false); + } + return this; + } + + public String toBinaryString() { + return String.format("%04d", Integer.parseInt(Integer.toBinaryString(toBinary()), 10)); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/ProcessLogItemDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/ProcessLogItemDTO.java new file mode 100644 index 000000000..5e2a02ea8 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/ProcessLogItemDTO.java @@ -0,0 +1,71 @@ +package cn.axzo.workflow.common.model.response; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 审批日志项模型 + * + * @author wangli + * @since 2025-10-30 14:22 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ProcessLogItemDTO { + @Builder.Default + private String label = "审批流程"; + + /** + * 审批意见 + */ + private String advice; + + /** + * 节点名称 + */ + @ApiModelProperty(value = "节点名称") + private String activityName; + + /** + * 操作描述 + */ + @ApiModelProperty(value = "操作描述") + private String operationDesc; + + /** + * 图片列表 + */ + @ApiModelProperty(value = "图片列表") + private List imageList; + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + private List fileList; + /** + * 手写签名地址 + */ + @ApiModelProperty(value = "手写签名地址") + private String signatureUrl; + + /** + * 操作时间 + */ + @ApiModelProperty(value = "操作时间") + private String operationTime; + + /** + * 日志项结果,对应{@link BpmnProcessInstanceResultEnum#name()} + */ + @ApiModelProperty(value = "日志项结果") + private String result; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/TableItemDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/TableItemDTO.java new file mode 100644 index 000000000..5329db1de --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/TableItemDTO.java @@ -0,0 +1,35 @@ +package cn.axzo.workflow.common.model.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 审批日志公共打印模板的字段项模型 + * + * @author wangli + * @since 2025-10-30 10:38 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TableItemDTO { + /** + * 中文 + */ + private String label; + /** + * 字段 code + */ + private String code; + /** + * 字段类型 + */ + private String type; + /** + * 值 + */ + private Object value; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/PrintData4LogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/PrintData4LogVO.java new file mode 100644 index 000000000..304f9c903 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/PrintData4LogVO.java @@ -0,0 +1,54 @@ +package cn.axzo.workflow.common.model.response.bpmn.process; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.response.ProcessLogItemDTO; +import cn.axzo.workflow.common.model.response.TableItemDTO; +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 新版的审批日志公共模板的数据响应模型 + * + * @author wangli + * @since 2025-10-30 10:28 + */ +@ApiModel("新版的审批日志公共模板的数据响应模型") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class PrintData4LogVO { + + /** + * 标题 + */ + private String processName; + /** + * 发起租户名称 + */ + private String tenantName; + /** + * 创建时间 + */ + private String createAt; + /** + * 审批状态 + */ + private BpmnProcessInstanceResultEnum result; + + /** + * 系统变量表格项 + */ + private List systemVarItems; + + /** + * 审批日志表格项 + */ + private List logItems; +} + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/ProcessLogPdfResultDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/ProcessLogPdfResultDTO.java new file mode 100644 index 000000000..0a06f1e41 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/ProcessLogPdfResultDTO.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.common.model.response.print; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 审批日志 PDF 查询结果相应模型 + * + * @author wangli + * @since 2025-11-07 18:13 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ProcessLogPdfResultDTO { + + /** + * 转换状态 + * INIT("初始"), + *

+ * CONVERTING("转换中"), + *

+ * SUCCESS("转换完成"), + *

+ * FAILED("转换失败"); + */ + private String status; + + /** + * 转换成功后的oss fileKey + */ + private String pdfFileKey; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java index b0217a7e0..af85769b5 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java @@ -31,6 +31,7 @@ import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnSignPendingProperty; import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty; import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; @@ -82,6 +83,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPIE import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CONDITION_PERMISSION; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION; @@ -93,6 +95,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SP import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_RANGE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_ONLY_IN_PROJECT_ENABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_LIMIT; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ORG_LIMIT; @@ -648,6 +651,10 @@ public final class BpmnMetaParserHelper { return defaultValid(flowElement, CONFIG_SPECIALTY_FILTER_ENABLE).map(element -> Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))).orElse(false); } + public static Boolean getOnlyInProjectEnable(FlowElement flowElement) { + return defaultValid(flowElement, CONFIG_ONLY_IN_PROJECT_ENABLE).map(element -> Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))).orElse(false); + } + private static Optional defaultValid(FlowElement flowElement, String elementName) { if (Objects.isNull(flowElement)) { return Optional.empty(); @@ -692,6 +699,10 @@ public final class BpmnMetaParserHelper { }.getType())); } + public static Optional> getConditionPermissionConf(FlowElement flowElement) { + return defaultValid(flowElement, CONFIG_CONDITION_PERMISSION).map(element -> JSON.parseObject(element.getElementText(), new TypeReference>() { + }.getType())); + } public static Optional> getFormFieldPermissionForCalc(FlowElement flowElement) { List fieldMetaInfos = getFormFieldPermissionConf(flowElement).orElse(new ArrayList<>()); return getFormFieldPermissionForModel(fieldMetaInfos); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java index 0ba5b3182..53fc11e8e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.conf; import cn.axzo.workflow.core.common.utils.SpringContextUtils; import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory; +import cn.axzo.workflow.core.engine.cfg.CustomDefaultInternalJobManager; import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory; import cn.axzo.workflow.core.engine.formhandler.CustomFormFieldHandler; import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator; @@ -26,6 +27,7 @@ import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearT import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler; import cn.axzo.workflow.core.engine.persistence.CustomMybatisHistoricProcessInstanceDataManager; import cn.axzo.workflow.core.service.BpmnProcessActivityService; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.azxo.framework.common.constatns.Constants; import cn.hutool.core.util.ReflectUtil; @@ -82,6 +84,7 @@ public class FlowableConfiguration { ObjectProvider listeners, CustomActivityBehaviorFactory customActivityBehaviorFactory, ExtAxHiTaskInstService extAxHiTaskInstService, + ExtAxDynamicSignRecordService extAxDynamicSignRecordService, BpmnProcessActivityService bpmnProcessActivityService, List jobProcessors, NacosServiceManager nacosServiceManager, @@ -109,7 +112,7 @@ public class FlowableConfiguration { configuration.addCustomJobHandler(new AsyncApproveTaskJobHandler()); configuration.addCustomJobHandler(new AsyncBackTaskJobHandler()); configuration.addCustomJobHandler(new AsyncCancelProcessInstanceJobHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService, extAxDynamicSignRecordService)); configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService)); configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService)); configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler()); @@ -141,6 +144,7 @@ public class FlowableConfiguration { configuration.setFormFieldValidationEnabled(true); configuration.setFormFieldHandler(new CustomFormFieldHandler()); configuration.setConfigurators(Lists.newArrayList(new CustomJobServiceConfiguration())); + configuration.setInternalJobManager(new CustomDefaultInternalJobManager(configuration)); }; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java index b57a30193..3cfdd25b0 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java @@ -109,4 +109,7 @@ public class SupportRefreshProperties { @Value("${workflow.ignoreMqAlterApplicationNames:}") private List ignoreMqAlterApplicationNames; + @Value("${workflow.processLogHtmlUrl:https://taskflow-web.axzo.cn/#/document/log?processInstanceId=%s&personId=%s}") + private String processLogHtmlUrl; + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java index 100756b9b..c41dda0ec 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java @@ -25,6 +25,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPIE import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CONDITION_PERMISSION; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_LEADER_RANGE_UNIT; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_EXCLUDE_COOPERATE_TYPES; @@ -68,6 +69,8 @@ public class ServiceTaskJsonConverter extends AbstractBpmnJsonConverter { setApprovalExtensionElement(node, userTask); // "表单权限设置" setFormFieldExtensionElement(node, userTask); + // "条件权限设置" + setConditionExtensionElement(node, userTask); // "高级设置",包含按钮配置,自动过审配置 setAdvancedExtensionElement(node, userTask); // "待办消息模板配置" @@ -183,6 +187,14 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { specialtyFilterEnableElement.addAttribute(specialtyFilterEnableAttribute); userTask.addExtensionElement(specialtyFilterEnableElement); + ExtensionElement onlyInProjectEnableElement = new ExtensionElement(); + onlyInProjectEnableElement.setName(CONFIG_ONLY_IN_PROJECT_ENABLE); + ExtensionAttribute onlyInProjectEnableAttribute = new ExtensionAttribute(); + onlyInProjectEnableAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + onlyInProjectEnableAttribute.setValue(String.valueOf(Boolean.TRUE.equals(node.getProperty().getOnlyInProjectEnable()))); + onlyInProjectEnableElement.addAttribute(onlyInProjectEnableAttribute); + userTask.addExtensionElement(onlyInProjectEnableElement); + //添加自动审批配置 ExtensionElement autoApprovalExtensionElement = new ExtensionElement(); ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute(); @@ -191,6 +203,17 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { userTask.addExtensionElement(autoApprovalExtensionElement); } + private static void setConditionExtensionElement(BpmnJsonNode node, UserTask userTask) { + if (Objects.isNull(node.getProperty()) || CollectionUtils.isEmpty(node.getProperty().getConditionPermission())) { + return; + } + ExtensionElement fieldElement = new ExtensionElement(); + fieldElement.setName(CONFIG_CONDITION_PERMISSION); + fieldElement.setElementText(Objects.nonNull(node.getProperty().getConditionPermission()) ? + JSON.toJSONString(node.getProperty().getConditionPermission()) : null); + userTask.addExtensionElement(fieldElement); + } + private static void setFormFieldExtensionElement(BpmnJsonNode node, UserTask userTask) { if (Objects.isNull(node.getProperty()) || CollectionUtils.isEmpty(node.getProperty().getFieldPermission())) { return; @@ -686,7 +709,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { * @param node * @param userTask */ - private static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) { + public static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) { List executionListeners = new ArrayList<>(); // 设置执行监听 @@ -725,7 +748,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { * * @param userTask */ - private static void setTaskListeners(UserTask userTask) { + public static void setTaskListeners(UserTask userTask) { List taskListeners = new ArrayList<>(); // 设置任务监听 FlowableListener taskListener = new FlowableListener(); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cfg/CustomDefaultInternalJobManager.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cfg/CustomDefaultInternalJobManager.java new file mode 100644 index 000000000..b573e1f83 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cfg/CustomDefaultInternalJobManager.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.core.engine.cfg; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.impl.cfg.DefaultInternalJobManager; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.job.api.Job; + +/** + * 为解决引擎底层定时任务的 NPE 问题 + * + * @author wangli + * @since 2025-10-24 17:42 + */ +@Slf4j +public class CustomDefaultInternalJobManager extends DefaultInternalJobManager { + public CustomDefaultInternalJobManager(ProcessEngineConfigurationImpl processEngineConfiguration) { + super(processEngineConfiguration); + } + + @Override + protected void handleJobDeleteInternal(Job job) { + ExecutionEntity executionEntity = getExecutionEntityManager().findById(job.getExecutionId()); + log.info("handleJobDeleteInternal job.eId:{}, job.piId:{} ", job.getExecutionId(), job.getProcessInstanceId()); + if (executionEntity == null) { + log.warn("handleJobDeleteInternal executionEntity is null for job.eId:{}, job.piId:{} ", job.getExecutionId(), job.getProcessInstanceId()); + return; + } + super.handleJobDeleteInternal(job); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java index 112c9413e..2b71bf786 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.BpmnProcessTaskResultEnum; import cn.axzo.workflow.common.model.dto.SignatureDTO; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; @@ -28,6 +29,7 @@ import org.springframework.util.StringUtils; import java.io.Serializable; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -81,6 +83,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria * 指定节点类型 */ private List nodeTypes; + private Map variables; @Override public String paramToJsonString() { @@ -92,6 +95,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria params.put("approver", JSON.toJSONString(approver)); params.put("nextApprover", JSON.toJSONString(nextApprover)); params.put("nodeTypes", JSON.toJSONString(nodeTypes)); + params.put("variables", JSON.toJSONString(variables)); return JSON.toJSONString(params); } @@ -115,6 +119,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria } else { this.operationDesc = "已同意"; } + this.variables = dto.getVariables(); } @Override @@ -156,8 +161,10 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria } task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); + // 更新流程内的变量 + runtimeService.setVariables(task.getProcessInstanceId(), variables); // 记录电子签名的图片 - recordSignature(task, runtimeService); + recordSignature(task, runtimeService, attachmentList, advice, approver); resetApproverNode(task.getProcessInstanceId()); @@ -185,7 +192,11 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria approver.setNodeId(logs.get(0).getAssigneeFull().get(0).getNodeId()); } - private void recordSignature(TaskEntity task, RuntimeService runtimeService) { + public static void recordSignature(TaskEntity task, + RuntimeService runtimeService, + List attachmentList, + String advice, + BpmnTaskDelegateAssigner approver) { List signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class); if (Objects.isNull(signatures)) { signatures = new ArrayList<>(); @@ -201,8 +212,11 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria .findFirst() .ifPresent(attachment -> dto.getSignatures().add(0, new SignatureDTO.SignDetail() + .setApproverName(approver.getAssignerName()) .setSignature(attachment.getUrl()) - .setAdvice(advice))); + .setAdvice(advice) + .setResult(BpmnProcessTaskResultEnum.APPROVED.getDesc()) + .setOperationTime(new Date()))); if (!any.isPresent()) { signatures.add(dto); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java index 81df46654..2c8b9426f 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java @@ -1,9 +1,7 @@ package cn.axzo.workflow.core.engine.cmd; -import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; import cn.axzo.workflow.common.exception.WorkflowEngineException; -import cn.axzo.workflow.common.model.dto.SignatureDTO; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; @@ -14,7 +12,6 @@ import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; import cn.axzo.workflow.core.service.ExtAxProcessLogService; import com.alibaba.fastjson.JSON; -import org.apache.commons.collections4.ListUtils; import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.RuntimeService; @@ -34,12 +31,10 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.io.Serializable; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -48,10 +43,10 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIG import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER; -import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_SUBMIT_FORM_VARIABLE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd.recordSignature; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; @@ -97,6 +92,10 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand implemen * 表单数据 */ private final Map formVariables; + /** + * 更新或新增的流程变量 + */ + private final Map variables; @Override public String paramToJsonString() { @@ -109,6 +108,7 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand implemen params.put("nextApprover", JSON.toJSONString(nextApprover)); params.put("nodeTypes", JSON.toJSONString(nodeTypes)); params.put("formVariables", JSON.toJSONString(formVariables)); + params.put("variables", JSON.toJSONString(variables)); return JSON.toJSONString(params); } @@ -133,6 +133,7 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand implemen } else { this.operationDesc = "已同意"; } + this.variables = dto.getVariables(); } @Override @@ -174,9 +175,10 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand implemen nextApprover); } task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); - + // 更新流程实例变量 + runtimeService.setVariables(task.getProcessInstanceId(), variables); // 记录电子签名的图片 - recordSignature(task, runtimeService); + recordSignature(task, runtimeService, attachmentList, advice, approver); resetApproverNode(task.getProcessInstanceId()); @@ -255,30 +257,4 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand implemen } } - private void recordSignature(TaskEntity task, RuntimeService runtimeService) { - List signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class); - if (Objects.isNull(signatures)) { - signatures = new ArrayList<>(); - } - Optional any = signatures.stream() - .filter(i -> Objects.equals(i.getActivityId(), task.getTaskDefinitionKey())).findAny(); - SignatureDTO dto = any.orElse(new SignatureDTO() - .setActivityId(task.getTaskDefinitionKey()) - .setActivityName(task.getName()) - .setSignatures(new ArrayList<>())); - ListUtils.emptyIfNull(attachmentList).stream() - .filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature)) - .findFirst() - .ifPresent(attachment -> dto.getSignatures().add(0, - new SignatureDTO.SignDetail() - .setSignature(attachment.getUrl()) - .setAdvice(advice))); - if (!any.isPresent()) { - signatures.add(dto); - } - - if (!CollectionUtils.isEmpty(signatures)) { - runtimeService.setVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, signatures); - } - } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java index 4d5f0a7ec..e91988449 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java @@ -1,22 +1,30 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper; import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; import cn.axzo.workflow.core.engine.model.AddComment; +import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.flowable.task.api.TaskInfo; import org.flowable.task.api.history.HistoricTaskInstance; @@ -29,17 +37,25 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; +import static cn.axzo.workflow.common.constant.BpmnConstants.BACK_ACTIVITY_FRAGMENT; import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ASSIGNER_SHOW_NUMBER; +import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_COUNT; +import static cn.axzo.workflow.common.constant.BpmnConstants.FORWARD_ACTIVITY_FRAGMENT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.COUNTERSIGN; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCategoryVersion; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addMultiTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.deleteMultiTasks; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getDuplicatePendingTasks; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; @@ -63,11 +79,14 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen private final List attachmentList; private final List targetTaskAssigneeList; private final ExtAxHiTaskInstService extAxHiTaskInstService; + private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService; public CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum countersignType, String originTaskId, BpmnTaskDelegateAssigner originTaskAssignee, String advice, List attachmentList, - List targetTaskAssigneeList, ExtAxHiTaskInstService extAxHiTaskInstService) { + List targetTaskAssigneeList, + ExtAxHiTaskInstService extAxHiTaskInstService, + ExtAxDynamicSignRecordService extAxDynamicSignRecordService) { this.countersignType = countersignType; this.originTaskId = originTaskId; this.originTaskAssignee = originTaskAssignee; @@ -75,6 +94,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen this.attachmentList = attachmentList; this.targetTaskAssigneeList = targetTaskAssigneeList; this.extAxHiTaskInstService = extAxHiTaskInstService; + this.extAxDynamicSignRecordService = extAxDynamicSignRecordService; } @Override @@ -100,6 +120,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen TaskService taskService = processEngineConfiguration.getTaskService(); TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(originTaskId).singleResult(); + // 1.5.4 版本,要求审批人必须回传 nodeId validTargetAssigneeNodeId(task.getProcessDefinitionId()); validTask(historicTaskInstance, task, originTaskAssignee, null); @@ -130,9 +151,11 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen switch (countersignType) { case FORWARD_COUNTERSIGN: // 加签的一种方式:前加签,具体定义由后续产品需求来定 + forwardAndBackCountSign(commandContext, task, valuTargetAssigneeList); break; case BACK_COUNTERSIGN: // 加签的另一种方式 + forwardAndBackCountSign(commandContext, task, valuTargetAssigneeList); break; default: // 共享签,不区分顺序 @@ -143,6 +166,148 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen return null; } + /** + * 前、后加签 + *

+ * 使用内存动态变更模型连接实现前加签功能 + * + * @param commandContext + * @param task + * @param valuTargetAssigneeList + */ + private void forwardAndBackCountSign(CommandContext commandContext, TaskEntity task, List valuTargetAssigneeList) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()); + UserTask originalUserTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey()); + + // 获取当前实例前加签次数 + Long counterSignCount = runtimeService.getVariable(processInstance.getId(), COUNTERSIGN_COUNT, Long.class); + if (Objects.isNull(counterSignCount)) { + counterSignCount = 0L; + } else { + counterSignCount = counterSignCount + 1; + } + runtimeService.setVariable(processInstance.getId(), COUNTERSIGN_COUNT, counterSignCount); + + BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR; + + // 生成加签节点ID + String newActivityId = originalUserTask.getId(); + switch (countersignType) { + case FORWARD_COUNTERSIGN: + newActivityId = processActivityId(newActivityId, FORWARD_ACTIVITY_FRAGMENT, counterSignCount); + break; + case BACK_COUNTERSIGN: + newActivityId = processActivityId(newActivityId, BACK_ACTIVITY_FRAGMENT, counterSignCount); + default: + break; + } + + // 创建被加签节点 + UserTask newUserTask = CustomBpmnModelHelper.createUserTask(processEngineConfiguration, originalUserTask, newActivityId, nodeMode, valuTargetAssigneeList); + // 加入模型 + process.addFlowElement(newUserTask); + // 重新连接顺序流 (Sequence Flow) + CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask, countersignType); + + saveCounterSignRecord(valuTargetAssigneeList, processInstance, originalUserTask, newUserTask, nodeMode); + + if (Objects.equals(FORWARD_COUNTERSIGN, countersignType)) { + log.info("前加签任务处理完成,原任务ID:{},新任务ID:{}", task.getId(), newUserTask.getId()); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdTo(task.getTaskDefinitionKey(), newUserTask.getId()) + .changeState(); + + } else { + } + } + + /** + * 前后加签动作记录,用于 JVM 重启后的动态恢复 + * + * @param valuTargetAssigneeList + * @param processInstance + * @param originalUserTask + * @param newUserTask + * @param nodeMode + */ + private void saveCounterSignRecord(List valuTargetAssigneeList, ProcessInstance processInstance, UserTask originalUserTask, UserTask newUserTask, BpmnFlowNodeMode nodeMode) { + ExtAxDynamicSignRecord entity = new ExtAxDynamicSignRecord(); + entity.setProcessInstanceId(processInstance.getProcessInstanceId()); + entity.setProcessDefinitionId(processInstance.getProcessDefinitionId()); + entity.setOriginalActivityId(originalUserTask.getId()); + entity.setTargetActivityId(newUserTask.getId()); + entity.setCounterSignType(countersignType.getType()); + entity.setTargetNodeMode(nodeMode.getType()); + entity.setAssignerList(valuTargetAssigneeList); + extAxDynamicSignRecordService.saveOrUpdate(entity); + } + + // 提取公共处理方法 + private String processActivityId(String originalId, String fragment, long count) { + if (originalId.contains(fragment)) { + int fragmentIndex = originalId.indexOf(fragment); + return originalId.substring(0, fragmentIndex) + fragment + count; + } + return originalId + fragment + count; // 不包含目标片段时,返回原字符串 + } + + + /** + * 后加签 + *

+ * 基于当前节点的后加签,内部实现主要依靠加减签的能力实现 + * + * @param commandContext + * @param historicTaskInstance + * @param task + * @param valuTargetAssigneeList + */ + @Deprecated + private void backCountSign(CommandContext commandContext, + HistoricTaskInstance historicTaskInstance, + TaskEntity task, + List valuTargetAssigneeList) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + TaskService taskService = processEngineConfiguration.getTaskService(); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(historicTaskInstance.getProcessDefinitionId()); + FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); + if (flowElement instanceof UserTask) { + UserTask userTask = (UserTask) flowElement; + BpmnFlowNodeMode nodeMode = Objects.equals(userTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR; + switch (nodeMode) { + case AND: + // 这里仅是加签,还需要再触发当前人的同意 + shareCountSign(commandContext, task, valuTargetAssigneeList); + break; + case OR: + // 修改审批人快照 + List originAssigners = runtimeService.getVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); + originAssigners.removeIf(e -> !Objects.equals(e.buildAssigneeId(), originTaskAssignee.buildAssigneeId())); + originAssigners.addAll(valuTargetAssigneeList); + runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), originAssigners); + + // 后加签的人 + valuTargetAssigneeList.forEach(e -> addMultiTask(commandContext, task, e)); + + // 删除除当前审批人的task + List currentTasks = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).taskDefinitionKey(task.getTaskDefinitionKey()).active().list(); + currentTasks.removeIf(e -> Objects.equals(e.getAssignee(), originTaskAssignee.buildAssigneeId())); + deleteMultiTasks(commandContext, currentTasks); + break; + default: + break; + } + } + } + /** * 共享签 * @@ -167,7 +332,6 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen private void resolveOriginTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService, TaskService taskService, TaskEntity task) { - // 构建评论内容 StringBuilder message = new StringBuilder("添加"); int end = Math.min(targetTaskAssigneeList.size(), COUNTERSIGN_ASSIGNER_SHOW_NUMBER); //加签人员数量显示指定个数 @@ -181,6 +345,19 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen message.append("等"); } message.append(targetTaskAssigneeList.size()).append("人进行审批"); + + switch (countersignType) { + case FORWARD_COUNTERSIGN: + message.append("(前加签)"); + break; + case BACK_COUNTERSIGN: + message.append("(后加签)"); + break; + default: + message.append("(并加签)"); + break; + } + Task virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(), task.getTaskDefinitionKey(), advice, originTaskAssignee, COUNTERSIGN.getStatus(), new AddComment(message.toString())); batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, originTaskAssignee); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetConditionPermissionsCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetConditionPermissionsCmd.java new file mode 100644 index 000000000..0b1a524c5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetConditionPermissionsCmd.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import com.alibaba.fastjson.JSON; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 获取指定流程节点配置的条件权限信息 + * + * @author wangli + * @since 2025-10-29 18:02 + */ +public class CustomGetConditionPermissionsCmd extends AbstractCommand> { + private final String processInstanceId; + + public CustomGetConditionPermissionsCmd(String processInstanceId) { + this.processInstanceId = processInstanceId; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + return JSON.toJSONString(params); + } + + @Override + public List execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(processInstance)) { + return Collections.emptyList(); + } + TaskService taskService = processEngineConfiguration.getTaskService(); + List tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list(); + if (CollectionUtils.isEmpty(tasks)) { + return Collections.emptyList(); + } + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processInstance.getProcessDefinitionId()); + FlowElement flowElement = bpmnModel.getFlowElement(tasks.get(0).getTaskDefinitionKey()); + List conditions = BpmnMetaParserHelper.getConditionPermissionConf(flowElement).orElse(Collections.emptyList()); + conditions.forEach(e -> e.setValue(null)); + return conditions; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java index bd84f4cf0..469dfe7d4 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.model.dto.SignatureDTO; import cn.axzo.workflow.core.common.utils.SpringContextUtils; import cn.axzo.workflow.core.service.CategoryService; @@ -22,10 +23,13 @@ import java.util.Objects; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_BELONG_TENANT_ID; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_RESULT; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; @@ -63,9 +67,15 @@ public class CustomGetProcessInstanceVariablesCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = 1L; + private final String processInstanceId; + private final Map variables; + + public CustomOverrideProcessVariablesCmd(String processInstanceId, Map variables) { + this.processInstanceId = processInstanceId; + this.variables = variables; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("formVariables", variables); + return JSON.toJSONString(params); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + if (CollectionUtils.isEmpty(variables)) { + return null; + } + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (Objects.nonNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); + } + + runtimeService.setVariables(processInstanceId, variables); + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomBpmnModelHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomBpmnModelHelper.java new file mode 100644 index 000000000..dcb7a0ddf --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomBpmnModelHelper.java @@ -0,0 +1,220 @@ +package cn.axzo.workflow.core.engine.cmd.helper; + +import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior; +import org.flowable.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_PRE_SIGN_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_USER_TASK_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SPECIFY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONLY_ONE; +import static cn.axzo.workflow.common.constant.BpmnConstants.SEQUENCE_FLOW_ID; +import static cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil.id; +import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setExecutionListeners; +import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setTaskListeners; + +/** + * 动态操作 BPMN 定义内容的帮助类 + * + * @author wangli + * @since 2025-10-15 17:05 + */ +@Slf4j +public class CustomBpmnModelHelper { + + private CustomBpmnModelHelper() { + + } + + /** + * 创建 BPMN 中的 UserTask + * + * @param processEngineConfiguration + * @param originalUserTask + * @param nodeMode + */ + public static UserTask createUserTask(ProcessEngineConfigurationImpl processEngineConfiguration, UserTask originalUserTask, String customActivityId, BpmnFlowNodeMode nodeMode, List assigners) { + UserTask newUserTask = new UserTask(); + if (StringUtils.hasText(customActivityId)) { + newUserTask.setId(customActivityId); + } else { + newUserTask.setId(originalUserTask.getId()); + } + newUserTask.setName(originalUserTask.getName()); + newUserTask.setDocumentation("由" + originalUserTask.getId() + "节点加签生成"); + + MultiInstanceLoopCharacteristics loopCharacteristics = new MultiInstanceLoopCharacteristics(); + loopCharacteristics.setInputDataItem(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId()); + loopCharacteristics.setElementVariable("assigneeName"); + newUserTask.setLoopCharacteristics(loopCharacteristics); + newUserTask.setAssignee("${assigneeName}"); + + switch (nodeMode) { + case AND: + loopCharacteristics.setSequential(false); + loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION); + break; + case OR: + loopCharacteristics.setSequential(false); + loopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONLY_ONE); + break; + case SEQUENCE: + loopCharacteristics.setSequential(true); + loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION); + break; + default: + throw new WorkflowEngineException(CREATE_BPMN_USER_TASK_ERROR, nodeMode.getType()); + } + + setTaskListeners(newUserTask); + setExecutionListeners(new BpmnJsonNode(), newUserTask); + + ExtensionElement approvalMethodElement = new ExtensionElement(); + approvalMethodElement.setName(CONFIG_APPROVAL_METHOD); + + ExtensionAttribute approvalMethodValueAttribute = new ExtensionAttribute(); + approvalMethodValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approvalMethodValueAttribute.setValue(ApprovalMethodEnum.human.getType()); + approvalMethodElement.addAttribute(approvalMethodValueAttribute); + + ExtensionAttribute approvalMethodDescAttribute = new ExtensionAttribute(); + approvalMethodDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approvalMethodDescAttribute.setValue("审批方式"); + approvalMethodElement.addAttribute(approvalMethodDescAttribute); + newUserTask.addExtensionElement(approvalMethodElement); + + ExtensionElement approverSpecifyElement = new ExtensionElement(); + approverSpecifyElement.setName(CONFIG_APPROVER_SPECIFY); + ExtensionAttribute approverSpecifyValueAttribute = new ExtensionAttribute(); + approverSpecifyValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverSpecifyValueAttribute.setValue(ApproverSpecifyEnum.fixedPerson.getType()); + approverSpecifyElement.addAttribute(approverSpecifyValueAttribute); + ExtensionAttribute approverSpecifyDescAttribute = new ExtensionAttribute(); + approverSpecifyDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverSpecifyDescAttribute.setValue("审批人指定"); + approverSpecifyElement.addAttribute(approverSpecifyDescAttribute); + approverSpecifyElement.setElementText(JSON.toJSONString(assigners)); + newUserTask.addExtensionElement(approverSpecifyElement); + + ExtensionElement nodeTypeElement = new ExtensionElement(); + nodeTypeElement.setName(CONFIG_NODE_TYPE); + nodeTypeElement.setElementText(BpmnFlowNodeType.NODE_TASK.getType()); + newUserTask.addExtensionElement(nodeTypeElement); + + // 追加设置审批人为空的配置,因为查找审批人模式是固定人员,会执行退场、离职校验,导致最终审批人可能为空 + // 审批人为空时 + ExtensionElement approverEmptyHandleTypeElement = new ExtensionElement(); + approverEmptyHandleTypeElement.setName(CONFIG_APPROVER_EMPTY_HANDLE_TYPE); + ExtensionAttribute approverEmptyHandleTypeValueAttribute = new ExtensionAttribute(); + approverEmptyHandleTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverEmptyHandleTypeValueAttribute.setValue(ApproverEmptyHandleTypeEnum.autoPassed.getType()); + approverEmptyHandleTypeElement.setElementText("[]"); + approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeValueAttribute); + + ExtensionAttribute approverEmptyHandleTypeDescAttribute = new ExtensionAttribute(); + approverEmptyHandleTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverEmptyHandleTypeDescAttribute.setValue("审批人为空时"); + approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeDescAttribute); + newUserTask.addExtensionElement(approverEmptyHandleTypeElement); + + ActivityBehaviorFactory activityBehaviorFactory = processEngineConfiguration.getActivityBehaviorFactory(); + UserTaskActivityBehavior userTaskActivityBehavior = activityBehaviorFactory.createUserTaskActivityBehavior(newUserTask); + ParallelMultiInstanceBehavior behavior = activityBehaviorFactory.createParallelMultiInstanceBehavior(newUserTask, userTaskActivityBehavior); + + behavior.setCollectionVariable(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId()); + behavior.setCollectionElementVariable("assigneeName"); + behavior.setCompletionCondition(loopCharacteristics.getCompletionCondition()); + newUserTask.setBehavior(behavior); + return newUserTask; + } + + public static void rewireSequenceFlows(Process process, UserTask originalUserTask, UserTask targetUserTask, BpmnCountersignTypeEnum countersignType) { + switch (countersignType) { + case FORWARD_COUNTERSIGN: + // 1. 找到所有指向原始节点的输入流 + List incomingFlows = originalUserTask.getIncomingFlows(); + if (incomingFlows.isEmpty()) { + throw new WorkflowEngineException(CREATE_BPMN_PRE_SIGN_ERROR, "节点 " + originalUserTask.getId() + " 没有输入流,无法进行前加签"); + } + + // 2. 将这些输入流的目标从 originalUserTask 修改为 targetUserTask + for (SequenceFlow incomingFlow : incomingFlows) { + incomingFlow.setTargetRef(targetUserTask.getId()); + // 如果需要,也可以更新FlowElement中的引用,但通常改TargetRef即可 + } + targetUserTask.setIncomingFlows(incomingFlows); + + // 3. 创建一个新的顺序流,从 targetUserTask 指向 originalUserTask + SequenceFlow newSequenceFlow = new SequenceFlow(); + newSequenceFlow.setId(id(SEQUENCE_FLOW_ID + "_ForwardSign")); + newSequenceFlow.setSourceRef(targetUserTask.getId()); + newSequenceFlow.setSourceFlowElement(targetUserTask); + newSequenceFlow.setTargetRef(originalUserTask.getId()); + newSequenceFlow.setTargetFlowElement(originalUserTask); + + targetUserTask.setOutgoingFlows(Collections.singletonList(newSequenceFlow)); + process.addFlowElement(newSequenceFlow); + + originalUserTask.setIncomingFlows(Collections.singletonList(newSequenceFlow)); + break; + case BACK_COUNTERSIGN: + // 1. 找到所有指向原始节点的输出流 + List outgoingFlows = originalUserTask.getOutgoingFlows(); + if (outgoingFlows.isEmpty()) { + throw new WorkflowEngineException(CREATE_BPMN_PRE_SIGN_ERROR, "节点 " + originalUserTask.getId() + " 没有输出流,无法进行后加签"); + } + + // 2. 将这些输出流的源从 originalUserTask 修改为 targetUserTask + for (SequenceFlow outgoingFlow : outgoingFlows) { + outgoingFlow.setSourceRef(targetUserTask.getId()); + // 如果需要,也可以更新FlowElement中的引用,但通常改SourceRef即可 + } + targetUserTask.setOutgoingFlows(outgoingFlows); + + // 3. 创建一个新的顺序流,从 originalUserTask 指向 targetUserTask + SequenceFlow backSequenceFlow = new SequenceFlow(); + backSequenceFlow.setId(id(SEQUENCE_FLOW_ID + "_BackSign")); + backSequenceFlow.setSourceRef(originalUserTask.getId()); + backSequenceFlow.setSourceFlowElement(originalUserTask); + backSequenceFlow.setTargetRef(targetUserTask.getId()); + backSequenceFlow.setTargetFlowElement(targetUserTask); + + originalUserTask.setOutgoingFlows(Collections.singletonList(backSequenceFlow)); + process.addFlowElement(backSequenceFlow); + + targetUserTask.setIncomingFlows(Collections.singletonList(backSequenceFlow)); + break; + default: + break; + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java index ab58d0007..b202b9ccb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java @@ -1,10 +1,10 @@ package cn.axzo.workflow.core.engine.cmd.helper; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; -import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; @@ -51,6 +51,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_TYPE_MISMATCH; import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ASSIGNER_LIMIT_NUMBER; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; @@ -61,12 +67,6 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ATTACHMENTS_VA import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; -import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_TYPE_MISMATCH; import static org.flowable.task.api.Task.DEFAULT_PRIORITY; /** @@ -78,6 +78,10 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY; @Slf4j public class CustomTaskHelper { + private CustomTaskHelper() { + + } + public static void addMultiTask(CommandContext commandContext, TaskEntity originTask, BpmnTaskDelegateAssigner newTaskAssignee) { if (Objects.isNull(originTask)) { @@ -225,7 +229,7 @@ public class CustomTaskHelper { } /** - * 校验人员数量是否超过限制 + * 校验人员数量是否超过60个人的限制 * * @param runtimeService * @param taskEntity @@ -352,8 +356,8 @@ public class CustomTaskHelper { */ public static TaskEntity createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService , String processInstanceId, String nodeName, String taskDefinitionKey, String advice, - BpmnTaskDelegateAssigner assigner, - String extTaskInstStatus, AddComment addComment) { + BpmnTaskDelegateAssigner assigner, + String extTaskInstStatus, AddComment addComment) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoryService historyService = processEngineConfiguration.getHistoryService(); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java index 5d0013b03..454bc099d 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.core.engine.job; import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; @@ -19,9 +20,12 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple public static final String TYPE = "async-countersign-task"; private final ExtAxHiTaskInstService extAxHiTaskInstService; + private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService; - public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService, + ExtAxDynamicSignRecordService extAxDynamicSignRecordService) { this.extAxHiTaskInstService = extAxHiTaskInstService; + this.extAxDynamicSignRecordService = extAxDynamicSignRecordService; } @Override @@ -42,6 +46,7 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(), - extAxHiTaskInstService)); + extAxHiTaskInstService, + extAxDynamicSignRecordService)); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDynamicSignRecord.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDynamicSignRecord.java new file mode 100644 index 000000000..bc24c784f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDynamicSignRecord.java @@ -0,0 +1,56 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 扩展动态前后加签记录,用于应用重启后的模型内容恢复 + * + * @author wangli + * @since 2025-10-17 18:23 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_dynamic_sign_record", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxDynamicSignRecord extends BaseEntity { + /** + * 流程实例 ID + */ + private String processInstanceId; + /** + * 流程实例对应的流程定义 ID + */ + private String processDefinitionId; + /** + * 加签原节点 + */ + private String originalActivityId; + /** + * 加签目标节点 + */ + private String targetActivityId; + /** + * 加签类型,前加签/后加签 + * {@link BpmnCountersignTypeEnum} + */ + private String counterSignType; + /** + * 目标节点的审批方式(会签、或签、顺序签) + */ + private String targetNodeMode; + /** + * 被加签的审批人 + */ + @TableField(value = "assigner_list", typeHandler = ListAssigneeTypeHandler.class) + private List assignerList; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDynamicSignRecordMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDynamicSignRecordMapper.java new file mode 100644 index 000000000..bde632d7a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDynamicSignRecordMapper.java @@ -0,0 +1,14 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord; +import org.apache.ibatis.annotations.Mapper; + +/** + * 动态前后加签记录 Mapper + * + * @author wangli + * @since 2025-10-17 18:28 + */ +@Mapper +public interface ExtAxDynamicSignRecordMapper extends BaseMapperX { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java index 185b648ac..3eefd77b1 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java @@ -1,9 +1,13 @@ package cn.axzo.workflow.core.repository.mapper; +import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler; import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; +import org.apache.ibatis.type.JdbcType; @Mapper public interface ExtAxProcessLogMapper extends BaseMapperX { @@ -13,5 +17,8 @@ public interface ExtAxProcessLogMapper extends BaseMapperX { @Select("select * from ext_ax_process_log WHERE process_instance_id = #{processInstanceId} and task_id = #{taskId}") + @Results({ + @Result(column = "assignee_full", property = "assigneeFull", jdbcType = JdbcType.ARRAY, typeHandler = ListAssigneeTypeHandler.class) + }) ExtAxProcessLog findByProcessIdAndTaskIdWithDeleted(String processInstanceId, String taskId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java index 7432d5fb7..9195fce8d 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java @@ -10,10 +10,12 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; @@ -211,4 +213,8 @@ public interface BpmnProcessInstanceService { boolean hasPrintTemplate(String processInstanceId, String processDefinitionId); List processInstanceSelectDocs(ProcessDocQueryDTO dto); + + void overrideProcessVariables(BpmnProcessInstanceVariablesUpdateDTO dto); + + List getConditions(String processInstanceId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDynamicSignRecordService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDynamicSignRecordService.java new file mode 100644 index 000000000..48ba5b43c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDynamicSignRecordService.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 动态前后加签记录服务接口 + * + * @author wangli + * @since 2025-10-17 18:30 + */ +public interface ExtAxDynamicSignRecordService extends IService { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java index cbc092036..c9ec7a8e1 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.service; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; @@ -18,5 +19,5 @@ public interface ExtAxReadRecordService extends IService { List queryReadStatus(ApproverReadStatusDTO dto); - Boolean changeReadStatus(ChangeApproverReadStatusDTO dto); + Boolean changeReadStatus(ChangeApproverReadStatusDTO dto, List customDocs); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java index 63eb0cd35..0c606bdc8 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java @@ -14,6 +14,7 @@ import cn.axzo.workflow.common.enums.BusinessTypeEnum; import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum; import cn.axzo.workflow.common.enums.WorkspaceType; import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.request.BpmnApproveConf; @@ -29,6 +30,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; @@ -38,6 +40,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; import cn.axzo.workflow.common.model.request.category.CategorySearchDTO; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationItemResultVO; @@ -64,8 +67,10 @@ import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd; import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd; import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd; +import cn.axzo.workflow.core.engine.cmd.CustomGetConditionPermissionsCmd; import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; import cn.axzo.workflow.core.engine.cmd.CustomOverrideFormVariablesByLatestInstanceCmd; +import cn.axzo.workflow.core.engine.cmd.CustomOverrideProcessVariablesCmd; import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener; import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; @@ -154,6 +159,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME; import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CREATE_PARAM_ERROR; @@ -187,6 +193,9 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIA import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATORIES; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_BASED_FILE_TAG_ORDER; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS; import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; @@ -495,6 +504,12 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic dto.getVariables().put(SIGN_PROCESS_ENABLE_DOC_IDS, dto.getDocIds()); dto.getVariables().put(SIGN_VARIABLE, dto.getBizCustomVariables()); dto.getVariables().put(SIGNATORIES, dto.getSignatories()); + // 业务自定义文档 + dto.getVariables().put(SIGN_BIZ_CUSTOM_DOCS, dto.getCustomDocs()); + // 业务自定义文档追加顺序类型 + dto.getVariables().put(SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE, StringUtils.hasText(dto.getDocAddOrderType()) ? dto.getDocAddOrderType() : "last"); + // 基于业务的标签排序 + dto.getVariables().put(SIGN_BIZ_BASED_FILE_TAG_ORDER, dto.getBasedFileTagOrder()); } }); dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson()); @@ -1798,7 +1813,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic }); } - public List getAttachmentByType(Map> attachmentByTaskMap, String + public static List getAttachmentByType(Map> attachmentByTaskMap, String taskId, AttachmentTypeEnum type) { return ListUtils.emptyIfNull(attachmentByTaskMap.get(taskId)).stream() .filter(attachment -> Objects.equals(type.getType(), attachment.getType())) @@ -1873,6 +1888,9 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); List docs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService)); + // 获取业务自定义传入的文档 + getAndAddBizCustomDocs(dto.getProcessInstanceId(), docs); + Map readStatusMap = new HashMap<>(); if (Objects.nonNull(dto.getAssigner())) { readStatusMap.putAll(extAxReadRecordService.queryReadStatus(ApproverReadStatusDTO.builder() @@ -1895,4 +1913,64 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic t.setReadStatus(readStatusMap.getOrDefault(t.getId(), false)); }); } + + private List getAndAddBizCustomDocs(String processInstanceId, List docs) { + String tenantId; + if (CollectionUtils.isEmpty(docs)) { + HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + tenantId = Objects.nonNull(processInstance) ? processInstance.getTenantId() : ""; + } else { + tenantId = docs.get(0).getTenantId(); + } + HistoricVariableInstance historicVariableInstance = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId) + .variableName(SIGN_BIZ_CUSTOM_DOCS).singleResult(); + List bizCustomDocs = Optional.ofNullable((List) historicVariableInstance.getValue()) + .orElse(Collections.emptyList()); + + // 业务自定义文档 + List customBizDocs = BeanMapper.copyList(bizCustomDocs, DocBaseVO.class, (s, t) -> { + t.setStatus(true); + t.setTempFile(false); + t.setTemplateName(s.getFileName()); + t.setTag(s.getFileTag()); + t.setTenantId(tenantId); + t.setFileRelationId(s.getFileKey()); + }); + + HistoricVariableInstance signBizOrder = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_BIZ_BASED_FILE_TAG_ORDER).singleResult(); + List basedFileTagOrder = Optional.ofNullable((List) signBizOrder.getValue()).orElse(Collections.emptyList()); + if (!CollectionUtils.isEmpty(basedFileTagOrder)) { + docs.addAll(customBizDocs); + // 基于 fileTag 排序 + Map fileTagOrderMap = IntStream.range(0, basedFileTagOrder.size()) + .boxed() + .collect(Collectors.toMap(basedFileTagOrder::get, i -> i, (a, b) -> a)); + + docs.sort(Comparator.comparing(d -> fileTagOrderMap.getOrDefault(d.getTag(), Integer.MAX_VALUE))); + docs = docs.stream().sorted(Comparator.comparingInt(d -> fileTagOrderMap.getOrDefault(d.getTag(), Integer.MAX_VALUE))) + .collect(Collectors.toList()); + } else { + HistoricVariableInstance bizDocOrderType = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE).singleResult(); + String customAddType = String.valueOf(bizDocOrderType.getValue()); + if ("last".equals(customAddType)) { + docs.addAll(customBizDocs); + } else { + docs.addAll(0, customBizDocs); + } + } + return docs; + + } + + @Override + public void overrideProcessVariables(BpmnProcessInstanceVariablesUpdateDTO dto) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + commandExecutor.execute(new CustomOverrideProcessVariablesCmd(dto.getProcessInstanceId(), dto.getBizCustomVariables())); + } + + @Override + public List getConditions(String processInstanceId) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + return commandExecutor.execute(new CustomGetConditionPermissionsCmd(processInstanceId)); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java index b7e2d8907..547338425 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java @@ -53,6 +53,7 @@ import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessTaskService; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.axzo.workflow.core.service.ExtAxProcessLogService; import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter; @@ -187,6 +188,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { private ExtAxProcessLogService processLogService; @Resource private SupportRefreshProperties refreshProperties; + @Resource + private ExtAxDynamicSignRecordService extAxDynamicSignRecordService; @Override public BpmPageResult getTodoTaskPage(BpmnTaskPageSearchDTO dto) { @@ -862,7 +865,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } else { commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(), dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(), - extAxHiTaskInstService)); + extAxHiTaskInstService, extAxDynamicSignRecordService)); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDynamicSignRecordServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDynamicSignRecordServiceImpl.java new file mode 100644 index 000000000..9b62d2e8b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDynamicSignRecordServiceImpl.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord; +import cn.axzo.workflow.core.repository.mapper.ExtAxDynamicSignRecordMapper; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 流程签署业务信息记录 + * + * @author wangli + * @since 2025-04-02 10:06 + */ +@Service +@Slf4j +public class ExtAxDynamicSignRecordServiceImpl extends ServiceImpl implements ExtAxDynamicSignRecordService { + + @Resource + private ExtAxDynamicSignRecordMapper extAxDynamicSignRecordMapper; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java index cdeec873f..ca1affeb0 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.service.impl; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; @@ -11,7 +12,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; -import org.flowable.engine.TaskService; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -34,8 +34,6 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl customDocs) { ExtAxReadRecord entity = new ExtAxReadRecord(); entity.setProcessInstanceId(dto.getProcessInstanceId()); entity.setPersonId(Long.valueOf(dto.getAssigner().getPersonId())); @@ -63,7 +61,11 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl 0 ? modelDocService.get(dto.getDocId()).getTag() + : customDocs.stream() + .filter(i -> Objects.equals(i.getId(), dto.getDocId())) + .findFirst() + .orElse(new CustomDocDTO()).getFileTag()) .readStatus(dto.getReadStatus()) .build())); extAxReadRecordMapper.insert(record); @@ -76,7 +78,11 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl 0 ? modelDocService.get(dto.getDocId()).getTag() + : customDocs.stream() + .filter(i -> Objects.equals(i.getId(), dto.getDocId())) + .findFirst() + .orElse(new CustomDocDTO()).getFileTag()) .readStatus(dto.getReadStatus()) .build()); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java index 31e42d3b1..5a3685b65 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java @@ -12,7 +12,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @MapperScan({"cn.axzo.workflow.core.**.mapper", "cn.axzo.workflow.admin.**.mapper"}) -@ComponentScan({"cn.axzo.workflow"}) +@ComponentScan({"cn.axzo.workflow", "cn.axzo.oss"}) @EnableFeignClients({"cn.axzo.oss", "cn.axzo.riven.client.feign", "cn.axzo.msg.center", "cn.axzo.basics.profiles"}) @SpringBootApplication(exclude = RabbitAutoConfiguration.class) @EnableTransactionManagement diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java index 6c892eb24..1c8e9d116 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java @@ -18,6 +18,7 @@ import cn.axzo.workflow.common.model.dto.UploadFieldDTO; import cn.axzo.workflow.common.model.dto.VariableObjectDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesToObjectCmd; +import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONException; import com.google.common.collect.Lists; @@ -113,6 +114,18 @@ public class WpsUtil { List signDetails = (List) variableObjectDTO.getValue(); if (!CollectionUtils.isEmpty(signDetails)) { SignatureDTO.SignDetail signDetail = signDetails.get(0); + signatureAndAdvices.add(VariableObjectDTO.builder() + .key(variableObjectDTO.getKey() + "_approverName") + .desc(variableObjectDTO.getDesc() + "姓名") + .value(signDetail.getApproverName()) + .type(VariableObjectDTO.Type.text) + .build()); + signatureAndAdvices.add(VariableObjectDTO.builder() + .key(variableObjectDTO.getKey() + "_activityResult") + .desc(variableObjectDTO.getDesc() + "审批结果") + .value(signDetail.getResult()) + .type(VariableObjectDTO.Type.text) + .build()); ApiSignUrlDownloadRequest request = ApiSignUrlDownloadRequest.builder() .fileKeys(Lists.newArrayList(signDetail.getSignature())).build(); List signUrl = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "获取手写签图片地址", request); @@ -128,6 +141,12 @@ public class WpsUtil { .value(signDetail.getAdvice()) .type(VariableObjectDTO.Type.text) .build()); + signatureAndAdvices.add(VariableObjectDTO.builder() + .key(variableObjectDTO.getKey() + "_activityOperationTime") + .desc(variableObjectDTO.getDesc() + "日期") + .value(DateUtil.format(signDetail.getOperationTime(), "yyyy.MM.dd")) + .type(VariableObjectDTO.Type.text) + .build()); } }); wpsVariables.addAll(signatureAndAdvices); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java index 880a0138c..682da5d49 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java @@ -7,6 +7,7 @@ import cn.axzo.karma.client.model.response.PersonProfileResp; import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp; import cn.axzo.workflow.common.enums.ApproverScopeEnum; import cn.axzo.workflow.common.enums.CarbonCopyObjectType; +import cn.axzo.workflow.common.exception.WorkflowApproverCalcException; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; @@ -45,6 +46,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import static cn.axzo.workflow.common.code.BpmnTaskRespCode.CALC_TASK_ASSIGNEE_ERROR; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.COOPERATION_NOT_EXIST_WITH_NODE; import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR; import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_PARAM_ERROR; @@ -122,6 +124,40 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign return Collections.emptyList(); } + protected final T parseFoundationApiResultWithErrCode(Supplier> supplier, String operatorDesc, + String extInfo, Object... param) { + StopWatch stopWatch = new StopWatch(operatorDesc); + log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param)); + stopWatch.start(); + cn.axzo.foundation.result.ApiResult result = supplier.get(); + stopWatch.stop(); + log.info("{}-Cost:{}, Result: {}", operatorDesc, + "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", + JSONUtil.toJsonStr(result)); + try { + if (stopWatch.getTotalTimeSeconds() > refreshProperties.getApiTimeout() && Boolean.TRUE.equals(refreshProperties.getSendDingTalk())) { + DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment() + .getProperty("spring.profiles.active"), + stopWatch.getTotalTimeSeconds(), + extInfo, + param, + result); + } + } catch (Exception e) { + // ignore + } + Assert.notNull(result, "服务调用异常"); + if (Objects.equals(30022100, result.getCode())) { + // 特殊需求,组织不存在时抛得的异常码 + throw new WorkflowApproverCalcException(COOPERATION_NOT_EXIST_WITH_NODE); + } + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode()) { + throw new WorkflowEngineException(CALC_TASK_ASSIGNEE_ERROR, "[API:" + extInfo + "]" + result.getMsg()); + } + return result.getData(); + } + protected final T parseFoundationApiResult(Supplier> supplier, String operatorDesc, String extInfo, Object... param) { StopWatch stopWatch = new StopWatch(operatorDesc); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityV2TaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityV2TaskAssigneeSelector.java index d62a73644..011428252 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityV2TaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityV2TaskAssigneeSelector.java @@ -9,9 +9,11 @@ import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum; import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.exception.WorkflowApproverCalcException; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; @@ -21,6 +23,7 @@ import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -33,12 +36,14 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TA import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN; import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyRange; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyRangeOrgLimit; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable; /** @@ -76,6 +81,7 @@ public class BasedIdentityV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne .initiatorPersonId(initiator.parsePersonId()) .areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet()) .specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet()) + .projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(orgDTO.getProjectIds()) : Sets.newHashSet()) .querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent()); switch (optRange.get()) { case within_the_project: @@ -118,8 +124,14 @@ public class BasedIdentityV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne break; } FlowTaskAssignerV2Req request = v2ReqBuilder.build(); - List apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询身份下的人" + execution.getProcessInstanceId() + flowElement.getId(), - "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request); + List apiResultUsers = new ArrayList<>(); + try { + apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询身份下的人" + execution.getProcessInstanceId() + flowElement.getId(), + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request)); + } catch (WorkflowApproverCalcException e) { + log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e); + execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true); + } return convertApprover(apiResultUsers); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderV2TaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderV2TaskAssigneeSelector.java index 47a2f50c3..498117d16 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderV2TaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderV2TaskAssigneeSelector.java @@ -7,8 +7,10 @@ import cn.axzo.orggateway.api.nodeuser.req.FlowTaskAssignerV2Req; import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.exception.WorkflowApproverCalcException; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.hutool.json.JSONUtil; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; @@ -17,17 +19,20 @@ import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN; import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin; import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getInitiatorLeaderRangeUnit; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable; /** @@ -69,11 +74,18 @@ public class BasedInitiatorLeaderV2TaskAssigneeSelector extends AbstractBpmnTask .initiatorPersonId(initiator.parsePersonId()) .areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet()) .specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet()) + .projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(orgDTO.getProjectIds()) : Sets.newHashSet()) .querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent()); FlowTaskAssignerV2Req request = v2ReqBuilder.build(); - List apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询发起人主管的审批人" + execution.getProcessInstanceId() + flowElement.getId(), - "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request); + List apiResultUsers = new ArrayList<>(); + try { + apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询发起人主管的审批人" + execution.getProcessInstanceId() + flowElement.getId(), + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request)); + } catch (WorkflowApproverCalcException e) { + log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e); + execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true); + } return convertApprover(apiResultUsers); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionV2TaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionV2TaskAssigneeSelector.java index 3ccff50b5..9bc846f88 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionV2TaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionV2TaskAssigneeSelector.java @@ -10,9 +10,11 @@ import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum; import cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum; import cn.axzo.workflow.common.enums.CooperateShipTypeEnum; import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.exception.WorkflowApproverCalcException; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; @@ -22,6 +24,7 @@ import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -33,6 +36,7 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_POSITIO import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN; import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin; import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; @@ -42,6 +46,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCooperateShipType; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable; /** @@ -80,6 +85,7 @@ public class BasedPositionV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne .initiatorPersonId(initiator.parsePersonId()) .areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet()) .specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet()) + .projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(orgDTO.getProjectIds()) : Sets.newHashSet()) .querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent()); switch (optRange.get()) { case within_the_project: @@ -141,8 +147,14 @@ public class BasedPositionV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne break; } FlowTaskAssignerV2Req request = v2ReqBuilder.build(); - List apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询岗位下的人" + execution.getProcessInstanceId() + flowElement.getId(), - "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request); + List apiResultUsers = new ArrayList<>(); + try { + apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询岗位下的人" + execution.getProcessInstanceId() + flowElement.getId(), + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request)); + } catch (WorkflowApproverCalcException e) { + log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e); + execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true); + } return convertApprover(apiResultUsers); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleV2TaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleV2TaskAssigneeSelector.java index 3827b1b39..527d89acb 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleV2TaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleV2TaskAssigneeSelector.java @@ -10,9 +10,11 @@ import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum; import cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum; import cn.axzo.workflow.common.enums.CooperateShipTypeEnum; import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.exception.WorkflowApproverCalcException; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; @@ -23,6 +25,7 @@ import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -34,6 +37,7 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_ROLE_V2 import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN; import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin; import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; @@ -43,6 +47,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCooperateShipType; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable; /** @@ -82,6 +87,7 @@ public class BasedRoleV2TaskAssigneeSelector extends AbstractBpmnTaskAssigneeSel .initiatorPersonId(initiator.parsePersonId()) .areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet()) .specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet()) + .projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(orgDTO.getProjectIds()) : Sets.newHashSet()) .querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent()); switch (optRange.get()) { case within_the_project: @@ -144,8 +150,14 @@ public class BasedRoleV2TaskAssigneeSelector extends AbstractBpmnTaskAssigneeSel break; } FlowTaskAssignerV2Req request = v2ReqBuilder.build(); - List apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询角色下的人" + execution.getProcessInstanceId() + flowElement.getId(), - "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request); + List apiResultUsers = new ArrayList<>(); + try { + apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询角色下的人" + execution.getProcessInstanceId() + flowElement.getId(), + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request)); + } catch (WorkflowApproverCalcException e) { + log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e); + execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true); + } return convertApprover(apiResultUsers); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java index a5d8b6e0e..fd0034a60 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.server.controller.listener.task; import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi; import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest; import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.VariableObjectDTO; import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; @@ -23,6 +24,7 @@ import cn.axzo.workflow.server.common.util.WpsUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.Process; import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.RuntimeService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; @@ -35,10 +37,17 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_BASED_FILE_TAG_ORDER; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; /** @@ -90,17 +99,58 @@ public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEve processSign.setProcessInstanceId(processInstanceId); processSign.setSignType(signConfig.get().getSignType().getType()); processSign.setPendingMessageId(signConfig.get().getSignPendingProperty().getPendingMessageId()); + + // 业务自定义文档 + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List customDocs = Optional.ofNullable( + runtimeService.getVariable(processInstanceId, SIGN_BIZ_CUSTOM_DOCS, List.class)) + .orElse(Collections.emptyList()); + if (CollectionUtils.isEmpty(docs)) { processSign.setDocTemplate(Collections.emptyList()); - processSign.setFileArchive(Collections.emptyList()); - } else { - // 复制基础模板 - List docTemplates = copyTempTemplate(docs); - processSign.setDocTemplate(docTemplates); - - List archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId); - processSign.setFileArchive(archives); } + if (CollectionUtils.isEmpty(customDocs)) { + processSign.setFileArchive(Collections.emptyList()); + } + // 复制基础模板 + List docTemplates = copyTempTemplate(docs); + + List customDocTemplates = new ArrayList<>(); + for (CustomDocDTO customDoc : customDocs) { + customDocTemplates.add(SignFileDTO.builder() + .id(customDoc.getId()) + .fileName(customDoc.getFileName()) + .templateName(customDoc.getFileName()) + .fileTag(customDoc.getFileTag()) + .fileCode(customDoc.getFileCode()) + .fileKey(customDoc.getFileKey()) + .fileType(customDoc.getFileType()) + .build()); + } + List basedFileTagOrder = runtimeService.getVariable(processInstanceId, SIGN_BIZ_BASED_FILE_TAG_ORDER, List.class); + if (Objects.nonNull(basedFileTagOrder)) { + docTemplates.addAll(customDocTemplates); + // 基于 fileTag 排序 + Map fileTagOrderMap = IntStream.range(0, basedFileTagOrder.size()) + .boxed() + .collect(Collectors.toMap(basedFileTagOrder::get, i -> i, (a, b) -> a)); + + docTemplates.sort(Comparator.comparing(d -> fileTagOrderMap.getOrDefault(d.getFileTag(), Integer.MAX_VALUE))); + docTemplates = docTemplates.stream().sorted(Comparator.comparingInt(d -> fileTagOrderMap.getOrDefault(d.getFileTag(), Integer.MAX_VALUE))) + .collect(Collectors.toList()); + } else { + // 基于前插还是后插排序 + String customAddType = runtimeService.getVariable(processInstanceId, SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE, String.class); + if (Objects.equals("last", customAddType)) { + docTemplates.addAll(customDocTemplates); + } else { + docTemplates.addAll(0, customDocTemplates); + } + } + processSign.setDocTemplate(docTemplates); + + List archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId); + processSign.setFileArchive(archives); // 没有可用的文档,但仍然记录库表 extAxProcessSignService.save(processSign); } @@ -117,7 +167,7 @@ public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEve signFileDTO.setFileType(template.getFileType()); signFileDTO.setFileCode(template.getFileCode()); if (Objects.equals(template.getFileType(), FileTypeEnum.WORD) || Objects.equals(template.getFileType(), FileTypeEnum.EXCEL)) { - String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), null, template.getTemplateName() + template.getFileType().getSuffix()); + String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), template.getFileKey(), template.getTemplateName() + template.getFileType().getSuffix()); signFileDTO.setFileKey(fileKey); } archives.add(signFileDTO); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java index 3d3ceb9ef..b82dd492c 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java @@ -1,6 +1,9 @@ package cn.axzo.workflow.server.controller.web; import cn.axzo.framework.domain.ServiceException; +import cn.axzo.oss.http.api.ServerFileServiceSdk; +import cn.axzo.oss.http.model.ServerFileUploadSdkRequest; +import cn.axzo.oss.http.model.ServerFileUploadSdkResponse; import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; import cn.axzo.workflow.common.model.dto.VariableObjectDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; @@ -23,6 +26,7 @@ import cn.axzo.workflow.server.xxljob.SpecifyProcessInstanceSyncEsJobHandler; import cn.azxo.framework.common.model.CommonResponse; import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.impl.interceptor.CommandExecutor; @@ -54,6 +58,8 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Date; import java.util.List; import java.util.Map; @@ -113,6 +119,8 @@ public class TestController { private TaskService taskService; @Resource private SupportRefreshProperties refreshProperties; + @Resource + private ServerFileServiceSdk serverFileServiceSdk; @RepeatSubmit @GetMapping("/test") @@ -405,5 +413,15 @@ public class TestController { public CommonResponse refreshProperties() { return CommonResponse.success(JSON.toJSONString(refreshProperties)); } + + @PostMapping("/server/file/upload") + @SneakyThrows + public CommonResponse serverFileUpload(@RequestBody ServerFileUploadSdkRequest request) { + String filePath = "/Users/wangli/Downloads/锦绣碧湖B区B-1工程模板施工专项方案4-30.docx"; + byte[] fileBytes = Files.readAllBytes(Paths.get(filePath)); + request.setFileContent(fileBytes); + ServerFileUploadSdkResponse serverFileUploadSdkResponse = serverFileServiceSdk.uploadFile(request); + return CommonResponse.success(JSON.toJSONString(serverFileUploadSdkResponse)); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java index 2ee5da05a..3e68d46b5 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java @@ -7,6 +7,7 @@ import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse; import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.CustomDocDTO; import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.request.bpmn.log.LogApproveSearchDTO; @@ -21,6 +22,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; @@ -28,6 +30,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverRead import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo; import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; @@ -66,6 +69,7 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.spring.SpringProcessEngineConfiguration; import org.springframework.beans.BeanUtils; @@ -95,6 +99,7 @@ import java.util.stream.Stream; import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_ID_NOT_IN_MODEL; import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_READ_PARAM_ERROR; import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_EXT_LOG_PARAM_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -306,8 +311,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController @PutMapping("/status/update") @RepeatSubmit @Override - public CommonResponse updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId, - @NotNull(message = "状态不能为空") @RequestParam Integer status) { + public CommonResponse updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer status) { log.info("获得流程实例 updateProcessStatus===>>>参数:{},{}", processDefinitionId, status); Boolean result = bpmnProcessInstanceService.updateProcessStatus(processDefinitionId, status); return success(result); @@ -323,8 +327,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController @Operation(summary = "获取审批流程实例的运行图") @GetMapping("/graphical") @Override - public CommonResponse processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, - @Nullable @RequestParam(required = false) String tenantId) { + public CommonResponse processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId) { return success(bpmnProcessInstanceService.getProcessInstanceGraphical(processInstanceId, tenantId)); } @@ -338,8 +341,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController @Operation(summary = "获取指定实例的全节点执行顺序推算") @GetMapping("/node/forecasting") @Override - public CommonResponse> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, - @Nullable String tenantId) { + public CommonResponse> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable String tenantId) { return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId)); } @@ -356,10 +358,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController @Operation(summary = "推断指定流程实例的过滤掉部分节点执行顺序") @GetMapping("/node/filter/forecasting") @Override - public CommonResponse> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, - @Nullable @RequestParam(required = false) String tenantId, - @RequestParam(required = false, defaultValue = "false") Boolean allNode, - @Nullable @RequestParam(required = false) List nodeDefinitionKeys) { + public CommonResponse> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, @Nullable @RequestParam(required = false) String tenantId, @RequestParam(required = false, defaultValue = "false") Boolean allNode, @Nullable @RequestParam(required = false) List nodeDefinitionKeys) { if (allNode) { return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId)); } else { @@ -377,13 +376,20 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController @Operation(summary = "获取指定流程实例的流程变量") @GetMapping("/cooperation-org") @Override - public CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, - @Nullable String tenantId) { - HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId, - tenantId, true); + public CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable String tenantId) { + HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId, tenantId, true); return success(processInstance.getProcessVariables()); } + @Override + @Operation(summary = "更新流程实例中的业务自定义变量集合") + @PostMapping("/biz/custom/variables/update") + public CommonResponse updateProcessBizCustomVariables(@Validated @RequestBody BpmnProcessInstanceVariablesUpdateDTO dto) { + log.info("更新流程实例中的业务自定义变量集合 updateProcessBizCustomVariables===>>>参数:{}", JSONUtil.toJsonStr(dto)); + bpmnProcessInstanceService.overrideProcessVariables(dto); + return success(true); + } + /** * 枢智业务(审批台账专用) * @@ -466,18 +472,10 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController } private void parseSignatureUrl(BpmnProcessInstanceLogVO log) { - List signUrls = log.getTaskDetails().stream() - .filter(i -> StringUtils.hasText(i.getTaskId())) - .map(BpmnTaskInstanceLogVO::getSignatureUrl) - .filter(Objects::nonNull) - .distinct() - .collect(Collectors.toList()); + List signUrls = log.getTaskDetails().stream().filter(i -> StringUtils.hasText(i.getTaskId())).map(BpmnTaskInstanceLogVO::getSignatureUrl).filter(Objects::nonNull).distinct().collect(Collectors.toList()); if (!CollectionUtils.isEmpty(signUrls)) { - Map ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream() - .collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s)); - log.getTaskDetails().stream() - .filter(i -> StringUtils.hasText(i.getTaskId())) - .forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null))); + Map ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s)); + log.getTaskDetails().stream().filter(i -> StringUtils.hasText(i.getTaskId())).forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null))); } } @@ -509,8 +507,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController } ApiSignUrlDownloadRequest ossRequest = new ApiSignUrlDownloadRequest(); ossRequest.setFileKeys(fileKeys); - Map ossUrlMap = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(ossRequest), "批量获取文件 OSS 地址", ossRequest) - .stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, Function.identity(), (s, t) -> s)); + Map ossUrlMap = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(ossRequest), "批量获取文件 OSS 地址", ossRequest).stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, Function.identity(), (s, t) -> s)); docs.forEach(i -> { if (StringUtils.hasText(i.getFileKey())) { ApiSignUrlDownloadResponse ossFileInfo = ossUrlMap.getOrDefault(i.getFileKey(), null); @@ -542,10 +539,20 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController } CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); List relationDocs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService)); - relationDocs.stream().filter(i -> Objects.equals(i.getId(), dto.getDocId())) - .findAny().orElseThrow(() -> new WorkflowEngineException(PROCESS_DOC_ID_NOT_IN_MODEL)); - return success(readRecordService.changeReadStatus(dto)); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List customDocs = ListUtils.emptyIfNull(runtimeService.getVariable(dto.getProcessInstanceId(), SIGN_BIZ_CUSTOM_DOCS, List.class)); + + // 检查 relationDocs 中是否存在具有指定 id 的对象 + boolean existsInRelationDocs = relationDocs.stream().anyMatch(doc -> Objects.equals(doc.getId(), dto.getDocId())); + + // 检查 customDocs 中是否存在具有指定 id 的对象 + boolean existsInCustomDocs = customDocs.stream().anyMatch(doc -> Objects.equals(doc.getId(), dto.getDocId())); + if (!existsInRelationDocs && !existsInCustomDocs) { + throw new WorkflowEngineException(PROCESS_DOC_ID_NOT_IN_MODEL); + } + + return success(readRecordService.changeReadStatus(dto, customDocs)); } @Override @@ -602,4 +609,17 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController return logVO; }).collect(Collectors.toList())); } + + /** + * 获取流程实例的条件字段信息 + * + * @param processInstanceId + * @return + */ + @Operation(summary = "获取流程实例的条件字段信息, 仅用于同意抽屉展示") + @GetMapping("/conditions") + @Override + public CommonResponse> getConditions(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId) { + return success(bpmnProcessInstanceService.getConditions(processInstanceId)); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java index 29b7a051b..0031aa928 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java @@ -36,6 +36,7 @@ import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.MapUtils; import org.flowable.form.api.FormInfo; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -54,6 +55,7 @@ import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_OPERATION_PARAM_INVALID; @@ -112,6 +114,10 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp if (!StringUtils.hasText(dto.getTaskId())) { dto.setTaskId(bpmnProcessTaskService.findTaskIdByInstanceIdAndPersonId(dto.getProcessInstanceId(), dto.getApprover().getPersonId())); } + + // 移除 variable 中 value 为 null 的 key + MapUtils.emptyIfNull(dto.getVariables()).entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + List tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>()); if (StringUtils.hasText(dto.getSignatureUrl())) { AttachmentDTO signature = new AttachmentDTO(); @@ -138,6 +144,10 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp if (!StringUtils.hasText(dto.getTaskId())) { dto.setTaskId(bpmnProcessTaskService.findTaskIdByInstanceIdAndPersonId(dto.getProcessInstanceId(), dto.getApprover().getPersonId())); } + + // 移除 variable 中 value 为 null 的 key + MapUtils.emptyIfNull(dto.getVariables()).entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + List tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>()); if (StringUtils.hasText(dto.getSignatureUrl())) { AttachmentDTO signature = new AttachmentDTO(); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java index 00cdd9200..3d67bd9c3 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java @@ -1,40 +1,66 @@ package cn.axzo.workflow.server.controller.web.manage; +import cn.axzo.basics.common.BeanMapper; import cn.axzo.maokai.api.client.OrganizationalNodeUserQueryApi; import cn.axzo.maokai.api.vo.request.OrgNodeUserBriefInfoListReq; import cn.axzo.maokai.api.vo.response.OrgNodeUserBriefInfoResp; +import cn.axzo.nanopart.doc.api.conversion.DocConversionApi; +import cn.axzo.nanopart.doc.api.conversion.req.QueryConversionTaskRequestV2; +import cn.axzo.nanopart.doc.api.conversion.req.SubmitConversionTaskRequest; +import cn.axzo.nanopart.doc.api.conversion.res.FileConvertResultResp; +import cn.axzo.nanopart.doc.api.enums.DocConversionTypeEnum; +import cn.axzo.oss.http.api.ServerFileServiceApi; +import cn.axzo.oss.http.model.ApiSignUrlDownloadRequest; +import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse; import cn.axzo.workflow.client.feign.manage.PrintAdminApi; +import cn.axzo.workflow.common.constant.VariableConstants; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.enums.VarTypeEnum; import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.dto.print.FieldAttributeDTO; import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.Print4ProcessLogDTO; import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintProcessLogPdfDTO; import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.QueryProcessLogPdfDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.response.ProcessLogItemDTO; +import cn.axzo.workflow.common.model.response.TableItemDTO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; +import cn.axzo.workflow.common.model.response.bpmn.process.PrintData4LogVO; import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; +import cn.axzo.workflow.common.model.response.print.ProcessLogPdfResultDTO; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.core.engine.cmd.CustomGetFormInstanceLatestValuesCmd; import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesCmd; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; import cn.axzo.workflow.core.service.BpmnProcessModelService; import cn.axzo.workflow.core.service.CategoryGroupService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.util.RpcExternalUtil; import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessInstanceController; import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; import io.swagger.v3.oas.annotations.Operation; -import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; import org.flowable.common.engine.api.FlowableObjectNotFoundException; import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.TaskService; +import org.flowable.engine.task.Attachment; import org.flowable.form.api.FormInfo; import org.flowable.form.api.FormRepositoryService; import org.flowable.form.model.FormContainer; @@ -42,6 +68,7 @@ import org.flowable.form.model.FormField; import org.flowable.form.model.FormFieldTypes; import org.flowable.form.model.SimpleFormModel; import org.flowable.spring.SpringProcessEngineConfiguration; +import org.slf4j.Logger; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -79,16 +106,24 @@ import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCE import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_UNIT; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_POSITION; @@ -97,12 +132,15 @@ import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCE import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_RESULT; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME; import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME_DESC; import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.form; import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.sign; import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.signature; import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.system; +import static cn.axzo.workflow.core.service.impl.BpmnProcessInstanceServiceImpl.getAttachmentByType; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -111,13 +149,13 @@ import static cn.azxo.framework.common.model.CommonResponse.success; * @author wangli * @since 2025-01-16 17:48 */ -@Slf4j @RequestMapping({"/web/v1/api/print/admin", "/api/print/admin"}) @RestController @ErrorReporter @Validated public class PrintAdminController implements PrintAdminApi { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(PrintAdminController.class); @Resource private FormRepositoryService formRepositoryService; @Resource @@ -134,6 +172,16 @@ public class PrintAdminController implements PrintAdminApi { private CategoryGroupService categoryGroupService; @Resource private BpmnProcessModelService bpmnProcessModelService; + @Resource + private ExtAxProcessLogService processLogService; + @Resource + private TaskService taskService; + @Resource + private ServerFileServiceApi serverFileServiceApi; + @Resource + private DocConversionApi docConversionApi; + @Resource + private SupportRefreshProperties refreshProperties; /** * 查询指定流程实例是否能使用打印 @@ -162,6 +210,7 @@ public class PrintAdminController implements PrintAdminApi { bpmnProcessModelService.printTemplateConfig(dto); return success(); } + /** * 获取打印模板中可打印的字段, 或者是 WPS 模板中可配置的变量字段 * @@ -180,17 +229,29 @@ public class PrintAdminController implements PrintAdminApi { List formFields = ((SimpleFormModel) formModel.getFormModel()).getFields(); formFields.forEach(formField -> { FormContainer formContainer = (FormContainer) formField; - printFields.addAll(formContainer.getFields().get(0).stream().map(field -> { - PrintFieldDTO printFieldDTO = new PrintFieldDTO() - .setName(field.getName()) - .setCode(field.getId()) - .setFieldCategoryType(form) - .setFieldFormType(field.getType()); - switchAmount(field, printFieldDTO); - switchFormContainer(field, printFieldDTO); - return printFieldDTO; - } - ).collect(Collectors.toList())); + printFields.addAll(formContainer.getFields().get(0).stream() + .filter(field -> { +// if (Objects.equals(Boolean.TRUE, dto.getFilterEnablePrint())) { + if (!CollectionUtils.isEmpty(field.getParams())) { + Optional optEnablePrint = Optional.ofNullable(field.getParam("enablePrint")); + return optEnablePrint.map(obj -> Boolean.parseBoolean(obj.toString())).orElse(Boolean.TRUE); + } + return true; +// } else { +// return false; +// } + }) + .map(field -> { + PrintFieldDTO printFieldDTO = new PrintFieldDTO() + .setName(field.getName()) + .setCode(field.getId()) + .setFieldCategoryType(form) + .setFieldFormType(field.getType()); + switchAmount(field, printFieldDTO); + switchFormContainer(field, printFieldDTO); + return printFieldDTO; + } + ).collect(Collectors.toList())); }); } catch (FlowableObjectNotFoundException e) { log.warn("can't found form model"); @@ -305,8 +366,13 @@ public class PrintAdminController implements PrintAdminApi { .setCode(activity.getId()) .setFieldCategoryType(signature) .setFieldFormType("signature") - .setAttributes(Lists.newArrayList(new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), - new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT)))) + .setAttributes(Lists.newArrayList( + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_APPROVER_NAME).setName(PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT).setName(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME).setName(PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT) + ))) .collect(Collectors.toList()) ); @@ -368,6 +434,7 @@ public class PrintAdminController implements PrintAdminApi { variables.put(PRINT_VAR_PROCESS_INITIATOR_NAME, user.isPresent() ? user.get().getRealName() : ""); variables.put(PRINT_VAR_PROCESS_INITIATOR_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : ""); variables.put(PRINT_VAR_PROCESS_INITIATOR_PHONE, user.isPresent() ? user.get().getProfile().getPhone() : ""); + variables.put(PRINT_VAR_PROCESS_INITIATOR_UNIT, user.isPresent() ? user.get().getOrganizationalUnitName() : ""); variables.remove(PRINT_VAR_PROCESS_INITIATOR); } // 填充审批日志 @@ -383,11 +450,14 @@ public class PrintAdminController implements PrintAdminApi { .forEach(taskLog -> { Map taskLogMap = new HashMap<>(); taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME, taskLog.getName()); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE, taskLog.getNodeType().getType()); Optional user = getUserInfo(taskLog.getAssigneeSnapshot()); taskLogMap.put(PRINT_VAR_PROCESS_LOG_APPROVER_NAME, user.isPresent() ? user.get().getRealName() : ""); taskLogMap.put(PRINT_VAR_PROCESS_LOG_UNIT, user.isPresent() ? user.get().getOrganizationalUnitName() : ""); taskLogMap.put(PRINT_VAR_PROCESS_LOG_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : ""); taskLogMap.put(PRINT_VAR_PROCESS_LOG_ADVICE, taskLog.getAdvice()); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_OPERATION, taskLog.getOperationDesc()); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT, taskLog.getResult().getStatus()); taskLogMap.put(PRINT_VAR_PROCESS_LOG_OPERATION_TIME, sdf.format(taskLog.getCreateTime())); taskLogMap.put(PRINT_VAR_PROCESS_LOG_SIGNATURE, taskLog.getSignatureUrl()); taskLogs.add(taskLogMap); @@ -427,4 +497,124 @@ public class PrintAdminController implements PrintAdminApi { return users.stream().sorted(Comparator.comparing(OrgNodeUserBriefInfoResp::getExited).reversed()) .collect(Collectors.toList()).stream().findFirst(); } + + @Operation(summary = "获取用于打印审批日志公共模板的数据") + @PostMapping("/process/log/data/v2") + @Override + public CommonResponse getPrintDataForProcessLog(@Validated @RequestBody Print4ProcessLogDTO dto) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + Map variables = commandExecutor.execute(new CustomGetProcessInstanceVariablesCmd(dto.getProcessInstanceId())); + + PrintData4LogVO vo = new PrintData4LogVO(); + vo.setProcessName((String) variables.getOrDefault(PRINT_VAR_PROCESS_NAME, "")); + vo.setTenantName(String.valueOf(variables.get(VariableConstants.PRINT_VAR_PROCESS_BELONG_TENANT_ID))); + vo.setCreateAt(String.valueOf(variables.get(VariableConstants.PRINT_VAR_PROCESS_START_TIME))); + vo.setResult((BpmnProcessInstanceResultEnum) variables.get(PRINT_VAR_PROCESS_RESULT)); + + List systemVarItems = new ArrayList<>(); + systemVarItems.add(TableItemDTO.builder() + .label(PRINT_VAR_PROCESS_INSTANCE_ID_DESC) + .code(PRINT_VAR_PROCESS_INSTANCE_ID) + .type(FORM_FIELD_TYPE_INPUT) + .value(variables.get(PRINT_VAR_PROCESS_INSTANCE_ID)) + .build()); + + // 解析发起人 + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(variables.getOrDefault(PRINT_VAR_PROCESS_INITIATOR, null)); + if (Objects.nonNull(initiator)) { + Optional user = getUserInfo(initiator); + systemVarItems.add(TableItemDTO.builder() + .label(PRINT_VAR_PROCESS_INITIATOR_NAME_DESC) + .code(PRINT_VAR_PROCESS_INITIATOR_NAME) + .type(FORM_FIELD_TYPE_INPUT) + .value(user.isPresent() ? user.get().getRealName() : "") + .build()); + systemVarItems.add(TableItemDTO.builder() + .label(PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC) + .code(PRINT_VAR_PROCESS_INITIATOR_UNIT) + .type(FORM_FIELD_TYPE_INPUT) + .value(user.isPresent() ? user.get().getOrganizationalUnitName() : "") + .build()); + } + + vo.setSystemVarItems(systemVarItems); + + // 审批日志 + List logItems = new ArrayList<>(); + Map> attachmentByTaskMap = + taskService.getProcessInstanceAttachments(dto.getProcessInstanceId()).stream() + .collect(Collectors.groupingBy(Attachment::getTaskId)); + ExtAxProcessLog query = new ExtAxProcessLog(); + query.setProcessInstanceId(dto.getProcessInstanceId()); + // TODO 这里可能需要结合节点隐藏来过滤 + processLogService.genericQuery(query).stream() + .sorted(Comparator.comparing(ExtAxProcessLog::getEndTime, Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()) + .forEach(log -> { + logItems.add(ProcessLogItemDTO.builder() + .advice(log.getAdvice()) + .activityName(log.getActivityName()) + .operationDesc(log.getOperationDesc()) + .imageList(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.image)) + .fileList(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.file)) + .signatureUrl(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.signature).stream().findFirst().orElse(new AttachmentDTO()).getUrl()) + .operationTime(DateUtil.format(log.getEndTime(), "yyyy.MM.dd HH:mm:ss")) + .result(log.getStatus()) + .build()); + }); + parseSignatureUrl(logItems); + vo.setLogItems(logItems); + + return success(vo); + } + + + private void parseSignatureUrl(List logItems) { + List signUrls = logItems.stream() + .map(ProcessLogItemDTO::getSignatureUrl) + .filter(StringUtils::hasText) + .distinct() + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(signUrls)) { + Map ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream() + .collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s)); + logItems.stream() + .filter(i -> StringUtils.hasText(i.getSignatureUrl())) + .forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null))); + } + } + + private List getSignPrivateUrl(List signUrls) { + ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest(); + request.setFileKeys(signUrls); + return RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "批量获取手写签私有访问地址", request); + } + + @Operation(summary = "后端请求指定流程日志 PDF 文件生成") + @PostMapping("/process/log/pdf") + @Override + public CommonResponse createProcessLogPdf(@Validated @RequestBody PrintProcessLogPdfDTO dto) { + SubmitConversionTaskRequest request = new SubmitConversionTaskRequest(); + request.setBizCode("workflow-process-log"); + request.setBizKey(dto.getProcessInstanceId()); + request.setConversionType(DocConversionTypeEnum.HTML_URL_TO_PDF); + request.setFileName(String.format(refreshProperties.getProcessLogHtmlUrl(), dto.getProcessInstanceId(), dto.getPersonId())); + String taskId = RpcExternalUtil.rpcApiResultProcessor(() -> docConversionApi.submitConvertTask(request), "创建网页转 PDF 的异步任务", request); + return CommonResponse.success(taskId); + } + + @Operation(summary = "后端查询指定审批日志 PDF 文件的生成结果") + @PostMapping("/process/log/pdf/result") + @Override + public CommonResponse queryProcessLogPdfResult(@Validated @RequestBody QueryProcessLogPdfDTO dto) { + QueryConversionTaskRequestV2 request = new QueryConversionTaskRequestV2(); + request.setBizCode("workflow-process-log"); + request.setBizKey(dto.getProcessInstanceId()); + List taskConvertResults = RpcExternalUtil.rpcApiResultProcessor(() -> docConversionApi.queryConvertResultByBiz(request), "查询流程日志转 PDF 的结果", request); + List results = BeanMapper.copyList(taskConvertResults, ProcessLogPdfResultDTO.class, (s, t) -> { + t.setPdfFileKey(s.getResultFileFileKey()); + t.setStatus(s.getStatus().name()); + }); + return CommonResponse.success(results.isEmpty() ? null : results.get(0)); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java index 5baa7408d..7f13d2825 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java @@ -41,6 +41,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; @@ -51,18 +52,24 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_BACK_COUNTERSIGN; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; import static cn.axzo.workflow.common.constant.BpmnConstants.SUPPORT_UPGRADE_VARIABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN; import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.nobody; +import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.BACK_COUNTERSIGN; +import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.HIDDEN; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.HANDLING; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; @@ -145,9 +152,17 @@ public class TaskEntityEventHandle implements EntityEventHandle { } else { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); - @SuppressWarnings("unchecked") - List assigneeList = runtimeService.getVariable(taskEntity.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class); + List assigneeList = new ArrayList<>(); + if (Objects.equals(FORWARD_COUNTERSIGN.getType(), taskEntity.getScopeType())) { + assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN + taskEntity.getParentTaskId(), List.class)); + } else if (Objects.equals(BACK_COUNTERSIGN.getType(), taskEntity.getScopeType())) { + assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + INTERNAL_ACTIVITY_BACK_COUNTERSIGN + taskEntity.getParentTaskId(), List.class)); + } else { + assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class)); + } ListUtils.emptyIfNull(assigneeList).stream().filter(e -> Objects.equals(e.buildAssigneeId(), taskEntity.getAssignee())).findAny() .ifPresent(assignee -> { ExtAxProcessLog queryLog = new ExtAxProcessLog(); @@ -272,12 +287,18 @@ public class TaskEntityEventHandle implements EntityEventHandle { String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class); if (StringUtils.hasText(completionType) && !Objects.equals(DELETED.getStatus(), completionType)) { update.setStatus(completionType); + Boolean hiddenLog = taskEntity.getVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + taskEntity.getTaskDefinitionKey(), Boolean.class); + if (Objects.equals(hiddenLog, Boolean.TRUE)) { + update.setStatus(HIDDEN.getStatus()); + } } else { // 多实例除操作人以外的任务,直接删除日志, 例如一个节点有两个人或签,A 人驳回了,那么 B 人不再需要操作,任务自动删除。而会签也同理 update.setStatus(DELETED.getStatus());// delete标志着是多实例删除 needDelete = true; } } + // 重置日志隐藏的标识为 false + taskEntity.removeVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + taskEntity.getTaskDefinitionKey()); update.setEndTime(new Date()); // 判断是否抄送节点,如果是的话,需要将抄送人集合放入对应字段 diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/RecoverDynamicCounterSignProcess.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/RecoverDynamicCounterSignProcess.java new file mode 100644 index 000000000..e59f5fb70 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/RecoverDynamicCounterSignProcess.java @@ -0,0 +1,125 @@ +package cn.axzo.workflow.server.initializer; + +import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper; +import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord; +import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.context.SmartLifecycle; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; + +/** + * 在框架提供 Web 服务前,恢复动态前后加签的流程实例的内存中的模型 + * + * @author wangli + * @since 2025-10-17 17:43 + */ +@Slf4j +@Component +public class RecoverDynamicCounterSignProcess implements SmartLifecycle { + private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService; + private final SpringProcessEngineConfiguration springProcessEngineConfiguration; + private boolean isRunning = false; + + public RecoverDynamicCounterSignProcess(ExtAxDynamicSignRecordService extAxDynamicSignRecordService, + SpringProcessEngineConfiguration springProcessEngineConfiguration) { + this.extAxDynamicSignRecordService = extAxDynamicSignRecordService; + this.springProcessEngineConfiguration = springProcessEngineConfiguration; + } + + + @Override + public void start() { + // 恢复动态会签中的流程实例 + log.info("executing before web server start"); + // 查询有过加签的审批实例 + List list = extAxDynamicSignRecordService.list(); + if (CollectionUtils.isEmpty(list)) { + return; + } + + RuntimeService runtimeService = springProcessEngineConfiguration.getRuntimeService(); + // 获取还在审批中的实例 + Set processInstanceIds = list.stream().map(ExtAxDynamicSignRecord::getProcessInstanceId).collect(Collectors.toSet()); + Set runningProcessIds = runtimeService.createProcessInstanceQuery() + .processInstanceIds(processInstanceIds) + .active() + .list().stream().map(ProcessInstance::getProcessInstanceId).collect(Collectors.toSet()); + if (CollectionUtils.isEmpty(runningProcessIds)) { + return; + } + + Map> grouped = list.stream() + .collect(Collectors.groupingBy( + ExtAxDynamicSignRecord::getProcessInstanceId, + Collectors.collectingAndThen( + Collectors.toList(), + l -> { + l.sort(Comparator.comparing( + ExtAxDynamicSignRecord::getCreateAt, + Comparator.nullsFirst(Comparator.naturalOrder()) + )); + return l; + } + ) + )); + + RepositoryService repositoryService = springProcessEngineConfiguration.getRepositoryService(); + grouped.forEach((k, v) -> { + if (runningProcessIds.contains(k)) { + log.info("recover dynamic counter sign process instance id: {}", k); + BpmnModel bpmnModel = repositoryService.getBpmnModel(v.get(0).getProcessDefinitionId()); + Process process = bpmnModel.getMainProcess(); + + v.forEach(sign -> { + UserTask originalUserTask = (UserTask) process.getFlowElement(sign.getOriginalActivityId()); + BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR; + if (StringUtils.hasText(sign.getTargetNodeMode())) { + nodeMode = BpmnFlowNodeMode.valueOfType(sign.getTargetNodeMode()); + } + // 创建加签节点 + UserTask newUserTask = CustomBpmnModelHelper.createUserTask(springProcessEngineConfiguration, originalUserTask, sign.getTargetActivityId(), nodeMode, sign.getAssignerList()); + process.addFlowElement(newUserTask); + CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask, Objects.requireNonNull(BpmnCountersignTypeEnum.valueOfType(sign.getCounterSignType()))); + }); + } + }); + + + isRunning = true; + } + + @Override + public void stop() { + isRunning = false; + } + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public int getPhase() { + return Integer.MAX_VALUE - 2; // 确保在Web服务器启动前执行 + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java index 883e85801..5bba3119e 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.starter.api; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; @@ -15,6 +16,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceChe import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; @@ -36,6 +38,8 @@ import cn.axzo.workflow.common.model.request.feature.DingTalkStarterAlterDTO; import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO; import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; +import cn.axzo.workflow.common.model.request.form.instance.FromDataSearchDTO; +import cn.axzo.workflow.common.model.request.form.model.WpsFileConfigVariableDTO; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; @@ -43,6 +47,7 @@ import cn.axzo.workflow.common.model.response.bpmn.process.NodesByModelVO; import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormDataVO; import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; import cn.axzo.workflow.common.util.ThreadUtil; import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; @@ -152,6 +157,7 @@ public interface WorkflowCoreService { /** * 获取指定审批业务的流程表单设置, + * * @param dto * @return */ @@ -172,6 +178,26 @@ public interface WorkflowCoreService { @InvokeMode(SYNC) FormInstanceVO getFormInstance(@Validated @RequestBody FormDetailDTO dto); + /** + * 获取指定表单审批的实例数据 + * + * @param dto + * @return + */ + @PostMapping("/api/form/admin/instance/form/data") + @InvokeMode(SYNC) + List getFormData(@Validated @RequestBody FromDataSearchDTO dto); + + /** + * 获取 WPS 文档中所有可配置的流程相关变量 + * + * @param dto + * @return + */ + @PostMapping("/api/form/admin/wps/file/config/variables") + @InvokeMode(SYNC) + List getWpsFileConfigVariables(@Validated @RequestBody WpsFileConfigVariableDTO dto); + /** * 创建流程前的节点列表 * 用于发起人自选 @@ -283,6 +309,17 @@ public interface WorkflowCoreService { @InvokeMode(SYNC) Map getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + /** + * 更新流程实例中的业务自定义变量集合 + * + * @param dto + * @return + */ + @Operation(summary = "更新流程实例中的业务自定义变量集合") + @PostMapping("/api/process/instance/biz/custom/variables/update") + @InvokeMode(SYNC) + Boolean updateProcessBizCustomVariables(@Validated @RequestBody BpmnProcessInstanceVariablesUpdateDTO dto); + /** * 校验指定流程实例下,是否存在指定的审批人正处理待审批 * diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java index 8a7f95145..fc13dd518 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java @@ -120,7 +120,7 @@ public interface WorkflowManageService { @GetMapping("/api/print/admin/field/variables") @Manageable @InvokeMode(SYNC) - Map getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId); + Map getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId, @RequestParam(required = false, defaultValue = "true") Boolean throwException); /** * 查询管理员