xiaoyi
10 天以前 8b4105c2d327e09ce2664bbd7cf96559be2f788b
fix(hrm): 补齐请假/离职/调岗的撤销接口,修复点击撤销报 404

## 问题
联调测试发现:4 个审批模块中 3 个缺 /cancel 接口,但前端都有「撤销」按钮:

模块 后端 /cancel 前端调用
请假 ✗ 无 有 → 点击报 404
加班 ✓ 有 有 → 正常
离职 ✗ 无 有 → 点击报 404
调岗 ✗ 无 有 → 点击报 404

实测:
POST /admin-api/hrm/leave/cancel?id=16
→ 请求地址不存在:admin-api/hrm/leave/cancel

而菜单里 hrm:leave:cancel(撤销)权限早已配好,属于接口漏实现。

## 修复
照加班模块的实现补齐三个模块:

Controller:
@PostMapping("/cancel")
@PreAuthorize("@ss.hasPermission('hrm:leave:cancel')")
cancel(@RequestParam Long id)

Service:
校验「仅本人 + 审批中状态」→ 调用 bpmProcessInstanceService
.cancelProcessInstanceByStartUser 撤销流程实例

## 验证(生产实测)
三个撤销接口均已存在(不再 404)
请假:创建 → 提交(审批中, pid=1e25c4d1-...) → 撤销 → 状态「已撤销」(code=40) ✓
权限校验正确:admin 撤销 ch102 的单据被拒(仅本人可撤销)✓
ch102 撤销自己的单据成功 ✓

## 顺带确认(非缺陷)
已撤销的单据不能删除(只允许草稿删除),这是合理设计——撤销单需留痕。
已修改9个文件
114 ■■■■■ 文件已修改
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java
@@ -87,7 +87,16 @@
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
        @PostMapping("/cancel")
    @Operation(summary = "撤销申请")
    @Parameter(name = "id", description = "申请ID", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:leave:cancel')")
    public CommonResult<Boolean> cancelLeaveApplication(@RequestParam("id") Long id) {
        leaveApplicationService.cancel(id, SecurityFrameworkUtils.getLoginUserId());
        return success(true);
    }
@GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PreAuthorize("@ss.hasPermission('hrm:leave:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java
@@ -92,7 +92,16 @@
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
        @PostMapping("/cancel")
    @Operation(summary = "撤销申请")
    @Parameter(name = "id", description = "申请ID", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:resignation:cancel')")
    public CommonResult<Boolean> cancelResignationApplication(@RequestParam("id") Long id) {
        resignationApplicationService.cancel(id, SecurityFrameworkUtils.getLoginUserId());
        return success(true);
    }
@GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PreAuthorize("@ss.hasPermission('hrm:resignation:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java
@@ -91,7 +91,16 @@
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
        @PostMapping("/cancel")
    @Operation(summary = "撤销申请")
    @Parameter(name = "id", description = "申请ID", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:transfer:cancel')")
    public CommonResult<Boolean> cancelTransferApplication(@RequestParam("id") Long id) {
        transferApplicationService.cancel(id, SecurityFrameworkUtils.getLoginUserId());
        return success(true);
    }
@GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PreAuthorize("@ss.hasPermission('hrm:transfer:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java
@@ -84,6 +84,14 @@
     * @param userId 提交人ID
     */
    void submitLeaveApplication(Long id, String processDefinitionKey, Long userId);
    /**
     * 撤销申请:仅本人、且处于审批中状态可撤销
     *
     * @param id          申请编号
     * @param loginUserId 当前登录用户编号
     */
    void cancel(Long id, Long loginUserId);
    /**
     * 获取可用的审批流程定义列表
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
@@ -7,9 +7,11 @@
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO;
@@ -69,6 +71,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -283,6 +287,21 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long id, Long loginUserId) {
        HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
        // 仅本人、且处于审批中状态可撤销
        if (!Objects.equals(application.getUserId(), loginUserId)
                || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(LEAVE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId,
                new BpmProcessInstanceCancelReqVO()
                        .setId(application.getProcessInstanceId())
                        .setReason("申请人撤销请假申请"));
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        // 1. 校验分类和流程定义是否存在
        validateApproveCategoryAndProcessDefinition();
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java
@@ -82,6 +82,14 @@
     * @param userId 提交人ID
     */
    void submitResignationApplication(Long id, String processDefinitionKey, Long userId);
    /**
     * 撤销申请:仅本人、且处于审批中状态可撤销
     *
     * @param id          申请编号
     * @param loginUserId 当前登录用户编号
     */
    void cancel(Long id, Long loginUserId);
    /**
     * 获取可用的审批流程定义列表
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -6,9 +6,11 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
@@ -75,6 +77,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Resource
    private HrmUserHandoverService handoverService;
@@ -289,6 +293,21 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long id, Long loginUserId) {
        HrmResignationApplicationDO application = validateResignationApplicationExists(id);
        // 仅本人、且处于审批中状态可撤销
        if (!Objects.equals(application.getUserId(), loginUserId)
                || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(RESIGNATION_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId,
                new BpmProcessInstanceCancelReqVO()
                        .setId(application.getProcessInstanceId())
                        .setReason("申请人撤销离职申请"));
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        validateApproveCategoryAndProcessDefinition();
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java
@@ -81,6 +81,14 @@
     * @param userId 提交人ID
     */
    void submitTransferApplication(Long id, String processDefinitionKey, Long userId);
    /**
     * 撤销申请:仅本人、且处于审批中状态可撤销
     *
     * @param id          申请编号
     * @param loginUserId 当前登录用户编号
     */
    void cancel(Long id, Long loginUserId);
    /**
     * 获取可用的审批流程定义列表
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -6,9 +6,11 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
@@ -73,6 +75,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -302,6 +306,21 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long id, Long loginUserId) {
        HrmTransferApplicationDO application = validateTransferApplicationExists(id);
        // 仅本人、且处于审批中状态可撤销
        if (!Objects.equals(application.getUserId(), loginUserId)
                || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(TRANSFER_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId,
                new BpmProcessInstanceCancelReqVO()
                        .setId(application.getProcessInstanceId())
                        .setReason("申请人撤销调岗申请"));
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        validateApproveCategoryAndProcessDefinition();
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService