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 撤销自己的单据成功 ✓
## 顺带确认(非缺陷)
已撤销的单据不能删除(只允许草稿删除),这是合理设计——撤销单需留痕。
| | |
| | | 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() { |
| | |
| | | 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() { |
| | |
| | | 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() { |
| | |
| | | * @param userId 提交人ID |
| | | */ |
| | | void submitLeaveApplication(Long id, String processDefinitionKey, Long userId); |
| | | /** |
| | | * 撤销申请:仅本人、且处于审批中状态可撤销 |
| | | * |
| | | * @param id 申请编号 |
| | | * @param loginUserId 当前登录用户编号 |
| | | */ |
| | | void cancel(Long id, Long loginUserId); |
| | | |
| | | |
| | | /** |
| | | * 获取可用的审批流程定义列表 |
| | |
| | | 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; |
| | |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | | private BpmProcessInstanceService bpmProcessInstanceService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | } |
| | | |
| | | @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(); |
| | |
| | | * @param userId 提交人ID |
| | | */ |
| | | void submitResignationApplication(Long id, String processDefinitionKey, Long userId); |
| | | /** |
| | | * 撤销申请:仅本人、且处于审批中状态可撤销 |
| | | * |
| | | * @param id 申请编号 |
| | | * @param loginUserId 当前登录用户编号 |
| | | */ |
| | | void cancel(Long id, Long loginUserId); |
| | | |
| | | |
| | | /** |
| | | * 获取可用的审批流程定义列表 |
| | |
| | | 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; |
| | |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | | private BpmProcessInstanceService bpmProcessInstanceService; |
| | | @Resource |
| | | private HrmUserHandoverService handoverService; |
| | | |
| | |
| | | } |
| | | |
| | | @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 |
| | |
| | | * @param userId 提交人ID |
| | | */ |
| | | void submitTransferApplication(Long id, String processDefinitionKey, Long userId); |
| | | /** |
| | | * 撤销申请:仅本人、且处于审批中状态可撤销 |
| | | * |
| | | * @param id 申请编号 |
| | | * @param loginUserId 当前登录用户编号 |
| | | */ |
| | | void cancel(Long id, Long loginUserId); |
| | | |
| | | |
| | | /** |
| | | * 获取可用的审批流程定义列表 |
| | |
| | | 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; |
| | |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | | private BpmProcessInstanceService bpmProcessInstanceService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | } |
| | | |
| | | @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 |