diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java index 788a2bd83..9ef8aded4 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java @@ -165,14 +165,6 @@ public class YudaoTenantAutoConfiguration { return ignoreUrls; } - // ========== Job ========== - - @Bean - @ConditionalOnClass(name = "com.xxl.job.core.handler.annotation.XxlJob") - public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { - return new TenantJobAspect(tenantFrameworkService); - } - // ========== MQ ========== /** @@ -203,6 +195,14 @@ public class YudaoTenantAutoConfiguration { return new TenantRocketMQInitializer(); } + // ========== Job ========== + + @Bean + @ConditionalOnClass(name = "com.xxl.job.core.handler.annotation.XxlJob") + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { + return new TenantJobAspect(tenantFrameworkService); + } + // ========== Redis ========== @Bean diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java index 4cbb91c2c..775447a06 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; @@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration { throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType)); } - @Bean - public JacksonTypeHandler jacksonTypeHandler(List objectMappers) { + @Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用! + public Object jacksonTypeHandler(List objectMappers) { // 特殊:设置 JacksonTypeHandler 的 ObjectMapper! ObjectMapper objectMapper = CollUtil.getFirst(objectMappers); if (objectMapper == null) { diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java index a79fb2a73..7e07fd8e3 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fhs.core.trans.vo.TransPojo; import lombok.Data; +import org.apache.ibatis.type.JdbcType; import java.io.Serializable; import java.time.LocalDateTime; @@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo { * * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 */ - @TableField(fill = FieldFill.INSERT) + @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR) private String creator; /** * 更新者,目前使用 SysUser 的 id 编号 * * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 */ - @TableField(fill = FieldFill.INSERT_UPDATE) + @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR) private String updater; /** * 是否删除 diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 3929b7106..460ea7811 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java @@ -60,6 +60,12 @@ public enum DbTypeEnum { * 人大金仓 */ KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + + /** + * OceanBase + */ + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0") + ; public static final Map MAP_BY_NAME = Arrays.stream(values()) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java index dffa4743d..2a47af6f9 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.swagger.config; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; @@ -12,6 +13,7 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import org.springdoc.core.*; import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.OperationCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.providers.JavadocProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -39,7 +41,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_ * * @author 芋道源码 */ -@AutoConfiguration +@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论 @ConditionalOnClass({OpenAPI.class}) @EnableConfigurationProperties(SwaggerProperties.class) @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 @@ -98,7 +100,6 @@ public class YudaoSwaggerAutoConfiguration { Optional> openApiBuilderCustomizers, Optional> serverBaseUrlCustomizers, Optional javadocProvider) { - return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); } @@ -124,6 +125,7 @@ public class YudaoSwaggerAutoConfiguration { .addOperationCustomizer((operation, handlerMethod) -> operation .addParametersItem(buildTenantHeaderParameter()) .addParametersItem(buildSecurityHeaderParameter())) + .addOperationCustomizer(buildOperationIdCustomizer()) .build(); } @@ -155,5 +157,26 @@ public class YudaoSwaggerAutoConfiguration { .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1 } + /** + * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」 + * + * @see app-api 前缀不生效,都是使用 admin-api + */ + private static OperationCustomizer buildOperationIdCustomizer() { + return (operation, handlerMethod) -> { + // 1. 获取控制器类名(如 UserController) + String className = handlerMethod.getBeanType().getSimpleName(); + // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User) + String classPrefix = className.replaceAll("Controller$", ""); + // 3. 获取方法名(如 list) + String methodName = handlerMethod.getMethod().getName(); + // 4. 组合生成 operationId(如 User_list) + String operationId = classPrefix + "_" + methodName; + // 5. 设置自定义 operationId + operation.setOperationId(operationId); + return operation; + }; + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java index 865a95c07..65fcd3428 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.web.config; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter; @@ -7,11 +8,14 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import com.google.common.collect.Maps; +import jakarta.servlet.Filter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -23,40 +27,57 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.annotation.Resource; -import javax.servlet.Filter; +import java.util.Map; +import java.util.function.Predicate; @AutoConfiguration @EnableConfigurationProperties(WebProperties.class) -public class YudaoWebAutoConfiguration implements WebMvcConfigurer { +public class YudaoWebAutoConfiguration { - @Resource - private WebProperties webProperties; /** * 应用名 */ @Value("${spring.application.name}") private String applicationName; - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurePathMatch(configurer, webProperties.getAdminApi()); - configurePathMatch(configurer, webProperties.getAppApi()); - } + @Bean + public WebMvcRegistrations webMvcRegistrations(WebProperties webProperties) { + return new WebMvcRegistrations() { - /** - * 设置 API 前缀,仅仅匹配 controller 包下的 - * - * @param configurer 配置 - * @param api API 配置 - */ - private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) { - AntPathMatcher antPathMatcher = new AntPathMatcher("."); - configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class) - && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包 + @Override + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + var mapping = new RequestMappingHandlerMapping(); + // 实例化时就带上前缀 + mapping.setPathPrefixes(buildPathPrefixes(webProperties)); + return mapping; + } + + /** + * 构建 prefix → 匹配条件的映射 + */ + private Map>> buildPathPrefixes(WebProperties webProperties) { + AntPathMatcher antPathMatcher = new AntPathMatcher("."); + Map>> pathPrefixes = Maps.newLinkedHashMapWithExpectedSize(2); + putPathPrefix(pathPrefixes, webProperties.getAdminApi(), antPathMatcher); + putPathPrefix(pathPrefixes, webProperties.getAppApi(), antPathMatcher); + return pathPrefixes; + } + + /** + * 设置 API 前缀,仅仅匹配 controller 包下的 + */ + private void putPathPrefix(Map>> pathPrefixes, WebProperties.Api api, AntPathMatcher matcher) { + if (api == null || StrUtil.isEmpty(api.getPrefix())) { + return; + } + pathPrefixes.put(api.getPrefix(), // api 前缀 + clazz -> clazz.isAnnotationPresent(RestController.class) + && matcher.match(api.getController(), clazz.getPackage().getName())); + } + + }; } @Bean diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index aeac0876f..bfd350d64 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -63,7 +63,7 @@ public interface ErrorCodeConstants { ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!"); ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!"); ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!"); - ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!"); + ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_020, "撤回失败,下一节点不满足撤回条件!"); // ========== 动态表单模块 1-009-010-000 ========== ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index 5b42df50f..87be32633 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.enums.task; +import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; @@ -47,4 +48,8 @@ public enum BpmProcessInstanceStatusEnum implements ArrayValuable { APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); } + public static BpmProcessInstanceStatusEnum valueOf(Integer status) { + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index 9ba3b5cb3..df5fd6e63 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -1,10 +1,14 @@ package cn.iocoder.yudao.module.bpm.enums.task; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.ArrayValuable; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * 流程任务 Task 的状态枚举 * @@ -12,7 +16,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmTaskStatusEnum { +public enum BpmTaskStatusEnum implements ArrayValuable { SKIP(-2, "跳过"), NOT_START(-1, "未开始"), @@ -35,6 +39,8 @@ public enum BpmTaskStatusEnum { */ WAIT(0, "待审批"); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmTaskStatusEnum::getStatus).toArray(Integer[]::new); + /** * 状态 *

@@ -46,6 +52,11 @@ public enum BpmTaskStatusEnum { */ private final String name; + @Override + public Integer[] array() { + return ARRAYS; + } + public static boolean isRejectStatus(Integer status) { return REJECT.getStatus().equals(status); } @@ -68,4 +79,8 @@ public enum BpmTaskStatusEnum { return ObjUtil.equal(status, CANCEL.getStatus()); } + public static BpmTaskStatusEnum valueOf(Integer status) { + return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values()); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index 886446d24..a166bd090 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -100,6 +100,10 @@ public class BpmModelMetaInfoVO { @Schema(description = "任务后置通知设置", example = "{}") private HttpRequestSetting taskAfterTriggerSetting; + @Schema(description = "自定义打印模板设置", example = "{}") + @Valid + private PrintTemplateSetting printTemplateSetting; + @Schema(description = "流程 ID 规则") @Data @Valid @@ -180,4 +184,17 @@ public class BpmModelMetaInfoVO { } + @Schema(description = "自定义打印模板设置") + @Data + public static class PrintTemplateSetting { + + @Schema(description = "是否自定义打印模板", example = "false") + @NotNull(message = "是否自定义打印模板不能为空") + private Boolean enable; + + @Schema(description = "打印模板", example = "

") + private String template; + + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 34d337625..364a9c60f 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; @@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -34,9 +36,11 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS; @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” @RestController @@ -192,8 +196,30 @@ public class BpmProcessInstanceController { @GetMapping("/get-bpmn-model-view") @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") @Parameter(name = "id", description = "流程实例的编号", required = true) - public CommonResult getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { + public CommonResult getProcessInstanceBpmnModelView( + @RequestParam(value = "id") String id) { return success(processInstanceService.getProcessInstanceBpmnModelView(id)); } + @GetMapping("/get-print-data") + @Operation(summary = "获得流程实例的打印数据") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult getProcessInstancePrintData( + @RequestParam("processInstanceId") String processInstanceId) { + HistoricProcessInstance historicProcessInstance = processInstanceService.getHistoricProcessInstance(processInstanceId); + if (historicProcessInstance == null) { + throw exception(PROCESS_INSTANCE_NOT_EXISTS); + } + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(historicProcessInstance.getStartUserId())).getCheckedData(); + DeptRespDTO dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); + List tasks = taskService.getFinishedTaskListByProcessInstanceIdWithoutCancel(processInstanceId); + Map userMap = adminUserApi.getUserMap( + convertSet(tasks, item -> Long.valueOf(item.getAssignee()))); + return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePrintData(historicProcessInstance, + processDefinitionService.getProcessDefinitionInfo(historicProcessInstance.getProcessDefinitionId()), + tasks, userMap, + new UserSimpleBaseVO().setNickname(startUser.getNickname()).setDeptName(dept.getName()))); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java index 496c72c61..36d4724a0 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @Schema(description = "管理后台 - 流程实例的取消 Request VO") @Data diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java index 207daeeff..b13ff561f 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import java.util.Map; diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java new file mode 100644 index 000000000..8f85da966 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessPrintDataRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 流程实例的打印数据 Response VO") +@Data +public class BpmProcessPrintDataRespVO { + + @Schema(description = "流程实例数据") + private BpmProcessInstanceRespVO processInstance; + + @Schema(description = "是否开启自定义打印模板", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean printTemplateEnable; + + @Schema(description = "自定义打印模板 HTML") + private String printTemplateHtml; + + @Schema(description = "审批任务列表") + private List tasks; + + @Schema(description = "流程任务") + @Data + public static class Task { + + @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "签名 URL", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + @Schema(description = "任务描述", requiredMode = Schema.RequiredMode.REQUIRED) + private String description; // 该字段由后端拼接 + + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java index f129e5a31..45729f9f2 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; @@ -21,6 +23,10 @@ public class BpmTaskPageReqVO extends PageParam { @Schema(description = "流程定义的标识", example = "2048") private String processDefinitionKey; // 精准匹配 + @Schema(description = "审批状态", example = "1") + @InEnum(BpmTaskStatusEnum.class) + private Integer status; // 仅【已办】使用 + @Schema(description = "创建时间") @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java index 45d5973f4..6ba51afec 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -1,23 +1,29 @@ package cn.iocoder.yudao.module.bpm.convert.task; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessPrintDataRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; @@ -35,10 +41,7 @@ import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -293,4 +296,47 @@ public interface BpmProcessInstanceConvert { .setActivityNodes(activityNodes); } + default BpmProcessPrintDataRespVO buildProcessInstancePrintData(HistoricProcessInstance historicProcessInstance, + BpmProcessDefinitionInfoDO processDefinitionInfo, + List tasks, + Map userMap, + UserSimpleBaseVO startUser) { + BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting = processDefinitionInfo.getPrintTemplateSetting(); + BpmProcessPrintDataRespVO printData = new BpmProcessPrintDataRespVO(); + // 打印模板是否开启 + printData.setPrintTemplateEnable(printTemplateSetting != null && Boolean.TRUE.equals(printTemplateSetting.getEnable())); + // 流程相关数据 + BpmProcessInstanceRespVO processInstance = new BpmProcessInstanceRespVO() + .setId(historicProcessInstance.getId()).setName(historicProcessInstance.getName()) + .setBusinessKey(historicProcessInstance.getBusinessKey()) + .setStartTime(DateUtils.of(historicProcessInstance.getStartTime())) + .setEndTime(DateUtils.of(historicProcessInstance.getEndTime())) + .setStartUser(startUser).setStatus(FlowableUtils.getProcessInstanceStatus(historicProcessInstance)) + .setFormVariables(historicProcessInstance.getProcessVariables()) + .setProcessDefinition(BeanUtils.toBean(processDefinitionInfo, BpmProcessDefinitionRespVO.class)); + printData.setProcessInstance(processInstance); + // 审批历史 + List approveTasks = new ArrayList<>(tasks.size()); + tasks.forEach(item -> { + Map taskLocalVariables = item.getTaskLocalVariables(); + BpmProcessPrintDataRespVO.Task approveTask = new BpmProcessPrintDataRespVO.Task(); + approveTask.setName(item.getName()); + approveTask.setId(item.getId()); + approveTask.setSignPicUrl((String) taskLocalVariables.get(BpmnVariableConstants.TASK_SIGN_PIC_URL)); + approveTask.setDescription(StrUtil.format("{} / {} / {} / {} / {}", + userMap.get(Long.valueOf(item.getAssignee())).getNickname(), + item.getName(), + DateUtil.formatDateTime(item.getEndTime()), + BpmTaskStatusEnum.valueOf((Integer) taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_STATUS)).getName(), + taskLocalVariables.get(BpmnVariableConstants.TASK_VARIABLE_REASON))); + approveTasks.add(approveTask); + }); + printData.setTasks(approveTasks); + // 自定义模板 + if (printData.getPrintTemplateEnable() && printTemplateSetting != null) { + printData.setPrintTemplateHtml(printTemplateSetting.getTemplate()); + } + return printData; + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 37e2c4462..6fd905ee3 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -224,4 +224,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; + /** + * 自定义打印模板设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting; + } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java index adabbc861..3e30bd4c0 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java @@ -51,8 +51,8 @@ public class BpmProcessIdRedisDAO { String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix(); String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix; Long no = stringRedisTemplate.opsForValue().increment(key); - if (StrUtil.isNotEmpty(infix)) { - // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始 + if (StrUtil.isEmpty(infix)) { + // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始。可见 https://t.zsxq.com/MU1E2 讨论 stringRedisTemplate.expire(key, Duration.ofDays(1L)); } return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index 893c4d053..b3ce94614 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -43,14 +43,23 @@ public class BpmnVariableConstants { * @see ProcessInstance#getProcessVariables() */ public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; + /** - * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} + * 流程实例的变量 - 用于判断流程实例变量节点是否驳回:格式 RETURN_FLAG_{节点 id} * - * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 + * 目的是:退回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 * * @see ProcessInstance#getProcessVariables() */ public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; + + /** + * 流程实例的变量前缀 - 用于退回操作,记录需要预测的节点:格式 NEED_SIMULATE_TASK_{节点定义 id} + * + * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测 + */ + public static final String PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX = "NEED_SIMULATE_TASK_"; + /** * 流程实例的变量 - 是否跳过表达式 * diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index a3414cedb..349f88048 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -658,10 +658,11 @@ public class BpmnModelUtils { // 根据类型,获取入口连线 List sequenceFlows = getElementIncomingFlows(source); + // 1. 没有入口连线,则返回 false if (CollUtil.isEmpty(sequenceFlows)) { - return true; + return false; } - // 循环找到目标元素 + // 2. 循环找目标元素, 找到目标节点 for (SequenceFlow sequenceFlow : sequenceFlows) { // 如果发现连线重复,说明循环了,跳过这个循环 if (visitedElements.contains(sequenceFlow.getId())) { @@ -669,21 +670,22 @@ public class BpmnModelUtils { } // 添加已经走过的连线 visitedElements.add(sequenceFlow.getId()); - // 这条线路存在目标节点,这条线路完成,进入下个线路 + // 这条线路存在目标节点,直接返回 true FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); if (target.getId().equals(sourceFlowElement.getId())) { + return true; + } + // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?) + if (sourceFlowElement instanceof ParallelGateway) { continue; } - // 如果目标节点为并行网关,则不继续 - if (sourceFlowElement instanceof ParallelGateway) { - return false; - } - // 否则就继续迭代 - if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) { - return false; + // 继续迭代,如果找到目标节点直接返回 true + if (isSequentialReachable(sourceFlowElement, target, visitedElements)) { + return true; } } - return true; + // 未找到返回 false + return false; } /** @@ -783,7 +785,6 @@ public class BpmnModelUtils { return resultElements; } - @SuppressWarnings("PatternVariableCanBeUsed") private static void simulateNextFlowElements(FlowElement currentElement, Map variables, List resultElements, Set visitElements) { // 如果为空,或者已经遍历过,则直接结束 diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 2582399a8..cfeb2c2de 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -737,10 +737,10 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = null; if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), - node.getDelaySetting().getDelayTime(), null, null); + null, null, node.getDelaySetting().getDelayTime()); } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), - null, null, node.getDelaySetting().getDelayTime()); + node.getDelaySetting().getDelayTime(), null, null); } else { throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting()); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index edd4ff324..2d0dfd7cb 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -71,6 +72,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -186,6 +188,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) { processVariables.putAll(reqVO.getProcessVariables()); } + // 特殊:如果是未发起的场景,则设置发起用户,解决“发起流程”时,需要使用到该变量的问题。例如说:https://t.zsxq.com/fMw5g + if (historicProcessInstance == null) { + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, loginUserId); + } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() @@ -217,10 +223,24 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 3.1 计算当前登录用户的待办任务 BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId()); - // 3.2 预测未运行节点的审批信息 + + // 3.2 获取由于退回操作,需要预测的节点。从流程变量中获取,回退操作会设置这些变量 + Set needSimulateTaskDefKeysByReturn = new HashSet<>(); + if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { + Map variables = runtimeService.getVariables(reqVO.getProcessInstanceId()); + Map simulateTaskVariables = MapUtil.filter(variables, + item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)); + simulateTaskVariables.forEach((key, value) -> + needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX))); + } + // 移除运行中的节点,运行中的节点无需预测 + // TODO @jason:是不是 foreach runActivityNodes,然后移除 needSimulateTaskDefKeysByReturn 更好?(理解成本低一点) + CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove); + + // 3.3 预测未运行节点的审批信息 List simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, - processVariables, activities); + processVariables, activities, needSimulateTaskDefKeysByReturn); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, @@ -460,7 +480,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } /** - * 获取结束节点的状态 + * 获取结束节点的状态 */ private Integer getEndActivityNodeStatus(HistoricTaskInstance task) { Integer status = FlowableUtils.getTaskStatus(task); @@ -545,7 +565,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService private List getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables, - List activities) { + List activities, + Set needSimulateTaskDefKeysByReturn) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 Set runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); @@ -554,7 +575,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService List flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn( startUserId, bpmnModel, flowElements, - processDefinitionInfo, processVariables, flowElement, runActivityIds)); + processDefinitionInfo, processVariables, flowElement, runActivityIds, needSimulateTaskDefKeysByReturn)); } // 情况二:SIMPLE 设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { @@ -563,17 +584,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService List simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple( startUserId, bpmnModel, - processDefinitionInfo, processVariables, simpleNode, runActivityIds)); + processDefinitionInfo, processVariables, simpleNode, runActivityIds, needSimulateTaskDefKeysByReturn)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables, - BpmSimpleModelNodeVO node, Set runActivityIds) { + BpmSimpleModelNodeVO node, Set runActivityIds, + Set needSimulateTaskDefKeysByReturn) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 - if (runActivityIds.contains(node.getId())) { + if (runActivityIds.contains(node.getId()) + && !needSimulateTaskDefKeysByReturn.contains(node.getId())) { // 特殊:回退操作时候,会记录需要预测的节点到流程变量中。即使在历史操作中,也需要预测 return null; } Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); @@ -620,13 +643,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List flowElements, BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables, - FlowElement node, Set runActivityIds) { - if (runActivityIds.contains(node.getId())) { + FlowElement node, Set runActivityIds, + Set needSimulateTaskDefKeysByReturn) { + // 回退操作时候,会记录需要预测的节点到流程变量中。即使节点在历史操作中,也需要预测。 + if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) { return null; } + Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); // 如果节点被跳过,状态设置为跳过 - if(BpmnModelUtils.isSkipNode(node, processVariables)){ + if (BpmnModelUtils.isSkipNode(node, processVariables)) { status = BpmTaskStatusEnum.SKIP.getStatus(); } ActivityNode activityNode = new ActivityNode().setId(node.getId()) diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 00d30adf9..98615b353 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -176,6 +176,14 @@ public interface BpmTaskService { */ List getHistoricActivityListByExecutionId(String executionId); + /** + * 获得指定流程实例的已完成的流程任务列表,不包含取消状态 + * + * @param processInstanceId 流程实例的编号 + * @return 流程任务列表 + */ + List getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId); + // ========== Update 写入相关方法 ========== /** diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index ca93f590c..886de113e 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -2,10 +2,12 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; @@ -68,8 +70,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE; +//import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; /** @@ -230,6 +231,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (StrUtil.isNotBlank(pageVO.getName())) { taskQuery.taskNameLike("%" + pageVO.getName() + "%"); } + if (pageVO.getStatus() != null) { + taskQuery.taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, pageVO.getStatus()); + } // if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { // taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); // taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); @@ -491,6 +495,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); } + @Override + public List getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId) { + return historyService.createHistoricTaskInstanceQuery() + .finished() + .includeTaskLocalVariables() + .processInstanceId(processInstanceId) + .taskVariableValueNotEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, + BpmTaskStatusEnum.CANCEL.getStatus()) + .orderByHistoricTaskInstanceStartTime().asc().list(); + } + /** * 判断指定用户,是否是当前任务的审批人 * @@ -590,7 +605,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { bpmnModel, reqVO.getNextAssignees(), instance); runtimeService.setVariables(task.getProcessInstanceId(), variables); - // 5. 调用 BPM complete 去完成任务 + // 5. 移除辅助预测的流程变量,这些变量在回退操作中设置 + // todo @jason:可以直接 + 拼接哈 + String simulateVariableName = StrUtil.concat(false, + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, task.getTaskDefinitionKey()); + runtimeService.removeVariable(task.getProcessInstanceId(), simulateVariableName); + + // 6. 调用 BPM complete 去完成任务 taskService.complete(task.getId(), variables, true); // 【加签专属】处理加签任务 @@ -840,34 +861,34 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (task.isSuspended()) { throw exception(TASK_IS_PENDING); } - // 1.2 校验源头和目标节点的关系,并返回目标元素 - FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), - reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); + // 1.2 获取流程模型信息 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + // 1.3 校验源头和目标节点的关系,并返回目标元素 + FlowElement targetElement = validateTargetTaskCanReturn(bpmnModel, task.getTaskDefinitionKey(), + reqVO.getTargetTaskDefinitionKey()); // 2. 调用 Flowable 框架的退回逻辑 - returnTask(userId, task, targetElement, reqVO); + returnTask(userId, bpmnModel, task, targetElement, reqVO); } /** * 退回流程节点时,校验目标任务节点是否可退回 * - * @param sourceKey 当前任务节点 Key - * @param targetKey 目标任务节点 key - * @param processDefinitionId 当前流程定义 ID + * @param bpmnModel 流程模型 + * @param sourceKey 当前任务节点 Key + * @param targetKey 目标任务节点 key * @return 目标任务节点元素 */ - private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { - // 1.1 获取流程模型信息 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); - // 1.3 获取当前任务节点元素 + private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) { + // 1.1 获取当前任务节点元素 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); - // 1.3 获取跳转的节点元素 + // 1.2 获取跳转的节点元素 FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey); if (target == null) { throw exception(TASK_TARGET_NODE_NOT_EXISTS); } - // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 + // 2. 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); } @@ -878,11 +899,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { * 执行退回逻辑 * * @param userId 用户编号 + * @param bpmnModel 流程模型 * @param currentTask 当前退回的任务 * @param targetElement 需要退回到的目标任务 * @param reqVO 前端参数封装 */ - public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { + public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 // 1.1 获取所有正常进行的任务节点 Key List taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); @@ -915,18 +937,54 @@ public class BpmTaskServiceImpl implements BpmTaskService { } }); - // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 - runtimeService.setVariable(currentTask.getProcessInstanceId(), - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); + // 3. 构建需要预测的任务流程变量 + // TODO @jason:【驳回预测相关】是不是搞成一个变量,里面是 set 更简洁一点呀? + Set taskDefinitionKeyList = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); + Map needSimulateVariables = convertMap(taskDefinitionKeyList, + taskId -> StrUtil.concat(false, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE); + // 4. 执行驳回 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因: // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) + // 设置需要预测的任务流程变量,用于辅助预测 + .processVariables(needSimulateVariables) + // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 + .localVariable(reqVO.getTargetTaskDefinitionKey(), + String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), + Boolean.TRUE) .changeState(); } + private Set getNeedSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) { + // 1. 获取需要预测的任务的 definition key。因为当前任务还没完成,也需要预测 + Set taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey()); + + // 2.1 从已结束任务中找到要回退的目标任务,按时间倒序最近的一个目标任务 + List endTaskList = CollectionUtils.filterList( + getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), + item -> item.getEndTime() != null); + // 2.2 遍历已结束的任务,找到在 targetTask 之后生成的任务,且串行可达的任务 + HistoricTaskInstance targetTask = findFirst(endTaskList, + item -> item.getTaskDefinitionKey().equals(targetElement.getId())); + // TODO @jason:【驳回预测相关】是不是 if targetTask 先判空? + endTaskList.forEach(item -> { + FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey()); + // 如果已结束的任务在回退目标节点之后生成,且串行可达,则标记为需要预算节点 + // TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗? 并行网关可能会有问题。 + // TODO @jason:【驳回预测相关】这里是不是判断 element 哈? + if (targetTask != null + // TODO @jason:【驳回预测相关】这里直接 createTime 的 compare 更简单?因为不太会出现空哈。 + && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0 + && BpmnModelUtils.isSequentialReachable(element, targetElement, null)) { + taskDefinitionKeys.add(item.getTaskDefinitionKey()); + } + }); + return taskDefinitionKeys; + } + @Override @Transactional(rollbackFor = Exception.class) public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { @@ -1438,12 +1496,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略(使用 local variable) + Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(), + String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), - PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); if (userTaskElement.getId().equals(START_USER_NODE_ID) && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java index d3282f35a..25a11104d 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java @@ -7,6 +7,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @Schema(description = "管理后台 - 线索分页 Request VO") @Data @@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam { @Schema(description = "跟进状态", example = "true") private Boolean followUpStatus; + @Schema(description = "创建时间", example = "[2023-01-01 00:00:00, 2023-01-31 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java index 88650dc89..8e52f9a23 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java @@ -33,6 +33,7 @@ public interface CrmClueMapper extends BaseMapperX { .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel()) .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource()) .eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus()) + .betweenIfPresent(CrmClueDO::getCreateTime, pageReqVO.getCreateTime()) .orderByDesc(CrmClueDO::getId); return selectJoinPage(pageReqVO, CrmClueDO.class, query); } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index dea52bc68..0c2fd3c81 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; @@ -113,7 +112,7 @@ public class CrmClueServiceImpl implements CrmClueService { private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { // 校验负责人 if (Objects.nonNull(reqVO.getOwnerUserId()) && - Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()).getCheckedData())) { throw exception(USER_NOT_EXISTS); } } diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java index 0ff9e56a9..7f55507e4 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java @@ -21,9 +21,8 @@ public enum CodegenFrontTypeEnum { VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版 VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版 - // TODO @puhui999::50、51 会好点; - VUE3_VBEN5_EP_SCHEMA(42), // Vue3 VBEN5 + EP + schema 模版 - VUE3_VBEN5_EP_GENERAL(43), // Vue3 VBEN5 + EP 标准模版 + VUE3_VBEN5_EP_SCHEMA(50), // Vue3 VBEN5 + EP + schema 模版 + VUE3_VBEN5_EP_GENERAL(51), // Vue3 VBEN5 + EP 标准模版 ; /** diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 7aed968e3..d8aba121f 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -26,6 +26,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.nio.charset.StandardCharsets; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -99,8 +100,10 @@ public class FileController { if (StrUtil.isEmpty(path)) { throw new IllegalArgumentException("结尾的 path 路径必须传递"); } - // 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ - path = URLUtil.decode(path); + // 解码,解决中文路径的问题 + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ + path = URLUtil.decode(path, StandardCharsets.UTF_8, false); // 读取内容 byte[] content = fileService.getFileContent(configId, path); diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index b3826da2d..c4ce19ed6 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -343,7 +343,7 @@ public class CodegenEngine { filePath = formatFilePath(filePath, bindingMap); String content = templateEngine.getTemplate(vmPath).render(bindingMap); // 格式化代码 - content = prettyCode(content); + content = prettyCode(content, vmPath); result.put(filePath, content); } @@ -383,11 +383,14 @@ public class CodegenEngine { * 如果不处理,Vue 的 Pretty 格式校验可能会报错 * * @param content 格式化前的代码 + * @param vmPath 模板路径 * @return 格式化后的代码 */ - private String prettyCode(String content) { - // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错 - content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + private String prettyCode(String content, String vmPath) { + // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5) + if (!StrUtil.contains(vmPath, "vben5")) { + content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + } // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 if (StrUtil.count(content, "dateFormatter") == 1) { content = StrUtils.removeLineContains(content, "dateFormatter"); diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue/api/api.js.vm index 835c0192e..0d9b66f88 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue/api/api.js.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue/api/api.js.vm @@ -27,6 +27,16 @@ export function delete${simpleClassName}(id) { }) } +#if ( $table.templateType != 2 && $deleteBatchEnable) +/** 批量删除${table.classComment} */ +export function delete${simpleClassName}List(ids) { + return request({ + url: `${baseURL}/delete-list?ids=${ids.join(',')}`, + method: 'delete' + }) +} +#end + // 获得${table.classComment} export function get${simpleClassName}(id) { return request({ @@ -130,6 +140,15 @@ export function export${simpleClassName}Excel(params) { method: 'delete' }) } + #if ($deleteBatchEnable) + /** 批量删除${subTable.classComment} */ + export function delete${subSimpleClassName}List(ids) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`, + method: 'delete' + }) + } + #end // 获得${subTable.classComment} export function get${subSimpleClassName}(id) { return request({ diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/api/api.ts.vm index c3044fb87..c9f736126 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/api/api.ts.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/api/api.ts.vm @@ -1,19 +1,56 @@ import request from '@/config/axios' +import type { Dayjs } from 'dayjs'; #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") -// ${table.classComment} VO -export interface ${simpleClassName}VO { -#foreach ($column in $columns) -#if ($column.createOperation || $column.updateOperation) -#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") - ${column.javaField}: number // ${column.columnComment} -#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") - ${column.javaField}: Date // ${column.columnComment} -#else - ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment} -#end -#end +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) + #set ($index = $foreach.count - 1) + #set ($subSimpleClassName = $subSimpleClassNames.get($index)) + #set ($subColumns = $subColumnsList.get($index))##当前字段数组 +/** ${subTable.classComment}信息 */ +export interface ${subSimpleClassName} { + #foreach ($column in $subColumns) + #if ($column.createOperation || $column.updateOperation) + #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment} + #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment} + #else + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment} + #end + #end + #end +} + #end +/** ${table.classComment}信息 */ +export interface ${simpleClassName} { + #foreach ($column in $columns) + #if ($column.createOperation || $column.updateOperation) + #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment} + #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment} + #else + ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment} + #end + #end + #end + #if ( $table.templateType == 2 ) + children?: ${simpleClassName}[]; + #end + ## 特殊:主子表专属逻辑 + #if ( $table.templateType == 10 || $table.templateType == 12 ) + #foreach ($subTable in $subTables) + #set ($index = $foreach.count - 1) + #set ($subSimpleClassName = $subSimpleClassNames.get($index)) + #if ( $subTable.subJoinMany ) + ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[] + #else + ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName} + #end + #end + #end } // ${table.classComment} API @@ -36,12 +73,12 @@ export const ${simpleClassName}Api = { }, // 新增${table.classComment} - create${simpleClassName}: async (data: ${simpleClassName}VO) => { + create${simpleClassName}: async (data: ${simpleClassName}) => { return await request.post({ url: `${baseURL}/create`, data }) }, // 修改${table.classComment} - update${simpleClassName}: async (data: ${simpleClassName}VO) => { + update${simpleClassName}: async (data: ${simpleClassName}) => { return await request.put({ url: `${baseURL}/update`, data }) }, @@ -50,6 +87,13 @@ export const ${simpleClassName}Api = { return await request.delete({ url: `${baseURL}/delete?id=` + id }) }, +#if ( $table.templateType != 2 && $deleteBatchEnable) + /** 批量删除${table.classComment} */ + delete${simpleClassName}List: async (ids: number[]) => { + return await request.delete({ url: `${baseURL}/delete-list?ids=${ids.join(',')}` }) + }, +#end + // 导出${table.classComment} Excel export${simpleClassName}: async (params) => { return await request.download({ url: `${baseURL}/export-excel`, params }) @@ -92,12 +136,12 @@ export const ${simpleClassName}Api = { ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 #if ( $table.templateType == 11 ) // 新增${subTable.classComment} - create${subSimpleClassName}: async (data) => { + create${subSimpleClassName}: async (data: ${subSimpleClassName}) => { return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data }) }, // 修改${subTable.classComment} - update${subSimpleClassName}: async (data) => { + update${subSimpleClassName}: async (data: ${subSimpleClassName}) => { return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data }) }, @@ -106,6 +150,13 @@ export const ${simpleClassName}Api = { return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id }) }, + #if ($deleteBatchEnable) + /** 批量删除${subTable.classComment} */ + delete${subSimpleClassName}List: async (ids: number[]) => { + return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}` }) + }, + #end + // 获得${subTable.classComment} get${subSimpleClassName}: async (id: number) => { return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id }) diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm index a94cab5a5..29df64de1 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm @@ -217,7 +217,7 @@ const handleDeleteBatch = async () => { const checkedIds = ref([]) const handleRowCheckboxChange = (records: ${subSimpleClassName}[]) => { - checkedIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id!); } #end #end diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm index dfb97804c..fb7485d6d 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm @@ -374,7 +374,7 @@ const handleDeleteBatch = async () => { const checkedIds = ref([]) const handleRowCheckboxChange = (records: ${simpleClassName}[]) => { - checkedIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id!); } #end diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm index f65994b8c..c3691a8b7 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm @@ -91,18 +91,17 @@ export function delete${simpleClassName}(id: number) { #if ( $table.templateType != 2 && $deleteBatchEnable) /** 批量删除${table.classComment} */ -export function delete${simpleClassName}ListByIds(ids: number[]) { +export function delete${simpleClassName}List(ids: number[]) { return requestClient.delete(`${baseURL}/delete-list?ids=${ids.join(',')}`) } #end /** 导出${table.classComment} */ export function export${simpleClassName}(params: any) { - return requestClient.download('${baseURL}/export-excel', params); + return requestClient.download('${baseURL}/export-excel', { params }); } ## 特殊:主子表专属逻辑 -## TODO @puhui999:下面这块缩进调整了,会乱掉么? #foreach ($subTable in $subTables) #set ($index = $foreach.count - 1) #set ($subSimpleClassName = $subSimpleClassNames.get($index)) @@ -115,54 +114,54 @@ export function export${simpleClassName}(params: any) { // ==================== 子表($subTable.classComment) ==================== - ## 情况一:MASTER_ERP 时,需要分查询页子表 - #if ( $table.templateType == 11 ) - /** 获得${subTable.classComment}分页 */ - export function get${subSimpleClassName}Page(params: PageParam) { - return requestClient.get>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params }); - } - ## 情况二:非 MASTER_ERP 时,需要列表查询子表 - #else - #if ( $subTable.subJoinMany ) - /** 获得${subTable.classComment}列表 */ - export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) { - return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`); - } - #else - /** 获得${subTable.classComment} */ - export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) { - return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`); - } - #end - #end - ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 - #if ( $table.templateType == 11 ) - /** 新增${subTable.classComment} */ - export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) { - return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data); - } +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) +/** 获得${subTable.classComment}分页 */ +export function get${subSimpleClassName}Page(params: PageParam) { + return requestClient.get>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params }); +} + ## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else +#if ( $subTable.subJoinMany ) +/** 获得${subTable.classComment}列表 */ +export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) { + return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`); +} +#else +/** 获得${subTable.classComment} */ +export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) { + return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`); +} +#end +#end +## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 +#if ( $table.templateType == 11 ) +/** 新增${subTable.classComment} */ +export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) { + return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data); +} - /** 修改${subTable.classComment} */ - export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) { - return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data); - } +/** 修改${subTable.classComment} */ +export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) { + return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data); +} - /** 删除${subTable.classComment} */ - export function delete${subSimpleClassName}(id: number) { - return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`); - } +/** 删除${subTable.classComment} */ +export function delete${subSimpleClassName}(id: number) { + return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`); +} - #if ($deleteBatchEnable) - /** 批量删除${subTable.classComment} */ - export function delete${subSimpleClassName}ListByIds(ids: number[]) { - return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`) - } - #end - - /** 获得${subTable.classComment} */ - export function get${subSimpleClassName}(id: number) { - return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`); - } - #end +#if ($deleteBatchEnable) +/** 批量删除${subTable.classComment} */ +export function delete${subSimpleClassName}List(ids: number[]) { + return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`) +} +#end + +/** 获得${subTable.classComment} */ +export function get${subSimpleClassName}(id: number) { + return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`); +} +#end #end diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm index 90c0a6e9c..06dc0c86c 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm @@ -1,12 +1,15 @@