liu
23 小时以前 dd69dd49c98e2d8fdd9b1c77e97923bde0d57b57
feat(bpm): 新增流程免审批开关,支持按流程或全局跳过 OA 审批

- 新增 bpm_process_auto_approve_config 表:按流程 Key 存储免审批开关,另以特殊 Key '*' 承载全局总开关

- 引擎在任务创建钩子 processTaskCreated 中识别开关,命中则自动 approveTask;流程实例、审批留痕与下游业务动作(生成采购订单/MPS/供应商档案等)均照常执行

- 新增管理接口:按 Key 设置开关、全局总开关设置与查询、配置列表/分页

- 修复自动通过时传 null 触发「该任务的审批人不是你」,并补充任务已推进时的幂等保护

Co-Authored-By: Claude <noreply@anthropic.com>
已添加8个文件
已修改3个文件
551 ■■■■■ 文件已修改
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessAutoApproveController.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigPageReqVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigRespVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigSaveReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessAutoApproveConfigDO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessAutoApproveConfigMapper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessAutoApproveService.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessAutoApproveServiceImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessAutoApproveController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessAutoApproveConfigDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessAutoApproveService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * BPM æµç¨‹å…å®¡æ‰¹é…ç½® Controller
 *
 * ç”¨äºŽæŒ‰æµç¨‹æŽ§åˆ¶ã€Œæ˜¯å¦è·³è¿‡ OA å®¡æ‰¹ã€ï¼šå¼€å…³å¼€å¯åŽï¼Œå•据提交时流程照常实例化,
 * ä½†æ‰€æœ‰äººå·¥å®¡æ ¸èŠ‚ç‚¹ç”±å¼•æ“Žè‡ªåŠ¨é€šè¿‡ï¼Œä¸‹æ¸¸ä¸šåŠ¡åŠ¨ä½œä¸å—å½±å“ã€‚
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - BPM æµç¨‹å…å®¡æ‰¹é…ç½®")
@RestController
@RequestMapping("/bpm/process-auto-approve")
@Validated
public class BpmProcessAutoApproveController {
    @Resource
    private BpmProcessAutoApproveService processAutoApproveService;
    @PutMapping("/update-by-key")
    @Operation(summary = "按流程定义的 Key è®¾ç½®å…å®¡æ‰¹å¼€å…³",
            description = "不存在则新增、存在则更新,供流程列表的行内开关直接调用")
    @Parameter(name = "processDefinitionKey", description = "流程定义的 Key", required = true, example = "purchase_request_approve")
    @Parameter(name = "autoApprove", description = "是否免审批", required = true, example = "true")
    @Parameter(name = "remark", description = "备注", example = "该流程暂不上 OA")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:update')")
    public CommonResult<Boolean> updateAutoApproveByKey(@RequestParam("processDefinitionKey") String processDefinitionKey,
                                                        @RequestParam("autoApprove") Boolean autoApprove,
                                                        @RequestParam(value = "remark", required = false) String remark) {
        processAutoApproveService.updateAutoApproveByKey(processDefinitionKey, autoApprove, remark);
        return success(true);
    }
    @PutMapping("/update-global")
    @Operation(summary = "设置全局免审批总开关",
            description = "开启后所有流程一律免审批(优先于各流程自己的开关);关闭后恢复为按各流程自己的开关判定")
    @Parameter(name = "autoApprove", description = "是否开启全局免审批", required = true, example = "true")
    @Parameter(name = "remark", description = "备注", example = "全部流程暂不上 OA")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:update')")
    public CommonResult<Boolean> updateGlobalAutoApprove(@RequestParam("autoApprove") Boolean autoApprove,
                                                         @RequestParam(value = "remark", required = false) String remark) {
        processAutoApproveService.updateGlobalAutoApprove(autoApprove, remark);
        return success(true);
    }
    @GetMapping("/global")
    @Operation(summary = "获得全局免审批总开关状态")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:query')")
    public CommonResult<Boolean> getGlobalAutoApprove() {
        return success(processAutoApproveService.isGlobalAutoApprove());
    }
    @GetMapping("/list")
    @Operation(summary = "获得全部免审批配置", description = "供前端一次性拉取「流程 Key -> æ˜¯å¦å…å®¡æ‰¹ã€çš„æ˜ å°„")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:query')")
    public CommonResult<List<BpmProcessAutoApproveConfigRespVO>> getConfigList() {
        List<BpmProcessAutoApproveConfigDO> list = processAutoApproveService.getConfigList();
        return success(BeanUtils.toBean(list, BpmProcessAutoApproveConfigRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获得免审批配置分页")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:query')")
    public CommonResult<PageResult<BpmProcessAutoApproveConfigRespVO>> getConfigPage(
            @Valid BpmProcessAutoApproveConfigPageReqVO pageReqVO) {
        PageResult<BpmProcessAutoApproveConfigDO> pageResult = processAutoApproveService.getConfigPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, BpmProcessAutoApproveConfigRespVO.class));
    }
    @PostMapping("/create")
    @Operation(summary = "创建免审批配置")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:update')")
    public CommonResult<Long> createConfig(@Valid @RequestBody BpmProcessAutoApproveConfigSaveReqVO createReqVO) {
        return success(processAutoApproveService.createConfig(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新免审批配置")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:update')")
    public CommonResult<Boolean> updateConfig(@Valid @RequestBody BpmProcessAutoApproveConfigSaveReqVO updateReqVO) {
        processAutoApproveService.updateConfig(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除免审批配置", description = "删除后该流程恢复为正常人工审批")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('bpm:process-auto-approve:update')")
    public CommonResult<Boolean> deleteConfig(@RequestParam("id") Long id) {
        processAutoApproveService.deleteConfig(id);
        return success(true);
    }
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - BPM æµç¨‹å…å®¡æ‰¹é…ç½®åˆ†é¡µ Request VO")
@Data
public class BpmProcessAutoApproveConfigPageReqVO extends PageParam {
    @Schema(description = "流程定义的 Key", example = "purchase_request_approve")
    private String processDefinitionKey;
    @Schema(description = "是否免审批", example = "true")
    private Boolean autoApprove;
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - BPM æµç¨‹å…å®¡æ‰¹é…ç½® Response VO")
@Data
public class BpmProcessAutoApproveConfigRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private Long id;
    @Schema(description = "流程定义的 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_request_approve")
    private String processDefinitionKey;
    @Schema(description = "是否免审批:true-提交后自动通过,false-正常人工审批",
            requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    private Boolean autoApprove;
    @Schema(description = "备注", example = "该流程暂不上 OA,提交后直接生效")
    private String remark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/autoapprove/BpmProcessAutoApproveConfigSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - BPM æµç¨‹å…å®¡æ‰¹é…ç½®æ–°å¢ž/修改 Request VO")
@Data
public class BpmProcessAutoApproveConfigSaveReqVO {
    @Schema(description = "编号", example = "1024")
    private Long id;
    @Schema(description = "流程定义的 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_request_approve")
    @NotEmpty(message = "流程定义的 Key ä¸èƒ½ä¸ºç©º")
    private String processDefinitionKey;
    @Schema(description = "是否免审批:true-提交后自动通过,false-正常人工审批",
            requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "是否免审批不能为空")
    private Boolean autoApprove;
    @Schema(description = "备注", example = "该流程暂不上 OA,提交后直接生效")
    private String remark;
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessAutoApproveConfigDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * BPM æµç¨‹å…å®¡æ‰¹é…ç½® DO
 *
 * ç”¨äºŽæŒ‰ã€Œæµç¨‹å®šä¹‰ Key」控制该流程是否跳过人工审批:
 * å¼€å…³å¼€å¯åŽï¼Œæµç¨‹å®žä¾‹ç…§å¸¸åˆ›å»ºï¼Œä½†æ‰€æœ‰ã€äººå·¥å®¡æ ¸ã€‘节点会被引擎自动通过,
 * ä¸šåŠ¡ä¾§çš„ã€Œå®¡æ‰¹é€šè¿‡ã€å›žè°ƒä¸Žä¸‹æ¸¸åŠ¨ä½œï¼ˆç”Ÿæˆé‡‡è´­è®¢å• / MPS / ä¾›åº”商档案等)全部照常执行。
 *
 * ä¹‹æ‰€ä»¥æŒ‰ Key è€Œä¸æ˜¯æŒ‰æµç¨‹å®šä¹‰ï¼ˆå«ç‰ˆæœ¬ï¼‰å­˜å‚¨ï¼šæµç¨‹æ¯æ¬¡é‡æ–°å‘布都会产生新的
 * processDefinitionId,若开关绑在版本上,改一次流程就要重配一次开关,容易漏配。
 *
 * @author è¶…级管理员
 */
@TableName("bpm_process_auto_approve_config")
@KeySequence("bpm_process_auto_approve_config_seq") // ç”¨äºŽ Oracle、PostgreSQL、Kingbase、DB2、H2 æ•°æ®åº“的主键自增。如果是 MySQL ç­‰æ•°æ®åº“,可不写。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmProcessAutoApproveConfigDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * æµç¨‹å®šä¹‰çš„ Key
     */
    private String processDefinitionKey;
    /**
     * æ˜¯å¦å…å®¡æ‰¹
     *
     * true - å…å®¡æ‰¹ï¼šæäº¤åŽç³»ç»Ÿè‡ªåŠ¨é€šè¿‡ï¼Œä¸è¿›å…¥äººå·¥å®¡æ‰¹
     * false - æ­£å¸¸äººå·¥å®¡æ‰¹
     */
    private Boolean autoApprove;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessAutoApproveConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessAutoApproveConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
 * BPM æµç¨‹å…å®¡æ‰¹é…ç½® Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface BpmProcessAutoApproveConfigMapper extends BaseMapperX<BpmProcessAutoApproveConfigDO> {
    default PageResult<BpmProcessAutoApproveConfigDO> selectPage(BpmProcessAutoApproveConfigPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessAutoApproveConfigDO>()
                .likeIfPresent(BpmProcessAutoApproveConfigDO::getProcessDefinitionKey, reqVO.getProcessDefinitionKey())
                .eqIfPresent(BpmProcessAutoApproveConfigDO::getAutoApprove, reqVO.getAutoApprove())
                .orderByDesc(BpmProcessAutoApproveConfigDO::getId));
    }
    default BpmProcessAutoApproveConfigDO selectByProcessDefinitionKey(String processDefinitionKey) {
        return selectOne(BpmProcessAutoApproveConfigDO::getProcessDefinitionKey, processDefinitionKey);
    }
    /**
     * æŒ‰å¤šä¸ªæµç¨‹ Key æ‰¹é‡æŸ¥è¯¢ï¼Œç”¨äºŽä¸€æ¬¡æŸ¥å‡ºã€Œå…¨å±€æ€»å¼€å…³ + è¯¥æµç¨‹å¼€å…³ã€
     */
    default List<BpmProcessAutoApproveConfigDO> selectListByKeys(Collection<String> processDefinitionKeys) {
        return selectList(new LambdaQueryWrapperX<BpmProcessAutoApproveConfigDO>()
                .in(BpmProcessAutoApproveConfigDO::getProcessDefinitionKey, processDefinitionKeys));
    }
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -93,4 +93,8 @@
    ErrorCode BUSINESS_ATTACH_NOT_EXISTS = new ErrorCode(1_009_015_000, "业务附件配置不存在");
    ErrorCode BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE = new ErrorCode(1_009_015_001, "流程定义的 Key({})已存在");
    // ========== BPM æµç¨‹å…å®¡æ‰¹é…ç½® 1-009-016-000 ==========
    ErrorCode PROCESS_AUTO_APPROVE_CONFIG_NOT_EXISTS = new ErrorCode(1_009_016_000, "流程免审批配置不存在");
    ErrorCode PROCESS_AUTO_APPROVE_CONFIG_KEY_DUPLICATE = new ErrorCode(1_009_016_001, "流程定义的 Key({})已存在免审批配置,请勿重复创建");
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
@@ -35,6 +35,7 @@
    ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
    APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
    APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
    PROCESS_AUTO_APPROVE("流程已配置免审批,提交后自动通过"), // åœºæ™¯ï¼šæµç¨‹å¼€å¯äº†å…å®¡æ‰¹å¼€å…³ï¼ˆBpmProcessAutoApproveService)
    CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
    CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"),
    ;
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessAutoApproveService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessAutoApproveConfigDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * BPM æµç¨‹å…å®¡æ‰¹é…ç½® Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface BpmProcessAutoApproveService {
    /**
     * å…¨å±€æ€»å¼€å…³åœ¨ä¸Šè¿°é…ç½®è¡¨ä¸­å ç”¨çš„æµç¨‹ Key
     *
     * è¯¥ Key ä¸å¯¹åº”任何真实流程,仅作为「所有流程一律免审批」的总闸标记
     */
    String GLOBAL_KEY = "*";
    /**
     * åˆ¤æ–­æŒ‡å®šæµç¨‹æ˜¯å¦å¼€å¯äº†å…å®¡æ‰¹
     *
     * åˆ¤å®šé¡ºåºï¼šå…¨å±€æ€»å¼€å…³ä¼˜å…ˆï¼Œå…¶æ¬¡çœ‹è¯¥æµç¨‹è‡ªå·±çš„开关。
     *
     * æ³¨æ„ï¼šæœ¬æ–¹æ³•只回答「开关是否打开」,不判断流程是否已部署。
     * æµç¨‹æœªéƒ¨ç½²æ—¶ï¼Œåˆ›å»ºæµç¨‹å®žä¾‹é˜¶æ®µå°±ä¼šæŠ›å‡º PROCESS_DEFINITION_NOT_EXISTS,走不到本方法。
     *
     * @param processDefinitionKey æµç¨‹å®šä¹‰çš„ Key
     * @return æ˜¯å¦å…å®¡æ‰¹
     */
    boolean isAutoApprove(String processDefinitionKey);
    /**
     * æ˜¯å¦å¼€å¯äº†å…¨å±€å…å®¡æ‰¹æ€»å¼€å…³
     *
     * @return æ˜¯å¦å¼€å¯
     */
    boolean isGlobalAutoApprove();
    /**
     * è®¾ç½®å…¨å±€å…å®¡æ‰¹æ€»å¼€å…³
     *
     * å¼€å¯åŽæ‰€æœ‰æµç¨‹ä¸€å¾‹å…å®¡æ‰¹ï¼ˆä¼˜å…ˆäºŽå„流程自己的开关);
     * å…³é—­åŽæ¢å¤ä¸ºæŒ‰å„流程自己的开关判定。
     *
     * @param autoApprove æ˜¯å¦å¼€å¯
     * @param remark      å¤‡æ³¨
     */
    void updateGlobalAutoApprove(Boolean autoApprove, String remark);
    /**
     * æŒ‰æµç¨‹å®šä¹‰çš„ Key è®¾ç½®å…å®¡æ‰¹å¼€å…³ï¼ˆä¸å­˜åœ¨åˆ™æ–°å¢žï¼Œå­˜åœ¨åˆ™æ›´æ–°ï¼‰
     *
     * ä¾›ã€Œæµç¨‹æ¨¡åž‹ã€åˆ—表的行内开关直接调用,无需先查询配置编号。
     *
     * @param processDefinitionKey æµç¨‹å®šä¹‰çš„ Key
     * @param autoApprove          æ˜¯å¦å…å®¡æ‰¹
     * @param remark               å¤‡æ³¨
     */
    void updateAutoApproveByKey(String processDefinitionKey, Boolean autoApprove, String remark);
    /**
     * èŽ·å¾—å…¨éƒ¨å…å®¡æ‰¹é…ç½®
     *
     * ä¾›å‰ç«¯ä¸€æ¬¡æ€§æ‹‰å–「流程 Key -> æ˜¯å¦å…å®¡æ‰¹ã€çš„æ˜ å°„关系
     *
     * @return é…ç½®åˆ—表
     */
    List<BpmProcessAutoApproveConfigDO> getConfigList();
    /**
     * èŽ·å¾—å…å®¡æ‰¹é…ç½®åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return é…ç½®åˆ†é¡µ
     */
    PageResult<BpmProcessAutoApproveConfigDO> getConfigPage(BpmProcessAutoApproveConfigPageReqVO pageReqVO);
    /**
     * åˆ›å»ºå…å®¡æ‰¹é…ç½®
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createConfig(@Valid BpmProcessAutoApproveConfigSaveReqVO createReqVO);
    /**
     * æ›´æ–°å…å®¡æ‰¹é…ç½®
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateConfig(@Valid BpmProcessAutoApproveConfigSaveReqVO updateReqVO);
    /**
     * åˆ é™¤å…å®¡æ‰¹é…ç½®
     *
     * @param id ç¼–号
     */
    void deleteConfig(Long id);
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessAutoApproveServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.autoapprove.BpmProcessAutoApproveConfigSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessAutoApproveConfigDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessAutoApproveConfigMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_AUTO_APPROVE_CONFIG_KEY_DUPLICATE;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_AUTO_APPROVE_CONFIG_NOT_EXISTS;
/**
 * BPM æµç¨‹å…å®¡æ‰¹é…ç½® Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Slf4j
@Service
@Validated
public class BpmProcessAutoApproveServiceImpl implements BpmProcessAutoApproveService {
    @Resource
    private BpmProcessAutoApproveConfigMapper bpmProcessAutoApproveConfigMapper;
    @Override
    public boolean isAutoApprove(String processDefinitionKey) {
        if (StrUtil.isBlank(processDefinitionKey)) {
            return false;
        }
        // ä¸€æ¬¡æŸ¥å‡ºã€Œå…¨å±€æ€»å¼€å…³ã€ä¸Žè¯¥æµç¨‹è‡ªå·±çš„开关,避免两次查询
        List<BpmProcessAutoApproveConfigDO> configs = bpmProcessAutoApproveConfigMapper
                .selectListByKeys(List.of(GLOBAL_KEY, processDefinitionKey));
        boolean globalOn = false;
        boolean processOn = false;
        for (BpmProcessAutoApproveConfigDO config : configs) {
            boolean on = Boolean.TRUE.equals(config.getAutoApprove());
            if (GLOBAL_KEY.equals(config.getProcessDefinitionKey())) {
                globalOn = on;
            } else if (processDefinitionKey.equals(config.getProcessDefinitionKey())) {
                processOn = on;
            }
        }
        // å…¨å±€æ€»å¼€å…³ä¼˜å…ˆï¼šå¼€å¯åŽæ‰€æœ‰æµç¨‹ä¸€å¾‹å…å®¡æ‰¹ï¼›å¦åˆ™çœ‹è¯¥æµç¨‹è‡ªå·±çš„开关
        return globalOn || processOn;
    }
    @Override
    public boolean isGlobalAutoApprove() {
        BpmProcessAutoApproveConfigDO config = bpmProcessAutoApproveConfigMapper.selectByProcessDefinitionKey(GLOBAL_KEY);
        return config != null && Boolean.TRUE.equals(config.getAutoApprove());
    }
    @Override
    public void updateGlobalAutoApprove(Boolean autoApprove, String remark) {
        updateAutoApproveByKey(GLOBAL_KEY, autoApprove, remark);
    }
    @Override
    public void updateAutoApproveByKey(String processDefinitionKey, Boolean autoApprove, String remark) {
        BpmProcessAutoApproveConfigDO config = bpmProcessAutoApproveConfigMapper.selectByProcessDefinitionKey(processDefinitionKey);
        if (config == null) {
            // é¦–次配置:新增
            bpmProcessAutoApproveConfigMapper.insert(BpmProcessAutoApproveConfigDO.builder()
                    .processDefinitionKey(processDefinitionKey)
                    .autoApprove(autoApprove)
                    .remark(remark)
                    .build());
            return;
        }
        // å·²æœ‰é…ç½®ï¼šæ›´æ–°å¼€å…³
        bpmProcessAutoApproveConfigMapper.updateById(BpmProcessAutoApproveConfigDO.builder()
                .id(config.getId())
                .autoApprove(autoApprove)
                .remark(remark)
                .build());
    }
    @Override
    public List<BpmProcessAutoApproveConfigDO> getConfigList() {
        return bpmProcessAutoApproveConfigMapper.selectList();
    }
    @Override
    public PageResult<BpmProcessAutoApproveConfigDO> getConfigPage(BpmProcessAutoApproveConfigPageReqVO pageReqVO) {
        return bpmProcessAutoApproveConfigMapper.selectPage(pageReqVO);
    }
    @Override
    public Long createConfig(BpmProcessAutoApproveConfigSaveReqVO createReqVO) {
        validateProcessDefinitionKeyUnique(createReqVO);
        BpmProcessAutoApproveConfigDO config = BeanUtils.toBean(createReqVO, BpmProcessAutoApproveConfigDO.class);
        bpmProcessAutoApproveConfigMapper.insert(config);
        return config.getId();
    }
    @Override
    public void updateConfig(BpmProcessAutoApproveConfigSaveReqVO updateReqVO) {
        validateConfigExists(updateReqVO.getId());
        validateProcessDefinitionKeyUnique(updateReqVO);
        bpmProcessAutoApproveConfigMapper.updateById(BeanUtils.toBean(updateReqVO, BpmProcessAutoApproveConfigDO.class));
    }
    @Override
    public void deleteConfig(Long id) {
        validateConfigExists(id);
        bpmProcessAutoApproveConfigMapper.deleteById(id);
    }
    private void validateProcessDefinitionKeyUnique(BpmProcessAutoApproveConfigSaveReqVO reqVO) {
        BpmProcessAutoApproveConfigDO config = bpmProcessAutoApproveConfigMapper.selectByProcessDefinitionKey(reqVO.getProcessDefinitionKey());
        if (config == null || ObjUtil.equal(config.getId(), reqVO.getId())) {
            return;
        }
        throw exception(PROCESS_AUTO_APPROVE_CONFIG_KEY_DUPLICATE, reqVO.getProcessDefinitionKey());
    }
    private void validateConfigExists(Long id) {
        if (bpmProcessAutoApproveConfigMapper.selectById(id) == null) {
            throw exception(PROCESS_AUTO_APPROVE_CONFIG_NOT_EXISTS);
        }
    }
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -29,6 +29,7 @@
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessAutoApproveService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
@@ -102,6 +103,8 @@
    private BpmMessageService messageService;
    @Resource
    private BpmFormService formService;
    @Resource
    private BpmProcessAutoApproveService processAutoApproveService;
    @Resource
    private AdminUserApi adminUserApi;
@@ -1400,6 +1403,10 @@
        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement);
        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement);
        // æµç¨‹çº§ã€å…å®¡æ‰¹ã€‘开关:仅对【人工审核】节点生效。
        // éžäººå·¥å®¡æ ¸çš„节点(AUTO_APPROVE / AUTO_REJECT)有其自身的设计器配置,不受本开关影响,避免两处配置互相干扰。
        boolean autoApproveEnabled = ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())
                && processAutoApproveService.isAutoApprove(processInstance.getProcessDefinitionKey());
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            /**
@@ -1418,6 +1425,26 @@
                        && getTask(task.getId()) == null) {
                    return;
                }
                // ç‰¹æ®Šæƒ…况零:流程开启了【免审批】开关,人工审核节点直接自动通过
                //   è¯´æ˜Žï¼šä¸æ”¹åŠ¨ä¸šåŠ¡ä¾§ä»»ä½•é€»è¾‘â€”â€”ä¸šåŠ¡ç…§å¸¸æŠŠå•æ®ç½®ä¸ºã€Œå®¡æ‰¹ä¸­ã€ï¼ŒéšåŽèµ°ä¸Žæ­£å¸¸å®¡æ‰¹é€šè¿‡
                //        å®Œå…¨ç›¸åŒçš„ approveTask æµç¨‹ï¼Œå› æ­¤ä¸šåŠ¡ç›‘å¬å™¨ã€å®¡æ‰¹ç•™ç—•ã€ä»¥åŠä¸‹æ¸¸åŠ¨ä½œ
                //        ï¼ˆç”Ÿæˆé‡‡è´­è®¢å• / MPS / ä¾›åº”商档案 / æŠ¥å·¥äº§å‡ºç­‰ï¼‰å…¨éƒ¨ç…§å¸¸æ‰§è¡Œã€‚
                if (autoApproveEnabled) {
                    // å¹‚等保护:任务可能已被推进(例如「发起人节点」由引擎自带的策略先行自动通过),
                    // æ­¤æ—¶ä»»åŠ¡å·²ä¸å­˜åœ¨ï¼Œç›´æŽ¥è·³è¿‡ï¼Œé¿å…é‡å¤å®¡æ‰¹æŠ¥é”™
                    Task autoApproveTask = getTask(task.getId());
                    if (autoApproveTask == null) {
                        return;
                    }
                    // å¿…须传入任务的实际审批人:approveTask ä¼šæ ¡éªŒã€Œæ“ä½œäººå¿…须是该任务的审批人」,
                    // ä¼  null ä»…对「审批人为空」的任务放行,否则会抛「该任务的审批人不是你」
                    Long assigneeUserId = StrUtil.isNotBlank(autoApproveTask.getAssignee())
                            ? NumberUtils.parseLong(autoApproveTask.getAssignee()) : null;
                    getSelf().approveTask(assigneeUserId, new BpmTaskApproveReqVO()
                            .setId(autoApproveTask.getId())
                            .setReason(BpmReasonEnum.PROCESS_AUTO_APPROVE.getReason()));
                    return;
                }
                // ç‰¹æ®Šæƒ…况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
                if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
                    // å¦‚果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝